diff --git a/qiskit_optimization/algorithms/admm_optimizer.py b/qiskit_optimization/algorithms/admm_optimizer.py index 2b4694d5d..dcfe439b6 100644 --- a/qiskit_optimization/algorithms/admm_optimizer.py +++ b/qiskit_optimization/algorithms/admm_optimizer.py @@ -29,9 +29,9 @@ from .slsqp_optimizer import SlsqpOptimizer from ..problems.constraint import Constraint from ..problems.linear_constraint import LinearConstraint -from ..problems.quadratic_objective import QuadraticObjective from ..problems.quadratic_program import QuadraticProgram from ..problems.variable import VarType, Variable +from ..converters import MaximizeToMinimize UPDATE_RHO_BY_TEN_PERCENT = 0 UPDATE_RHO_BY_RESIDUALS = 1 @@ -300,12 +300,10 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: # map integer variables to binary variables from ..converters.integer_to_binary import IntegerToBinary - int2bin = IntegerToBinary() - original_problem = problem - problem = int2bin.convert(problem) - # we deal with minimization in the optimizer, so turn the problem to minimization - problem, sense = self._turn_to_minimization(problem) + converters = [IntegerToBinary(), MaximizeToMinimize()] + original_problem = problem + problem = self._convert(problem, converters) # create our computation state. self._state = ADMMState(problem, self._params.rho_initial) @@ -394,53 +392,23 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: binary_vars, continuous_vars, objective_value = self._get_best_merit_solution() solution = self._revert_solution_indexes(binary_vars, continuous_vars) - # flip the objective sign again if required - objective_value = objective_value * sense - self._log.debug( - "solution=%s, objective=%s at iteration=%s", - solution, - objective_value, - iteration, + "solution=%s, objective=%s at iteration=%s", solution, objective_value, iteration ) - # convert back integer to binary + # convert back integer to binary and eventually minimization to maximization # `state` is our internal state of computations. return cast( ADMMOptimizationResult, self._interpret( x=solution, - converters=int2bin, + converters=converters, problem=original_problem, result_class=ADMMOptimizationResult, state=self._state, ), ) - @staticmethod - def _turn_to_minimization( - problem: QuadraticProgram, - ) -> Tuple[QuadraticProgram, float]: - """ - Turns the problem to `ObjSense.MINIMIZE` by flipping the sign of the objective function - if initially it is `ObjSense.MAXIMIZE`. Otherwise returns the original problem. - - Args: - problem: a problem to turn to minimization. - - Returns: - A copy of the problem if sign flip is required, otherwise the original problem and - the original sense of the problem in the numerical representation. - """ - sense = problem.objective.sense.value - if problem.objective.sense == QuadraticObjective.Sense.MAXIMIZE: - problem = copy.deepcopy(problem) - problem.objective.sense = QuadraticObjective.Sense.MINIMIZE - problem.objective.constant = (-1) * problem.objective.constant - problem.objective.linear = (-1) * problem.objective.linear.coefficients - problem.objective.quadratic = (-1) * problem.objective.quadratic.coefficients - return problem, sense - @staticmethod def _get_variable_indices(op: QuadraticProgram, var_type: VarType) -> List[int]: """Returns a list of indices of the variables of the specified type. diff --git a/qiskit_optimization/algorithms/cobyla_optimizer.py b/qiskit_optimization/algorithms/cobyla_optimizer.py index a4329bd34..478124fd1 100644 --- a/qiskit_optimization/algorithms/cobyla_optimizer.py +++ b/qiskit_optimization/algorithms/cobyla_optimizer.py @@ -23,6 +23,7 @@ from ..infinity import INFINITY from ..problems.constraint import Constraint from ..problems.quadratic_program import QuadraticProgram +from ..converters import MaximizeToMinimize class CobylaOptimizer(MultiStartOptimizer): @@ -117,9 +118,10 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: """ self._verify_compatibility(problem) - # construct quadratic objective function - def objective(x): - return problem.objective.sense.value * problem.objective.evaluate(x) + # we deal with minimization in the optimizer, so turn the problem to minimization + max2min = MaximizeToMinimize() + original_problem = problem + problem = self._convert(problem, max2min) # initialize constraints list constraints = [] @@ -164,7 +166,7 @@ def ub_constraint(x, u_b=upperbound, j=i): # actual minimization function to be called by multi_start_solve def _minimize(x_0: np.ndarray) -> Tuple[np.ndarray, Any]: x = fmin_cobyla( - objective, + problem.objective.evaluate, x_0, constraints, rhobeg=self._rhobeg, @@ -175,4 +177,8 @@ def _minimize(x_0: np.ndarray) -> Tuple[np.ndarray, Any]: ) return x, None - return self.multi_start_solve(_minimize, problem) + result = self.multi_start_solve(_minimize, problem) + # eventually convert back minimization to maximization + return self._interpret( + x=result.x, problem=original_problem, converters=max2min, raw_results=result.raw_results + ) diff --git a/qiskit_optimization/algorithms/grover_optimizer.py b/qiskit_optimization/algorithms/grover_optimizer.py index 855c8904b..0307b292e 100644 --- a/qiskit_optimization/algorithms/grover_optimizer.py +++ b/qiskit_optimization/algorithms/grover_optimizer.py @@ -172,19 +172,11 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: self._verify_compatibility(problem) - # convert problem to QUBO + # convert problem to minimization QUBO problem problem_ = self._convert(problem, self._converters) problem_init = deepcopy(problem_) - # convert to minimization problem - if problem_.objective.sense == problem_.objective.Sense.MAXIMIZE: - problem_.objective.sense = problem_.objective.Sense.MINIMIZE - problem_.objective.constant = -problem_.objective.constant - for i, val in problem_.objective.linear.to_dict().items(): - 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()) + self._num_key_qubits = len(problem_.objective.linear.to_array()) # type: ignore # Variables for tracking the optimum. optimum_found = False @@ -268,7 +260,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: raw_samples = self._eigenvector_to_solutions( self._circuit_results, problem_init ) - raw_samples.sort(key=lambda x: problem_.objective.sense.value * x.fval) + raw_samples.sort(key=lambda x: x.fval) samples = self._interpret_samples(problem, raw_samples, self._converters) else: # Using Durr and Hoyer method, increase m. @@ -299,10 +291,10 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: optimum_key = 0 opt_x = np.array([1 if s == "1" else 0 for s in ("{0:%sb}" % n_key).format(optimum_key)]) - # Compute function value + # Compute function value of minimization QUBO fval = problem_init.objective.evaluate(opt_x) - # cast binaries back to integers + # cast binaries back to integers and eventually minimization to maximization return cast( GroverOptimizationResult, self._interpret( @@ -386,14 +378,14 @@ def __init__( operation_counts: The counts of each operation performed per iteration. n_input_qubits: The number of qubits used to represent the input. n_output_qubits: The number of qubits used to represent the output. - intermediate_fval: The intermediate value of the objective function of the solution, - that is expected to be identical with ``fval``. + intermediate_fval: The intermediate value of the objective function of the + minimization qubo solution, that is expected to be consistent to ``fval``. threshold: The threshold of Grover algorithm. status: the termination status of the optimization algorithm. samples: the x values, the objective function value of the original problem, the probability, and the status of sampling. - raw_samples: the x values of the QUBO, the objective function value of the QUBO, - and the probability of sampling. + raw_samples: the x values of the QUBO, the objective function value of the + minimization QUBO, and the probability of sampling. """ super().__init__( x=x, diff --git a/qiskit_optimization/algorithms/minimum_eigen_optimizer.py b/qiskit_optimization/algorithms/minimum_eigen_optimizer.py index 31ecd9daa..31a824816 100644 --- a/qiskit_optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit_optimization/algorithms/minimum_eigen_optimizer.py @@ -188,7 +188,7 @@ def solve(self, problem: QuadraticProgram) -> MinimumEigenOptimizationResult: """ self._verify_compatibility(problem) - # convert problem to QUBO + # convert problem to QUBO minimization problem problem_ = self._convert(problem, self._converters) # construct operator and offset @@ -219,7 +219,7 @@ def _solve_internal( raw_samples = self._eigenvector_to_solutions( eigen_result.eigenstate, converted_problem ) - raw_samples.sort(key=lambda x: converted_problem.objective.sense.value * x.fval) + raw_samples.sort(key=lambda x: x.fval) x = raw_samples[0].x fval = raw_samples[0].fval @@ -241,7 +241,8 @@ def _solve_internal( raw_samples=None, min_eigen_solver_result=eigen_result, ) - # translate result back to integers + + # translate result back to integers and eventually maximization samples = self._interpret_samples(original_problem, raw_samples, self._converters) return cast( MinimumEigenOptimizationResult, diff --git a/qiskit_optimization/algorithms/multistart_optimizer.py b/qiskit_optimization/algorithms/multistart_optimizer.py index 57ed26394..87c0894ab 100644 --- a/qiskit_optimization/algorithms/multistart_optimizer.py +++ b/qiskit_optimization/algorithms/multistart_optimizer.py @@ -17,7 +17,7 @@ import logging import time from abc import ABC -from typing import Optional, Callable, Tuple, Any +from typing import Callable, Tuple, Any, Optional import numpy as np from scipy.stats import uniform @@ -25,6 +25,7 @@ from ..problems.quadratic_program import QuadraticProgram from ..infinity import INFINITY from .optimization_algorithm import OptimizationAlgorithm, OptimizationResult +from ..converters import MaximizeToMinimize logger = logging.getLogger(__name__) @@ -73,6 +74,11 @@ def multi_start_solve( x_sol: Optional[np.ndarray] = None rest_sol: Optional[Tuple] = None + # we deal with minimization in the optimizer, so turn the problem to minimization + max2min = MaximizeToMinimize() + original_problem = problem + problem = self._convert(problem, max2min) + # Implementation of multi-start optimizer for trial in range(self._trials): x_0 = np.zeros(problem.get_num_vars()) @@ -86,21 +92,15 @@ def multi_start_solve( x, rest = minimize(x_0) logger.debug("minimize done in: %s seconds", str(time.time() - t_0)) - # we minimize, to get actual objective value we must multiply by the sense value - fval = problem.objective.evaluate(x) * problem.objective.sense.value + fval = problem.objective.evaluate(x) # we minimize the objective if fval < fval_sol: - # here we get back to the original sense of the problem - fval_sol = fval * problem.objective.sense.value + fval_sol = fval x_sol = x rest_sol = rest - - return OptimizationResult( - x=x_sol, - fval=fval_sol, - variables=problem.variables, - status=self._get_feasibility_status(problem, x_sol), - raw_results=rest_sol, + # eventually convert back minimization to maximization + return self._interpret( + x_sol, problem=original_problem, converters=max2min, raw_results=rest_sol ) @property diff --git a/qiskit_optimization/algorithms/optimization_algorithm.py b/qiskit_optimization/algorithms/optimization_algorithm.py index ec74e7a87..359b987d6 100644 --- a/qiskit_optimization/algorithms/optimization_algorithm.py +++ b/qiskit_optimization/algorithms/optimization_algorithm.py @@ -22,10 +22,7 @@ from qiskit.opflow import StateFn, DictStateFn from ..exceptions import QiskitOptimizationError -from ..converters.quadratic_program_to_qubo import ( - QuadraticProgramToQubo, - QuadraticProgramConverter, -) +from ..converters.quadratic_program_to_qubo import QuadraticProgramToQubo, QuadraticProgramConverter from ..problems.quadratic_program import QuadraticProgram, Variable diff --git a/qiskit_optimization/algorithms/slsqp_optimizer.py b/qiskit_optimization/algorithms/slsqp_optimizer.py index 3473e0ed0..a5e0d09e0 100644 --- a/qiskit_optimization/algorithms/slsqp_optimizer.py +++ b/qiskit_optimization/algorithms/slsqp_optimizer.py @@ -23,6 +23,7 @@ from ..problems import Variable from ..problems.constraint import Constraint from ..problems.quadratic_program import QuadraticProgram +from ..converters import MaximizeToMinimize logger = logging.getLogger(__name__) @@ -183,13 +184,10 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ self._verify_compatibility(problem) - - # construct quadratic objective function - def _objective(x): - return problem.objective.sense.value * problem.objective.evaluate(x) - - def _objective_gradient(x): - return problem.objective.sense.value * problem.objective.evaluate_gradient(x) + # we deal with minimization in the optimizer, so turn the problem to minimization + max2min = MaximizeToMinimize() + original_problem = problem + problem = self._convert(problem, max2min) # initialize constraints and bounds slsqp_bounds = [] @@ -222,12 +220,12 @@ def _objective_gradient(x): # actual minimization function to be called by multi_start_solve def _minimize(x_0: np.ndarray) -> Tuple[np.ndarray, Any]: output = fmin_slsqp( - _objective, + problem.objective.evaluate, x_0, eqcons=slsqp_eq_constraints, ieqcons=slsqp_ineq_constraints, bounds=slsqp_bounds, - fprime=_objective_gradient, + fprime=problem.objective.evaluate_gradient, iter=self._iter, acc=self._acc, iprint=self._iprint, @@ -241,6 +239,10 @@ def _minimize(x_0: np.ndarray) -> Tuple[np.ndarray, Any]: # actual optimization goes here result = self.multi_start_solve(_minimize, problem) + # eventually convert back minimization to maximization + result = self._interpret( + x=result.x, problem=original_problem, converters=max2min, raw_results=result.raw_results + ) if self._full_output: return SlsqpOptimizationResult( diff --git a/qiskit_optimization/algorithms/warm_start_qaoa_optimizer.py b/qiskit_optimization/algorithms/warm_start_qaoa_optimizer.py index 53148fecd..d0f8fb531 100644 --- a/qiskit_optimization/algorithms/warm_start_qaoa_optimizer.py +++ b/qiskit_optimization/algorithms/warm_start_qaoa_optimizer.py @@ -297,7 +297,7 @@ def solve(self, problem: QuadraticProgram) -> MinimumEigenOptimizationResult: if len(message) > 0: raise QiskitOptimizationError(f"Incompatible problem: {message}") - # convert problem to QUBO or another form if converters are specified + # convert problem to minimization QUBO or another form if converters are specified converted_problem = self._convert(problem, self._converters) # if the pre-solver can't solve the problem then it should be relaxed. @@ -336,7 +336,7 @@ def solve(self, problem: QuadraticProgram) -> MinimumEigenOptimizationResult: return results[0] else: samples = self._aggregator.aggregate(results) - samples.sort(key=lambda sample: converted_problem.objective.sense.value * sample.fval) + samples.sort(key=lambda sample: problem.objective.sense.value * sample.fval) # translate result back to the original variables return cast( diff --git a/qiskit_optimization/converters/__init__.py b/qiskit_optimization/converters/__init__.py index 997b58984..d3ba27b25 100644 --- a/qiskit_optimization/converters/__init__.py +++ b/qiskit_optimization/converters/__init__.py @@ -40,6 +40,7 @@ InequalityToEquality IntegerToBinary LinearEqualityToPenalty + MaximizeToMinimize QuadraticProgramToQubo """ @@ -47,6 +48,7 @@ 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 .quadratic_program_to_qubo import QuadraticProgramToQubo from .quadratic_program_converter import QuadraticProgramConverter @@ -54,6 +56,7 @@ "InequalityToEquality", "IntegerToBinary", "LinearEqualityToPenalty", + "MaximizeToMinimize", "QuadraticProgramConverter", "QuadraticProgramToQubo", ] diff --git a/qiskit_optimization/converters/maximize_to_minimize.py b/qiskit_optimization/converters/maximize_to_minimize.py new file mode 100644 index 000000000..28a05e2d5 --- /dev/null +++ b/qiskit_optimization/converters/maximize_to_minimize.py @@ -0,0 +1,76 @@ +# 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. + +"""Converter to convert a maximization problem to minimization problem.""" + +import copy +from typing import Optional, Union, List + +import numpy as np + +from .quadratic_program_converter import QuadraticProgramConverter +from ..exceptions import QiskitOptimizationError +from ..problems.quadratic_objective import QuadraticObjective +from ..problems.quadratic_program import QuadraticProgram + + +class MaximizeToMinimize(QuadraticProgramConverter): + """Convert a maximization problem to minimization problem.""" + + def __init__(self) -> None: + self._src_num_vars = None # type: Optional[int] + + def convert(self, problem: QuadraticProgram) -> QuadraticProgram: + """Convert a problem into a minimization problem. + + Args: + problem: The problem to be solved, that is a maximization or minimization problem. + + Returns: + The converted problem, that is a minimization problem. + Original problem is returned if it already is a minimization problem. + """ + + # Copy original number of variables as reference. + self._src_num_vars = problem.get_num_vars() + # only convert maximization problem + problem_min = problem + + if problem.objective.sense == QuadraticObjective.Sense.MAXIMIZE: + # Turn the problem to `ObjSense.MINIMIZE` by flipping the sign of the objective function + problem_min = copy.deepcopy(problem) + problem_min.objective.sense = QuadraticObjective.Sense.MINIMIZE + problem_min.objective.constant = (-1) * problem.objective.constant + problem_min.objective.linear = (-1) * problem.objective.linear.coefficients + problem_min.objective.quadratic = (-1) * problem.objective.quadratic.coefficients + + return problem_min + + 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( + "The number of variables in the passed result differs from " + "that of the original problem." + ) + return np.asarray(x) diff --git a/qiskit_optimization/converters/quadratic_program_to_qubo.py b/qiskit_optimization/converters/quadratic_program_to_qubo.py index 55fd993e1..63fedbe0d 100644 --- a/qiskit_optimization/converters/quadratic_program_to_qubo.py +++ b/qiskit_optimization/converters/quadratic_program_to_qubo.py @@ -43,19 +43,21 @@ 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 self._int_to_bin = IntegerToBinary() self._ineq_to_eq = InequalityToEquality(mode="integer") self._penalize_lin_eq_constraints = LinearEqualityToPenalty(penalty=penalty) + self._max_to_min = MaximizeToMinimize() def convert(self, problem: QuadraticProgram) -> QuadraticProgram: - """Convert a problem with linear equality constraints into new one with a QUBO form. + """Convert a problem with linear constraints into new one with a QUBO form. Args: - problem: The problem with linear equality constraints to be solved. + problem: The problem with linear constraints to be solved. Returns: - The problem converted in QUBO format. + The problem converted in QUBO format as minimization problem. Raises: QiskitOptimizationError: In case of an incompatible problem. @@ -75,6 +77,9 @@ def convert(self, problem: QuadraticProgram) -> QuadraticProgram: # Penalize linear equality constraints with only binary variables problem_ = self._penalize_lin_eq_constraints.convert(problem_) + # Convert maximization to minimization problem + problem_ = self._max_to_min.convert(problem_) + # Return QUBO return problem_ @@ -87,6 +92,7 @@ def interpret(self, x: Union[np.ndarray, List[float]]) -> np.ndarray: Returns: The result of the original problem. """ + x = self._max_to_min.interpret(x) x = self._penalize_lin_eq_constraints.interpret(x) x = self._int_to_bin.interpret(x) x = self._ineq_to_eq.interpret(x) diff --git a/releasenotes/notes/maximize-to-minimize-58cd1cbd752ed382.yaml b/releasenotes/notes/maximize-to-minimize-58cd1cbd752ed382.yaml new file mode 100644 index 000000000..e9b41bf3a --- /dev/null +++ b/releasenotes/notes/maximize-to-minimize-58cd1cbd752ed382.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Introduced a new converter class ``MaximizeToMinimize`` for ``QuadraticProgram``. + It converts a problem to a minimization problem. The converter was added to + the default converters in ``QuadraticProgramToQubo``. Algorithms that + previously did the conversion internally are now using ``MaximizeToMinimize``. + diff --git a/test/algorithms/test_grover_optimizer.py b/test/algorithms/test_grover_optimizer.py index 6b21bfba0..9cb68de07 100644 --- a/test/algorithms/test_grover_optimizer.py +++ b/test/algorithms/test_grover_optimizer.py @@ -30,6 +30,7 @@ InequalityToEquality, IntegerToBinary, LinearEqualityToPenalty, + MaximizeToMinimize, QuadraticProgramToQubo, ) from qiskit_optimization.problems import QuadraticProgram @@ -60,7 +61,10 @@ def validate_results(self, problem, results): # Validate results. np.testing.assert_array_almost_equal(comp_result.x, results.x) self.assertEqual(comp_result.fval, results.fval) - self.assertAlmostEqual(results.fval, results.intermediate_fval) + # optimizer internally deals with minimization problem + self.assertAlmostEqual( + results.fval, problem.objective.sense.value * results.intermediate_fval + ) def test_qubo_gas_int_zero(self): """Test for when the answer is zero.""" @@ -165,7 +169,8 @@ def test_converter_list(self): ineq2eq = InequalityToEquality() int2bin = IntegerToBinary() penalize = LinearEqualityToPenalty() - converters = [ineq2eq, int2bin, penalize] + max2min = MaximizeToMinimize() + converters = [ineq2eq, int2bin, penalize, max2min] gmf = GroverOptimizer( 4, num_iterations=self.n_iter, diff --git a/test/algorithms/test_min_eigen_optimizer.py b/test/algorithms/test_min_eigen_optimizer.py index f335e3953..885ee9804 100644 --- a/test/algorithms/test_min_eigen_optimizer.py +++ b/test/algorithms/test_min_eigen_optimizer.py @@ -35,6 +35,7 @@ InequalityToEquality, IntegerToBinary, LinearEqualityToPenalty, + MaximizeToMinimize, QuadraticProgramToQubo, ) from qiskit_optimization.problems import QuadraticProgram @@ -190,7 +191,8 @@ def test_converter_list(self): ineq2eq = InequalityToEquality() int2bin = IntegerToBinary() penalize = LinearEqualityToPenalty() - converters = [ineq2eq, int2bin, penalize] + max2min = MaximizeToMinimize() + converters = [ineq2eq, int2bin, penalize, max2min] min_eigen_optimizer = MinimumEigenOptimizer(min_eigen_solver, converters=converters) result = min_eigen_optimizer.solve(op) self.assertEqual(result.fval, 4) @@ -230,7 +232,10 @@ def test_samples_numpy_eigen_solver(self): self.assertEqual(result.samples[0].status, success) self.assertEqual(len(result.raw_samples), 1) np.testing.assert_array_almost_equal(result.raw_samples[0].x, [0, 0, 1, 0]) - self.assertAlmostEqual(result.raw_samples[0].fval, opt_sol) + # optimizer internally deals with minimization problem + self.assertAlmostEqual( + self.op_maximize.objective.sense.value * result.raw_samples[0].fval, opt_sol + ) self.assertAlmostEqual(result.raw_samples[0].probability, 1.0) self.assertEqual(result.raw_samples[0].status, success) @@ -273,7 +278,11 @@ def test_samples_qaoa(self, simulator): self.assertAlmostEqual(sum(s.probability for s in result.raw_samples), 1) self.assertAlmostEqual(max(s.fval for s in result.samples), 5) self.assertAlmostEqual(max(s.fval for s in result.samples if s.status == success), opt_sol) - self.assertAlmostEqual(max(s.fval for s in result.raw_samples), opt_sol) + # optimizer internally deals with minimization problem + self.assertAlmostEqual( + max(self.op_maximize.objective.sense.value * s.fval for s in result.raw_samples), + opt_sol, + ) for sample in result.raw_samples: self.assertEqual(sample.status, success) np.testing.assert_array_almost_equal(result.x, [0, 1]) @@ -283,7 +292,10 @@ def test_samples_qaoa(self, simulator): self.assertAlmostEqual(result.samples[0].fval, opt_sol) self.assertEqual(result.samples[0].status, success) np.testing.assert_array_almost_equal(result.raw_samples[0].x, [0, 0, 1, 0]) - self.assertAlmostEqual(result.raw_samples[0].fval, opt_sol) + # optimizer internally deals with minimization problem + self.assertAlmostEqual( + self.op_maximize.objective.sense.value * result.raw_samples[0].fval, opt_sol + ) self.assertEqual(result.raw_samples[0].status, success) # test bit ordering opt_sol = -2 diff --git a/test/algorithms/test_warm_start_qaoa.py b/test/algorithms/test_warm_start_qaoa.py index 78b336117..724fe4ea0 100644 --- a/test/algorithms/test_warm_start_qaoa.py +++ b/test/algorithms/test_warm_start_qaoa.py @@ -59,6 +59,7 @@ def test_max_cut(self): epsilon=0.25, num_initial_solutions=10, aggregator=aggregator, + converters=[], ) result_warm = optimizer.solve(problem) diff --git a/test/converters/test_converters.py b/test/converters/test_converters.py index 7f93e97e5..6291e3ddd 100644 --- a/test/converters/test_converters.py +++ b/test/converters/test_converters.py @@ -34,6 +34,7 @@ InequalityToEquality, IntegerToBinary, LinearEqualityToPenalty, + MaximizeToMinimize, ) from qiskit_optimization.problems import Constraint, Variable @@ -66,6 +67,8 @@ def test_empty_problem(self): op = conv.convert(op) conv = LinearEqualityToPenalty() op = conv.convert(op) + conv = MaximizeToMinimize() + op = conv.convert(op) _, shift = op.to_ising() self.assertEqual(shift, 0.0) @@ -349,6 +352,31 @@ 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()