Skip to content

Commit

Permalink
Converts a minimization problem to maximization in `GoemansWilliamson…
Browse files Browse the repository at this point in the history
…Optimizer` (#144)

* fix gw and stuff

* fixed warm start qaoa test

* added reno

* fix black
  • Loading branch information
adekusar-drl authored May 25, 2021
1 parent fc98f8f commit 3ccb48f
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 117 deletions.
22 changes: 14 additions & 8 deletions qiskit_optimization/algorithms/goemans_williamson_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
Requires CVXPY to run.
"""
import logging
from typing import Optional, List, Tuple, Union
from typing import Optional, List, Tuple, Union, cast

import numpy as np
from qiskit.exceptions import MissingOptionalLibraryError
Expand All @@ -26,6 +26,7 @@
OptimizationAlgorithm,
SolutionSample,
)
from ..converters.flip_problem_sense import MinimizeToMaximize
from ..problems.quadratic_program import QuadraticProgram
from ..problems.variable import Variable

Expand Down Expand Up @@ -149,6 +150,9 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult:
"""
self._verify_compatibility(problem)

min2max = MinimizeToMaximize()
problem = min2max.convert(problem)

adj_matrix = self._extract_adjacency_matrix(problem)

try:
Expand Down Expand Up @@ -186,13 +190,15 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult:
for solution in numeric_solutions
]

