Repository: Qiskit/qiskit

Issue:
Rewrite Grover with primitives.



Discussion:
- Need to update circuits.
I can take care of this issue.

Resolving patch:
diff --git a/qiskit/algorithms/amplitude_amplifiers/amplitude_amplifier.py b/qiskit/algorithms/amplitude_amplifiers/amplitude_amplifier.py
--- a/qiskit/algorithms/amplitude_amplifiers/amplitude_amplifier.py
+++ b/qiskit/algorithms/amplitude_amplifiers/amplitude_amplifier.py
@@ -46,6 +46,8 @@ def __init__(self) -> None:
         self._top_measurement = None
         self._assignment = None
         self._oracle_evaluation = None
+        self._circuit_results = None
+        self._max_probability = None
 
     @property
     def top_measurement(self) -> Optional[str]:
diff --git a/qiskit/algorithms/amplitude_amplifiers/grover.py b/qiskit/algorithms/amplitude_amplifiers/grover.py
--- a/qiskit/algorithms/amplitude_amplifiers/grover.py
+++ b/qiskit/algorithms/amplitude_amplifiers/grover.py
@@ -1,6 +1,6 @@
 # This code is part of Qiskit.
 #
-# (C) Copyright IBM 2018, 2021.
+# (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
@@ -15,13 +15,17 @@
 import itertools
 import operator
 from typing import Iterator, List, Optional, Union
+import warnings
 
 import numpy as np
 
 from qiskit import ClassicalRegister, QuantumCircuit
+from qiskit.algorithms.exceptions import AlgorithmError
+from qiskit.primitives import BaseSampler
 from qiskit.providers import Backend
 from qiskit.quantum_info import partial_trace
 from qiskit.utils import QuantumInstance
+from qiskit.utils.deprecation import deprecate_function
 from .amplification_problem import AmplificationProblem
 from .amplitude_amplifier import AmplitudeAmplifier, AmplitudeAmplifierResult
 
@@ -113,6 +117,7 @@ def __init__(
         growth_rate: Optional[float] = None,
         sample_from_iterations: bool = False,
         quantum_instance: Optional[Union[QuantumInstance, Backend]] = None,
+        sampler: Optional[BaseSampler] = None,
     ) -> None:
         r"""
         Args:
@@ -131,7 +136,8 @@ def __init__(
             sample_from_iterations: If True, instead of taking the values in ``iterations`` as
                 powers of the Grover operator, a random integer sample between 0 and smaller value
                 than the iteration is used as a power, see [1], Section 4.
-            quantum_instance: A Quantum Instance or Backend to run the circuits.
+            quantum_instance: Pending deprecation: A Quantum Instance or Backend to run the circuits.
+            sampler: A Sampler to use for sampling the results of the circuits.
 
         Raises:
             ValueError: If ``growth_rate`` is a float but not larger than 1.
@@ -156,24 +162,58 @@ def __init__(
         else:
             self._iterations = iterations
 
+        if quantum_instance is not None and sampler is not None:
+            raise ValueError("Only one of quantum_instance or sampler can be passed, not both!")
+
+        # check positionally passing the sampler in the place of quantum_instance
+        # which will be removed in future
+        if isinstance(quantum_instance, BaseSampler):
+            sampler = quantum_instance
+            quantum_instance = None
+
         self._quantum_instance = None
         if quantum_instance is not None:
-            self.quantum_instance = quantum_instance
+            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,
+                stacklevel=2,
+            )
+            with warnings.catch_warnings():
+                warnings.simplefilter("ignore", category=PendingDeprecationWarning)
+                self.quantum_instance = quantum_instance
+
+        self._sampler = sampler
 
         self._sample_from_iterations = sample_from_iterations
         self._iterations_arg = iterations
 
     @property
+    @deprecate_function(
+        "The Grover.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) -> Optional[QuantumInstance]:
-        """Get the quantum instance.
+        r"""Pending deprecation\; Get the quantum instance.
+
         Returns:
             The quantum instance used to run this algorithm.
         """
         return self._quantum_instance
 
     @quantum_instance.setter
