From f283cd21a09235290095f5711136e35c49e18cb1 Mon Sep 17 00:00:00 2001 From: Kahan Majmudar Date: Thu, 13 Aug 2020 21:31:48 +0530 Subject: [PATCH 01/14] Draft for issue#1134 --- .../optimization/algorithms/admm_optimizer.py | 9 ++- .../algorithms/cplex_optimizer.py | 11 +++- .../problems/quadratic_program.py | 59 ++++++++++++++++++- 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 214d7ed424..d101247ca2 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -23,7 +23,7 @@ from qiskit.aqua.algorithms import NumPyMinimumEigensolver from .minimum_eigen_optimizer import MinimumEigenOptimizer -from .optimization_algorithm import OptimizationAlgorithm, OptimizationResult +from .optimization_algorithm import OptimizationResultStatus, OptimizationAlgorithm, OptimizationResult from .slsqp_optimizer import SlsqpOptimizer from ..problems.constraint import Constraint from ..problems.linear_constraint import LinearConstraint @@ -371,6 +371,13 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: # convert back integer to binary result = cast(ADMMOptimizationResult, int2bin.decode(result)) + + # check for feasibility + if QuadraticProgram.is_feasible(problem, result.x): + result.status = OptimizationResultStatus.SUCCESS + else: + result.status = OptimizationResultStatus.INFEASIBLE + # debug self._log.debug("solution=%s, objective=%s at iteration=%s", solution, objective_value, iteration) diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index 5ec6a680cb..d8b31d1853 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -18,7 +18,7 @@ from typing import Optional import logging -from .optimization_algorithm import OptimizationAlgorithm, OptimizationResult +from .optimization_algorithm import OptimizationResultStatus, OptimizationAlgorithm, OptimizationResult from ..exceptions import QiskitOptimizationError from ..problems.quadratic_program import QuadraticProgram @@ -134,11 +134,18 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: # process results sol = cplex.solution + # check for feasibility + if QuadraticProgram.is_feasible(problem, sol.get_values()): + status = OptimizationResultStatus.SUCCESS + else: + status = OptimizationResultStatus.INFEASIBLE + # create results result = OptimizationResult(x=sol.get_values(), variables=problem.variables, fval=sol.get_objective_value(), - results=sol) + results=sol, + status=status) # return solution return result diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index d2de190e02..6dc42e19f0 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -14,11 +14,12 @@ """Quadratic Program.""" +import numpy as np from typing import cast, List, Union, Dict, Optional, Tuple import logging from collections import defaultdict from enum import Enum -from math import fsum +from math import fsum, isclose from docplex.mp.constr import (LinearConstraint as DocplexLinearConstraint, QuadraticConstraint as DocplexQuadraticConstraint, @@ -852,6 +853,62 @@ def substitute_variables( return SubstituteVariables().substitute_variables(self, constants, variables) + def is_feasible(self, x: Union[List[float], np.ndarray], detailed = False) -> bool: + + satisfied_variables = [] + for i, v in enumerate(x): + variable = self.get_variable(i) + if variable._lowerbound <= v <= variable._upperbound : + satisfied_variables.append(True) + # print(f'{variable._name} is within the bounds') + else : + satisfied_variables.append(False) + # print(f'{variable._name} is outside the bounds') + + satisfied_linear_constraints = [] + for constraint in self._linear_constraints: + lhs = constraint.evaluate(x) + if constraint.sense == ConstraintSense.LE: + satisfied_linear_constraints.append(lhs <= constraint.rhs) + elif constraint.sense == ConstraintSense.GE: + satisfied_linear_constraints.append(lhs >= constraint.rhs) + elif constraint.sense == ConstraintSense.EQ: + satisfied_linear_constraints.append(isclose(lhs, constraint.rhs)) + else: + raise QiskitOptimizationError("Invalid sense!") + + satisfied_quadratic_constraints = [] + for constraint in self._quadratic_constraints: + lhs = constraint.evaluate(x) + if constraint.sense == ConstraintSense.LE: + satisfied_quadratic_constraints.append(lhs <= constraint.rhs) + elif constraint.sense == ConstraintSense.GE: + satisfied_quadratic_constraints.append(lhs >= constraint.rhs) + elif constraint.sense == ConstraintSense.EQ: + satisfied_quadratic_constraints.append(isclose(lhs, constraint.rhs)) + else: + raise QiskitOptimizationError("Invalid sense!") + + + if(detailed == True): + + for i in range(len(satisfied_variables)): + (print(f'{self.get_variable(i)._name} is within the bounds') if satisfied_variables[i] + else logger.warning(f'{self.get_variable(i)._name} is outside the bounds')) + + for i in range(len(satisfied_linear_constraints)): + (print(f'{self.get_linear_constraint(i).name} is statisfied') if satisfied_linear_constraints[i] + else logger.warning(f'{self.get_linear_constraint(i).name} is not statisfied')) + + for i in range(len(satisfied_quadratic_constraints)): + (print(f'{self.get_quadratic_constraint(i).name} is statisfied') if satisfied_linear_constraints[i] + else logger.warning(f'{self.get_quadratic_constraint(i).name} is not statisfied')) + + return (np.sum(satisfied_variables) == len(self._variables) and + np.sum(satisfied_linear_constraints) == len(self._linear_constraints) and + np.sum(satisfied_quadratic_constraints) == len(self._quadratic_constraints)) + + class SubstituteVariables: """A class to substitute variables of an optimization problem with constants for other variables""" From 96e36223d64a4e9d8644a0769601f4c068c43503 Mon Sep 17 00:00:00 2001 From: Kahan Majmudar Date: Thu, 13 Aug 2020 22:57:51 +0530 Subject: [PATCH 02/14] resolved issue in quadratic_problem.py --- qiskit/optimization/problems/quadratic_program.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index ae7d3a0736..b3a00e42f9 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -19,12 +19,7 @@ import logging from collections import defaultdict from enum import Enum -<<<<<<< HEAD from math import fsum, isclose -======= -from math import fsum -import warnings ->>>>>>> master from docplex.mp.constr import (LinearConstraint as DocplexLinearConstraint, QuadraticConstraint as DocplexQuadraticConstraint, From 1eab962b8a28b06c13acaca2d90f03f89be76d09 Mon Sep 17 00:00:00 2001 From: Kahan Majmudar Date: Fri, 14 Aug 2020 19:23:40 +0530 Subject: [PATCH 03/14] incorporated changes as suggested by @adekusar-drl --- .../optimization/algorithms/admm_optimizer.py | 13 ++-- .../algorithms/cplex_optimizer.py | 8 +-- .../problems/quadratic_program.py | 61 ++++++++----------- 3 files changed, 35 insertions(+), 47 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 306fb57fd8..d344e269e3 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -175,7 +175,7 @@ class ADMMOptimizationResult(OptimizationResult): """ ADMMOptimization Result.""" def __init__(self, x: np.ndarray, fval: float, variables: List[Variable], - state: ADMMState) -> None: + state: ADMMState, status: OptimizationResultStatus) -> None: """ Args: x: the optimal value found by ADMM. @@ -183,7 +183,7 @@ def __init__(self, x: np.ndarray, fval: float, variables: List[Variable], variables: the list of variables of the optimization problem. state: the internal computation state of ADMM. """ - super().__init__(x=x, fval=fval, variables=variables, raw_results=state) + super().__init__(x=x, fval=fval, variables=variables, raw_results=state, status=status) @property def state(self) -> ADMMState: @@ -379,11 +379,12 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: result = cast(ADMMOptimizationResult, int2bin.decode(result)) # check for feasibility - if QuadraticProgram.is_feasible(problem, result.x): - result.status = OptimizationResultStatus.SUCCESS - else: - result.status = OptimizationResultStatus.INFEASIBLE + status = OptimizationResultStatus.SUCCESS if problem.is_feasible(result.x) else OptimizationResultStatus.INFEASIBLE + result = ADMMOptimizationResult(x=result.x, fval=result.fval, + variables=result.variables, + state=self._state, + status=status) # debug self._log.debug("solution=%s, objective=%s at iteration=%s", solution, objective_value, iteration) diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index 7cce04558c..d087a37a1f 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -137,15 +137,13 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: sol = cplex.solution # check for feasibility - if QuadraticProgram.is_feasible(problem, sol.get_values()): - status = OptimizationResultStatus.SUCCESS - else: - status = OptimizationResultStatus.INFEASIBLE + status = OptimizationResultStatus.SUCCESS if problem.is_feasible(sol.get_values()) else OptimizationResultStatus.INFEASIBLE # create results result = OptimizationResult(x=sol.get_values(), fval=sol.get_objective_value(), - results=sol, + variables=problem.variables, + raw_results=problem.variables, status=status) # return solution diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index b3a00e42f9..8c6481a414 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -20,6 +20,7 @@ from collections import defaultdict from enum import Enum from math import fsum, isclose +import warnings from docplex.mp.constr import (LinearConstraint as DocplexLinearConstraint, QuadraticConstraint as DocplexQuadraticConstraint, @@ -1095,59 +1096,47 @@ def from_ising(self, def is_feasible(self, x: Union[List[float], np.ndarray], detailed = False) -> bool: - satisfied_variables = [] + if len(x) != self.get_num_vars(): + raise QiskitOptimizationError( + 'The size of `x` differs from the total number of variables.' + ' size of `x`: {}, num. of vars: {}'.format(len(x), self.get_num_vars()) + ) + + satisfied_variables = {} for i, v in enumerate(x): variable = self.get_variable(i) if variable._lowerbound <= v <= variable._upperbound : - satisfied_variables.append(True) + satisfied_variables[variable.name] = True # print(f'{variable._name} is within the bounds') else : - satisfied_variables.append(False) + satisfied_variables[variable.name] = False # print(f'{variable._name} is outside the bounds') - satisfied_linear_constraints = [] - for constraint in self._linear_constraints: + satisfied_constraints = {} + for constraint in self._linear_constraints + self._quadratic_constraints: lhs = constraint.evaluate(x) if constraint.sense == ConstraintSense.LE: - satisfied_linear_constraints.append(lhs <= constraint.rhs) + satisfied_constraints[constraint.name] = lhs <= constraint.rhs elif constraint.sense == ConstraintSense.GE: - satisfied_linear_constraints.append(lhs >= constraint.rhs) + satisfied_constraints[constraint.name] = lhs >= constraint.rhs elif constraint.sense == ConstraintSense.EQ: - satisfied_linear_constraints.append(isclose(lhs, constraint.rhs)) - else: - raise QiskitOptimizationError("Invalid sense!") + satisfied_constraints[constraint.name] = isclose(lhs, constraint.rhs) - satisfied_quadratic_constraints = [] - for constraint in self._quadratic_constraints: - lhs = constraint.evaluate(x) - if constraint.sense == ConstraintSense.LE: - satisfied_quadratic_constraints.append(lhs <= constraint.rhs) - elif constraint.sense == ConstraintSense.GE: - satisfied_quadratic_constraints.append(lhs >= constraint.rhs) - elif constraint.sense == ConstraintSense.EQ: - satisfied_quadratic_constraints.append(isclose(lhs, constraint.rhs)) - else: - raise QiskitOptimizationError("Invalid sense!") - + + final_dict = { k:v for k, v in {**satisfied_variables, **satisfied_constraints}.items() if not v } if(detailed == True): - for i in range(len(satisfied_variables)): - (print(f'{self.get_variable(i)._name} is within the bounds') if satisfied_variables[i] - else logger.warning(f'{self.get_variable(i)._name} is outside the bounds')) - - for i in range(len(satisfied_linear_constraints)): - (print(f'{self.get_linear_constraint(i).name} is statisfied') if satisfied_linear_constraints[i] - else logger.warning(f'{self.get_linear_constraint(i).name} is not statisfied')) - - for i in range(len(satisfied_quadratic_constraints)): - (print(f'{self.get_quadratic_constraint(i).name} is statisfied') if satisfied_linear_constraints[i] - else logger.warning(f'{self.get_quadratic_constraint(i).name} is not statisfied')) + print(list(final_dict.values)) + # if (final_dict.keys()): + # return False, list(final_dict.keys()) - return (np.sum(satisfied_variables) == len(self._variables) and - np.sum(satisfied_linear_constraints) == len(self._linear_constraints) and - np.sum(satisfied_quadratic_constraints) == len(self._quadratic_constraints)) + # else: + # return True, [] + return (len(satisfied_variables) == len(self._variables) and + len(satisfied_constraints) == (len(self._linear_constraints) + len(self._quadratic_constraints))) + class SubstituteVariables: """A class to substitute variables of an optimization problem with constants for other From 791838e091e3a33fa2012a57e80b62a4d754bd8d Mon Sep 17 00:00:00 2001 From: Kahan Majmudar Date: Wed, 19 Aug 2020 22:54:03 +0530 Subject: [PATCH 04/14] fix linting issues --- .../optimization/algorithms/admm_optimizer.py | 12 +++-- .../algorithms/cplex_optimizer.py | 6 ++- .../problems/quadratic_program.py | 47 ++++++++++++------- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 0f80d2165a..d075d1c733 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -17,13 +17,14 @@ import logging import time import warnings -from typing import List, Optional, Tuple +from typing import cast, List, Optional, Tuple import numpy as np from qiskit.aqua.algorithms import NumPyMinimumEigensolver from .minimum_eigen_optimizer import MinimumEigenOptimizer -from .optimization_algorithm import OptimizationResultStatus, OptimizationAlgorithm, OptimizationResult +from .optimization_algorithm import (OptimizationResultStatus, OptimizationAlgorithm, + OptimizationResult) from .slsqp_optimizer import SlsqpOptimizer from ..problems.constraint import Constraint from ..problems.linear_constraint import LinearConstraint @@ -189,6 +190,7 @@ def __init__(self, x: np.ndarray, fval: float, variables: List[Variable], fval: the optimal function value. variables: the list of variables of the optimization problem. state: the internal computation state of ADMM. + status: Termination status of an optimization algorithm """ super().__init__(x=x, fval=fval, variables=variables, raw_results=state, status=status) @@ -383,13 +385,15 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: # third parameter is our internal state of computations. result = ADMMOptimizationResult(x=base_result.x, fval=base_result.fval, variables=base_result.variables, - state=self._state) + state=self._state, + status=base_result.status) # convert back integer to binary result = cast(ADMMOptimizationResult, int2bin.decode(result)) # check for feasibility - status = OptimizationResultStatus.SUCCESS if problem.is_feasible(result.x) else OptimizationResultStatus.INFEASIBLE + status = OptimizationResultStatus.SUCCESS if problem.is_feasible(result.x) \ + else OptimizationResultStatus.INFEASIBLE result = ADMMOptimizationResult(x=result.x, fval=result.fval, variables=result.variables, diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index d087a37a1f..412d9846b1 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -18,7 +18,8 @@ import logging from typing import Optional -from .optimization_algorithm import OptimizationResultStatus, OptimizationAlgorithm, OptimizationResult +from .optimization_algorithm import (OptimizationResultStatus, OptimizationAlgorithm, + OptimizationResult) from ..exceptions import QiskitOptimizationError from ..problems.quadratic_program import QuadraticProgram @@ -137,7 +138,8 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: sol = cplex.solution # check for feasibility - status = OptimizationResultStatus.SUCCESS if problem.is_feasible(sol.get_values()) else OptimizationResultStatus.INFEASIBLE + status = OptimizationResultStatus.SUCCESS if problem.is_feasible(sol.get_values()) \ + else OptimizationResultStatus.INFEASIBLE # create results result = OptimizationResult(x=sol.get_values(), diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 0af905da13..5766951c8d 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -14,13 +14,15 @@ """Quadratic Program.""" -import numpy as np from typing import cast, List, Union, Dict, Optional, Tuple import logging from collections import defaultdict from enum import Enum from math import fsum, isclose import warnings +import numpy as np +from numpy import (ndarray, zeros, bool as nbool) +from scipy.sparse import spmatrix from docplex.mp.constr import (LinearConstraint as DocplexLinearConstraint, QuadraticConstraint as DocplexQuadraticConstraint, @@ -29,8 +31,6 @@ from docplex.mp.model import Model from docplex.mp.model_reader import ModelReader from docplex.mp.quad import QuadExpr -from numpy import (ndarray, zeros, bool as nbool) -from scipy.sparse import spmatrix from qiskit.aqua import MissingOptionalLibraryError from qiskit.aqua.operators import I, OperatorBase, PauliOp, WeightedPauliOperator, SummedOp, ListOp @@ -1093,27 +1093,38 @@ def from_ising(self, self.minimize(constant=offset, linear=linear_terms, quadratic=quadratic_terms) offset -= offset + def is_feasible(self, x: Union[List[float], np.ndarray], detailed: bool = False) -> bool: + """Returns whether a solution provided by the optimizer is feasible or not + Args: + x: the input result list returned by the optimizer + detailed: whether or not to print the violations + Returns: + is_feasible: Whether the solution provided by the optimizer is feasible or not. - def is_feasible(self, x: Union[List[float], np.ndarray], detailed = False) -> bool: - + Raises: + QiskitOptimizationError: If the input `x` is not same len as total vars + """ + # if input `x` is not the same len as the total vars, raise an error if len(x) != self.get_num_vars(): raise QiskitOptimizationError( 'The size of `x` differs from the total number of variables.' ' size of `x`: {}, num. of vars: {}'.format(len(x), self.get_num_vars()) ) + # check whether the input satisfy the bounds of the problem satisfied_variables = {} - for i, v in enumerate(x): + for i, val in enumerate(x): variable = self.get_variable(i) - if variable._lowerbound <= v <= variable._upperbound : + if variable._lowerbound <= val <= variable._upperbound: satisfied_variables[variable.name] = True - # print(f'{variable._name} is within the bounds') - else : + # print(f'{variable._name} is within the bounds') + else: satisfied_variables[variable.name] = False # print(f'{variable._name} is outside the bounds') - satisfied_constraints = {} - for constraint in self._linear_constraints + self._quadratic_constraints: + # check whether the input satisfy the constraints of the problem + satisfied_constraints = {} + for constraint in self._linear_constraints + self._quadratic_constraints: lhs = constraint.evaluate(x) if constraint.sense == ConstraintSense.LE: satisfied_constraints[constraint.name] = lhs <= constraint.rhs @@ -1122,12 +1133,13 @@ def is_feasible(self, x: Union[List[float], np.ndarray], detailed = False) -> bo elif constraint.sense == ConstraintSense.EQ: satisfied_constraints[constraint.name] = isclose(lhs, constraint.rhs) + # create a dict containing only unsatisfied variable(s)/constraint(s) + final_dict = {k: v for k, v in {**satisfied_variables, **satisfied_constraints} + .items() if not v} - final_dict = { k:v for k, v in {**satisfied_variables, **satisfied_constraints}.items() if not v } - - if(detailed == True): + if detailed: - print(list(final_dict.values)) + print(list(final_dict.values)) # if (final_dict.keys()): # return False, list(final_dict.keys()) @@ -1135,8 +1147,9 @@ def is_feasible(self, x: Union[List[float], np.ndarray], detailed = False) -> bo # return True, [] return (len(satisfied_variables) == len(self._variables) and - len(satisfied_constraints) == (len(self._linear_constraints) + len(self._quadratic_constraints))) - + len(satisfied_constraints) == (len(self._linear_constraints) + + len(self._quadratic_constraints))) + class SubstituteVariables: """A class to substitute variables of an optimization problem with constants for other From 6cc3f65dcc743a9b7d4c49bf0ed399b0bf7ccb4d Mon Sep 17 00:00:00 2001 From: Kahan Majmudar Date: Fri, 21 Aug 2020 00:04:22 +0530 Subject: [PATCH 05/14] added is_feasible method in all optimizers, added changes suggested by @ woodsp-ibm --- .../optimization/algorithms/admm_optimizer.py | 3 --- .../algorithms/cplex_optimizer.py | 1 + .../algorithms/grover_optimizer.py | 15 ++++++++++---- .../algorithms/minimum_eigen_optimizer.py | 17 ++++++++++++---- .../algorithms/multistart_optimizer.py | 14 +++++++++---- .../recursive_minimum_eigen_optimizer.py | 16 +++++++++++---- .../algorithms/slsqp_optimizer.py | 17 +++++++++++----- .../problems/quadratic_program.py | 20 ++++++++++--------- 8 files changed, 70 insertions(+), 33 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index d075d1c733..d2766372b7 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -388,9 +388,6 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: state=self._state, status=base_result.status) - # convert back integer to binary - result = cast(ADMMOptimizationResult, int2bin.decode(result)) - # check for feasibility status = OptimizationResultStatus.SUCCESS if problem.is_feasible(result.x) \ else OptimizationResultStatus.INFEASIBLE diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index 412d9846b1..c58d041aa4 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -18,6 +18,7 @@ import logging from typing import Optional +from qiskit.aqua import MissingOptionalLibraryError from .optimization_algorithm import (OptimizationResultStatus, OptimizationAlgorithm, OptimizationResult) from ..exceptions import QiskitOptimizationError diff --git a/qiskit/optimization/algorithms/grover_optimizer.py b/qiskit/optimization/algorithms/grover_optimizer.py index 7b145c6ad4..2b61ed7345 100644 --- a/qiskit/optimization/algorithms/grover_optimizer.py +++ b/qiskit/optimization/algorithms/grover_optimizer.py @@ -26,7 +26,8 @@ from qiskit.aqua.algorithms.amplitude_amplifiers.grover import Grover from qiskit.aqua.components.initial_states import Custom from qiskit.aqua.components.oracles import CustomCircuitOracle -from .optimization_algorithm import OptimizationAlgorithm, OptimizationResult +from .optimization_algorithm import (OptimizationResultStatus, OptimizationAlgorithm, + OptimizationResult) from ..problems import Variable from ..problems.quadratic_program import QuadraticProgram from ..converters.quadratic_program_to_qubo import QuadraticProgramToQubo @@ -266,9 +267,13 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: # cast binaries back to integers result = self._qubo_converter.interpret(result) + # check for feasibility + status = OptimizationResultStatus.SUCCESS if problem.is_feasible(result.x) \ + else OptimizationResultStatus.INFEASIBLE + return GroverOptimizationResult(x=result.x, fval=result.fval, variables=result.variables, operation_counts=operation_count, n_input_qubits=n_key, - n_output_qubits=n_value) + n_output_qubits=n_value, status=status) def _measure(self, circuit: QuantumCircuit) -> str: """Get probabilities from the given backend, and picks a random outcome.""" @@ -332,7 +337,8 @@ class GroverOptimizationResult(OptimizationResult): def __init__(self, x: Union[List[float], np.ndarray], fval: float, variables: List[Variable], operation_counts: Dict[int, Dict[str, int]], n_input_qubits: int, - n_output_qubits: int) -> None: + n_output_qubits: int, + status: OptimizationResultStatus) -> None: """ Constructs a result object with the specific Grover properties. @@ -343,8 +349,9 @@ def __init__(self, x: Union[List[float], np.ndarray], fval: float, variables: Li 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. + status: the termination status of the optimization algorithm. """ - super().__init__(x, fval, variables, None) + super().__init__(x, fval, variables, None, status) self._operation_counts = operation_counts self._n_input_qubits = n_input_qubits self._n_output_qubits = n_output_qubits diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 5f1e2e526c..1dcf7ea4c6 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -19,7 +19,8 @@ from qiskit.aqua.algorithms import MinimumEigensolver, MinimumEigensolverResult from qiskit.aqua.operators import StateFn, DictStateFn -from .optimization_algorithm import OptimizationAlgorithm, OptimizationResult +from .optimization_algorithm import (OptimizationResultStatus, OptimizationAlgorithm, + OptimizationResult) from ..converters.quadratic_program_to_qubo import QuadraticProgramToQubo from ..problems.quadratic_program import QuadraticProgram, Variable @@ -30,7 +31,8 @@ class MinimumEigenOptimizationResult(OptimizationResult): def __init__(self, x: Union[List[float], np.ndarray], fval: float, variables: List[Variable], samples: List[Tuple[str, float, float]], - min_eigen_solver_result: Optional[MinimumEigensolverResult] = None) -> None: + min_eigen_solver_result: Optional[MinimumEigensolverResult] = None, + status: OptimizationResultStatus = OptimizationResultStatus.SUCCESS) -> None: """ Args: x: the optimal value found by ``MinimumEigensolver``. @@ -38,8 +40,9 @@ def __init__(self, x: Union[List[float], np.ndarray], fval: float, variables: the list of variables of the optimization problem. samples: the basis state as bitstring, the QUBO value, and the probability of sampling. min_eigen_solver_result: the result obtained from the underlying algorithm. + status: the termination status of the optimization algorithm. """ - super().__init__(x, fval, variables, None) + super().__init__(x, fval, variables, None, status) self._samples = samples self._min_eigen_solver_result = min_eigen_solver_result @@ -182,10 +185,16 @@ def solve(self, problem: QuadraticProgram) -> MinimumEigenOptimizationResult: # translate result back to integers result = OptimizationResult(x=x, fval=fval, variables=problem_.variables) result = self._qubo_converter.interpret(result) + + # check for feasibility + status = OptimizationResultStatus.SUCCESS if problem.is_feasible(result.x) \ + else OptimizationResultStatus.INFEASIBLE + return MinimumEigenOptimizationResult(x=result.x, fval=result.fval, variables=result.variables, samples=samples, - min_eigen_solver_result=eigen_result) + min_eigen_solver_result=eigen_result, + status=status) def _eigenvector_to_solutions(eigenvector: Union[dict, np.ndarray, StateFn], diff --git a/qiskit/optimization/algorithms/multistart_optimizer.py b/qiskit/optimization/algorithms/multistart_optimizer.py index 8110acc6f2..ed6bfc1c42 100644 --- a/qiskit/optimization/algorithms/multistart_optimizer.py +++ b/qiskit/optimization/algorithms/multistart_optimizer.py @@ -25,8 +25,8 @@ from scipy.stats import uniform from qiskit.optimization import QuadraticProgram, INFINITY -from qiskit.optimization.algorithms.optimization_algorithm import (OptimizationAlgorithm, - OptimizationResult) +from .optimization_algorithm import (OptimizationResultStatus, OptimizationAlgorithm, + OptimizationResult) logger = logging.getLogger(__name__) @@ -94,8 +94,14 @@ def multi_start_solve(self, minimize: Callable[[np.array], Tuple[np.array, Any]] x_sol = x rest_sol = rest - return OptimizationResult(x=x_sol, fval=fval_sol, variables=problem.variables, - raw_results=rest_sol) + # check for feasibility + status = OptimizationResultStatus.SUCCESS if problem.is_feasible(x_sol) \ + else OptimizationResultStatus.INFEASIBLE + + return OptimizationResult(x=x_sol, fval=fval_sol, + variables=problem.variables, + raw_results=rest_sol, + status=status) @property def trials(self) -> int: diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 790c6b9006..ce12586200 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -23,7 +23,8 @@ from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.aqua.utils.validation import validate_min -from .optimization_algorithm import OptimizationAlgorithm, OptimizationResult +from .optimization_algorithm import (OptimizationResultStatus, OptimizationAlgorithm, + OptimizationResult) from .minimum_eigen_optimizer import MinimumEigenOptimizer, MinimumEigenOptimizationResult from ..converters.quadratic_program_to_qubo import QuadraticProgramToQubo from ..exceptions import QiskitOptimizationError @@ -55,7 +56,8 @@ class RecursiveMinimumEigenOptimizationResult(OptimizationResult): def __init__(self, x: Union[List[float], np.ndarray], fval: float, variables: List[Variable], replacements: Dict[str, Tuple[str, int]], - history: Tuple[List[MinimumEigenOptimizationResult], OptimizationResult]) -> None: + history: Tuple[List[MinimumEigenOptimizationResult], OptimizationResult], + status: OptimizationResultStatus = OptimizationResultStatus.SUCCESS) -> None: """ Constructs an instance of the result class. @@ -71,8 +73,9 @@ def __init__(self, x: Union[List[float], np.ndarray], fval: float, the second element is an instance of :class:`~qiskit.optimization.algorithm.OptimizationResult` obtained at the last step via `min_num_vars_optimizer`. + status: the termination status of the optimization algorithm. """ - super().__init__(x, fval, variables, None) + super().__init__(x, fval, variables, None, status) self._replacements = replacements self._history = history @@ -286,10 +289,15 @@ def find_value(x, replacements, var_values): result = OptimizationResult(x=x_v, fval=fval, variables=problem_ref.variables) result = self._qubo_converter.interpret(result) + # check for feasibility + status = OptimizationResultStatus.SUCCESS if problem.is_feasible(result.x) \ + else OptimizationResultStatus.INFEASIBLE + return RecursiveMinimumEigenOptimizationResult(x=result.x, fval=result.fval, variables=result.variables, replacements=replacements, - history=history) + history=history, + status=status) def _find_strongest_correlation(self, correlations): diff --git a/qiskit/optimization/algorithms/slsqp_optimizer.py b/qiskit/optimization/algorithms/slsqp_optimizer.py index f5d1784556..2e42159348 100644 --- a/qiskit/optimization/algorithms/slsqp_optimizer.py +++ b/qiskit/optimization/algorithms/slsqp_optimizer.py @@ -20,7 +20,7 @@ from scipy.optimize import fmin_slsqp from .multistart_optimizer import MultiStartOptimizer -from .optimization_algorithm import OptimizationResult +from .optimization_algorithm import OptimizationResultStatus, OptimizationResult from ..exceptions import QiskitOptimizationError from ..problems import Variable from ..problems.constraint import Constraint @@ -35,7 +35,8 @@ class SlsqpOptimizationResult(OptimizationResult): """ def __init__(self, x: Union[List[float], np.ndarray], fval: float, variables: List[Variable], fx: Optional[np.ndarray] = None, its: Optional[int] = None, - imode: Optional[int] = None, smode: Optional[str] = None) -> None: + imode: Optional[int] = None, smode: Optional[str] = None, + status: OptimizationResultStatus = OptimizationResultStatus.SUCCESS) -> None: """ Constructs a result object with properties specific to SLSQP. @@ -48,8 +49,9 @@ def __init__(self, x: Union[List[float], np.ndarray], fval: float, variables: Li imode: The exit mode from the optimizer (see the documentation of ``scipy.optimize.fmin_slsqp``). smode: Message describing the exit mode from the optimizer. + status: the termination status of the optimization algorithm. """ - super().__init__(x, fval, variables, None) + super().__init__(x, fval, variables, None, status) self._fx = fx self._its = its self._imode = imode @@ -218,9 +220,14 @@ def _minimize(x_0: np.array) -> Tuple[np.array, Any]: # actual optimization goes here result = self.multi_start_solve(_minimize, problem) + # check for feasibility + status = OptimizationResultStatus.SUCCESS if problem.is_feasible(result.x) \ + else OptimizationResultStatus.INFEASIBLE + if self._full_output: return SlsqpOptimizationResult(result.x, result.fval, result.variables, fx=result.raw_results[0], its=result.raw_results[1], - imode=result.raw_results[2], smode=result.raw_results[3]) + imode=result.raw_results[2], smode=result.raw_results[3], + status=status) else: - return SlsqpOptimizationResult(result.x, result.fval, result.variables) + return SlsqpOptimizationResult(result.x, result.fval, result.variables, status=status) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 5766951c8d..c39547332e 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -1094,12 +1094,12 @@ def from_ising(self, offset -= offset def is_feasible(self, x: Union[List[float], np.ndarray], detailed: bool = False) -> bool: - """Returns whether a solution provided by the optimizer is feasible or not + """Returns whether a solution is feasible or not Args: x: the input result list returned by the optimizer - detailed: whether or not to print the violations + detailed: [not implemented] returns the violations if set to True Returns: - is_feasible: Whether the solution provided by the optimizer is feasible or not. + is_feasible: Whether the solution provided is feasible or not. Raises: QiskitOptimizationError: If the input `x` is not same len as total vars @@ -1107,8 +1107,8 @@ def is_feasible(self, x: Union[List[float], np.ndarray], detailed: bool = False) # if input `x` is not the same len as the total vars, raise an error if len(x) != self.get_num_vars(): raise QiskitOptimizationError( - 'The size of `x` differs from the total number of variables.' - ' size of `x`: {}, num. of vars: {}'.format(len(x), self.get_num_vars()) + 'The size of solution `x`: {}, does not match the number of problem variables: {}' + .format(len(x), self.get_num_vars()) ) # check whether the input satisfy the bounds of the problem @@ -1117,14 +1117,13 @@ def is_feasible(self, x: Union[List[float], np.ndarray], detailed: bool = False) variable = self.get_variable(i) if variable._lowerbound <= val <= variable._upperbound: satisfied_variables[variable.name] = True - # print(f'{variable._name} is within the bounds') else: satisfied_variables[variable.name] = False - # print(f'{variable._name} is outside the bounds') # check whether the input satisfy the constraints of the problem satisfied_constraints = {} - for constraint in self._linear_constraints + self._quadratic_constraints: + for constraint in cast(List[Constraint], self._linear_constraints) + \ + cast(List[Constraint], self._quadratic_constraints): lhs = constraint.evaluate(x) if constraint.sense == ConstraintSense.LE: satisfied_constraints[constraint.name] = lhs <= constraint.rhs @@ -1137,9 +1136,12 @@ def is_feasible(self, x: Union[List[float], np.ndarray], detailed: bool = False) final_dict = {k: v for k, v in {**satisfied_variables, **satisfied_constraints} .items() if not v} + # debug + logger.debug("Violations: %s", final_dict) + if detailed: - print(list(final_dict.values)) + print(list(final_dict.keys())) # if (final_dict.keys()): # return False, list(final_dict.keys()) From 721dffe7f93e1b54a94c6678f493e611ef1284df Mon Sep 17 00:00:00 2001 From: Kahan Majmudar Date: Fri, 21 Aug 2020 00:32:01 +0530 Subject: [PATCH 06/14] removed cast import --- qiskit/optimization/algorithms/admm_optimizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index d2766372b7..4ad12b231f 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -17,7 +17,7 @@ import logging import time import warnings -from typing import cast, List, Optional, Tuple +from typing import List, Optional, Tuple import numpy as np from qiskit.aqua.algorithms import NumPyMinimumEigensolver From 538a13fc793aa986122754fa7358f13f4599ec3a Mon Sep 17 00:00:00 2001 From: Kahan Majmudar Date: Thu, 3 Sep 2020 00:15:34 +0530 Subject: [PATCH 07/14] made changes as per points 1 to 3 --- .../optimization/algorithms/admm_optimizer.py | 10 +---- .../algorithms/cplex_optimizer.py | 6 +-- .../algorithms/grover_optimizer.py | 7 +--- .../algorithms/minimum_eigen_optimizer.py | 6 +-- .../algorithms/multistart_optimizer.py | 6 +-- .../algorithms/optimization_algorithm.py | 35 +++++++++++++---- .../recursive_minimum_eigen_optimizer.py | 7 +--- .../algorithms/slsqp_optimizer.py | 5 ++- .../problems/quadratic_program.py | 38 +++++++++++++------ 9 files changed, 64 insertions(+), 56 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 4ad12b231f..4670bd2580 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -386,16 +386,8 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: result = ADMMOptimizationResult(x=base_result.x, fval=base_result.fval, variables=base_result.variables, state=self._state, - status=base_result.status) + status=self.get_feasibility_status(problem, base_result.x)) - # check for feasibility - status = OptimizationResultStatus.SUCCESS if problem.is_feasible(result.x) \ - else OptimizationResultStatus.INFEASIBLE - - result = ADMMOptimizationResult(x=result.x, fval=result.fval, - variables=result.variables, - state=self._state, - status=status) # debug self._log.debug("solution=%s, objective=%s at iteration=%s", solution, objective_value, iteration) diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index d19f49fa61..376f4bc320 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -138,16 +138,12 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: # process results sol = cplex.solution - # check for feasibility - status = OptimizationResultStatus.SUCCESS if problem.is_feasible(sol.get_values()) \ - else OptimizationResultStatus.INFEASIBLE - # create results result = OptimizationResult(x=sol.get_values(), fval=sol.get_objective_value(), variables=problem.variables, raw_results=problem.variables, - status=status) + status=self.get_feasibility_status(problem, sol.get_values())) # return solution return result diff --git a/qiskit/optimization/algorithms/grover_optimizer.py b/qiskit/optimization/algorithms/grover_optimizer.py index 2bdaaef42e..e694764e9e 100644 --- a/qiskit/optimization/algorithms/grover_optimizer.py +++ b/qiskit/optimization/algorithms/grover_optimizer.py @@ -269,14 +269,11 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: # cast binaries back to integers result = self._qubo_converter.interpret(result) - # check for feasibility - status = OptimizationResultStatus.SUCCESS if problem.is_feasible(result.x) \ - else OptimizationResultStatus.INFEASIBLE - return GroverOptimizationResult(x=result.x, fval=result.fval, variables=result.variables, operation_counts=operation_count, n_input_qubits=n_key, n_output_qubits=n_value, intermediate_fval=fval, - threshold=threshold, status=status) + threshold=threshold, + status=self.get_feasibility_status(problem, result.x)) def _measure(self, circuit: QuantumCircuit) -> str: """Get probabilities from the given backend, and picks a random outcome.""" diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 1dcf7ea4c6..6396938563 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -186,15 +186,11 @@ def solve(self, problem: QuadraticProgram) -> MinimumEigenOptimizationResult: result = OptimizationResult(x=x, fval=fval, variables=problem_.variables) result = self._qubo_converter.interpret(result) - # check for feasibility - status = OptimizationResultStatus.SUCCESS if problem.is_feasible(result.x) \ - else OptimizationResultStatus.INFEASIBLE - return MinimumEigenOptimizationResult(x=result.x, fval=result.fval, variables=result.variables, samples=samples, min_eigen_solver_result=eigen_result, - status=status) + status=self.get_feasibility_status(problem, result.x)) def _eigenvector_to_solutions(eigenvector: Union[dict, np.ndarray, StateFn], diff --git a/qiskit/optimization/algorithms/multistart_optimizer.py b/qiskit/optimization/algorithms/multistart_optimizer.py index ed6bfc1c42..fffb166cb4 100644 --- a/qiskit/optimization/algorithms/multistart_optimizer.py +++ b/qiskit/optimization/algorithms/multistart_optimizer.py @@ -94,14 +94,10 @@ def multi_start_solve(self, minimize: Callable[[np.array], Tuple[np.array, Any]] x_sol = x rest_sol = rest - # check for feasibility - status = OptimizationResultStatus.SUCCESS if problem.is_feasible(x_sol) \ - else OptimizationResultStatus.INFEASIBLE - return OptimizationResult(x=x_sol, fval=fval_sol, variables=problem.variables, raw_results=rest_sol, - status=status) + status=self.get_feasibility_status(self, problem, x_sol)) @property def trials(self) -> int: diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index 9e43c177c7..89dd046002 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -24,6 +24,19 @@ from ..problems.quadratic_program import QuadraticProgram, Variable +class OptimizationResultStatus(Enum): + """Termination status of an optimization algorithm.""" + + SUCCESS = 0 + """the optimization algorithm succeeded to find an optimal solution.""" + + FAILURE = 1 + """the optimization algorithm ended in a failure.""" + + INFEASIBLE = 2 + """the optimization algorithm obtained an infeasible solution.""" + + class OptimizationAlgorithm(ABC): """An abstract class for optimization algorithms in Qiskit's optimization module.""" @@ -86,18 +99,24 @@ def _verify_compatibility(self, problem: QuadraticProgram) -> None: if msg: raise QiskitOptimizationError('Incompatible problem: {}'.format(msg)) + def get_feasibility_status(self, problem: QuadraticProgram, x: Union[List[float], np.ndarray]) \ + -> OptimizationResultStatus: + """Returns whether the input result is feasible or not for the given problem. -class OptimizationResultStatus(Enum): - """Termination status of an optimization algorithm.""" + Args: + problem: Problem to verify. + x: the input result list. - SUCCESS = 0 - """the optimization algorithm succeeded to find an optimal solution.""" + Returns: + The status of the result. - FAILURE = 1 - """the optimization algorithm ended in a failure.""" + Raises: + None + """ + is_feasible = problem.is_feasible(x) - INFEASIBLE = 2 - """the optimization algorithm obtained an infeasible solution.""" + return OptimizationResultStatus.SUCCESS if is_feasible \ + else OptimizationResultStatus.INFEASIBLE class OptimizationResult: diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index ce12586200..f228e4abaa 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -289,15 +289,12 @@ def find_value(x, replacements, var_values): result = OptimizationResult(x=x_v, fval=fval, variables=problem_ref.variables) result = self._qubo_converter.interpret(result) - # check for feasibility - status = OptimizationResultStatus.SUCCESS if problem.is_feasible(result.x) \ - else OptimizationResultStatus.INFEASIBLE - return RecursiveMinimumEigenOptimizationResult(x=result.x, fval=result.fval, variables=result.variables, replacements=replacements, history=history, - status=status) + status=(self.get_feasibility_status + (problem, result.x))) def _find_strongest_correlation(self, correlations): diff --git a/qiskit/optimization/algorithms/slsqp_optimizer.py b/qiskit/optimization/algorithms/slsqp_optimizer.py index 2e42159348..bd1f6d9ea8 100644 --- a/qiskit/optimization/algorithms/slsqp_optimizer.py +++ b/qiskit/optimization/algorithms/slsqp_optimizer.py @@ -228,6 +228,7 @@ def _minimize(x_0: np.array) -> Tuple[np.array, Any]: return SlsqpOptimizationResult(result.x, result.fval, result.variables, fx=result.raw_results[0], its=result.raw_results[1], imode=result.raw_results[2], smode=result.raw_results[3], - status=status) + status=self.get_feasibility_status(problem, result.x)) else: - return SlsqpOptimizationResult(result.x, result.fval, result.variables, status=status) + return SlsqpOptimizationResult(result.x, result.fval, result.variables, + status=self.get_feasibility_status(problem, result.x)) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 610ecf63c8..15facb1c97 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -1093,13 +1093,15 @@ def from_ising(self, self.minimize(constant=offset, linear=linear_terms, quadratic=quadratic_terms) offset -= offset - def is_feasible(self, x: Union[List[float], np.ndarray], detailed: bool = False) -> bool: - """Returns whether a solution is feasible or not + def get_feasiblity_info(self, x: Union[List[float], np.ndarray]) \ + -> Tuple[bool, List[Constraint], List[Variable]]: + """Returns whether a solution is feasible or not along with the violations. Args: x: the input result list returned by the optimizer - detailed: [not implemented] returns the violations if set to True Returns: is_feasible: Whether the solution provided is feasible or not. + List[Constraint]: List of constraints which are violated. + List[Variable]: List of variables which are violated. Raises: QiskitOptimizationError: If the input `x` is not same len as total vars @@ -1139,18 +1141,30 @@ def is_feasible(self, x: Union[List[float], np.ndarray], detailed: bool = False) # debug logger.debug("Violations: %s", final_dict) - if detailed: + is_feasible = (len(satisfied_variables) == len(self._variables) and + len(satisfied_constraints) == (len(self._linear_constraints) + + len(self._quadratic_constraints))) - print(list(final_dict.keys())) - # if (final_dict.keys()): - # return False, list(final_dict.keys()) + violated_constraints = list(satisfied_constraints.keys()) - # else: - # return True, [] + violated_variables = list(satisfied_variables.keys()) - return (len(satisfied_variables) == len(self._variables) and - len(satisfied_constraints) == (len(self._linear_constraints) + - len(self._quadratic_constraints))) + return (is_feasible, violated_constraints, violated_variables) + + def is_feasible(self, x: Union[List[float], np.ndarray]) -> bool: + """Returns whether a solution is feasible or not. + Args: + x: the input result list returned by the optimizer + + Returns: + is_feasible: Whether the solution provided is feasible or not. + + Raises: + None + """ + is_feasible, _, _ = self.get_feasiblity_info(x) + + return is_feasible class SubstituteVariables: From 5ec3a6db8128528787a2f877c93955747b36cd54 Mon Sep 17 00:00:00 2001 From: Kahan Majmudar Date: Thu, 3 Sep 2020 18:12:07 +0530 Subject: [PATCH 08/14] made suggested changes by @adekusar-drl --- qiskit/optimization/algorithms/cplex_optimizer.py | 5 ++--- .../algorithms/minimum_eigen_optimizer.py | 6 +++--- .../algorithms/multistart_optimizer.py | 5 ++--- .../algorithms/optimization_algorithm.py | 2 -- .../recursive_minimum_eigen_optimizer.py | 6 +++--- qiskit/optimization/algorithms/slsqp_optimizer.py | 14 +++++--------- qiskit/optimization/problems/quadratic_program.py | 9 --------- 7 files changed, 15 insertions(+), 32 deletions(-) diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index 376f4bc320..c112fac8dd 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -19,8 +19,7 @@ from typing import Optional from qiskit.aqua import MissingOptionalLibraryError -from .optimization_algorithm import (OptimizationResultStatus, OptimizationAlgorithm, - OptimizationResult) +from .optimization_algorithm import OptimizationAlgorithm, OptimizationResult from ..exceptions import QiskitOptimizationError from ..problems.quadratic_program import QuadraticProgram @@ -142,7 +141,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: result = OptimizationResult(x=sol.get_values(), fval=sol.get_objective_value(), variables=problem.variables, - raw_results=problem.variables, + raw_results=sol, status=self.get_feasibility_status(problem, sol.get_values())) # return solution diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 6396938563..5d3b471102 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -28,11 +28,11 @@ class MinimumEigenOptimizationResult(OptimizationResult): """ Minimum Eigen Optimizer Result.""" - def __init__(self, x: Union[List[float], np.ndarray], fval: float, + def __init__(self, status: OptimizationResultStatus, + x: Union[List[float], np.ndarray], fval: float, variables: List[Variable], samples: List[Tuple[str, float, float]], - min_eigen_solver_result: Optional[MinimumEigensolverResult] = None, - status: OptimizationResultStatus = OptimizationResultStatus.SUCCESS) -> None: + min_eigen_solver_result: Optional[MinimumEigensolverResult] = None) -> None: """ Args: x: the optimal value found by ``MinimumEigensolver``. diff --git a/qiskit/optimization/algorithms/multistart_optimizer.py b/qiskit/optimization/algorithms/multistart_optimizer.py index fffb166cb4..dfa16982ed 100644 --- a/qiskit/optimization/algorithms/multistart_optimizer.py +++ b/qiskit/optimization/algorithms/multistart_optimizer.py @@ -25,8 +25,7 @@ from scipy.stats import uniform from qiskit.optimization import QuadraticProgram, INFINITY -from .optimization_algorithm import (OptimizationResultStatus, OptimizationAlgorithm, - OptimizationResult) +from .optimization_algorithm import OptimizationAlgorithm, OptimizationResult logger = logging.getLogger(__name__) @@ -97,7 +96,7 @@ def multi_start_solve(self, minimize: Callable[[np.array], Tuple[np.array, Any]] return OptimizationResult(x=x_sol, fval=fval_sol, variables=problem.variables, raw_results=rest_sol, - status=self.get_feasibility_status(self, problem, x_sol)) + status=self.get_feasibility_status(problem, x_sol)) @property def trials(self) -> int: diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index 89dd046002..96aebcfaa9 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -110,8 +110,6 @@ def get_feasibility_status(self, problem: QuadraticProgram, x: Union[List[float] Returns: The status of the result. - Raises: - None """ is_feasible = problem.is_feasible(x) diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index f228e4abaa..97866f4f7c 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -53,11 +53,11 @@ class IntermediateResult(Enum): class RecursiveMinimumEigenOptimizationResult(OptimizationResult): """Recursive Eigen Optimizer Result.""" - def __init__(self, x: Union[List[float], np.ndarray], fval: float, + def __init__(self, status: OptimizationResultStatus, + x: Union[List[float], np.ndarray], fval: float, variables: List[Variable], replacements: Dict[str, Tuple[str, int]], - history: Tuple[List[MinimumEigenOptimizationResult], OptimizationResult], - status: OptimizationResultStatus = OptimizationResultStatus.SUCCESS) -> None: + history: Tuple[List[MinimumEigenOptimizationResult], OptimizationResult]) -> None: """ Constructs an instance of the result class. diff --git a/qiskit/optimization/algorithms/slsqp_optimizer.py b/qiskit/optimization/algorithms/slsqp_optimizer.py index bd1f6d9ea8..e32334eff7 100644 --- a/qiskit/optimization/algorithms/slsqp_optimizer.py +++ b/qiskit/optimization/algorithms/slsqp_optimizer.py @@ -33,10 +33,10 @@ class SlsqpOptimizationResult(OptimizationResult): """ SLSQP optimization result, defines additional properties that may be returned by the optimizer. """ - def __init__(self, x: Union[List[float], np.ndarray], fval: float, variables: List[Variable], + def __init__(self, status: OptimizationResultStatus, + x: Union[List[float], np.ndarray], fval: float, variables: List[Variable], fx: Optional[np.ndarray] = None, its: Optional[int] = None, - imode: Optional[int] = None, smode: Optional[str] = None, - status: OptimizationResultStatus = OptimizationResultStatus.SUCCESS) -> None: + imode: Optional[int] = None, smode: Optional[str] = None) -> None: """ Constructs a result object with properties specific to SLSQP. @@ -220,15 +220,11 @@ def _minimize(x_0: np.array) -> Tuple[np.array, Any]: # actual optimization goes here result = self.multi_start_solve(_minimize, problem) - # check for feasibility - status = OptimizationResultStatus.SUCCESS if problem.is_feasible(result.x) \ - else OptimizationResultStatus.INFEASIBLE - if self._full_output: - return SlsqpOptimizationResult(result.x, result.fval, result.variables, + return SlsqpOptimizationResult(x=result.x, fval=result.fval, variables=result.variables, fx=result.raw_results[0], its=result.raw_results[1], imode=result.raw_results[2], smode=result.raw_results[3], status=self.get_feasibility_status(problem, result.x)) else: - return SlsqpOptimizationResult(result.x, result.fval, result.variables, + return SlsqpOptimizationResult(x=result.x, fval=result.fval, variables=result.variables, status=self.get_feasibility_status(problem, result.x)) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 15facb1c97..247e1d2069 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -1134,13 +1134,6 @@ def get_feasiblity_info(self, x: Union[List[float], np.ndarray]) \ elif constraint.sense == ConstraintSense.EQ: satisfied_constraints[constraint.name] = isclose(lhs, constraint.rhs) - # create a dict containing only unsatisfied variable(s)/constraint(s) - final_dict = {k: v for k, v in {**satisfied_variables, **satisfied_constraints} - .items() if not v} - - # debug - logger.debug("Violations: %s", final_dict) - is_feasible = (len(satisfied_variables) == len(self._variables) and len(satisfied_constraints) == (len(self._linear_constraints) + len(self._quadratic_constraints))) @@ -1159,8 +1152,6 @@ def is_feasible(self, x: Union[List[float], np.ndarray]) -> bool: Returns: is_feasible: Whether the solution provided is feasible or not. - Raises: - None """ is_feasible, _, _ = self.get_feasiblity_info(x) From 274f9bcbe3a33f60d4bc4c5a56bfdec2cbacd171 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Wed, 9 Sep 2020 19:55:22 +0100 Subject: [PATCH 09/14] some cleaning --- .../optimization/algorithms/admm_optimizer.py | 7 +-- .../algorithms/cplex_optimizer.py | 7 ++- .../algorithms/grover_optimizer.py | 7 +-- .../algorithms/minimum_eigen_optimizer.py | 19 ++++--- .../algorithms/multistart_optimizer.py | 7 ++- .../algorithms/optimization_algorithm.py | 9 ++-- .../recursive_minimum_eigen_optimizer.py | 15 +++--- .../algorithms/slsqp_optimizer.py | 16 +++--- .../converters/inequality_to_equality.py | 2 +- .../converters/integer_to_binary.py | 2 +- .../converters/linear_equality_to_penalty.py | 4 +- .../problems/quadratic_program.py | 49 ++++++++----------- test/optimization/test_converters.py | 18 ++++--- test/optimization/test_quadratic_program.py | 36 ++++++++++++++ 14 files changed, 115 insertions(+), 83 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 8a68916196..4566a00daf 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -190,7 +190,7 @@ def __init__(self, x: np.ndarray, fval: float, variables: List[Variable], state: the internal computation state of ADMM. status: Termination status of an optimization algorithm """ - super().__init__(x=x, fval=fval, variables=variables, raw_results=state, status=status) + super().__init__(x=x, fval=fval, variables=variables, status=status, raw_results=state) @property def state(self) -> ADMMState: @@ -377,14 +377,15 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: objective_value = objective_value * sense # convert back integer to binary - base_result = OptimizationResult(solution, objective_value, original_variables) + base_result = OptimizationResult(solution, objective_value, original_variables, + OptimizationResultStatus.SUCCESS) base_result = int2bin.interpret(base_result) # third parameter is our internal state of computations. result = ADMMOptimizationResult(x=base_result.x, fval=base_result.fval, variables=base_result.variables, state=self._state, - status=self.get_feasibility_status(problem, base_result.x)) + status=self._get_feasibility_status(problem, base_result.x)) # debug self._log.debug("solution=%s, objective=%s at iteration=%s", diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index a2b12ab68e..ffe716402f 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -136,11 +136,10 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: sol = cplex.solution # create results - result = OptimizationResult(x=sol.get_values(), - fval=sol.get_objective_value(), + result = OptimizationResult(x=sol.get_values(), fval=sol.get_objective_value(), variables=problem.variables, - raw_results=sol, - status=self.get_feasibility_status(problem, sol.get_values())) + status=self._get_feasibility_status(problem, sol.get_values()), + raw_results=sol) # return solution return result diff --git a/qiskit/optimization/algorithms/grover_optimizer.py b/qiskit/optimization/algorithms/grover_optimizer.py index 57a21b648f..836d82fd16 100644 --- a/qiskit/optimization/algorithms/grover_optimizer.py +++ b/qiskit/optimization/algorithms/grover_optimizer.py @@ -262,7 +262,8 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: # Compute function value fval = problem_init.objective.evaluate(opt_x) - result = OptimizationResult(x=opt_x, fval=fval, variables=problem_.variables) + result = OptimizationResult(x=opt_x, fval=fval, variables=problem_.variables, + status=OptimizationResultStatus.SUCCESS) # cast binaries back to integers result = self._qubo_converter.interpret(result) @@ -271,7 +272,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: operation_counts=operation_count, n_input_qubits=n_key, n_output_qubits=n_value, intermediate_fval=fval, threshold=threshold, - status=self.get_feasibility_status(problem, result.x)) + status=self._get_feasibility_status(problem, result.x)) def _measure(self, circuit: QuantumCircuit) -> str: """Get probabilities from the given backend, and picks a random outcome.""" @@ -352,7 +353,7 @@ def __init__(self, x: Union[List[float], np.ndarray], fval: float, variables: Li threshold: The threshold of Grover algorithm. status: the termination status of the optimization algorithm. """ - super().__init__(x, fval, variables, None, status) + super().__init__(x, fval, variables, status, None) self._operation_counts = operation_counts self._n_input_qubits = n_input_qubits self._n_output_qubits = n_output_qubits diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index ee4c0ffd9b..cf9bfed68a 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -26,21 +26,19 @@ class MinimumEigenOptimizationResult(OptimizationResult): """ Minimum Eigen Optimizer Result.""" - def __init__(self, status: OptimizationResultStatus, - x: Union[List[float], np.ndarray], fval: float, - variables: List[Variable], - samples: List[Tuple[str, float, float]], + def __init__(self, x: Union[List[float], np.ndarray], fval: float, variables: List[Variable], + status: OptimizationResultStatus, samples: List[Tuple[str, float, float]], min_eigen_solver_result: Optional[MinimumEigensolverResult] = None) -> None: """ Args: x: the optimal value found by ``MinimumEigensolver``. fval: the optimal function value. variables: the list of variables of the optimization problem. + status: the termination status of the optimization algorithm. samples: the basis state as bitstring, the QUBO value, and the probability of sampling. min_eigen_solver_result: the result obtained from the underlying algorithm. - status: the termination status of the optimization algorithm. """ - super().__init__(x, fval, variables, None, status) + super().__init__(x, fval, variables, status, None) self._samples = samples self._min_eigen_solver_result = min_eigen_solver_result @@ -181,14 +179,15 @@ def solve(self, problem: QuadraticProgram) -> MinimumEigenOptimizationResult: samples = [(x_str, offset, 1.0)] # translate result back to integers - result = OptimizationResult(x=x, fval=fval, variables=problem_.variables) + result = OptimizationResult(x=x, fval=fval, variables=problem_.variables, + status=OptimizationResultStatus.SUCCESS) result = self._qubo_converter.interpret(result) return MinimumEigenOptimizationResult(x=result.x, fval=result.fval, variables=result.variables, - samples=samples, - min_eigen_solver_result=eigen_result, - status=self.get_feasibility_status(problem, result.x)) + status=self._get_feasibility_status(problem, + result.x), + samples=samples, min_eigen_solver_result=eigen_result) def _eigenvector_to_solutions(eigenvector: Union[dict, np.ndarray, StateFn], diff --git a/qiskit/optimization/algorithms/multistart_optimizer.py b/qiskit/optimization/algorithms/multistart_optimizer.py index c2ce198567..e3874cc367 100644 --- a/qiskit/optimization/algorithms/multistart_optimizer.py +++ b/qiskit/optimization/algorithms/multistart_optimizer.py @@ -91,10 +91,9 @@ def multi_start_solve(self, minimize: Callable[[np.array], Tuple[np.array, Any]] x_sol = x rest_sol = rest - return OptimizationResult(x=x_sol, fval=fval_sol, - variables=problem.variables, - raw_results=rest_sol, - status=self.get_feasibility_status(problem, x_sol)) + return OptimizationResult(x=x_sol, fval=fval_sol, variables=problem.variables, + status=self._get_feasibility_status(problem, x_sol), + raw_results=rest_sol) @property def trials(self) -> int: diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index e9c833cb0e..ca7f281617 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -97,8 +97,8 @@ def _verify_compatibility(self, problem: QuadraticProgram) -> None: if msg: raise QiskitOptimizationError('Incompatible problem: {}'.format(msg)) - def get_feasibility_status(self, problem: QuadraticProgram, x: Union[List[float], np.ndarray]) \ - -> OptimizationResultStatus: + def _get_feasibility_status(self, problem: QuadraticProgram, + x: Union[List[float], np.ndarray]) -> OptimizationResultStatus: """Returns whether the input result is feasible or not for the given problem. Args: @@ -107,7 +107,6 @@ def get_feasibility_status(self, problem: QuadraticProgram, x: Union[List[float] Returns: The status of the result. - """ is_feasible = problem.is_feasible(x) @@ -157,8 +156,8 @@ class OptimizationResult: def __init__(self, x: Union[List[float], np.ndarray], fval: float, variables: List[Variable], - raw_results: Optional[Any] = None, - status: OptimizationResultStatus = OptimizationResultStatus.SUCCESS) -> None: + status: OptimizationResultStatus, + raw_results: Optional[Any] = None) -> None: """ Args: x: the optimal value found in the optimization. diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index b2ed2b85fe..fa8a3b8202 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -51,10 +51,8 @@ class IntermediateResult(Enum): class RecursiveMinimumEigenOptimizationResult(OptimizationResult): """Recursive Eigen Optimizer Result.""" - def __init__(self, status: OptimizationResultStatus, - x: Union[List[float], np.ndarray], fval: float, - variables: List[Variable], - replacements: Dict[str, Tuple[str, int]], + def __init__(self, x: Union[List[float], np.ndarray], fval: float, variables: List[Variable], + status: OptimizationResultStatus, replacements: Dict[str, Tuple[str, int]], history: Tuple[List[MinimumEigenOptimizationResult], OptimizationResult]) -> None: """ Constructs an instance of the result class. @@ -63,6 +61,7 @@ def __init__(self, status: OptimizationResultStatus, x: the optimal value found in the optimization. fval: the optimal function value. variables: the list of variables of the optimization problem. + status: the termination status of the optimization algorithm. replacements: a dictionary of substituted variables. Key is a variable being substituted, value is a tuple of substituting variable and a weight, either 1 or -1. history: a tuple containing intermediate results. The first element is a list of @@ -71,9 +70,8 @@ def __init__(self, status: OptimizationResultStatus, the second element is an instance of :class:`~qiskit.optimization.algorithm.OptimizationResult` obtained at the last step via `min_num_vars_optimizer`. - status: the termination status of the optimization algorithm. """ - super().__init__(x, fval, variables, None, status) + super().__init__(x, fval, variables, status, None) self._replacements = replacements self._history = history @@ -284,14 +282,15 @@ def find_value(x, replacements, var_values): # construct result x_v = [var_values[x_aux.name] for x_aux in problem_ref.variables] fval = result.fval - result = OptimizationResult(x=x_v, fval=fval, variables=problem_ref.variables) + result = OptimizationResult(x=x_v, fval=fval, variables=problem_ref.variables, + status=OptimizationResultStatus.SUCCESS) result = self._qubo_converter.interpret(result) return RecursiveMinimumEigenOptimizationResult(x=result.x, fval=result.fval, variables=result.variables, replacements=replacements, history=history, - status=(self.get_feasibility_status + status=(self._get_feasibility_status (problem, result.x))) def _find_strongest_correlation(self, correlations): diff --git a/qiskit/optimization/algorithms/slsqp_optimizer.py b/qiskit/optimization/algorithms/slsqp_optimizer.py index 997e853849..96670916de 100644 --- a/qiskit/optimization/algorithms/slsqp_optimizer.py +++ b/qiskit/optimization/algorithms/slsqp_optimizer.py @@ -31,10 +31,10 @@ class SlsqpOptimizationResult(OptimizationResult): """ SLSQP optimization result, defines additional properties that may be returned by the optimizer. """ - def __init__(self, status: OptimizationResultStatus, - x: Union[List[float], np.ndarray], fval: float, variables: List[Variable], - fx: Optional[np.ndarray] = None, its: Optional[int] = None, - imode: Optional[int] = None, smode: Optional[str] = None) -> None: + def __init__(self, x: Union[List[float], np.ndarray], fval: float, variables: List[Variable], + status: OptimizationResultStatus, fx: Optional[np.ndarray] = None, + its: Optional[int] = None, imode: Optional[int] = None, + smode: Optional[str] = None) -> None: """ Constructs a result object with properties specific to SLSQP. @@ -49,7 +49,7 @@ def __init__(self, status: OptimizationResultStatus, smode: Message describing the exit mode from the optimizer. status: the termination status of the optimization algorithm. """ - super().__init__(x, fval, variables, None, status) + super().__init__(x, fval, variables, status, None) self._fx = fx self._its = its self._imode = imode @@ -220,9 +220,9 @@ def _minimize(x_0: np.array) -> Tuple[np.array, Any]: if self._full_output: return SlsqpOptimizationResult(x=result.x, fval=result.fval, variables=result.variables, + status=self._get_feasibility_status(problem, result.x), fx=result.raw_results[0], its=result.raw_results[1], - imode=result.raw_results[2], smode=result.raw_results[3], - status=self.get_feasibility_status(problem, result.x)) + imode=result.raw_results[2], smode=result.raw_results[3]) else: return SlsqpOptimizationResult(x=result.x, fval=result.fval, variables=result.variables, - status=self.get_feasibility_status(problem, result.x)) + status=self._get_feasibility_status(problem, result.x)) diff --git a/qiskit/optimization/converters/inequality_to_equality.py b/qiskit/optimization/converters/inequality_to_equality.py index 9b36ce6aa1..4087e7b4e2 100644 --- a/qiskit/optimization/converters/inequality_to_equality.py +++ b/qiskit/optimization/converters/inequality_to_equality.py @@ -352,7 +352,7 @@ def interpret(self, result: OptimizationResult) -> OptimizationResult: names = [x.name for x in self._dst.variables] new_x = self._interpret_var(names, result.x) return OptimizationResult(x=new_x, fval=result.fval, variables=self._src.variables, - raw_results=result.raw_results, status=result.status) + status=result.status, raw_results=result.raw_results) def _interpret_var(self, names, vals) -> List[int]: # interpret slack variables diff --git a/qiskit/optimization/converters/integer_to_binary.py b/qiskit/optimization/converters/integer_to_binary.py index 98a70e00b4..bf43747ad5 100644 --- a/qiskit/optimization/converters/integer_to_binary.py +++ b/qiskit/optimization/converters/integer_to_binary.py @@ -219,7 +219,7 @@ def interpret(self, result: OptimizationResult) -> OptimizationResult: """ new_x = self._interpret_var(result.x) return OptimizationResult(x=new_x, fval=result.fval, variables=self._src.variables, - raw_results=result.raw_results) + status=result.status, raw_results=result.raw_results) def _interpret_var(self, vals: Union[List[float], np.ndarray]) -> List[float]: # interpret integer values diff --git a/qiskit/optimization/converters/linear_equality_to_penalty.py b/qiskit/optimization/converters/linear_equality_to_penalty.py index 624ddbe99c..b0e84e7870 100644 --- a/qiskit/optimization/converters/linear_equality_to_penalty.py +++ b/qiskit/optimization/converters/linear_equality_to_penalty.py @@ -190,8 +190,8 @@ def interpret(self, result: OptimizationResult) -> OptimizationResult: new_status = OptimizationResultStatus.INFEASIBLE return OptimizationResult(x=result.x, fval=substituted_qp.objective.constant, - variables=self._src.variables, raw_results=result.raw_results, - status=new_status) + variables=self._src.variables, status=new_status, + raw_results=result.raw_results) @property def penalty(self) -> Optional[float]: diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 420bfe456d..ae504dadaa 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -1091,15 +1091,15 @@ def from_ising(self, self.minimize(constant=offset, linear=linear_terms, quadratic=quadratic_terms) offset -= offset - def get_feasiblity_info(self, x: Union[List[float], np.ndarray]) \ - -> Tuple[bool, List[Constraint], List[Variable]]: + def get_feasibility_info(self, x: Union[List[float], np.ndarray]) \ + -> Tuple[bool, List[Variable], List[Constraint]]: """Returns whether a solution is feasible or not along with the violations. Args: x: the input result list returned by the optimizer Returns: - is_feasible: Whether the solution provided is feasible or not. - List[Constraint]: List of constraints which are violated. + feasible: Whether the solution provided is feasible or not. List[Variable]: List of variables which are violated. + List[Constraint]: List of constraints which are violated. Raises: QiskitOptimizationError: If the input `x` is not same len as total vars @@ -1112,48 +1112,41 @@ def get_feasiblity_info(self, x: Union[List[float], np.ndarray]) \ ) # check whether the input satisfy the bounds of the problem - satisfied_variables = {} + violated_variables = [] # type: List[Variable] for i, val in enumerate(x): variable = self.get_variable(i) - if variable._lowerbound <= val <= variable._upperbound: - satisfied_variables[variable.name] = True - else: - satisfied_variables[variable.name] = False + if val < variable.lowerbound or variable.upperbound < val: + violated_variables.append(variable) # check whether the input satisfy the constraints of the problem - satisfied_constraints = {} + violated_constraints = [] # type: List[Constraint] for constraint in cast(List[Constraint], self._linear_constraints) + \ cast(List[Constraint], self._quadratic_constraints): lhs = constraint.evaluate(x) - if constraint.sense == ConstraintSense.LE: - satisfied_constraints[constraint.name] = lhs <= constraint.rhs - elif constraint.sense == ConstraintSense.GE: - satisfied_constraints[constraint.name] = lhs >= constraint.rhs - elif constraint.sense == ConstraintSense.EQ: - satisfied_constraints[constraint.name] = isclose(lhs, constraint.rhs) - - is_feasible = (len(satisfied_variables) == len(self._variables) and - len(satisfied_constraints) == (len(self._linear_constraints) + - len(self._quadratic_constraints))) + if constraint.sense == ConstraintSense.LE and lhs > constraint.rhs: + violated_constraints.append(constraint) + elif constraint.sense == ConstraintSense.GE and lhs < constraint.rhs: + violated_constraints.append(constraint) + elif constraint.sense == ConstraintSense.EQ and not isclose(lhs, constraint.rhs): + violated_constraints.append(constraint) - violated_constraints = list(satisfied_constraints.keys()) + feasible = len(violated_variables) == 0 and len(violated_constraints) == 0 - violated_variables = list(satisfied_variables.keys()) - - return (is_feasible, violated_constraints, violated_variables) + return feasible, violated_variables, violated_constraints def is_feasible(self, x: Union[List[float], np.ndarray]) -> bool: """Returns whether a solution is feasible or not. + Args: - x: the input result list returned by the optimizer + x: the input result list returned by the optimizer. Returns: - is_feasible: Whether the solution provided is feasible or not. + ``True`` if the solution provided is feasible otherwise ``False``. """ - is_feasible, _, _ = self.get_feasiblity_info(x) + feasible, _, _ = self.get_feasibility_info(x) - return is_feasible + return feasible class SubstituteVariables: diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index feed200116..25b1de2566 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -145,7 +145,8 @@ def test_inequality_binary(self): lst = [op2.variables[6].lowerbound, op2.variables[6].upperbound] self.assertListEqual(lst, [0, 4]) - result = OptimizationResult(x=np.arange(7), fval=0, variables=op2.variables) + result = OptimizationResult(x=np.arange(7), fval=0, variables=op2.variables, + status=OptimizationResultStatus.SUCCESS) new_result = conv.interpret(result) np.testing.assert_array_almost_equal(new_result.x, np.arange(3)) self.assertListEqual(new_result.variable_names, ['x0', 'x1', 'x2']) @@ -216,7 +217,8 @@ def test_inequality_integer(self): lst = [op2.variables[6].lowerbound, op2.variables[6].upperbound] self.assertListEqual(lst, [0, 60]) - result = OptimizationResult(x=np.arange(7), fval=0, variables=op2.variables) + result = OptimizationResult(x=np.arange(7), fval=0, variables=op2.variables, + status=OptimizationResultStatus.SUCCESS) new_result = conv.interpret(result) np.testing.assert_array_almost_equal(new_result.x, np.arange(3)) self.assertListEqual(new_result.variable_names, ['x0', 'x1', 'x2']) @@ -307,7 +309,8 @@ def test_penalize_binary(self): op2 = conv.convert(op) self.assertEqual(op2.get_num_linear_constraints(), 0) - result = OptimizationResult(x=np.arange(3), fval=0, variables=op2.variables) + result = OptimizationResult(x=np.arange(3), fval=0, variables=op2.variables, + status=OptimizationResultStatus.SUCCESS) new_result = conv.interpret(result) self.assertEqual(new_result.status, OptimizationResultStatus.INFEASIBLE) np.testing.assert_array_almost_equal(new_result.x, np.arange(3)) @@ -332,7 +335,8 @@ def test_penalize_integer(self): op2 = conv.convert(op) self.assertEqual(op2.get_num_linear_constraints(), 0) - result = OptimizationResult(x=[0, 1, -1], fval=1, variables=op2.variables) + result = OptimizationResult(x=[0, 1, -1], fval=1, variables=op2.variables, + status=OptimizationResultStatus.SUCCESS) new_result = conv.interpret(result) self.assertAlmostEqual(new_result.fval, 1) self.assertEqual(new_result.status, OptimizationResultStatus.SUCCESS) @@ -374,7 +378,8 @@ def test_binary_to_integer(self): op.linear_constraint(linear, Constraint.Sense.EQ, 6, 'x0x1x2') conv = IntegerToBinary() op2 = conv.convert(op) - result = OptimizationResult(x=[0, 1, 1, 1, 1], fval=17, variables=op2.variables) + result = OptimizationResult(x=[0, 1, 1, 1, 1], fval=17, variables=op2.variables, + status=OptimizationResultStatus.SUCCESS) new_result = conv.interpret(result) np.testing.assert_array_almost_equal(new_result.x, [0, 1, 5]) self.assertEqual(new_result.fval, 17) @@ -551,7 +556,8 @@ def test_linear_equality_to_penalty_decode(self): self.assertListEqual(decoded_result.variable_names, ['x', 'y', 'z']) self.assertDictEqual(decoded_result.variables_dict, {'x': 1.0, 'y': 1.0, 'z': 0.0}) - infeasible_result = OptimizationResult(x=[1, 1, 1], fval=0, variables=qprog.variables) + infeasible_result = OptimizationResult(x=[1, 1, 1], fval=0, variables=qprog.variables, + status=OptimizationResultStatus.SUCCESS) decoded_infeasible_result = lineq2penalty.interpret(infeasible_result) self.assertEqual(decoded_infeasible_result.fval, 5) np.testing.assert_array_almost_equal(decoded_infeasible_result.x, [1, 1, 1]) diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index 2d46d41211..eebe15712c 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -664,6 +664,42 @@ def test_substitute_variables(self): self.assertEqual(cst.sense.name, 'LE') self.assertEqual(cst.rhs, -1) + def test_feasibility(self): + """Tests feasibility methods.""" + mod = Model('test') + # 0, 5 + x = mod.continuous_var(-1, 1, 'x', ) + y = mod.continuous_var(-10, 10, 'y') + mod.minimize(x + y) + mod.add(x + y <= 10, 'c0') + mod.add(x + y >= -10, 'c1') + mod.add(x + y == 5, 'c2') + mod.add(x * x + y <= 10, 'c3') + mod.add(x * x + y >= 5, 'c4') + mod.add(x * x + y * y == 25, 'c5') + q_p = QuadraticProgram() + q_p.from_docplex(mod) + + self.assertTrue(q_p.is_feasible([0, 5])) + self.assertFalse(q_p.is_feasible([1, 10])) + self.assertFalse(q_p.is_feasible([1, -12])) + self.assertFalse(q_p.is_feasible([1, 5])) + self.assertFalse(q_p.is_feasible([5, 0])) + self.assertFalse(q_p.is_feasible([1, 1])) + self.assertFalse(q_p.is_feasible([0, 0])) + + feasible, variables, constraints = q_p.get_feasibility_info([10, 0]) + self.assertFalse(feasible) + self.assertIsNotNone(variables) + self.assertEqual(1, len(variables)) + self.assertEqual('x', variables[0].name) + + self.assertIsNotNone(constraints) + self.assertEqual(3, len(constraints)) + self.assertEqual('c2', constraints[0].name) + self.assertEqual('c3', constraints[1].name) + self.assertEqual('c5', constraints[2].name) + if __name__ == '__main__': unittest.main() From d58f9343b4d37c7089dc6f10d795301d9e91d842 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Thu, 10 Sep 2020 10:12:13 +0100 Subject: [PATCH 10/14] added reno --- .../notes/feasibility-check-b99605f771e745b7.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 releasenotes/notes/feasibility-check-b99605f771e745b7.yaml diff --git a/releasenotes/notes/feasibility-check-b99605f771e745b7.yaml b/releasenotes/notes/feasibility-check-b99605f771e745b7.yaml new file mode 100644 index 0000000000..1f69465f5f --- /dev/null +++ b/releasenotes/notes/feasibility-check-b99605f771e745b7.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + A feasibility check of the obtained solution has been added to all optimizers in the + optimization stack. This has been implemented by adding two new method to ``QuadraticProgram``: + * ``get_feasibility_info(self, x: Union[List[float], np.ndarray])`` accepts an array and returns + whether this solution is feasible and a list of violated variables(violated bounds) and + a list of violated constraints. + * ``is_feasible(self, x: Union[List[float], np.ndarray])`` accepts an array and returns whether + this solutuion is feasible or not. From 818cadde630cd03e04cdb2f1840aa18812f2d51d Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Thu, 10 Sep 2020 11:05:41 +0100 Subject: [PATCH 11/14] fixed reno --- releasenotes/notes/feasibility-check-b99605f771e745b7.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/releasenotes/notes/feasibility-check-b99605f771e745b7.yaml b/releasenotes/notes/feasibility-check-b99605f771e745b7.yaml index 1f69465f5f..abba66d996 100644 --- a/releasenotes/notes/feasibility-check-b99605f771e745b7.yaml +++ b/releasenotes/notes/feasibility-check-b99605f771e745b7.yaml @@ -4,7 +4,7 @@ features: A feasibility check of the obtained solution has been added to all optimizers in the optimization stack. This has been implemented by adding two new method to ``QuadraticProgram``: * ``get_feasibility_info(self, x: Union[List[float], np.ndarray])`` accepts an array and returns - whether this solution is feasible and a list of violated variables(violated bounds) and - a list of violated constraints. + whether this solution is feasible and a list of violated variables(violated bounds) and + a list of violated constraints. * ``is_feasible(self, x: Union[List[float], np.ndarray])`` accepts an array and returns whether - this solutuion is feasible or not. + this solutuion is feasible or not. From 8a0a10e0451237a5e5eb86fff902d38ffe8c7788 Mon Sep 17 00:00:00 2001 From: Anton Dekusar <62334182+adekusar-drl@users.noreply.github.com> Date: Thu, 10 Sep 2020 22:50:31 +0100 Subject: [PATCH 12/14] Update releasenotes/notes/feasibility-check-b99605f771e745b7.yaml Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com> --- releasenotes/notes/feasibility-check-b99605f771e745b7.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/feasibility-check-b99605f771e745b7.yaml b/releasenotes/notes/feasibility-check-b99605f771e745b7.yaml index abba66d996..a3d58677d9 100644 --- a/releasenotes/notes/feasibility-check-b99605f771e745b7.yaml +++ b/releasenotes/notes/feasibility-check-b99605f771e745b7.yaml @@ -2,7 +2,7 @@ features: - | A feasibility check of the obtained solution has been added to all optimizers in the - optimization stack. This has been implemented by adding two new method to ``QuadraticProgram``: + optimization stack. This has been implemented by adding two new methods to ``QuadraticProgram``: * ``get_feasibility_info(self, x: Union[List[float], np.ndarray])`` accepts an array and returns whether this solution is feasible and a list of violated variables(violated bounds) and a list of violated constraints. From 286d68d735fd860a9c03bf33cce35a4b3589585a Mon Sep 17 00:00:00 2001 From: Anton Dekusar <62334182+adekusar-drl@users.noreply.github.com> Date: Thu, 10 Sep 2020 22:50:54 +0100 Subject: [PATCH 13/14] Update releasenotes/notes/feasibility-check-b99605f771e745b7.yaml Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com> --- releasenotes/notes/feasibility-check-b99605f771e745b7.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/feasibility-check-b99605f771e745b7.yaml b/releasenotes/notes/feasibility-check-b99605f771e745b7.yaml index a3d58677d9..53f01bd92d 100644 --- a/releasenotes/notes/feasibility-check-b99605f771e745b7.yaml +++ b/releasenotes/notes/feasibility-check-b99605f771e745b7.yaml @@ -7,4 +7,4 @@ features: whether this solution is feasible and a list of violated variables(violated bounds) and a list of violated constraints. * ``is_feasible(self, x: Union[List[float], np.ndarray])`` accepts an array and returns whether - this solutuion is feasible or not. + this solution is feasible or not. From cb56d8cb926673019083773eafda8bb530a8afd7 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Thu, 10 Sep 2020 23:09:13 +0100 Subject: [PATCH 14/14] code review --- qiskit/optimization/problems/quadratic_program.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index ae504dadaa..8c8eb3ffda 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -1095,7 +1095,7 @@ def get_feasibility_info(self, x: Union[List[float], np.ndarray]) \ -> Tuple[bool, List[Variable], List[Constraint]]: """Returns whether a solution is feasible or not along with the violations. Args: - x: the input result list returned by the optimizer + x: a solution value, such as returned in an optimizer result. Returns: feasible: Whether the solution provided is feasible or not. List[Variable]: List of variables which are violated. @@ -1130,7 +1130,7 @@ def get_feasibility_info(self, x: Union[List[float], np.ndarray]) \ elif constraint.sense == ConstraintSense.EQ and not isclose(lhs, constraint.rhs): violated_constraints.append(constraint) - feasible = len(violated_variables) == 0 and len(violated_constraints) == 0 + feasible = not violated_variables and not violated_constraints return feasible, violated_variables, violated_constraints @@ -1138,7 +1138,7 @@ def is_feasible(self, x: Union[List[float], np.ndarray]) -> bool: """Returns whether a solution is feasible or not. Args: - x: the input result list returned by the optimizer. + x: a solution value, such as returned in an optimizer result. Returns: ``True`` if the solution provided is feasible otherwise ``False``.