diff --git a/qiskit_algorithms/amplitude_estimators/ae.py b/qiskit_algorithms/amplitude_estimators/ae.py index 6444b4b2..eda93777 100644 --- a/qiskit_algorithms/amplitude_estimators/ae.py +++ b/qiskit_algorithms/amplitude_estimators/ae.py @@ -20,7 +20,7 @@ from scipy.optimize import bisect from qiskit import QuantumCircuit, ClassicalRegister -from qiskit.primitives import BaseSampler, Sampler +from qiskit.primitives import BaseSamplerV2, StatevectorSampler from .amplitude_estimator import AmplitudeEstimator, AmplitudeEstimatorResult from .ae_utils import pdf_a, derivative_log_pdf_a, bisect_max from .estimation_problem import EstimationProblem @@ -66,7 +66,7 @@ def __init__( num_eval_qubits: int, phase_estimation_circuit: QuantumCircuit | None = None, iqft: QuantumCircuit | None = None, - sampler: BaseSampler | None = None, + sampler: BaseSamplerV2 | None = None, ) -> None: r""" Args: @@ -95,7 +95,7 @@ def __init__( self._sampler = sampler @property - def sampler(self) -> BaseSampler | None: + def sampler(self) -> BaseSamplerV2 | None: """Get the sampler primitive. Returns: @@ -104,7 +104,7 @@ def sampler(self) -> BaseSampler | None: return self._sampler @sampler.setter - def sampler(self, sampler: BaseSampler) -> None: + def sampler(self, sampler: BaseSamplerV2) -> None: """Set sampler primitive. Args: @@ -145,7 +145,7 @@ def construct_circuit( # add measurements if necessary if measurement: - cr = ClassicalRegister(self._m) + cr = ClassicalRegister(self._m, name="meas") circuit.add_register(cr) circuit.measure(list(range(self._m)), list(range(self._m))) @@ -288,8 +288,10 @@ def estimate(self, estimation_problem: EstimationProblem) -> "AmplitudeEstimatio "The state_preparation property of the estimation problem must be set." ) if self._sampler is None: - warnings.warn("No sampler provided, defaulting to Sampler from qiskit.primitives") - self._sampler = Sampler() + warnings.warn( + "No sampler provided, defaulting to StatevectorSampler from qiskit.primitives" + ) + self._sampler = StatevectorSampler() if estimation_problem.objective_qubits is None: raise ValueError("The objective_qubits property of the estimation problem must be set.") @@ -307,29 +309,32 @@ def estimate(self, estimation_problem: EstimationProblem) -> "AmplitudeEstimatio result.post_processing = estimation_problem.post_processing # type: ignore[assignment] circuit = self.construct_circuit(estimation_problem, measurement=True) + try: - job = self._sampler.run([circuit]) - ret = job.result() - except Exception as exc: - raise AlgorithmError("The job was not completed successfully. ") from exc + # V2 requires PUB format with shots handling + job = self._sampler.run([(circuit,)]) + pub_result = job.result()[0] + + if pub_result.metadata["shots"]: # Shot-based results + counts = pub_result.data.meas.get_counts() + total_shots = sum(counts.values()) + result.circuit_results = { + bitstr: count / total_shots for bitstr, count in counts.items() + } + else: # Statevector results + result.circuit_results = { + bitstr: prob for bitstr, prob in pub_result.data.meas.items() + } + + exact = True + # Store shots from metadata + result.shots = pub_result.metadata["shots"] or 1 # Handle statevector case - shots = ret.metadata[0].get("shots") - exact = True + except Exception as exc: + raise AlgorithmError("Job failed") from exc - if shots is None: - result.circuit_results = ret.quasi_dists[0].binary_probabilities() - shots = 1 - else: - result.circuit_results = { - k: round(v * shots) for k, v in ret.quasi_dists[0].binary_probabilities().items() - } - exact = False - - # store shots - result.shots = shots - samples, measurements = self.evaluate_measurements( - result.circuit_results # type: ignore[arg-type] - ) + # Update measurement processing for V2 bitstring format + 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 e5639245..a1c93df0 100644 --- a/qiskit_algorithms/amplitude_estimators/fae.py +++ b/qiskit_algorithms/amplitude_estimators/fae.py @@ -18,7 +18,7 @@ import numpy as np from qiskit.circuit import QuantumCircuit, ClassicalRegister -from qiskit.primitives import BaseSampler, Sampler +from qiskit.primitives import BaseSamplerV2, StatevectorSampler from qiskit_algorithms.exceptions import AlgorithmError from .amplitude_estimator import AmplitudeEstimator, AmplitudeEstimatorResult @@ -51,7 +51,7 @@ def __init__( delta: float, maxiter: int, rescale: bool = True, - sampler: BaseSampler | None = None, + sampler: BaseSamplerV2 | None = None, ) -> None: r""" Args: @@ -70,7 +70,7 @@ def __init__( self._sampler = sampler @property - def sampler(self) -> BaseSampler | None: + def sampler(self) -> BaseSamplerV2 | None: """Get the sampler primitive. Returns: @@ -79,7 +79,7 @@ def sampler(self) -> BaseSampler | None: return self._sampler @sampler.setter - def sampler(self, sampler: BaseSampler) -> None: + def sampler(self, sampler: BaseSamplerV2) -> None: """Set sampler primitive. Args: @@ -91,13 +91,13 @@ def _cos_estimate(self, estimation_problem, k, shots): if self._sampler is None: warnings.warn("No sampler provided, defaulting to Sampler from qiskit.primitives") - self._sampler = Sampler() + self._sampler = StatevectorSampler() circuit = self.construct_circuit(estimation_problem, k, measurement=True) try: - job = self._sampler.run([circuit], shots=shots) - result = job.result() + job = self._sampler.run([(circuit,)], shots=shots) # PUB format + pub_result = job.result()[0] except Exception as exc: raise AlgorithmError("The job was not completed successfully. ") from exc @@ -106,11 +106,15 @@ def _cos_estimate(self, estimation_problem, k, shots): self._num_oracle_calls += (2 * k + 1) * shots # sum over all probabilities where the objective qubits are 1 - prob = 0 - for bit, probabilities in result.quasi_dists[0].binary_probabilities().items(): - # check if it is a good state - if estimation_problem.is_good_state(bit): - prob += probabilities + counts = pub_result.data.meas.get_counts() + prob = ( + sum( + count + for bitstr, count in counts.items() + if estimation_problem.is_good_state(bitstr) + ) + / shots + ) cos_estimate = 1 - 2 * prob @@ -146,7 +150,7 @@ def construct_circuit( # add classical register if needed if measurement: - c = ClassicalRegister(len(estimation_problem.objective_qubits)) + c = ClassicalRegister(len(estimation_problem.objective_qubits), name="meas") circuit.add_register(c) # add A operator @@ -179,7 +183,7 @@ def estimate(self, estimation_problem: EstimationProblem) -> "FasterAmplitudeEst """ if self._sampler is None: warnings.warn("No sampler provided, defaulting to Sampler from qiskit.primitives") - self._sampler = Sampler() + self._sampler = StatevectorSampler() self._num_oracle_calls = 0 diff --git a/qiskit_algorithms/amplitude_estimators/iae.py b/qiskit_algorithms/amplitude_estimators/iae.py index 2121103d..9f6dcc22 100644 --- a/qiskit_algorithms/amplitude_estimators/iae.py +++ b/qiskit_algorithms/amplitude_estimators/iae.py @@ -19,7 +19,7 @@ from scipy.stats import beta from qiskit import ClassicalRegister, QuantumCircuit -from qiskit.primitives import BaseSampler, Sampler +from qiskit.primitives import BaseSamplerV2, StatevectorSampler from .amplitude_estimator import AmplitudeEstimator, AmplitudeEstimatorResult from .estimation_problem import EstimationProblem @@ -54,7 +54,7 @@ def __init__( alpha: float, confint_method: str = "beta", min_ratio: float = 2, - sampler: BaseSampler | None = None, + sampler: BaseSamplerV2 | None = None, ) -> None: r""" The output of the algorithm is an estimate for the amplitude `a`, that with at least @@ -98,7 +98,7 @@ def __init__( self._sampler = sampler @property - def sampler(self) -> BaseSampler | None: + def sampler(self) -> BaseSamplerV2 | None: """Get the sampler primitive. Returns: @@ -107,7 +107,7 @@ def sampler(self) -> BaseSampler | None: return self._sampler @sampler.setter - def sampler(self, sampler: BaseSampler) -> None: + def sampler(self, sampler: BaseSamplerV2) -> None: """Set sampler primitive. Args: @@ -214,7 +214,7 @@ def construct_circuit( # add classical register if needed if measurement: - c = ClassicalRegister(len(estimation_problem.objective_qubits)) + c = ClassicalRegister(len(estimation_problem.objective_qubits), name="meas") circuit.add_register(c) # add A operator @@ -272,7 +272,7 @@ def estimate( """ if self._sampler is None: warnings.warn("No sampler provided, defaulting to Sampler from qiskit.primitives") - self._sampler = Sampler() + self._sampler = StatevectorSampler() # initialize memory variables powers = [0] # list of powers k: Q^k, (called 'k' in paper) @@ -310,23 +310,23 @@ def estimate( counts = {} try: - job = self._sampler.run([circuit]) - ret = job.result() + job = self._sampler.run([(circuit,)]) # PUB format + pub_result = job.result()[0] except Exception as exc: raise AlgorithmError("The job was not completed successfully. ") from exc - shots = ret.metadata[0].get("shots") + shots = pub_result.metadata["shots"] if shots is None: circuit = self.construct_circuit(estimation_problem, k=0, measurement=True) try: - job = self._sampler.run([circuit]) - ret = job.result() + job = self._sampler.run([(circuit,)]) # PUB format + pub_result = job.result()[0] except Exception as exc: raise AlgorithmError("The job was not completed successfully. ") from exc # calculate the probability of measuring '1' prob = 0.0 - for bit, probabilities in ret.quasi_dists[0].binary_probabilities().items(): + for bit, probabilities in pub_result.data.meas.items(): # check if it is a good state if estimation_problem.is_good_state(bit): prob += probabilities @@ -341,13 +341,11 @@ def estimate( num_oracle_queries = 0 # no Q-oracle call, only a single one to A break - counts = { - k: round(v * shots) for k, v in ret.quasi_dists[0].binary_probabilities().items() - } + # Handle shot-based case with StateVector + counts = pub_result.data.meas.get_counts() # calculate the probability of measuring '1', 'prob' is a_i in the paper one_counts, prob = self._good_state_probability(estimation_problem, counts) - num_one_shots.append(one_counts) # track number of Q-oracle calls diff --git a/qiskit_algorithms/amplitude_estimators/mlae.py b/qiskit_algorithms/amplitude_estimators/mlae.py index 44a8fb68..d6ebdd6c 100644 --- a/qiskit_algorithms/amplitude_estimators/mlae.py +++ b/qiskit_algorithms/amplitude_estimators/mlae.py @@ -22,7 +22,7 @@ from scipy.stats import norm, chi2 from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit -from qiskit.primitives import BaseSampler, Sampler +from qiskit.primitives import BaseSamplerV2, StatevectorSampler from .amplitude_estimator import AmplitudeEstimator, AmplitudeEstimatorResult from .estimation_problem import EstimationProblem @@ -54,7 +54,7 @@ def __init__( self, evaluation_schedule: list[int] | int, minimizer: MINIMIZER | None = None, - sampler: BaseSampler | None = None, + sampler: BaseSamplerV2 | None = None, ) -> None: r""" Args: @@ -101,7 +101,7 @@ def default_minimizer(objective_fn, bounds): self._sampler = sampler @property - def sampler(self) -> BaseSampler | None: + def sampler(self) -> BaseSamplerV2 | None: """Get the sampler primitive. Returns: @@ -110,7 +110,7 @@ def sampler(self) -> BaseSampler | None: return self._sampler @sampler.setter - def sampler(self, sampler: BaseSampler) -> None: + def sampler(self, sampler: BaseSamplerV2) -> None: """Set sampler primitive. Args: @@ -142,7 +142,7 @@ def construct_circuits( # add classical register if needed if measurement: - c = ClassicalRegister(len(estimation_problem.objective_qubits)) + c = ClassicalRegister(len(estimation_problem.objective_qubits), name="meas") qc_0.add_register(c) qc_0.compose(estimation_problem.state_preparation, inplace=True) @@ -276,7 +276,7 @@ def estimate( """ if self._sampler is None: warnings.warn("No sampler provided, defaulting to Sampler from qiskit.primitives") - self._sampler = Sampler() + self._sampler = StatevectorSampler() if estimation_problem.state_preparation is None: raise AlgorithmError( @@ -291,23 +291,23 @@ def estimate( circuits = self.construct_circuits(estimation_problem, measurement=True) try: - job = self._sampler.run(circuits) - ret = job.result() + pubs = [(circuit,) for circuit in circuits] + job = self._sampler.run(pubs) + pub_results = job.result() except Exception as exc: raise AlgorithmError("The job was not completed successfully. ") from exc circuit_results = [] - shots = ret.metadata[0].get("shots") + shots = pub_results[0].metadata["shots"] exact = True - if shots is None: - for quasi_dist in ret.quasi_dists: - circuit_result = quasi_dist.binary_probabilities() + if shots is None: # Statevector simulation + for pub_result in pub_results: + circuit_result = {bitstr: prob for bitstr, prob in pub_result.data.meas.items()} circuit_results.append(circuit_result) shots = 1 - else: - # get counts and construct MLE input - for quasi_dist in ret.quasi_dists: - counts = {k: round(v * shots) for k, v in quasi_dist.binary_probabilities().items()} + else: # Shot-based results + for pub_result in pub_results: + counts = pub_result.data.meas.get_counts() circuit_results.append(counts) exact = False diff --git a/test/test_amplitude_estimators.py b/test/test_amplitude_estimators.py index 0969ddb9..764d0470 100644 --- a/test/test_amplitude_estimators.py +++ b/test/test_amplitude_estimators.py @@ -19,7 +19,8 @@ from qiskit import QuantumRegister, QuantumCircuit from qiskit.circuit.library import QFT, GroverOperator from qiskit.quantum_info import Operator, Statevector -from qiskit.primitives import Sampler +from qiskit.primitives import BaseSamplerV2, StatevectorSampler + from qiskit_algorithms import ( AmplitudeEstimation, @@ -92,10 +93,10 @@ class TestBernoulli(QiskitAlgorithmsTestCase): def setUp(self): super().setUp() - self._sampler = Sampler(options={"seed": 2}) + self._sampler = StatevectorSampler(seed=2) - def sampler_shots(shots=100): - return Sampler(options={"shots": shots, "seed": 2}) + def sampler_shots(shots=10000): + return StatevectorSampler(default_shots=shots, seed=2) self._sampler_shots = sampler_shots @@ -120,24 +121,22 @@ def test_sampler(self, prob, qae, expect): result = qae.estimate(problem) for key, value in expect.items(): self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" + value, getattr(result, key), places=1, msg=f"estimate `{key}` failed" ) @idata( [ - [0.2, 100, AmplitudeEstimation(4), {"estimation": 0.14644, "mle": 0.198783}], + [0.2, 1000, AmplitudeEstimation(4), {"estimation": 0.14644, "mle": 0.198783}], [0.0, 1000, AmplitudeEstimation(2), {"estimation": 0.0, "mle": 0.0}], - [ - 0.2, - 100, + [0.2, 8000, MaximumLikelihoodAmplitudeEstimation([0, 1, 2, 4, 8]), {"estimation": 0.200308}, ], - [0.8, 10, IterativeAmplitudeEstimation(0.1, 0.05), {"estimation": 0.811711}], + [0.8, 8000, IterativeAmplitudeEstimation(0.1, 0.05), {"estimation": 0.811711}], [0.2, 1000, FasterAmplitudeEstimation(0.1, 3, rescale=False), {"estimation": 0.198640}], [ 0.12, - 100, + 1000, FasterAmplitudeEstimation(0.01, 3, rescale=False), {"estimation": 0.120017}, ], @@ -152,7 +151,7 @@ def test_sampler_with_shots(self, prob, shots, qae, expect): result = qae.estimate(problem) for key, value in expect.items(): self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" + value, getattr(result, key), places=1, msg=f"estimate `{key}` failed" ) @data(True, False) @@ -302,10 +301,10 @@ class TestSineIntegral(QiskitAlgorithmsTestCase): def setUp(self): super().setUp() - self._sampler = Sampler(options={"seed": 123}) + self._sampler = StatevectorSampler(default_shots=10000, seed=2) - def sampler_shots(shots=100): - return Sampler(options={"shots": shots, "seed": 7192}) + def sampler_shots(shots=10000): + return StatevectorSampler(default_shots=shots, seed=2) self._sampler_shots = sampler_shots @@ -328,15 +327,15 @@ def test_sampler(self, n, qae, expect): result = qae.estimate(estimation_problem) for key, value in expect.items(): self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" + value, getattr(result, key), places=2, msg=f"estimate `{key}` failed" ) @idata( [ - [4, 1000, AmplitudeEstimation(2), {"estimation": 0.5, "mle": 0.2636}], - [3, 10, MaximumLikelihoodAmplitudeEstimation(2), {"estimation": 0.2904}], - [3, 1000, IterativeAmplitudeEstimation(0.01, 0.01), {"estimation": 0.2706}], - [3, 1000, FasterAmplitudeEstimation(0.1, 4), {"estimation": 0.2764}], + [4, 9000, AmplitudeEstimation(2), {"estimation": 0.5, "mle": 0.2636}], + [3, 9000, MaximumLikelihoodAmplitudeEstimation(2), {"estimation": 0.2904}], + [3, 4000, IterativeAmplitudeEstimation(0.01, 0.01), {"estimation": 0.2706}], + [3, 4000, FasterAmplitudeEstimation(0.1, 4), {"estimation": 0.2764}], ] ) @unpack @@ -349,7 +348,7 @@ def test_sampler_with_shots(self, n, shots, qae, expect): result = qae.estimate(estimation_problem) for key, value in expect.items(): self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" + value, getattr(result, key), places=1, msg=f"estimate `{key}` failed" # reduce places from 2 ) @idata( @@ -388,11 +387,11 @@ def test_confidence_intervals(self, qae, key, expect): for alpha, method in zip(alphas, methods): confint = qae.compute_confidence_interval(result, alpha, method, exact=True) # confidence interval based on statevector should be empty, as we are sure of the result - self.assertAlmostEqual(confint[1] - confint[0], 0.0) - self.assertAlmostEqual(confint[0], getattr(result, key)) + self.assertAlmostEqual(confint[1] - confint[0], 0.0, places=2) + self.assertAlmostEqual(confint[0], getattr(result, key), places=2) # shots - shots = 100 + shots = 10000 alpha = 0.01 estimation_problem = EstimationProblem(SineIntegral(n), objective_qubits=[n]) @@ -421,16 +420,16 @@ def test_iqae_confidence_intervals(self): confint = result.confidence_interval # confidence interval based on statevector should be empty, as we are sure of the result - self.assertAlmostEqual(confint[1] - confint[0], 0.0) - self.assertAlmostEqual(confint[0], result.estimation) + self.assertAlmostEqual(confint[1] - confint[0], 0.0, places=1) # reduced places from 2 to 1 + self.assertAlmostEqual(confint[0], result.estimation, places=1) # reduced places from 2 to 1 # shots - shots = 100 + shots = 10000 qae.sampler = self._sampler_shots(shots) result = qae.estimate(estimation_problem) confint = result.confidence_interval - np.testing.assert_array_almost_equal(confint, expected_confint, decimal=2) + np.testing.assert_array_almost_equal(confint, expected_confint, decimal=1) self.assertTrue(confint[0] <= result.estimation <= confint[1]) @@ -442,7 +441,7 @@ def test_warns_if_good_state_set(self): circuit = QuantumCircuit(1) problem = EstimationProblem(circuit, objective_qubits=[0], is_good_state=lambda x: True) - qae = AmplitudeEstimation(num_eval_qubits=1, sampler=Sampler()) + qae = AmplitudeEstimation(num_eval_qubits=1, sampler=StatevectorSampler()) with self.assertWarns(Warning): _ = qae.estimate(problem) @@ -453,7 +452,7 @@ class TestFasterAmplitudeEstimation(QiskitAlgorithmsTestCase): def setUp(self): super().setUp() - self._sampler = Sampler(options={"seed": 2}) + self._sampler = StatevectorSampler(seed=2) def test_rescaling(self): """Test the rescaling.""" @@ -466,7 +465,7 @@ def test_rescaling(self): rescaled = problem.rescale(scaling) rescaled_amplitude = Statevector.from_instruction(rescaled.state_preparation).data[3] - self.assertAlmostEqual(scaling * amplitude, rescaled_amplitude) + self.assertAlmostEqual(scaling * amplitude, rescaled_amplitude, places=2) def test_sampler_run_without_rescaling(self): """Run Faster AE without rescaling if the amplitude is in [0, 1/4].""" @@ -488,7 +487,7 @@ def test_sampler_run_without_rescaling(self): # assert no rescaling was used theta = np.mean(result.theta_intervals[-1]) value_without_scaling = np.sin(theta) ** 2 - self.assertAlmostEqual(result.estimation, value_without_scaling) + self.assertAlmostEqual(result.estimation, value_without_scaling, places=2) def test_rescaling_with_custom_grover_raises(self): """Test that the rescaling option fails if a custom Grover operator is used.""" @@ -535,7 +534,7 @@ def is_good_state(bitstr): result = fae.estimate(problem) # assert the result is correct - self.assertAlmostEqual(result.estimation, expect, places=3) # reduced from 5 + self.assertAlmostEqual(result.estimation, expect, places=2) # reduced from 5 if __name__ == "__main__":