From 561fb090929c296a673222eff55d8d128ccc740c Mon Sep 17 00:00:00 2001 From: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com> Date: Sat, 15 May 2021 11:09:25 +0900 Subject: [PATCH 1/2] Fix GroverOptimizer's rotation_count (#132) * Fix rotation_count in GroverOptimizer * Simpify GroverOptimizer._measure * Simplify unit tests of GroverOptimizer (cherry picked from commit b0d411a912e26e1bbb4ae4a3e9845fa5ab489a0d) # Conflicts: # qiskit_optimization/algorithms/grover_optimizer.py # test/algorithms/test_grover_optimizer.py --- .../algorithms/grover_optimizer.py | 15 ++- ...rover-rotation-count-853baefc6b7e8476.yaml | 12 ++ test/algorithms/test_grover_optimizer.py | 113 +++++++++++++++++- 3 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/fix-grover-rotation-count-853baefc6b7e8476.yaml diff --git a/qiskit_optimization/algorithms/grover_optimizer.py b/qiskit_optimization/algorithms/grover_optimizer.py index ca14d64dd..868cdebf4 100644 --- a/qiskit_optimization/algorithms/grover_optimizer.py +++ b/qiskit_optimization/algorithms/grover_optimizer.py @@ -128,7 +128,7 @@ def _get_oracle(self, qr_key_value): oracle = QuantumCircuit(qr_key_value, oracle_bit) oracle.z(self._num_key_qubits) # recognize negative values. - def is_good_state(self, measurement): + def is_good_state(measurement): """Check whether ``measurement`` is a good state or not.""" value = measurement[self._num_key_qubits:self._num_key_qubits + self._num_value_qubits] return value[0] == '1' @@ -209,7 +209,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: while not improvement_found: # Determine the number of rotations. loops_with_no_improvement += 1 - rotation_count = int(np.ceil(algorithm_globals.random.uniform(0, m - 1))) + rotation_count = algorithm_globals.random.integers(0, m) rotations += rotation_count # Apply Grover's Algorithm to find values below the threshold. # TODO: Utilize Grover's incremental feature - requires changes to Grover. @@ -290,11 +290,15 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: def _measure(self, circuit: QuantumCircuit) -> str: """Get probabilities from the given backend, and picks a random outcome.""" probs = self._get_probs(circuit) - freq = sorted(probs.items(), key=lambda x: x[1], reverse=True) + logger.info("Frequencies: %s", probs) # Pick a random outcome. +<<<<<<< HEAD idx = algorithm_globals.random.choice(len(freq), 1, p=[x[1] for x in freq])[0] logger.info('Frequencies: %s', freq) return freq[idx][0] +======= + return algorithm_globals.random.choice(list(probs.keys()), 1, p=list(probs.values()))[0] +>>>>>>> b0d411a... Fix GroverOptimizer's rotation_count (#132) def _get_probs(self, qc: QuantumCircuit) -> Dict[str, float]: """Gets probabilities from a given backend.""" @@ -312,8 +316,13 @@ def _get_probs(self, qc: QuantumCircuit) -> Dict[str, float]: else: state = result.get_counts(qc) shots = self.quantum_instance.run_config.shots +<<<<<<< HEAD hist = {key[::-1]: val / shots for key, val in state.items() if val > 0} self._circuit_results = {b[::-1]: np.sqrt(v / shots) for (b, v) in state.items()} +======= + hist = {key[::-1]: val / shots for key, val in sorted(state.items()) if val > 0} + self._circuit_results = {b: (v / shots) ** 0.5 for (b, v) in state.items()} +>>>>>>> b0d411a... Fix GroverOptimizer's rotation_count (#132) return hist @staticmethod diff --git a/releasenotes/notes/fix-grover-rotation-count-853baefc6b7e8476.yaml b/releasenotes/notes/fix-grover-rotation-count-853baefc6b7e8476.yaml new file mode 100644 index 000000000..4ac4210b7 --- /dev/null +++ b/releasenotes/notes/fix-grover-rotation-count-853baefc6b7e8476.yaml @@ -0,0 +1,12 @@ +--- +fixes: + - | + Fixes ``rotation_count`` in :class`qiskit_optimization.algorithms.GroverOptimizer`. + This fix uses ``algorithm_globals.random.integers(0, m)`` to generate a random integer + in a range 0..``m``-1. + - | + Sorts the order of ``result.get_counts(qc)`` by bitstring + in :class`qiskit_optimization.algorithms.GroverOptimizer` when ``qasm_simulator`` is used + so that the algorithm behaves deterministically. + The previous version sorts the counts by probabilities, but some bitstrings may have + the same probability and the algorithm could behave probabilistically. diff --git a/test/algorithms/test_grover_optimizer.py b/test/algorithms/test_grover_optimizer.py index 0047a9628..a094c3375 100644 --- a/test/algorithms/test_grover_optimizer.py +++ b/test/algorithms/test_grover_optimizer.py @@ -38,10 +38,22 @@ class TestGroverOptimizer(QiskitOptimizationTestCase): def setUp(self): super().setUp() algorithm_globals.random_seed = 1 +<<<<<<< HEAD self.sv_simulator = QuantumInstance(Aer.get_backend('statevector_simulator'), seed_simulator=921, seed_transpiler=200) self.qasm_simulator = QuantumInstance(Aer.get_backend('qasm_simulator'), seed_simulator=123, seed_transpiler=123) +======= + self.sv_simulator = QuantumInstance( + Aer.get_backend("statevector_simulator"), + seed_simulator=921, + seed_transpiler=200, + ) + self.qasm_simulator = QuantumInstance( + Aer.get_backend("qasm_simulator"), seed_simulator=123, seed_transpiler=123 + ) + self.n_iter = 8 +>>>>>>> b0d411a... Fix GroverOptimizer's rotation_count (#132) def validate_results(self, problem, results): """Validate the results object returned by GroverOptimizer.""" @@ -84,8 +96,7 @@ def test_qubo_gas_int_simple(self): op.from_docplex(model) # Get the optimum key and value. - n_iter = 8 - gmf = GroverOptimizer(4, num_iterations=n_iter, quantum_instance=self.sv_simulator) + gmf = GroverOptimizer(4, num_iterations=self.n_iter, quantum_instance=self.sv_simulator) results = gmf.solve(op) self.validate_results(op, results) @@ -105,8 +116,7 @@ def test_qubo_gas_int_simple_maximize(self): op.from_docplex(model) # Get the optimum key and value. - n_iter = 8 - gmf = GroverOptimizer(4, num_iterations=n_iter, quantum_instance=self.sv_simulator) + gmf = GroverOptimizer(4, num_iterations=self.n_iter, quantum_instance=self.sv_simulator) results = gmf.solve(op) self.validate_results(op, results) @@ -127,10 +137,15 @@ def test_qubo_gas_int_paper_example(self, simulator): op.from_docplex(model) # Get the optimum key and value. +<<<<<<< HEAD n_iter = 10 q_instance = self.sv_simulator if simulator == 'sv' else self.qasm_simulator gmf = GroverOptimizer(6, num_iterations=n_iter, quantum_instance=q_instance) +======= + q_instance = self.sv_simulator if simulator == "sv" else self.qasm_simulator + gmf = GroverOptimizer(6, num_iterations=self.n_iter, quantum_instance=q_instance) +>>>>>>> b0d411a... Fix GroverOptimizer's rotation_count (#132) results = gmf.solve(op) self.validate_results(op, results) @@ -145,11 +160,19 @@ def test_converter_list(self): op.from_docplex(model) # Get the optimum key and value. - n_iter = 8 # a single converter. qp2qubo = QuadraticProgramToQubo() +<<<<<<< HEAD gmf = GroverOptimizer(4, num_iterations=n_iter, quantum_instance=self.sv_simulator, converters=qp2qubo) +======= + gmf = GroverOptimizer( + 4, + num_iterations=self.n_iter, + quantum_instance=self.sv_simulator, + converters=qp2qubo, + ) +>>>>>>> b0d411a... Fix GroverOptimizer's rotation_count (#132) results = gmf.solve(op) self.validate_results(op, results) # a list of converters @@ -157,20 +180,43 @@ def test_converter_list(self): int2bin = IntegerToBinary() penalize = LinearEqualityToPenalty() converters = [ineq2eq, int2bin, penalize] +<<<<<<< HEAD gmf = GroverOptimizer(4, num_iterations=n_iter, quantum_instance=self.sv_simulator, converters=converters) +======= + gmf = GroverOptimizer( + 4, + num_iterations=self.n_iter, + quantum_instance=self.sv_simulator, + converters=converters, + ) +>>>>>>> b0d411a... Fix GroverOptimizer's rotation_count (#132) results = gmf.solve(op) self.validate_results(op, results) # invalid converters with self.assertRaises(TypeError): invalid = [qp2qubo, "invalid converter"] +<<<<<<< HEAD GroverOptimizer(4, num_iterations=n_iter, quantum_instance=self.sv_simulator, converters=invalid) def test_samples_and_raw_samples(self): +======= + GroverOptimizer( + 4, + num_iterations=self.n_iter, + quantum_instance=self.sv_simulator, + converters=invalid, + ) + + @data("sv", "qasm") + def test_samples_and_raw_samples(self, simulator): +>>>>>>> b0d411a... Fix GroverOptimizer's rotation_count (#132) """Test samples and raw_samples""" + algorithm_globals.random_seed = 2 op = QuadraticProgram() +<<<<<<< HEAD op.integer_var(0, 3, 'x') op.binary_var('y') op.minimize(linear={'x': 1, 'y': 2}) @@ -189,6 +235,63 @@ def test_samples_and_raw_samples(self): self.assertAlmostEqual(min(s.fval for s in result.samples if s.status == success), opt_sol) self.assertAlmostEqual(min(s.fval for s in result.raw_samples), opt_sol) for sample in result.raw_samples: +======= + op.integer_var(0, 3, "x") + op.binary_var("y") + op.minimize(linear={"x": 1, "y": 2}) + op.linear_constraint(linear={"x": 1, "y": 1}, sense=">=", rhs=1, name="xy") + q_instance = self.sv_simulator if simulator == "sv" else self.qasm_simulator + grover_optimizer = GroverOptimizer( + 8, num_iterations=self.n_iter, quantum_instance=q_instance + ) + opt_sol = 1 + success = OptimizationResultStatus.SUCCESS + results = grover_optimizer.solve(op) + self.assertEqual(len(results.samples), 8) + self.assertEqual(len(results.raw_samples), 32) + self.assertAlmostEqual(sum(s.probability for s in results.samples), 1) + self.assertAlmostEqual(sum(s.probability for s in results.raw_samples), 1) + self.assertAlmostEqual(min(s.fval for s in results.samples), 0) + self.assertAlmostEqual(min(s.fval for s in results.samples if s.status == success), opt_sol) + self.assertAlmostEqual(min(s.fval for s in results.raw_samples), opt_sol) + for sample in results.raw_samples: + self.assertEqual(sample.status, success) + np.testing.assert_array_almost_equal(results.x, results.samples[0].x) + self.assertAlmostEqual(results.fval, results.samples[0].fval) + self.assertEqual(results.status, results.samples[0].status) + self.assertAlmostEqual(results.fval, results.raw_samples[0].fval) + self.assertEqual(results.status, results.raw_samples[0].status) + np.testing.assert_array_almost_equal([1, 0, 0, 0, 0], results.raw_samples[0].x) + + @data("sv", "qasm") + def test_bit_ordering(self, simulator): + """Test bit ordering""" + # test minimize + algorithm_globals.random_seed = 2 + q_instance = self.sv_simulator if simulator == "sv" else self.qasm_simulator + mdl = Model("docplex model") + x = mdl.binary_var("x") + y = mdl.binary_var("y") + mdl.minimize(x - 2 * y) + op = QuadraticProgram() + op.from_docplex(mdl) + opt_sol = -2 + success = OptimizationResultStatus.SUCCESS + grover_optimizer = GroverOptimizer( + 3, num_iterations=self.n_iter, quantum_instance=q_instance + ) + results = grover_optimizer.solve(op) + self.assertEqual(results.fval, opt_sol) + np.testing.assert_array_almost_equal(results.x, [0, 1]) + self.assertEqual(results.status, success) + results.raw_samples.sort(key=lambda x: x.probability, reverse=True) + self.assertAlmostEqual(sum(s.probability for s in results.samples), 1, delta=1e-5) + self.assertAlmostEqual(sum(s.probability for s in results.raw_samples), 1, delta=1e-5) + self.assertAlmostEqual(min(s.fval for s in results.samples), -2) + self.assertAlmostEqual(min(s.fval for s in results.samples if s.status == success), opt_sol) + self.assertAlmostEqual(min(s.fval for s in results.raw_samples), opt_sol) + for sample in results.raw_samples: +>>>>>>> b0d411a... Fix GroverOptimizer's rotation_count (#132) self.assertEqual(sample.status, success) np.testing.assert_array_almost_equal(result.x, result.raw_samples[0].x[0:2]) self.assertAlmostEqual(result.fval, result.raw_samples[0].fval) From 74531fd86d8ab4c1fe4f32afd350cce0acc34019 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Sat, 15 May 2021 12:39:27 +0900 Subject: [PATCH 2/2] resolve conflict --- .../algorithms/grover_optimizer.py | 11 -- test/algorithms/test_grover_optimizer.py | 113 +++++------------- 2 files changed, 31 insertions(+), 93 deletions(-) diff --git a/qiskit_optimization/algorithms/grover_optimizer.py b/qiskit_optimization/algorithms/grover_optimizer.py index 868cdebf4..8884da37c 100644 --- a/qiskit_optimization/algorithms/grover_optimizer.py +++ b/qiskit_optimization/algorithms/grover_optimizer.py @@ -292,13 +292,7 @@ def _measure(self, circuit: QuantumCircuit) -> str: probs = self._get_probs(circuit) logger.info("Frequencies: %s", probs) # Pick a random outcome. -<<<<<<< HEAD - idx = algorithm_globals.random.choice(len(freq), 1, p=[x[1] for x in freq])[0] - logger.info('Frequencies: %s', freq) - return freq[idx][0] -======= return algorithm_globals.random.choice(list(probs.keys()), 1, p=list(probs.values()))[0] ->>>>>>> b0d411a... Fix GroverOptimizer's rotation_count (#132) def _get_probs(self, qc: QuantumCircuit) -> Dict[str, float]: """Gets probabilities from a given backend.""" @@ -316,13 +310,8 @@ def _get_probs(self, qc: QuantumCircuit) -> Dict[str, float]: else: state = result.get_counts(qc) shots = self.quantum_instance.run_config.shots -<<<<<<< HEAD - hist = {key[::-1]: val / shots for key, val in state.items() if val > 0} - self._circuit_results = {b[::-1]: np.sqrt(v / shots) for (b, v) in state.items()} -======= hist = {key[::-1]: val / shots for key, val in sorted(state.items()) if val > 0} self._circuit_results = {b: (v / shots) ** 0.5 for (b, v) in state.items()} ->>>>>>> b0d411a... Fix GroverOptimizer's rotation_count (#132) return hist @staticmethod diff --git a/test/algorithms/test_grover_optimizer.py b/test/algorithms/test_grover_optimizer.py index a094c3375..6b21bfba0 100644 --- a/test/algorithms/test_grover_optimizer.py +++ b/test/algorithms/test_grover_optimizer.py @@ -21,13 +21,17 @@ from qiskit import Aer from qiskit.utils import QuantumInstance, algorithm_globals from qiskit.algorithms import NumPyMinimumEigensolver -from qiskit_optimization.algorithms import (GroverOptimizer, - MinimumEigenOptimizer, - OptimizationResultStatus) -from qiskit_optimization.converters import (InequalityToEquality, - IntegerToBinary, - LinearEqualityToPenalty, - QuadraticProgramToQubo) +from qiskit_optimization.algorithms import ( + GroverOptimizer, + MinimumEigenOptimizer, + OptimizationResultStatus, +) +from qiskit_optimization.converters import ( + InequalityToEquality, + IntegerToBinary, + LinearEqualityToPenalty, + QuadraticProgramToQubo, +) from qiskit_optimization.problems import QuadraticProgram @@ -38,12 +42,6 @@ class TestGroverOptimizer(QiskitOptimizationTestCase): def setUp(self): super().setUp() algorithm_globals.random_seed = 1 -<<<<<<< HEAD - self.sv_simulator = QuantumInstance(Aer.get_backend('statevector_simulator'), - seed_simulator=921, seed_transpiler=200) - self.qasm_simulator = QuantumInstance(Aer.get_backend('qasm_simulator'), - seed_simulator=123, seed_transpiler=123) -======= self.sv_simulator = QuantumInstance( Aer.get_backend("statevector_simulator"), seed_simulator=921, @@ -53,14 +51,12 @@ def setUp(self): Aer.get_backend("qasm_simulator"), seed_simulator=123, seed_transpiler=123 ) self.n_iter = 8 ->>>>>>> b0d411a... Fix GroverOptimizer's rotation_count (#132) def validate_results(self, problem, results): """Validate the results object returned by GroverOptimizer.""" # Get expected value. solver = MinimumEigenOptimizer(NumPyMinimumEigensolver()) comp_result = solver.solve(problem) - # Validate results. np.testing.assert_array_almost_equal(comp_result.x, results.x) self.assertEqual(comp_result.fval, results.fval) @@ -71,9 +67,9 @@ def test_qubo_gas_int_zero(self): # Input. model = Model() - x_0 = model.binary_var(name='x0') - x_1 = model.binary_var(name='x1') - model.minimize(0*x_0+0*x_1) + x_0 = model.binary_var(name="x0") + x_1 = model.binary_var(name="x1") + model.minimize(0 * x_0 + 0 * x_1) op = QuadraticProgram() op.from_docplex(model) @@ -89,9 +85,9 @@ def test_qubo_gas_int_simple(self): # Input. model = Model() - x_0 = model.binary_var(name='x0') - x_1 = model.binary_var(name='x1') - model.minimize(-x_0+2*x_1) + x_0 = model.binary_var(name="x0") + x_1 = model.binary_var(name="x1") + model.minimize(-x_0 + 2 * x_1) op = QuadraticProgram() op.from_docplex(model) @@ -109,9 +105,9 @@ def test_qubo_gas_int_simple_maximize(self): # Input. model = Model() - x_0 = model.binary_var(name='x0') - x_1 = model.binary_var(name='x1') - model.maximize(-x_0+2*x_1) + x_0 = model.binary_var(name="x0") + x_1 = model.binary_var(name="x1") + model.maximize(-x_0 + 2 * x_1) op = QuadraticProgram() op.from_docplex(model) @@ -120,7 +116,7 @@ def test_qubo_gas_int_simple_maximize(self): results = gmf.solve(op) self.validate_results(op, results) - @data('sv', 'qasm') + @data("sv", "qasm") def test_qubo_gas_int_paper_example(self, simulator): """ Test the example from https://arxiv.org/abs/1912.04088 using the state vector simulator @@ -129,80 +125,58 @@ def test_qubo_gas_int_paper_example(self, simulator): # Input. model = Model() - x_0 = model.binary_var(name='x0') - x_1 = model.binary_var(name='x1') - x_2 = model.binary_var(name='x2') - model.minimize(-x_0+2*x_1-3*x_2-2*x_0*x_2-1*x_1*x_2) + x_0 = model.binary_var(name="x0") + x_1 = model.binary_var(name="x1") + x_2 = model.binary_var(name="x2") + model.minimize(-x_0 + 2 * x_1 - 3 * x_2 - 2 * x_0 * x_2 - 1 * x_1 * x_2) op = QuadraticProgram() op.from_docplex(model) # Get the optimum key and value. -<<<<<<< HEAD - n_iter = 10 - - q_instance = self.sv_simulator if simulator == 'sv' else self.qasm_simulator - gmf = GroverOptimizer(6, num_iterations=n_iter, quantum_instance=q_instance) -======= q_instance = self.sv_simulator if simulator == "sv" else self.qasm_simulator gmf = GroverOptimizer(6, num_iterations=self.n_iter, quantum_instance=q_instance) ->>>>>>> b0d411a... Fix GroverOptimizer's rotation_count (#132) results = gmf.solve(op) self.validate_results(op, results) def test_converter_list(self): """Test converters list""" # Input. + model = Model() - x_0 = model.binary_var(name='x0') - x_1 = model.binary_var(name='x1') - model.maximize(-x_0+2*x_1) + x_0 = model.binary_var(name="x0") + x_1 = model.binary_var(name="x1") + model.maximize(-x_0 + 2 * x_1) op = QuadraticProgram() op.from_docplex(model) # Get the optimum key and value. # a single converter. qp2qubo = QuadraticProgramToQubo() -<<<<<<< HEAD - gmf = GroverOptimizer(4, num_iterations=n_iter, quantum_instance=self.sv_simulator, - converters=qp2qubo) -======= gmf = GroverOptimizer( 4, num_iterations=self.n_iter, quantum_instance=self.sv_simulator, converters=qp2qubo, ) ->>>>>>> b0d411a... Fix GroverOptimizer's rotation_count (#132) results = gmf.solve(op) self.validate_results(op, results) + # a list of converters ineq2eq = InequalityToEquality() int2bin = IntegerToBinary() penalize = LinearEqualityToPenalty() converters = [ineq2eq, int2bin, penalize] -<<<<<<< HEAD - gmf = GroverOptimizer(4, num_iterations=n_iter, quantum_instance=self.sv_simulator, - converters=converters) -======= gmf = GroverOptimizer( 4, num_iterations=self.n_iter, quantum_instance=self.sv_simulator, converters=converters, ) ->>>>>>> b0d411a... Fix GroverOptimizer's rotation_count (#132) results = gmf.solve(op) self.validate_results(op, results) # invalid converters with self.assertRaises(TypeError): invalid = [qp2qubo, "invalid converter"] -<<<<<<< HEAD - GroverOptimizer(4, num_iterations=n_iter, - quantum_instance=self.sv_simulator, - converters=invalid) - - def test_samples_and_raw_samples(self): -======= GroverOptimizer( 4, num_iterations=self.n_iter, @@ -212,30 +186,9 @@ def test_samples_and_raw_samples(self): @data("sv", "qasm") def test_samples_and_raw_samples(self, simulator): ->>>>>>> b0d411a... Fix GroverOptimizer's rotation_count (#132) """Test samples and raw_samples""" algorithm_globals.random_seed = 2 op = QuadraticProgram() -<<<<<<< HEAD - op.integer_var(0, 3, 'x') - op.binary_var('y') - op.minimize(linear={'x': 1, 'y': 2}) - op.linear_constraint(linear={'x': 1, 'y': 1}, sense='>=', rhs=1, name='xy') - opt_sol = 1 - success = OptimizationResultStatus.SUCCESS - algorithm_globals.random_seed = 1 - grover_optimizer = GroverOptimizer( - 8, num_iterations=5, quantum_instance=self.qasm_simulator) - result = grover_optimizer.solve(op) - self.assertEqual(len(result.samples), 8) - self.assertEqual(len(result.raw_samples), 32) - self.assertAlmostEqual(sum(s.probability for s in result.samples), 1) - self.assertAlmostEqual(sum(s.probability for s in result.raw_samples), 1) - self.assertAlmostEqual(min(s.fval for s in result.samples), 0) - self.assertAlmostEqual(min(s.fval for s in result.samples if s.status == success), opt_sol) - self.assertAlmostEqual(min(s.fval for s in result.raw_samples), opt_sol) - for sample in result.raw_samples: -======= op.integer_var(0, 3, "x") op.binary_var("y") op.minimize(linear={"x": 1, "y": 2}) @@ -291,12 +244,8 @@ def test_bit_ordering(self, simulator): self.assertAlmostEqual(min(s.fval for s in results.samples if s.status == success), opt_sol) self.assertAlmostEqual(min(s.fval for s in results.raw_samples), opt_sol) for sample in results.raw_samples: ->>>>>>> b0d411a... Fix GroverOptimizer's rotation_count (#132) self.assertEqual(sample.status, success) - np.testing.assert_array_almost_equal(result.x, result.raw_samples[0].x[0:2]) - self.assertAlmostEqual(result.fval, result.raw_samples[0].fval) - self.assertEqual(result.status, result.raw_samples[0].status) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main()