return GoemansWilliamsonOptimizationResult(
x=samples[0].x,
fval=samples[0].fval,
variables=problem.variables,
status=OptimizationResultStatus.SUCCESS,
samples=samples,
sdp_solution=chi,
return cast(
GoemansWilliamsonOptimizationResult,
self._interpret(
x=samples[0].x,
problem=problem,
converters=[min2max],
result_class=GoemansWilliamsonOptimizationResult,
samples=samples,
),
)

def _get_unique_cuts(
Expand Down
5 changes: 4 additions & 1 deletion qiskit_optimization/converters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,16 @@
IntegerToBinary
LinearEqualityToPenalty
MaximizeToMinimize
MinimizeToMaximize
QuadraticProgramToQubo
"""

from .integer_to_binary import IntegerToBinary
from .inequality_to_equality import InequalityToEquality
from .linear_equality_to_penalty import LinearEqualityToPenalty
from .maximize_to_minimize import MaximizeToMinimize
from .flip_problem_sense import MaximizeToMinimize
from .flip_problem_sense import MinimizeToMaximize
from .quadratic_program_to_qubo import QuadraticProgramToQubo
from .quadratic_program_converter import QuadraticProgramConverter

Expand All @@ -57,6 +59,7 @@
"IntegerToBinary",
"LinearEqualityToPenalty",
"MaximizeToMinimize",
"MinimizeToMaximize",
"QuadraticProgramConverter",
"QuadraticProgramToQubo",
]
109 changes: 109 additions & 0 deletions qiskit_optimization/converters/flip_problem_sense.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# 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
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Converters to flip problem sense, e.g. maximization to minimization and vice versa."""

import copy
from typing import Optional, List, Union

import numpy as np

from .quadratic_program_converter import QuadraticProgramConverter
from ..exceptions import QiskitOptimizationError
from ..problems.quadratic_objective import ObjSense
from ..problems.quadratic_program import QuadraticProgram


class _FlipProblemSense(QuadraticProgramConverter):
"""Flip the sense of a problem, e.g. converts from maximization to minimization and
vice versa, regardless of the current sense."""

def __init__(self) -> None:
self._src_num_vars = None # type: Optional[int]

def convert(self, problem: QuadraticProgram) -> QuadraticProgram:
"""Flip the sense of a problem.
Args:
problem: The problem to be flipped.
Returns:
A converted problem, that has the flipped sense.
"""

# copy original number of variables as reference.
self._src_num_vars = problem.get_num_vars()
desired_sense = self._get_desired_sense(problem)

# flip the problem sense
if problem.objective.sense != desired_sense:
desired_problem = copy.deepcopy(problem)
desired_problem.objective.sense = desired_sense
desired_problem.objective.constant = (-1) * problem.objective.constant
desired_problem.objective.linear = (-1) * problem.objective.linear.coefficients
desired_problem.objective.quadratic = (-1) * problem.objective.quadratic.coefficients
else:
desired_problem = problem

return desired_problem

def _get_desired_sense(self, problem: QuadraticProgram) -> ObjSense:
"""
Computes a desired sense of the problem. By default, flip the sense.
Args:
problem: a problem to check
Returns:
A desired sense, if the problem was a minimization problem, then the sense is
maximization and vice versa.
"""
if problem.objective.sense == ObjSense.MAXIMIZE:
return ObjSense.MINIMIZE
else:
return ObjSense.MAXIMIZE

def interpret(self, x: Union[np.ndarray, List[float]]) -> np.ndarray:
"""Convert the result of the converted problem back to that of the original problem.
Args:
x: The result of the converted problem or the given result in case of FAILURE.
Returns:
The result of the original problem.
Raises:
QiskitOptimizationError: if the number of variables in the result differs from
that of the original problem.
"""
if len(x) != self._src_num_vars:
raise QiskitOptimizationError(
f"The number of variables in the passed result differs from "
f"that of the original problem, should be {self._src_num_vars}, but got {len(x)}."
)
return np.asarray(x)


class MaximizeToMinimize(_FlipProblemSense):
"""Convert a maximization problem to a minimization problem only if it is a maximization
problem, otherwise problem's sense is unchanged."""

def _get_desired_sense(self, problem: QuadraticProgram) -> ObjSense:
return ObjSense.MINIMIZE


class MinimizeToMaximize(_FlipProblemSense):
"""Convert a minimization problem to a maximization problem only if it is a minimization
problem, otherwise problem's sense is unchanged."""

def _get_desired_sense(self, problem: QuadraticProgram) -> ObjSense:
return ObjSense.MAXIMIZE
76 changes: 0 additions & 76 deletions qiskit_optimization/converters/maximize_to_minimize.py

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def __init__(self, penalty: Optional[float] = None) -> None:
from ..converters.integer_to_binary import IntegerToBinary
from ..converters.inequality_to_equality import InequalityToEquality
from ..converters.linear_equality_to_penalty import LinearEqualityToPenalty
from ..converters.maximize_to_minimize import MaximizeToMinimize
from ..converters.flip_problem_sense import MaximizeToMinimize

self._int_to_bin = IntegerToBinary()
self._ineq_to_eq = InequalityToEquality(mode="integer")
Expand Down
9 changes: 9 additions & 0 deletions releasenotes/notes/fix-gw-0c0cd70d0aa44a2e.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
features:
- |
Introduced a new converter class ``MinimizeToMaximize`` for ``QuadraticProgram``.
It converts a problem to a maximization problem.
fixes:
- |
Fixes the Goemans-Williamson optimizer. If a minimization problem is passed to the optimizer,
then it is converted to a maximization problem and then solved.
30 changes: 25 additions & 5 deletions test/algorithms/test_goemans_williamson_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@
GoemansWilliamsonOptimizationResult,
)
from qiskit_optimization.applications.max_cut import Maxcut
from qiskit_optimization.converters import MaximizeToMinimize


class TestGoemansWilliamson(QiskitOptimizationTestCase):
"""Test Goemans-Williamson optimizer."""

@requires_extra_library
def test_all_cuts(self):
"""Basic test of the Goemans-Williamson optimizer."""
graph = np.array(
def setUp(self) -> None:
super().setUp()
self.graph = np.array(
[
[0.0, 1.0, 2.0, 0.0],
[1.0, 0.0, 1.0, 0.0],
Expand All @@ -37,9 +37,13 @@ def test_all_cuts(self):
]
)

@requires_extra_library
def test_all_cuts(self):
"""Basic test of the Goemans-Williamson optimizer."""

optimizer = GoemansWilliamsonOptimizer(num_cuts=10, seed=0)

problem = Maxcut(graph).to_quadratic_program()
problem = Maxcut(self.graph).to_quadratic_program()
self.assertIsNotNone(problem)

results = optimizer.solve(problem)
Expand All @@ -54,3 +58,19 @@ def test_all_cuts(self):

self.assertIsNotNone(results.samples)
self.assertEqual(3, len(results.samples))

@requires_extra_library
def test_minimization_problem(self):
"""Tests the optimizer with a minimization problem"""
optimizer = GoemansWilliamsonOptimizer(num_cuts=10, seed=0)

problem = Maxcut(self.graph).to_quadratic_program()

# artificially convert to minimization
max2min = MaximizeToMinimize()
problem = max2min.convert(problem)

results = optimizer.solve(problem)

np.testing.assert_almost_equal([0, 1, 1, 0], results.x, 3)
np.testing.assert_almost_equal(4, results.fval, 3)
1 change: 0 additions & 1 deletion test/algorithms/test_warm_start_qaoa.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ def test_max_cut(self):
epsilon=0.25,
num_initial_solutions=10,
aggregator=aggregator,
converters=[],
)
result_warm = optimizer.solve(problem)

Expand Down
25 changes: 0 additions & 25 deletions test/converters/test_converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,31 +352,6 @@ def test_penalize_integer(self):
new_x = conv.interpret([0, 1, -1])
np.testing.assert_array_almost_equal(new_x, [0, 1, -1])

def test_maximize_to_minimize(self):
"""Test maximization to minimization conversion"""
op_max = QuadraticProgram()
op_min = QuadraticProgram()
for i in range(2):
op_max.binary_var(name="x{}".format(i))
op_min.binary_var(name="x{}".format(i))
op_max.integer_var(name="x{}".format(2), lowerbound=-3, upperbound=3)
op_min.integer_var(name="x{}".format(2), lowerbound=-3, upperbound=3)
op_max.maximize(constant=3, linear={"x0": 1}, quadratic={("x1", "x2"): 2})
op_min.minimize(constant=3, linear={"x0": 1}, quadratic={("x1", "x2"): 2})
# check conversion of maximization problem
conv = MaximizeToMinimize()
op_conv = conv.convert(op_max)
self.assertEqual(op_conv.objective.sense, op_conv.objective.Sense.MINIMIZE)
x = [0, 1, 2]
fval_min = op_conv.objective.evaluate(conv.interpret(x))
self.assertAlmostEqual(fval_min, -7)
self.assertAlmostEqual(op_max.objective.evaluate(x), -fval_min)
# check conversion of minimization problem
op_conv = conv.convert(op_min)
self.assertEqual(op_conv.objective.sense, op_min.objective.sense)
fval_min = op_conv.objective.evaluate(conv.interpret(x))
self.assertAlmostEqual(op_min.objective.evaluate(x), fval_min)

def test_integer_to_binary(self):
"""Test integer to binary"""
op = QuadraticProgram()
Expand Down
Loading

0 comments on commit 3ccb48f

Please sign in to comment.