Repository: Qiskit/qiskit

Issue:
Allow running Grover without `is_good_state` if the number of iterations is provided
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues to confirm this idea does not exist. -->

### What is the expected behavior?

Allow running Grover without `is_good_state` if the number of iterations is provided and just return the most likely state in that case.



Discussion:

Resolving patch:
diff --git a/qiskit/algorithms/amplitude_amplifiers/amplification_problem.py b/qiskit/algorithms/amplitude_amplifiers/amplification_problem.py
--- a/qiskit/algorithms/amplitude_amplifiers/amplification_problem.py
+++ b/qiskit/algorithms/amplitude_amplifiers/amplification_problem.py
@@ -54,9 +54,6 @@ def __init__(
                 if the ``oracle`` argument has an ``evaluate_bitstring`` method (currently only
                 provided by the :class:`~qiskit.circuit.library.PhaseOracle` class) this will be
                 used, otherwise this kwarg is required and **must** be specified.
-
-        Raises:
-            TypeError: if ``is_good_state`` is not provided and is required
         """
         self._oracle = oracle
         self._state_preparation = state_preparation
@@ -68,7 +65,7 @@ def __init__(
         elif hasattr(oracle, "evaluate_bitstring"):
             self._is_good_state = oracle.evaluate_bitstring
         else:
-            raise TypeError("A is_good_state function is required with the provided oracle")
+            self._is_good_state = None
 
     @property
     def oracle(self) -> Union[QuantumCircuit, Statevector]:
@@ -166,8 +163,8 @@ def is_good_state(self) -> Callable[[str], bool]:
             A callable that takes in a bitstring and returns True if the measurement is a good
             state, False otherwise.
         """
-        if callable(self._is_good_state):
-            return self._is_good_state
+        if (self._is_good_state is None) or callable(self._is_good_state):
+            return self._is_good_state  # returns None if no is_good_state arg has been set
         elif isinstance(self._is_good_state, list):
             if all(isinstance(good_bitstr, str) for good_bitstr in self._is_good_state):
                 return lambda bitstr: bitstr in self._is_good_state
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
@@ -111,11 +111,13 @@ def __init__(
         Args:
             iterations: Specify the number of iterations/power of Grover's operator to be checked.
                 * If an int, only one circuit is run with that power of the Grover operator.
-                If the number of solutions is known, this is option should be used with the optimal
+                If the number of solutions is known, this option should be used with the optimal
                 power. The optimal power can be computed with ``Grover.optimal_num_iterations``.
                 * If a list, all the powers in the list are run in the specified order.
                 * If an iterator, the powers yielded by the iterator are checked, until a maximum
                 number of iterations or maximum power is reached.
+                * If ``None``, the :obj:`AmplificationProblem` provided must have an ``is_good_state``,
+                and circuits are run until that good state is reached.
             growth_rate: If specified, the iterator is set to increasing powers of ``growth_rate``,
                 i.e. to ``int(growth_rate ** 1), int(growth_rate ** 2), ...`` until a maximum
                 number of iterations is reached.
@@ -152,6 +154,7 @@ def __init__(
             self.quantum_instance = quantum_instance
 
         self._sample_from_iterations = sample_from_iterations
+        self._iterations_arg = iterations
 
     @property
     def quantum_instance(self) -> Optional[QuantumInstance]:
@@ -182,6 +185,10 @@ def amplify(self, amplification_problem: AmplificationProblem) -> "GroverResult"
         Returns:
             The result as a ``GroverResult``, where e.g. the most likely state can be queried
             as ``result.top_measurement``.
+
+        Raises:
+            TypeError: If ``is_good_state`` is not provided and is required (i.e. when iterations
+            is ``None`` or a ``list``)
         """
         if isinstance(self._iterations, list):
             max_iterations = len(self._iterations)
@@ -246,7 +253,22 @@ def amplify(self, amplification_problem: AmplificationProblem) -> "GroverResult"
                 )
 
             all_circuit_results.append(circuit_results)
+
+            if (isinstance(self._iterations_arg, int)) and (
+                amplification_problem.is_good_state is None
+            ):
+                oracle_evaluation = None  # cannot check for good state without is_good_state arg
+                break
+
+            # is_good_state arg must be provided if iterations arg is not an integer
+            if (
+                self._iterations_arg is None or isinstance(self._iterations_arg, list)
+            ) and amplification_problem.is_good_state is None:
+                raise TypeError("An is_good_state function is required with the provided oracle")
+
+            # only check if top measurement is a good state if an is_good_state arg is provided
             oracle_evaluation = amplification_problem.is_good_state(top_measurement)
+
             if oracle_evaluation is True:
                 break  # we found a solution
 


Test patch:
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
@@ -83,6 +83,7 @@ def is_good_state(bitstr):
         self.assertListEqual(expected, actual)
 
 
+@ddt
 class TestGrover(QiskitAlgorithmsTestCase):
     """Test for the functionality of Grover"""
 
@@ -103,20 +104,31 @@ def test_implicit_phase_oracle_is_good_state(self):
         result = grover.amplify(problem)
         self.assertEqual(result.top_measurement, "0")
 
-    def test_fixed_iterations(self):
-        """Test the iterations argument"""
-        grover = Grover(iterations=2, quantum_instance=self.statevector)
+    @data([1, 2, 3], None, 2)
+    def test_iterations_with_good_state(self, iterations):
+        """Test the algorithm with different iteration types and with good state"""
+        grover = Grover(iterations, quantum_instance=self.statevector)
         problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"])
         result = grover.amplify(problem)
         self.assertEqual(result.top_measurement, "111")
 
-    def test_multiple_iterations(self):
-        """Test the algorithm for a list of iterations."""
-        grover = Grover(iterations=[1, 2, 3], quantum_instance=self.statevector)
-        problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"])
+    def test_fixed_iterations_without_good_state(self):
+        """Test the algorithm with iterations as an int and without good state"""
+        grover = Grover(iterations=2, quantum_instance=self.statevector)
+        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):
+        """Test the correct error is thrown for none/list of iterations and without good state"""
+        grover = Grover(iterations, quantum_instance=self.statevector)
+        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):
         """Test running the algorithm on an iterator."""