+    @deprecate_function(
+        "The Grover.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.
+        r"""Pending deprecation\; Set quantum instance.
+
         Args:
             quantum_instance: The quantum instance used to run this algorithm.
         """
@@ -181,6 +221,24 @@ def quantum_instance(self, quantum_instance: Union[QuantumInstance, Backend]) ->
             quantum_instance = QuantumInstance(quantum_instance)
         self._quantum_instance = quantum_instance
 
+    @property
+    def sampler(self) -> Optional[BaseSampler]:
+        """Get the sampler.
+
+        Returns:
+            The sampler used to run this algorithm.
+        """
+        return self._sampler
+
+    @sampler.setter
+    def sampler(self, sampler: BaseSampler) -> None:
+        """Set the sampler.
+
+        Args:
+            sampler: The sampler used to run this algorithm.
+        """
+        self._sampler = sampler
+
     def amplify(self, amplification_problem: AmplificationProblem) -> "GroverResult":
         """Run the Grover algorithm.
 
@@ -192,9 +250,17 @@ def amplify(self, amplification_problem: AmplificationProblem) -> "GroverResult"
             as ``result.top_measurement``.
 
         Raises:
+            ValueError: If a quantum instance or sampler is not set.
+            AlgorithmError: If a sampler job fails.
             TypeError: If ``is_good_state`` is not provided and is required (i.e. when iterations
             is ``None`` or a ``list``)
         """
+        if self._sampler is None and self._quantum_instance is None:
+            raise ValueError("A quantum instance or sampler must be provided.")
+
+        if self._quantum_instance is not None and self._sampler is not None:
+            raise ValueError("Only one of quantum_instance or sampler can be passed, not both!")
+
         if isinstance(self._iterations, list):
             max_iterations = len(self._iterations)
             max_power = np.inf  # no cap on the power
@@ -228,34 +294,50 @@ def amplify(self, amplification_problem: AmplificationProblem) -> "GroverResult"
             if self._sample_from_iterations:
                 power = np.random.randint(power)
             # Run a grover experiment for a given power of the Grover operator.
-            if self._quantum_instance.is_statevector:
-                qc = self.construct_circuit(amplification_problem, power, measurement=False)
-                circuit_results = self._quantum_instance.execute(qc).get_statevector()
+            if self._sampler is not None:
+                qc = self.construct_circuit(amplification_problem, power, measurement=True)
+                job = self._sampler.run([qc])
+
+                try:
+                    results = job.result()
+                except Exception as exc:
+                    raise AlgorithmError("Sampler job failed.") from exc
+
                 num_bits = len(amplification_problem.objective_qubits)
+                circuit_results = {
+                    np.binary_repr(k, num_bits): v for k, v in results.quasi_dists[0].items()
+                }
+                top_measurement, max_probability = max(circuit_results.items(), key=lambda x: x[1])
 
-                # trace out work qubits
-                if qc.width() != num_bits:
-                    indices = [
-                        i
-                        for i in range(qc.num_qubits)
-                        if i not in amplification_problem.objective_qubits
-                    ]
-                    rho = partial_trace(circuit_results, indices)
-                    circuit_results = np.diag(rho.data)
-
-                max_amplitude = max(circuit_results.max(), circuit_results.min(), key=abs)
-                max_amplitude_idx = np.where(circuit_results == max_amplitude)[0][0]
-                top_measurement = np.binary_repr(max_amplitude_idx, num_bits)
-                max_probability = np.abs(max_amplitude) ** 2
-                shots = 1
             else:
-                qc = self.construct_circuit(amplification_problem, power, measurement=True)
-                circuit_results = self._quantum_instance.execute(qc).get_counts(qc)
-                top_measurement = max(circuit_results.items(), key=operator.itemgetter(1))[0]
-                shots = sum(circuit_results.values())
-                max_probability = (
-                    max(circuit_results.items(), key=operator.itemgetter(1))[1] / shots
-                )
+                if self._quantum_instance.is_statevector:
+                    qc = self.construct_circuit(amplification_problem, power, measurement=False)
+                    circuit_results = self._quantum_instance.execute(qc).get_statevector()
+                    num_bits = len(amplification_problem.objective_qubits)
+
+                    # trace out work qubits
+                    if qc.width() != num_bits:
+                        indices = [
+                            i
+                            for i in range(qc.num_qubits)
+                            if i not in amplification_problem.objective_qubits
+                        ]
+                        rho = partial_trace(circuit_results, indices)
+                        circuit_results = np.diag(rho.data)
+
+                    max_amplitude = max(circuit_results.max(), circuit_results.min(), key=abs)
+                    max_amplitude_idx = np.where(circuit_results == max_amplitude)[0][0]
+                    top_measurement = np.binary_repr(max_amplitude_idx, num_bits)
+                    max_probability = np.abs(max_amplitude) ** 2
+                    shots = 1
+                else:
+                    qc = self.construct_circuit(amplification_problem, power, measurement=True)
+                    circuit_results = self._quantum_instance.execute(qc).get_counts(qc)
+                    top_measurement = max(circuit_results.items(), key=operator.itemgetter(1))[0]
+                    shots = sum(circuit_results.values())
+                    max_probability = (
+                        max(circuit_results.items(), key=operator.itemgetter(1))[1] / shots
+                    )
 
             all_circuit_results.append(circuit_results)
 
@@ -341,8 +423,6 @@ class GroverResult(AmplitudeAmplifierResult):
     def __init__(self) -> None:
         super().__init__()
         self._iterations = None
-        self._circuit_results = None
-        self._shots = None
 
     @property
     def iterations(self) -> List[int]:


Test patch:
diff --git a/test/python/algorithms/test_backendv1.py b/test/python/algorithms/test_backendv1.py
--- a/test/python/algorithms/test_backendv1.py
+++ b/test/python/algorithms/test_backendv1.py
@@ -85,7 +85,8 @@ def test_run_circuit_oracle(self):
         qi = QuantumInstance(
             self._provider.get_backend("fake_vigo"), seed_simulator=12, seed_transpiler=32
         )
-        grover = Grover(quantum_instance=qi)
+        with self.assertWarns(PendingDeprecationWarning):
+            grover = Grover(quantum_instance=qi)
         result = grover.amplify(problem)
         self.assertIn(result.top_measurement, ["11"])
 
@@ -97,7 +98,8 @@ def test_run_circuit_oracle_single_experiment_backend(self):
         backend = self._provider.get_backend("fake_vigo")
         backend._configuration.max_experiments = 1
         qi = QuantumInstance(backend, seed_simulator=12, seed_transpiler=32)
-        grover = Grover(quantum_instance=qi)
+        with self.assertWarns(PendingDeprecationWarning):
+            grover = Grover(quantum_instance=qi)
         result = grover.amplify(problem)
         self.assertIn(result.top_measurement, ["11"])
 
diff --git a/test/python/algorithms/test_backendv2.py b/test/python/algorithms/test_backendv2.py
--- a/test/python/algorithms/test_backendv2.py
+++ b/test/python/algorithms/test_backendv2.py
@@ -85,7 +85,8 @@ def test_run_circuit_oracle(self):
         qi = QuantumInstance(
             self._provider.get_backend("fake_yorktown"), seed_simulator=12, seed_transpiler=32
         )
-        grover = Grover(quantum_instance=qi)
+        with self.assertWarns(PendingDeprecationWarning):
+            grover = Grover(quantum_instance=qi)
         result = grover.amplify(problem)
         self.assertIn(result.top_measurement, ["11"])
 
@@ -99,7 +100,8 @@ def test_run_circuit_oracle_single_experiment_backend(self):
         qi = QuantumInstance(
             self._provider.get_backend("fake_yorktown"), seed_simulator=12, seed_transpiler=32
         )
-        grover = Grover(quantum_instance=qi)
+        with self.assertWarns(PendingDeprecationWarning):
+            grover = Grover(quantum_instance=qi)
         result = grover.amplify(problem)
         self.assertIn(result.top_measurement, ["11"])
 
diff --git a/test/python/algorithms/test_grover.py b/test/python/algorithms/test_grover.py
--- a/test/python/algorithms/test_grover.py
+++ b/test/python/algorithms/test_grover.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
@@ -16,12 +16,14 @@
 from test.python.algorithms import QiskitAlgorithmsTestCase
 import itertools
 import numpy as np
-from ddt import ddt, data
+from ddt import ddt, data, idata, unpack
+
 
 from qiskit import BasicAer, QuantumCircuit
 from qiskit.utils import QuantumInstance
 from qiskit.algorithms import Grover, AmplificationProblem
 from qiskit.circuit.library import GroverOperator, PhaseOracle
+from qiskit.primitives import Sampler
 from qiskit.quantum_info import Operator, Statevector
 
 
@@ -95,41 +97,48 @@ def setUp(self):
         self.qasm = QuantumInstance(
             BasicAer.get_backend("qasm_simulator"), seed_simulator=12, seed_transpiler=32
         )
+        self._sampler = Sampler()
+        self._sampler_with_shots = Sampler(options={"shots": 1024, "seed": 123})
 
-    def test_implicit_phase_oracle_is_good_state(self):
+    @data("ideal", "shots", False)
+    def test_implicit_phase_oracle_is_good_state(self, use_sampler):
         """Test implicit default for is_good_state with PhaseOracle."""
-        grover = Grover(iterations=2, quantum_instance=self.statevector)
-        oracle = PhaseOracle("x | x")
+        grover = self._prepare_grover(use_sampler)
+        oracle = PhaseOracle("x & y")
         problem = AmplificationProblem(oracle)
         result = grover.amplify(problem)
-        self.assertEqual(result.top_measurement, "0")
+        self.assertEqual(result.top_measurement, "11")
 
-    @data([1, 2, 3], None, 2)
-    def test_iterations_with_good_state(self, iterations):
+    @idata(itertools.product(["ideal", "shots", False], [[1, 2, 3], None, 2]))
+    @unpack
+    def test_iterations_with_good_state(self, use_sampler, iterations):
         """Test the algorithm with different iteration types and with good state"""
-        grover = Grover(iterations, quantum_instance=self.statevector)
+        grover = self._prepare_grover(use_sampler, iterations)
         problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"])
         result = grover.amplify(problem)
         self.assertEqual(result.top_measurement, "111")
 
-    def test_fixed_iterations_without_good_state(self):
+    @data("ideal", "shots", False)
+    def test_fixed_iterations_without_good_state(self, use_sampler):
         """Test the algorithm with iterations as an int and without good state"""
-        grover = Grover(iterations=2, quantum_instance=self.statevector)
+        grover = self._prepare_grover(use_sampler, iterations=2)
         problem = AmplificationProblem(Statevector.from_label("111"))
         result = grover.amplify(problem)
         self.assertEqual(result.top_measurement, "111")
 
-    @data([1, 2, 3], None)
-    def test_iterations_without_good_state(self, iterations):
+    @idata(itertools.product(["ideal", "shots", False], [[1, 2, 3], None]))
+    @unpack
+    def test_iterations_without_good_state(self, use_sampler, iterations):
         """Test the correct error is thrown for none/list of iterations and without good state"""
-        grover = Grover(iterations, quantum_instance=self.statevector)
+        grover = self._prepare_grover(use_sampler, iterations=iterations)
         problem = AmplificationProblem(Statevector.from_label("111"))
         with self.assertRaisesRegex(
             TypeError, "An is_good_state function is required with the provided oracle"
         ):
             grover.amplify(problem)
 
-    def test_iterator(self):
+    @data("ideal", "shots", False)
+    def test_iterator(self, use_sampler):
         """Test running the algorithm on an iterator."""
 
         # step-function iterator
@@ -141,59 +150,63 @@ def iterator():
                 if count % wait == 0:
                     value += 1
 
-        grover = Grover(iterations=iterator(), quantum_instance=self.statevector)
+        grover = self._prepare_grover(use_sampler, iterations=iterator())
         problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"])
         result = grover.amplify(problem)
         self.assertEqual(result.top_measurement, "111")
 
-    def test_growth_rate(self):
+    @data("ideal", "shots", False)
+    def test_growth_rate(self, use_sampler):
         """Test running the algorithm on a growth rate"""
-        grover = Grover(growth_rate=8 / 7, quantum_instance=self.statevector)
+        grover = self._prepare_grover(use_sampler, growth_rate=8 / 7)
         problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"])
         result = grover.amplify(problem)
         self.assertEqual(result.top_measurement, "111")
 
-    def test_max_num_iterations(self):
+    @data("ideal", "shots", False)
+    def test_max_num_iterations(self, use_sampler):
         """Test the iteration stops when the maximum number of iterations is reached."""
 
         def zero():
             while True:
                 yield 0
 
-        grover = Grover(iterations=zero(), quantum_instance=self.statevector)
+        grover = self._prepare_grover(use_sampler, iterations=zero())
         n = 5
         problem = AmplificationProblem(Statevector.from_label("1" * n), is_good_state=["1" * n])
         result = grover.amplify(problem)
         self.assertEqual(len(result.iterations), 2**n)
 
-    def test_max_power(self):
+    @data("ideal", "shots", False)
+    def test_max_power(self, use_sampler):
         """Test the iteration stops when the maximum power is reached."""
         lam = 10.0
-        grover = Grover(growth_rate=lam, quantum_instance=self.statevector)
+        grover = self._prepare_grover(use_sampler, growth_rate=lam)
         problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"])
         result = grover.amplify(problem)
         self.assertEqual(len(result.iterations), 0)
 
-    def test_run_circuit_oracle(self):
+    @data("ideal", "shots", False)
+    def test_run_circuit_oracle(self, use_sampler):
         """Test execution with a quantum circuit oracle"""
         oracle = QuantumCircuit(2)
         oracle.cz(0, 1)
         problem = AmplificationProblem(oracle, is_good_state=["11"])
-
-        grover = Grover(quantum_instance=self.qasm)
+        grover = self._prepare_grover(use_sampler)
         result = grover.amplify(problem)
         self.assertIn(result.top_measurement, ["11"])
 
-    def test_run_state_vector_oracle(self):
+    @data("ideal", "shots", False)
+    def test_run_state_vector_oracle(self, use_sampler):
         """Test execution with a state vector oracle"""
         mark_state = Statevector.from_label("11")
         problem = AmplificationProblem(mark_state, is_good_state=["11"])
-
-        grover = Grover(quantum_instance=self.qasm)
+        grover = self._prepare_grover(use_sampler)
         result = grover.amplify(problem)
         self.assertIn(result.top_measurement, ["11"])
 
-    def test_run_custom_grover_operator(self):
+    @data("ideal", "shots", False)
+    def test_run_custom_grover_operator(self, use_sampler):
         """Test execution with a grover operator oracle"""
         oracle = QuantumCircuit(2)
         oracle.cz(0, 1)
@@ -201,8 +214,7 @@ def test_run_custom_grover_operator(self):
         problem = AmplificationProblem(
             oracle=oracle, grover_operator=grover_op, is_good_state=["11"]
         )
-
-        grover = Grover(quantum_instance=self.qasm)
+        grover = self._prepare_grover(use_sampler)
         ret = grover.amplify(problem)
         self.assertIn(ret.top_measurement, ["11"])
 
@@ -230,40 +242,73 @@ def test_construct_circuit(self):
 
         self.assertTrue(Operator(constructed).equiv(Operator(expected)))
 
-    def test_circuit_result(self):
+    @data("ideal", "shots", False)
+    def test_circuit_result(self, use_sampler):
         """Test circuit_result"""
         oracle = QuantumCircuit(2)
         oracle.cz(0, 1)
         # is_good_state=['00'] is intentionally selected to obtain a list of results
         problem = AmplificationProblem(oracle, is_good_state=["00"])
-        grover = Grover(iterations=[1, 2, 3, 4], quantum_instance=self.qasm)
+        grover = self._prepare_grover(use_sampler, iterations=[1, 2, 3, 4])
         result = grover.amplify(problem)
-        expected_results = [
-            {"11": 1024},
-            {"00": 238, "01": 253, "10": 263, "11": 270},
-            {"00": 238, "01": 253, "10": 263, "11": 270},
-            {"11": 1024},
-        ]
-        self.assertEqual(result.circuit_results, expected_results)
-
-    def test_max_probability(self):
+        if use_sampler:
+            for i, dist in enumerate(result.circuit_results):
+                keys, values = zip(*sorted(dist.items()))
+                self.assertTupleEqual(keys, ("00", "01", "10", "11"))
+                if i in (0, 3):
+                    np.testing.assert_allclose(values, [0, 0, 0, 1], atol=0.2)
+                else:
+                    np.testing.assert_allclose(values, [0.25, 0.25, 0.25, 0.25], atol=0.2)
+        else:
+            expected_results = [
+                {"11": 1024},
+                {"00": 238, "01": 253, "10": 263, "11": 270},
+                {"00": 238, "01": 253, "10": 263, "11": 270},
+                {"11": 1024},
+            ]
+            self.assertEqual(result.circuit_results, expected_results)
+
+    @data("ideal", "shots", False)
+    def test_max_probability(self, use_sampler):
         """Test max_probability"""
         oracle = QuantumCircuit(2)
         oracle.cz(0, 1)
         problem = AmplificationProblem(oracle, is_good_state=["11"])
-        grover = Grover(quantum_instance=self.qasm)
+        grover = self._prepare_grover(use_sampler)
         result = grover.amplify(problem)
-        self.assertEqual(result.max_probability, 1.0)
+        self.assertAlmostEqual(result.max_probability, 1.0)
 
-    def test_oracle_evaluation(self):
+    @data("ideal", "shots", False)
+    def test_oracle_evaluation(self, use_sampler):
         """Test oracle_evaluation for PhaseOracle"""
         oracle = PhaseOracle("x1 & x2 & (not x3)")
         problem = AmplificationProblem(oracle, is_good_state=oracle.evaluate_bitstring)
-        grover = Grover(quantum_instance=self.qasm)
+        grover = self._prepare_grover(use_sampler)
         result = grover.amplify(problem)
         self.assertTrue(result.oracle_evaluation)
         self.assertEqual("011", result.top_measurement)
 
+    def test_sampler_setter(self):
+        """Test sampler setter"""
+        grover = Grover()
+        grover.sampler = self._sampler
+        self.assertEqual(grover.sampler, self._sampler)
+
+    def _prepare_grover(self, use_sampler, iterations=None, growth_rate=None):
+        """Prepare Grover instance for test"""
+        if use_sampler == "ideal":
+            grover = Grover(sampler=self._sampler, iterations=iterations, growth_rate=growth_rate)
+        elif use_sampler == "shots":
+            grover = Grover(
+                sampler=self._sampler_with_shots, iterations=iterations, growth_rate=growth_rate
+            )
+        else:
+            with self.assertWarns(PendingDeprecationWarning):
+                grover = Grover(
+                    quantum_instance=self.qasm, iterations=iterations, growth_rate=growth_rate
+                )
+        return grover
+
 
 if __name__ == "__main__":
     unittest.main()