Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .pylintdict
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ qp
quadratically
quadraticconstraint
quadraticprogram
quantuminstance
qubit
qubits
qubo
Expand Down
29 changes: 14 additions & 15 deletions qiskit_optimization/algorithms/grover_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ def __init__(self, num_value_qubits: int, num_iterations: int = 3,
TypeError: When there one of converters is an invalid type.
"""
self._num_value_qubits = num_value_qubits
self._num_key_qubits = None
self._num_key_qubits = 0
self._n_iterations = num_iterations
self._quantum_instance = None
self._circuit_results = {} # type: ignore
self._quantum_instance = None # type: Optional[QuantumInstance]
self._circuit_results = {} # type: dict

if quantum_instance is not None:
self.quantum_instance = quantum_instance
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -168,7 +168,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult:
problem_.objective.linear[i] = -val
for (i, j), val in problem_.objective.quadratic.to_dict().items():
problem_.objective.quadratic[i, j] = -val
self._num_key_qubits = len(problem_.objective.linear.to_array()) # type: ignore
self._num_key_qubits = len(problem_.objective.linear.to_array())

# Variables for tracking the optimum.
optimum_found = False
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -237,13 +237,14 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult:
threshold = optimum_value

# trace out work qubits and store samples
if self._quantum_instance.is_statevector: # type: ignore
if self._quantum_instance.is_statevector:
indices = list(range(n_key, len(outcome)))
rho = partial_trace(self._circuit_results, indices)
self._circuit_results = np.diag(rho.data) ** 0.5
else:
self._circuit_results = {i[0:n_key]: v for i,
v in self._circuit_results.items()}
self._circuit_results = {
i[-1 * n_key:]: v for i, v in self._circuit_results.items()
}

raw_samples = self._eigenvector_to_solutions(self._circuit_results,
problem_init)
Expand Down Expand Up @@ -290,11 +291,9 @@ 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.
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]

def _get_probs(self, qc: QuantumCircuit) -> Dict[str, float]:
"""Gets probabilities from a given backend."""
Expand All @@ -312,8 +311,8 @@ def _get_probs(self, qc: QuantumCircuit) -> Dict[str, float]:
else:
state = result.get_counts(qc)
shots = self.quantum_instance.run_config.shots
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()}
return hist

@staticmethod
Expand Down
13 changes: 7 additions & 6 deletions qiskit_optimization/algorithms/optimization_algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,22 +503,23 @@ def _eigenvector_to_solutions(eigenvector: Union[dict, np.ndarray, StateFn],
TypeError: If the type of eigenvector is not supported.
"""
if isinstance(eigenvector, DictStateFn):
eigenvector = {bitstr: val ** 2 for (bitstr, val) in eigenvector.primitive.items()}
eigenvector = eigenvector.primitive
elif isinstance(eigenvector, StateFn):
eigenvector = eigenvector.to_matrix()

def generate_solution(bitstr, qubo, probability):
x = np.fromiter(list(bitstr), dtype=int)
x = np.fromiter(list(bitstr[::-1]), dtype=int)
fval = qubo.objective.evaluate(x)
return SolutionSample(x=x, fval=fval, probability=probability,
status=OptimizationResultStatus.SUCCESS)

solutions = []
if isinstance(eigenvector, dict):
all_counts = sum(eigenvector.values())
# When eigenvector is a dict, square the values since the values are normalized.
# See https://github.com/Qiskit/qiskit-terra/pull/5496 for more details.
probabilities = {bitstr: val ** 2 for (bitstr, val) in eigenvector.items()}
# iterate over all samples
for bitstr, count in eigenvector.items():
sampling_probability = count / all_counts
for bitstr, sampling_probability in probabilities.items():
# add the bitstring, if the sampling probability exceeds the threshold
if sampling_probability >= min_probability:
solutions.append(generate_solution(bitstr, qubo, sampling_probability))
Expand All @@ -531,7 +532,7 @@ def generate_solution(bitstr, qubo, probability):
for i, sampling_probability in enumerate(probabilities):
# add the i-th state if the sampling probability exceeds the threshold
if sampling_probability >= min_probability:
bitstr = '{:b}'.format(i).rjust(num_qubits, '0')[::-1]
bitstr = "{:b}".format(i).rjust(num_qubits, "0")
solutions.append(generate_solution(bitstr, qubo, sampling_probability))

else:
Expand Down
12 changes: 10 additions & 2 deletions qiskit_optimization/problems/quadratic_program.py
Original file line number Diff line number Diff line change
Expand Up @@ -1153,7 +1153,7 @@ def substitute_variables(
Returns:
An optimization problem by substituting variables with constants or other variables.
If the substitution is valid, `QuadraticProgram.status` is still
`QuadraticProgram.Status.VALIAD`.
`QuadraticProgram.Status.VALID`.
Otherwise, it gets `QuadraticProgram.Status.INFEASIBLE`.

Raises:
Expand All @@ -1166,6 +1166,10 @@ def substitute_variables(
def to_ising(self) -> Tuple[OperatorBase, float]:
"""Return the Ising Hamiltonian of this problem.

Variables are mapped to qubits in the same order, i.e.,
i-th variable is mapped to i-th qubit.
See https://github.com/Qiskit/qiskit-terra/issues/1148 for details.

Returns:
qubit_op: The qubit operator for the problem
offset: The constant value in the Ising Hamiltonian.
Expand Down Expand Up @@ -1265,6 +1269,10 @@ def from_ising(self,
offset: float = 0.0, linear: bool = False) -> None:
r"""Create a quadratic program from a qubit operator and a shift value.

Variables are mapped to qubits in the same order, i.e.,
i-th variable is mapped to i-th qubit.
See https://github.com/Qiskit/qiskit-terra/issues/1148 for details.

Args:
qubit_op: The qubit operator of the problem.
offset: The constant value in the Ising Hamiltonian.
Expand Down Expand Up @@ -1464,7 +1472,7 @@ def substitute_variables(
Returns:
An optimization problem by substituting variables with constants or other variables.
If the substitution is valid, `QuadraticProgram.status` is still
`QuadraticProgram.Status.VALIAD`.
`QuadraticProgram.Status.VALID`.
Otherwise, it gets `QuadraticProgram.Status.INFEASIBLE`.

Raises:
Expand Down
8 changes: 8 additions & 0 deletions releasenotes/notes/fix-bit-ordering-e807ec9f4b206ec3.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
fixes:
- |
Fix bit ordering in :class`qiskit_optimization.algorithms.minimum_eigen_optimizer.MinimumEigenOptimizer`
with qasm_simulator.
- |
Fix probabilities of solution samples with qasm_simulator.
See https://github.com/Qiskit/qiskit-optimization/pull/97 for details.
Original file line number Diff line number Diff line change
@@ -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.
130 changes: 89 additions & 41 deletions test/algorithms/test_grover_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,21 @@ class TestGroverOptimizer(QiskitOptimizationTestCase):
def setUp(self):
super().setUp()
algorithm_globals.random_seed = 1
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

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)
Expand Down Expand Up @@ -84,8 +88,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)

Expand All @@ -105,8 +108,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)

Expand All @@ -127,16 +129,15 @@ def test_qubo_gas_int_paper_example(self, simulator):
op.from_docplex(model)

# Get the optimum key and value.
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)
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')
Expand All @@ -145,54 +146,101 @@ def test_converter_list(self):
op.from_docplex(model)

# Get the optimum key and value.
n_iter = 8
# a single converter.
qp2qubo = QuadraticProgramToQubo()
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,
)
results = gmf.solve(op)
self.validate_results(op, results)

# a list of converters
ineq2eq = InequalityToEquality()
int2bin = IntegerToBinary()
penalize = LinearEqualityToPenalty()
converters = [ineq2eq, int2bin, penalize]
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,
)
results = gmf.solve(op)
self.validate_results(op, results)
# invalid converters
with self.assertRaises(TypeError):
invalid = [qp2qubo, "invalid converter"]
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):
"""Test samples and raw_samples"""
algorithm_globals.random_seed = 2
op = QuadraticProgram()
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')
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
algorithm_globals.random_seed = 1
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(
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:
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:
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__':
Expand Down
Loading