From 1b2453f1c59162257d3292672e27f13ace10224a Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Tue, 3 Mar 2020 15:17:14 +0100 Subject: [PATCH 001/323] add new optimization stack files --- qiskit/optimization/algorithms/__init__.py | 32 + .../optimization/algorithms/admm_optimizer.py | 439 +++++++++ .../algorithms/cobyla_optimizer.py | 183 ++++ .../algorithms/cplex_optimizer.py | 122 +++ .../algorithms/min_eigen_optimizer.py | 165 ++++ .../algorithms/optimization_algorithm.py | 58 ++ .../algorithms/recursive_ising_optimizer.py | 49 + qiskit/optimization/applications/__init__.py | 0 .../applications/ising/__init__.py | 49 + .../optimization/applications/ising/clique.py | 200 ++++ .../optimization/applications/ising/common.py | 167 ++++ .../applications/ising/docplex.py | 295 ++++++ .../applications/ising/exact_cover.py | 167 ++++ .../applications/ising/graph_partition.py | 156 +++ .../applications/ising/max_cut.py | 137 +++ .../applications/ising/partition.py | 112 +++ .../applications/ising/set_packing.py | 158 +++ .../applications/ising/stable_set.py | 140 +++ qiskit/optimization/applications/ising/tsp.py | 288 ++++++ .../applications/ising/vehicle_routing.py | 204 ++++ .../applications/ising/vertex_cover.py | 172 ++++ qiskit/optimization/converters/__init__.py | 41 + .../inequality_to_equality_converter.py | 216 +++++ .../converters/integer_to_binary_converter.py | 225 +++++ .../optimization_problem_to_operator.py | 125 +++ .../penalize_linear_equality_constraints.py | 132 +++ qiskit/optimization/problems/__init__.py | 43 + .../problems/linear_constraint.py | 901 ++++++++++++++++++ qiskit/optimization/problems/objective.py | 552 +++++++++++ .../problems/optimization_problem.py | 554 +++++++++++ qiskit/optimization/problems/problem_type.py | 81 ++ .../problems/quadratic_constraint.py | 491 ++++++++++ qiskit/optimization/problems/variables.py | 761 +++++++++++++++ qiskit/optimization/results/__init__.py | 38 + .../results/optimization_result.py | 97 ++ .../optimization/results/quality_metrics.py | 83 ++ qiskit/optimization/results/solution.py | 253 +++++ .../optimization/results/solution_status.py | 146 +++ qiskit/optimization/utils/__init__.py | 40 + qiskit/optimization/utils/base.py | 80 ++ .../utils/eigenvector_to_solutions.py | 97 ++ qiskit/optimization/utils/helpers.py | 170 ++++ .../utils/qiskit_optimization_error.py | 25 + test/optimization/common.py | 108 +++ test/optimization/resources/op_ip1.lp | 13 + test/optimization/resources/op_ip2.lp | 12 + test/optimization/resources/op_lp1.lp | 11 + test/optimization/resources/op_mip1.lp | 13 + test/optimization/test_admm_algorithm.py | 66 ++ test/optimization/test_admm_bpp.py | 361 +++++++ test/optimization/test_admm_miskp.py | 346 +++++++ test/optimization/test_cobyla_optimizer.py | 53 ++ test/optimization/test_converters.py | 143 +++ test/optimization/test_cplex_optimizer.py | 56 ++ test/optimization/test_linear_constraints.py | 268 ++++++ test/optimization/test_min_eigen_optimizer.py | 82 ++ test/optimization/test_objective.py | 29 + .../optimization/test_optimization_problem.py | 128 +++ test/optimization/test_variables.py | 219 +++++ 59 files changed, 10352 insertions(+) create mode 100644 qiskit/optimization/algorithms/__init__.py create mode 100644 qiskit/optimization/algorithms/admm_optimizer.py create mode 100644 qiskit/optimization/algorithms/cobyla_optimizer.py create mode 100644 qiskit/optimization/algorithms/cplex_optimizer.py create mode 100644 qiskit/optimization/algorithms/min_eigen_optimizer.py create mode 100644 qiskit/optimization/algorithms/optimization_algorithm.py create mode 100644 qiskit/optimization/algorithms/recursive_ising_optimizer.py create mode 100644 qiskit/optimization/applications/__init__.py create mode 100644 qiskit/optimization/applications/ising/__init__.py create mode 100644 qiskit/optimization/applications/ising/clique.py create mode 100644 qiskit/optimization/applications/ising/common.py create mode 100644 qiskit/optimization/applications/ising/docplex.py create mode 100644 qiskit/optimization/applications/ising/exact_cover.py create mode 100644 qiskit/optimization/applications/ising/graph_partition.py create mode 100644 qiskit/optimization/applications/ising/max_cut.py create mode 100644 qiskit/optimization/applications/ising/partition.py create mode 100644 qiskit/optimization/applications/ising/set_packing.py create mode 100644 qiskit/optimization/applications/ising/stable_set.py create mode 100644 qiskit/optimization/applications/ising/tsp.py create mode 100644 qiskit/optimization/applications/ising/vehicle_routing.py create mode 100644 qiskit/optimization/applications/ising/vertex_cover.py create mode 100644 qiskit/optimization/converters/__init__.py create mode 100644 qiskit/optimization/converters/inequality_to_equality_converter.py create mode 100644 qiskit/optimization/converters/integer_to_binary_converter.py create mode 100644 qiskit/optimization/converters/optimization_problem_to_operator.py create mode 100644 qiskit/optimization/converters/penalize_linear_equality_constraints.py create mode 100755 qiskit/optimization/problems/__init__.py create mode 100755 qiskit/optimization/problems/linear_constraint.py create mode 100755 qiskit/optimization/problems/objective.py create mode 100755 qiskit/optimization/problems/optimization_problem.py create mode 100755 qiskit/optimization/problems/problem_type.py create mode 100755 qiskit/optimization/problems/quadratic_constraint.py create mode 100755 qiskit/optimization/problems/variables.py create mode 100755 qiskit/optimization/results/__init__.py create mode 100644 qiskit/optimization/results/optimization_result.py create mode 100755 qiskit/optimization/results/quality_metrics.py create mode 100755 qiskit/optimization/results/solution.py create mode 100755 qiskit/optimization/results/solution_status.py create mode 100755 qiskit/optimization/utils/__init__.py create mode 100755 qiskit/optimization/utils/base.py create mode 100644 qiskit/optimization/utils/eigenvector_to_solutions.py create mode 100755 qiskit/optimization/utils/helpers.py create mode 100644 qiskit/optimization/utils/qiskit_optimization_error.py create mode 100755 test/optimization/common.py create mode 100644 test/optimization/resources/op_ip1.lp create mode 100644 test/optimization/resources/op_ip2.lp create mode 100644 test/optimization/resources/op_lp1.lp create mode 100644 test/optimization/resources/op_mip1.lp create mode 100644 test/optimization/test_admm_algorithm.py create mode 100644 test/optimization/test_admm_bpp.py create mode 100644 test/optimization/test_admm_miskp.py create mode 100644 test/optimization/test_cobyla_optimizer.py create mode 100644 test/optimization/test_converters.py create mode 100644 test/optimization/test_cplex_optimizer.py create mode 100644 test/optimization/test_linear_constraints.py create mode 100644 test/optimization/test_min_eigen_optimizer.py create mode 100644 test/optimization/test_objective.py create mode 100644 test/optimization/test_optimization_problem.py create mode 100644 test/optimization/test_variables.py diff --git a/qiskit/optimization/algorithms/__init__.py b/qiskit/optimization/algorithms/__init__.py new file mode 100644 index 0000000000..5cbe3f2ab8 --- /dev/null +++ b/qiskit/optimization/algorithms/__init__.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + +""" +======================================================== +Optimization stack for Aqua (:mod:`qiskit.optimization`) +======================================================== + +.. currentmodule:: qiskit.optimization.algorithms + +Structures for defining an optimization algorithms +========== + +""" + +from qiskit.optimization.algorithms.optimization_algorithm import OptimizationAlgorithm +from qiskit.optimization.algorithms.cplex_optimizer import CplexOptimizer +from qiskit.optimization.algorithms.cobyla_optimizer import CobylaOptimizer +from qiskit.optimization.algorithms.min_eigen_optimizer import MinEigenOptimizer + +__all__ = ["OptimizationAlgorithm", "CplexOptimizer", "CobylaOptimizer", "MinEigenOptimizer"] diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py new file mode 100644 index 0000000000..870fc8241e --- /dev/null +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -0,0 +1,439 @@ +from typing import List + +import numpy as np +from cplex import SparsePair +from qiskit.optimization.algorithms.optimization_algorithm import OptimizationAlgorithm +from qiskit.optimization.problems.optimization_problem import OptimizationProblem +from qiskit.optimization.problems.variables import CPX_BINARY, CPX_CONTINUOUS + +from qiskit.optimization.results.optimization_result import OptimizationResult + + +class ADMMParameters: + def __init__(self, rho=10000, factor_c=100000, beta=1000) -> None: + """ + Defines parameter for ADMM. + :param rho: Rho parameter of ADMM. + :param factor_c: Penalizing factor for equality constraints, when mapping to QUBO. + :param beta: Penalization for y decision variables. + """ + super().__init__() + self.factor_c = factor_c + self.rho = rho + self.beta = beta + + +class ADMMState: + def __init__(self, binary_size: int) -> None: + super().__init__() + self.y = np.zeros(binary_size) + self.z = np.zeros(binary_size) + self.lambda_mult = np.zeros(binary_size) + self.x0 = np.zeros(binary_size) + + +class ADMMOptimizer(OptimizationAlgorithm): + def __init__(self, params: ADMMParameters = None) -> None: + super().__init__() + self._op = None + self._binary_indices = [] + self._continuous_indices = [] + if params is None: + # create default params + params = ADMMParameters() + # todo: keep parameters as ADMMParameters or copy to the class level? + self._factor_c = params.factor_c + self._rho = params.rho + self._beta = params.beta + + # internal state where we'll keep intermediate solution + self._state = None + + def is_compatible(self, problem: OptimizationProblem): + # 1. only binary and continuous variables are supported + for var_index, var_type in enumerate(problem.variables.get_types()): + if var_type != CPX_BINARY and var_type != CPX_CONTINUOUS: + # var var_index is not binary and not continuous + return False + + self._op = problem + self._binary_indices = self._get_variable_indices(CPX_BINARY) + self._continuous_indices = self._get_variable_indices(CPX_CONTINUOUS) + + # 2. binary and continuous variables are separable in objective + for binary_index in self._binary_indices: + for continuous_index in self._continuous_indices: + coeff = problem.objective.get_quadratic_coefficients(binary_index, continuous_index) + if coeff != 0: + # binary and continuous vars are mixed + return False + + # 3. no quadratic constraints are supported + quad_constraints = problem.quadratic_constraints.get_num() + if quad_constraints is not None and quad_constraints > 0: + # quadratic constraints are not supported + return False + + # todo: verify other properties of the problem + return True + + def solve(self, problem: OptimizationProblem): + self._op = problem + + # parse problem and convert to an ADMM specific representation + self._binary_indices = self._get_variable_indices(CPX_BINARY) + self._continuous_indices = self._get_variable_indices(CPX_CONTINUOUS) + + # create our computation state + self._state = ADMMState(len(self._binary_indices)) + + # debug + # self.__dump_matrices_and_vectors() + + op1 = self._create_step1_problem() + # debug + op1.write("op1.lp") + + op2 = self._create_step2_problem() + op2.write("op2.lp") + + op3 = self._create_step3_problem() + op3.write("op3.lp") + + # solve the problem + # ... + # prepare the solution + + # actual results + x = 0 + # function value + fval = 0 + # third parameter is our internal state of computations + result = OptimizationResult(x, fval, self._state) + return result + + def _get_variable_indices(self, var_type: str) -> List[int]: + indices = [] + for i, variable_type in enumerate(self._op.variables.get_types()): + if variable_type == var_type: + indices.append(i) + + return indices + + def get_q0(self): + return self._get_q(self._binary_indices) + + def get_q1(self): + return self._get_q(self._continuous_indices) + + def _get_q(self, variable_indices: List[int]) -> np.ndarray: + size = len(variable_indices) + q = np.zeros(shape=(size, size)) + # fill in the matrix + # in fact we use re-indexed variables + [q.itemset((i, j), self._op.objective.get_quadratic_coefficients(var_index_i, var_index_j)) + for i, var_index_i in enumerate(variable_indices) + for j, var_index_j in enumerate(variable_indices)] + return q + + def _get_c(self, variable_indices: List[int]) -> np.ndarray: + c = np.array(self._op.objective.get_linear(variable_indices)) + return c + + def get_c0(self): + return self._get_c(self._binary_indices) + + def get_c1(self): + return self._get_c(self._continuous_indices) + + def _assign_row_values(self, matrix: List[List[float]], vector: List[float], constraint_index, variable_indices): + # assign matrix row + row = [] + [row.append(self._op.linear_constraints.get_coefficients(constraint_index, var_index)) + for var_index in variable_indices] + matrix.append(row) + + # assign vector row + vector.append(self._op.linear_constraints.get_rhs(constraint_index)) + + # flip the sign if constraint is G, we want L constraints + if self._op.linear_constraints.get_senses(constraint_index) == "G": + # invert the sign to make constraint "L" + matrix[-1] = [-1 * el for el in matrix[-1]] + vector[-1] = -1 * vector[-1] + + def _create_ndarrays(self, matrix: List[List[float]], vector: List[float], size: int) -> (np.ndarray, np.ndarray): + # if we don't have such constraints, return just dummy arrays + if len(matrix) != 0: + return np.array(matrix), np.array(vector) + else: + return np.array([0] * size).reshape((1, -1)), np.zeros(shape=(1,)) + + def get_a0_b0(self) -> (np.ndarray, np.ndarray): + matrix = [] + vector = [] + + senses = self._op.linear_constraints.get_senses() + index_set = set(self._binary_indices) + for constraint_index, sense in enumerate(senses): + # we check only equality constraints here + if sense != "E": + continue + row = self._op.linear_constraints.get_rows(constraint_index) + if set(row.ind).issubset(index_set): + self._assign_row_values(matrix, vector, constraint_index, self._binary_indices) + else: + raise ValueError( + "Linear constraint with the 'E' sense must contain only binary variables, " + "row indices: {}, binary variable indices: {}".format(row, self._binary_indices)) + + return self._create_ndarrays(matrix, vector, len(self._binary_indices)) + + def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) -> (List[List[float]], List[float]): + # extracting matrix and vector from constraints like Ax <= b + matrix = [] + vector = [] + senses = self._op.linear_constraints.get_senses() + + index_set = set(variable_indices) + for constraint_index, sense in enumerate(senses): + if sense == "E" or sense == "R": + # TODO: Ranged constraints should be supported + continue + # sense either G or L + row = self._op.linear_constraints.get_rows(constraint_index) + if set(row.ind).issubset(index_set): + self._assign_row_values(matrix, vector, constraint_index, variable_indices) + + return matrix, vector + + def get_a1_b1(self) -> (np.ndarray, np.ndarray): + matrix, vector = self._get_inequality_matrix_and_vector(self._binary_indices) + return self._create_ndarrays(matrix, vector, len(self._binary_indices)) + + def get_a4_b3(self) -> (np.ndarray, np.ndarray): + matrix, vector = self._get_inequality_matrix_and_vector(self._continuous_indices) + + return self._create_ndarrays(matrix, vector, len(self._continuous_indices)) + + def get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): + matrix = [] + vector = [] + senses = self._op.linear_constraints.get_senses() + + binary_index_set = set(self._binary_indices) + continuous_index_set = set(self._continuous_indices) + all_variables = self._binary_indices + self._continuous_indices + for constraint_index, sense in enumerate(senses): + if sense == "E" or sense == "R": + # TODO: Ranged constraints should be supported as well + continue + # sense either G or L + row = self._op.linear_constraints.get_rows(constraint_index) + row_indices = set(row.ind) + # we must have a least one binary and one continuous variable, otherwise it is another type of constraints + if len(row_indices & binary_index_set) != 0 and len(row_indices & continuous_index_set) != 0: + self._assign_row_values(matrix, vector, constraint_index, all_variables) + + matrix, b2 = self._create_ndarrays(matrix, vector, len(all_variables)) + # a2 + a2 = matrix[:, 0:len(self._binary_indices)] + a3 = matrix[:, len(self._binary_indices):] + return a2, a3, b2 + + def _create_step1_problem(self): + op1 = OptimizationProblem() + + binary_size = len(self._binary_indices) + # create the same binary variables + # op1.variables.add(names=["x0_" + str(i + 1) for i in range(binary_size)], types=["B"] * binary_size) + op1.variables.add(names=["x0_" + str(i + 1) for i in range(binary_size)], + types=["I"] * binary_size, + lb=[0.] * binary_size, + ub=[1.] * binary_size) + + # prepare and set quadratic objective. NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. + a0, b0 = self.get_a0_b0() + quadratic_objective = 2 * ( + self.get_q0() + self._factor_c / 2 * np.dot(a0.transpose(), a0) + + self._rho / 2 * np.eye(binary_size) + ) + for i in range(binary_size): + for j in range(i, binary_size): + op1.objective.set_quadratic_coefficients(i, j, quadratic_objective[i, j]) + op1.objective.set_quadratic_coefficients(j, i, quadratic_objective[i, j]) + + # prepare and set linear objective + c0 = self.get_c0() + linear_objective = c0 - self._factor_c * np.dot(b0, a0) + self._rho * (self._state.y - self._state.z) + for i in range(binary_size): + op1.objective.set_linear(i, linear_objective[i]) + return op1 + + def _create_step2_problem(self): + op2 = OptimizationProblem() + + continuous_size = len(self._continuous_indices) + binary_size = len(self._binary_indices) + lb = self._op.variables.get_lower_bounds(self._binary_indices) + ub = self._op.variables.get_upper_bounds(self._binary_indices) + if continuous_size: + # add u variables + op2.variables.add(names=["u0_" + str(i + 1) for i in range(continuous_size)], + types=["C"] * continuous_size, lb=lb, ub=ub) + + # add z variables + op2.variables.add(names=["z0_" + str(i + 1) for i in range(binary_size)], + types=["C"] * binary_size, + lb=[0.] * binary_size, + ub=[1.] * binary_size) + + # set quadratic objective coefficients for u variables + if continuous_size: + q_u = 2 * (self.get_q1()) #NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. + for i in range(continuous_size): + for j in range(i, continuous_size): + op2.objective.set_quadratic_coefficients(i, j, q_u[i, j]) + op2.objective.set_quadratic_coefficients(j, i, q_u[i, j]) + + # set quadratic objective coefficients for z variables. + q_z = 2 * (self._rho / 2 * np.eye(binary_size)) # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. + for i in range(binary_size): + for j in range(i, binary_size): + op2.objective.set_quadratic_coefficients(i + continuous_size, j + continuous_size, q_z[i, j]) + op2.objective.set_quadratic_coefficients(j + continuous_size, i + continuous_size, q_z[i, j]) + + # set linear objective for u variables + if continuous_size: + linear_u = self.get_c1() + for i in range(continuous_size): + op2.objective.set_linear(i, linear_u[i]) + + # set linear objective for z variables + linear_z = -1 * self._state.lambda_mult - self._rho * (self._state.x0 + self._state.y) + for i in range(binary_size): + op2.objective.set_linear(i + continuous_size, linear_z[i]) + + # constraints for z + # A1 z <= b1 + a1, b1 = self.get_a1_b1() + constraint_count = a1.shape[0] + # in SparsePair val="something from numpy" causes an exception when saving a model via cplex method. rhs="something from numpy" is ok + # so, we convert every single value to python float, todo: consider removing this conversion + lin_expr = [SparsePair(ind=list(range(continuous_size, continuous_size + binary_size)), val=self._to_list(a1[i, :])) for i in range(constraint_count)] + op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, rhs=list(b1)) + + if continuous_size: + # A2 z + A3 u <= b2 + a2, a3, b2 = self.get_a2_a3_b2() + constraint_count = a2.shape[0] + lin_expr = [SparsePair(ind=list(range(continuous_size + binary_size)), val=self._to_list(a3[i, :]) + self._to_list(a2[i, :])) for i in range(constraint_count)] + op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, rhs=self._to_list(b2)) + + if continuous_size: + # A4 u <= b3 + a4, b3 = self.get_a4_b3() + constraint_count = a4.shape[0] + lin_expr = [SparsePair(ind=list(range(continuous_size)), val=self._to_list(a4[i, :])) for i in range(constraint_count)] + op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, rhs=self._to_list(b3)) + + # todo: do we keep u bounds, z bounds as bounds or as constraints. I would keep bounds as bounds. + + return op2 + + def _create_step3_problem(self): + op3 = OptimizationProblem() + # add y variables + binary_size = len(self._binary_indices) + op3.variables.add(names=["y_" + str(i + 1) for i in range(binary_size)], + types=["C"] * binary_size) + + # set quadratic objective. NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. + q_y = 2 * (self._beta / 2 * np.eye(binary_size) + self._rho / 2 * np.eye(binary_size)) + for i in range(binary_size): + for j in range(i, binary_size): + op3.objective.set_quadratic_coefficients(i, j, q_y[i, j]) + op3.objective.set_quadratic_coefficients(j, i, q_y[i, j]) + + linear_y = self._state.lambda_mult + self._rho * (self._state.x0 - self._state.z) + for i in range(binary_size): + op3.objective.set_linear(i, linear_y[i]) + + return op3 + + # when a plain list() call is used a numpy type of values makes cplex to fail when cplex.write() is called. + # for debug only, list() should be used instead + def _to_list(self, values): + out_list = [] + for el in values: + out_list.append(float(el)) + return out_list + + # only for debugging! + def __dump_matrices_and_vectors(self): + print("In admm_optimizer.py") + q0 = self.get_q0() + print("Q0") + print(q0) + print("Q0 shape") + print(q0.shape) + q1 = self.get_q1() + print("Q1") + print(q1) + print("Q1") + print(q1.shape) + + c0 = self.get_c0() + print("c0") + print(c0) + print("c0 shape") + print(c0.shape) + c1 = self.get_c1() + print("c1") + print(c1) + print("c1 shape") + print(c1.shape) + + a0, b0 = self.get_a0_b0() + print("A0") + print(a0) + print("A0") + print(a0.shape) + print("b0") + print(b0) + print("b0 shape") + print(b0.shape) + + a1, b1 = self.get_a1_b1() + print("A1") + print(a1) + print("A1 shape") + print(a1.shape) + print("b1") + print(b1) + print("b1 shape") + print(b1.shape) + + a4, b3 = self.get_a4_b3() + print("A4") + print(a4) + print("A4 shape") + print(a4.shape) + print("b3") + print(b3) + print("b3 shape") + print(b3.shape) + + a2, a3, b2 = self.get_a2_a3_b2() + print("A2") + print(a2) + print("A2 shape") + print(a2.shape) + print("A3") + print(a3) + print("A3") + print(a3.shape) + print("b2") + print(b2) + print("b2 shape") + print(b2.shape) diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py new file mode 100644 index 0000000000..d066926727 --- /dev/null +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -0,0 +1,183 @@ + +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +"""The COBYLA optimizer wrapped to be used within Qiskit Optimization. + + Examples: + >>> problem = OptimizationProblem() + >>> # specify problem here + >>> optimizer = CobylaOptimizer() + >>> result = optimizer.solve(problem) +""" + +from typing import Optional + +from qiskit.optimization.algorithms import OptimizationAlgorithm +from qiskit.optimization.results import OptimizationResult +from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization import QiskitOptimizationError +from qiskit.optimization import infinity + +import numpy as np +from scipy.optimize import fmin_cobyla + + +class CobylaOptimizer(OptimizationAlgorithm): + """The COBYLA optimizer wrapped to be used within Qiskit Optimization. + + This class provides a wrapper for ``scipy.optimize.fmin_cobyla`` + (https://docs.scipy.org/doc/scipy-0.14.0/reference/generated/scipy.optimize.fmin_cobyla.html) + to be used within Qiskit Optimization. + The arguments for ``fmin_cobyla`` are passed via the constructor. + """ + + def __init__(self, rhobeg: float = 1.0, rhoend: float = 1e-4, maxfun: int = 1000, + disp: Optional[int] = None, catol: float = 2e-4) -> None: + """Initializes the CobylaOptimizer. + + This initializer takes the algorithmic parameters of COBYLA and stores them for later use + of ``fmin_cobyla`` when ``solve()`` is invoked. + This optimizer can be applied to find a (local) optimum for problems consisting of only + continuous variables. + + Args: + rhobeg: Reasonable initial changes to the variables. + rhoend: Final accuracy in the optimization (not precisely guaranteed). + This is a lower bound on the size of the trust region. + disp: Controls the frequency of output; 0 implies no output. + Feasible values are {0, 1, 2, 3}. + maxfun: Maximum number of function evaluations. + catol: Absolute tolerance for constraint violations. + """ + + self._rhobeg = rhobeg + self._rhoend = rhoend + self._maxfun = maxfun + self._disp = disp + self._catol = catol + + def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + """Checks whether a given problem can be solved with this optimizer. + + Checks whether the given problem is compatible, i.e., whether the problem contains only + continuous variables, and otherwise, returns a message explaining the incompatibility. + + Args: + problem: The optization problem to check compatibility. + + Returns: + Returns ``None`` if the problem is compatible and else a string with the error message. + """ + + # initialize message + msg = '' + + # check whether there are variables of type other than continuous + if problem.variables.get_num() > problem.variables.get_num_continuous(): + msg += 'This optimizer supports only continuous variables! ' + + # if an error occurred, return error message, otherwise, return None + if len(msg) > 0: + return msg.strip() + else: + return None + + def solve(self, problem: OptimizationProblem) -> OptimizationResult: + """Tries to solves the given problem using the optimizer. + + Runs the optimizer to try to solve the optimization problem. + + Args: + problem: The problem to be solved. + + Returns: + The result of the optimizer applied to the problem. + + Raises: + QiskitOptimizationError: If the problem is incompatible with the optimizer. + """ + + # check compatibility and raise exception if incompatible + msg = self.is_compatible(problem) + if msg is not None: + raise QiskitOptimizationError('Incompatible problem: %s' % msg) + + # get number of variables + num_vars = problem.variables.get_num() + + # construct objective function from linear and quadratic part of objective + offset = problem.objective.get_offset() + linear_dict = problem.objective.get_linear() + quadratic_dict = problem.objective.get_quadratic() + linear = np.zeros(num_vars) + quadratic = np.zeros((num_vars, num_vars)) + for i, v in linear_dict.items(): + linear[i] = v + for i, vi in quadratic_dict.items(): + for j, v in vi.items(): + quadratic[i, j] = v + + def objective(x): + value = problem.objective.get_sense() * ( + np.dot(linear, x) + np.dot(x, np.dot(quadratic, x)) / 2 + offset + ) + return value + + # initialize constraints + constraints = [] + + # add variable lower and upper bounds + lbs = problem.variables.get_lower_bounds() + ubs = problem.variables.get_upper_bounds() + for i in range(num_vars): + if lbs[i] > -infinity: + constraints += [lambda x: x - lbs[i]] + if ubs[i] < infinity: + constraints += [lambda x: ubs[i] - x] + + # add linear constraints + for i in range(problem.linear_constraints.get_num()): + rhs = problem.linear_constraints.get_rhs(i) + sense = problem.linear_constraints.get_senses(i) + row = problem.linear_constraints.get_rows(i) + row_array = np.zeros(num_vars) + for j, v in zip(row.ind, row.val): + row_array[j] = v + + if sense == 'E': + constraints += [ + lambda x: rhs - np.dot(x, row_array), + lambda x: np.dot(x, row_array) - rhs + ] + elif sense == 'L': + constraints += [lambda x: rhs - np.dot(x, row_array)] + elif sense == 'G': + constraints += [lambda x: np.dot(x, row_array) - rhs] + else: + # TODO: add range constraints + raise QiskitOptimizationError('Unsupported constraint type!') + + # TODO: add quadratic constraints + + # TODO: derive x0 from lower/upper bounds + x0 = np.zeros(problem.variables.get_num()) + + # run optimization + x = fmin_cobyla(objective, x0, constraints, rhobeg=self._rhobeg, rhoend=self._rhoend, + maxfun=self._maxfun, disp=self._disp, catol=self._catol) + fval = problem.objective.get_sense() * objective(x) + + # return results + return OptimizationResult(x, fval, x) diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py new file mode 100644 index 0000000000..3296c85b1c --- /dev/null +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -0,0 +1,122 @@ + +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +"""The CPLEX optimizer wrapped to be used within Qiskit Optimization. + + Examples: + >>> problem = OptimizationProblem() + >>> # specify problem here + >>> optimizer = CplexOptimizer() + >>> result = optimizer.solve(problem) +""" + +from typing import Optional + +from qiskit.optimization import QiskitOptimizationError +from qiskit.optimization.algorithms import OptimizationAlgorithm +from qiskit.optimization.results import OptimizationResult +from qiskit.optimization.problems import OptimizationProblem +from cplex import ParameterSet +from cplex.exceptions import CplexSolverError + + +class CplexOptimizer(OptimizationAlgorithm): + """The CPLEX optimizer wrapped to be used within the Qiskit Optimization. + + This class provides a wrapper for ``cplex.Cplex`` (https://pypi.org/project/cplex/) + to be used within Qiskit Optimization. + + TODO: The arguments for ``Cplex`` are passed via the constructor. + """ + + def __init__(self, parameter_set: Optional[ParameterSet] = None) -> None: + """Initializes the CplexOptimizer. + + TODO: This initializer takes the algorithmic parameters of CPLEX and stores them for later + use when ``solve()`` is invoked. + + Args: + parameter_set: The CPLEX parameter set + """ + self._parameter_set = parameter_set + + @property + def parameter_set(self) -> Optional[ParameterSet]: + """Returns the parameter set. + Returns the algorithmic parameters for CPLEX. + Returns: + The CPLEX parameter set. + """ + return self._parameter_set + + @parameter_set.setter + def parameter_set(self, parameter_set: Optional[ParameterSet]): + """Set the parameter set. + Args: + parameter_set: The new parameter set. + """ + self._parameter_set = parameter_set + + def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + """Checks whether a given problem can be solved with this optimizer. + + Returns ``True`` since CPLEX accepts all problems that can be modeled using the + ``OptimizationProblem``. CPLEX may throw an exception in case the problem is determined + to be non-convex. This case could be addressed by setting CPLEX parameters accordingly. + + Args: + problem: The optization problem to check compatibility. + + Returns: + Returns ``None`` if the problem is compatible and else a string with the error message. + """ + return None + + def solve(self, problem: OptimizationProblem) -> OptimizationResult: + """Tries to solves the given problem using the optimizer. + + Runs the optimizer to try to solve the optimization problem. If problem is not convex, + this optimizer may raise an exception due to incompatibility, depending on the settings. + + Args: + problem: The problem to be solved. + + Returns: + The result of the optimizer applied to the problem. + + Raises: + QiskitOptimizationError: If the problem is incompatible with the optimizer. + """ + + # convert to CPLEX problem + cplex = problem.to_cplex() + + # set parameters + # TODO: need to find a good way to set the parameters + + # solve problem + try: + cplex.solve() + except CplexSolverError: + raise QiskitOptimizationError("Non convex/symmetric matrix.") + + # process results + sol = cplex.solution + + # create results + result = OptimizationResult(sol.get_values(), sol.get_objective_value(), sol) + + # return solution + return result diff --git a/qiskit/optimization/algorithms/min_eigen_optimizer.py b/qiskit/optimization/algorithms/min_eigen_optimizer.py new file mode 100644 index 0000000000..4827ae02b4 --- /dev/null +++ b/qiskit/optimization/algorithms/min_eigen_optimizer.py @@ -0,0 +1,165 @@ + +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +"""A wrapper for minimum eigen solvers from Qiskit Aqua to be used within Qiskit Optimization. + + Examples: + >>> problem = OptimizationProblem() + >>> # specify problem here + >>> # specify minimum eigen solver to be used, e.g., QAOA + >>> qaoa = QAOA(...) + >>> optimizer = MinEigenOptimizer(qaoa) + >>> result = optimizer.solve(problem) +""" + +from typing import Optional + +from qiskit.aqua.algorithms import MinEigenSolver +from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.algorithms import OptimizationAlgorithm +from qiskit.optimization.utils import QiskitOptimizationError +from qiskit.optimization.converters import (OptimizationProblemToOperator, + PenalizeLinearEqualityConstraints, + IntegerToBinaryConverter) +from qiskit.optimization.utils import eigenvector_to_solutions +from qiskit.optimization.results import OptimizationResult + +from qiskit import Aer + + +class MinEigenOptimizer(OptimizationAlgorithm): + """A wrapper for minimum eigen solvers from Qiskit Aqua to be used within Qiskit Optimization. + + This class provides a wrapper for minimum eigen solvers from Qiskit Aqua. + It assumes a problem consisting only of binary or integer variables as well as linear equality + constraints thereof. It converts such a problem into a Quadratic Unconstrained Binary + Optimization (QUBO) problem by expanding integer variables into binary variables and by adding + the linear equality constraints as weighted penalty terms to the objective function. The + resulting QUBO is then translated into an Ising Hamiltonian whose minimal eigen vector and + corresponding eigenstate correspond to the optimal solution of the original optimization + problem. The provided minimum eigen solver is then used to approximate the groundstate of the + Hamiltonian to find a good solution for the optimization problem. + + """ + + def __init__(self, min_eigen_solver: MinEigenSolver, penalty: Optional[float] = None) -> None: + """Initializes the minimum eigen optimizer. + + This initializer takes the minimum eigen solver to be used to approximate the groundstate + of the resulting Hamiltonian as well as a optional penalty factor to scale penalty terms + representing linear equality constraints. If no penalty factor is provided, a default + is computed during the algorithm (TODO). + + Args: + min_eigen_solver: The eigen solver to find the groundstate of the Hamiltonian. + penalty: The penalty factor to be used, or ``None`` for applying a default logic. + """ + self._min_eigen_solver = min_eigen_solver + self._penalty = penalty + + def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + """Checks whether a given problem can be solved with this optimizer. + + Checks whether the given problem is compatible, i.e., whether the problem contains only + binary and integer variables as well as linear equality constraints, and otherwise, + returns a message explaining the incompatibility. + + Args: + problem: The optization problem to check compatibility. + + Returns: + Returns ``None`` if the problem is compatible and else a string with the error message. + """ + + # initialize message + msg = '' + + # check whether there are incompatible variable types + if problem.variables.get_num_continuous() > 0: + msg += 'Continuous variables are not supported! ' + if problem.variables.get_num_semicontinuous() > 0: + msg += 'Semi-continuous variables are not supported! ' + # if problem.variables.get_num_integer() > 0: + # # TODO: to be removed once integer to binary mapping is introduced + # msg += 'Integer variables are not supported! ' + if problem.variables.get_num_semiinteger() > 0: + # TODO: to be removed once semi-integer to binary mapping is introduced + msg += 'Semi-integer variables are not supported! ' + + # check whether there are incompatible constraint types + if not all([sense == 'E' for sense in problem.linear_constraints.get_senses()]): + msg += 'Only linear equality constraints are supported.' + + # TODO: check for quadratic constraints + + # if an error occurred, return error message, otherwise, return None + if len(msg) > 0: + return msg.strip() + else: + return None + + def solve(self, problem: OptimizationProblem) -> OptimizationResult: + """Tries to solves the given problem using the optimizer. + + Runs the optimizer to try to solve the optimization problem. + + Args: + problem: The problem to be solved. + + Returns: + The result of the optimizer applied to the problem. + + Raises: + QiskitOptimizationError: If the problem is incompatible with the optimizer. + """ + + # analyze compatibility of problem + msg = self.is_compatible(problem) + if msg is not None: + raise QiskitOptimizationError('Incompatible problem: %s' % msg) + + # map integer variables to binary variables + int_to_bin_converter = IntegerToBinaryConverter() + problem_ = int_to_bin_converter.encode(problem) + + # penalize linear equality constraints with only binary variables + if self._penalty is None: + # TODO: should be derived from problem + penalty = 1e5 + else: + penalty = self._penalty + lin_eq_converter = PenalizeLinearEqualityConstraints() + problem_ = lin_eq_converter.encode(problem_, penalty_factor=penalty) + + # construct operator and offset + operator_converter = OptimizationProblemToOperator() + operator, offset = operator_converter.encode(problem_) + + # approximate ground state of operator using min eigen solver + eigenvector, _ = self._min_eigen_solver.compute_min_eigenvalue(operator) + + # analyze results + # TODO: handle min_probability depending on backend or some other property + results = eigenvector_to_solutions(eigenvector, operator, min_probability=0) + results = [(res[0], problem_.objective.get_sense() * (res[1] + offset), res[2]) + for res in results] + results.sort(key=lambda x: problem_.objective.get_sense() * x[1]) + + # translate result back to integers + opt_res = OptimizationResult(results[0][0], results[0][1], (results, int_to_bin_converter)) + opt_res = int_to_bin_converter.decode(opt_res) + + # translate results back to original problem + return opt_res diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py new file mode 100644 index 0000000000..2869fc8bb0 --- /dev/null +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -0,0 +1,58 @@ + +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +"""An abstract class for optimization algorithms in Qiskit Optimization. +""" + +from abc import abstractmethod + +from typing import Optional + +from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.results import OptimizationResult + + +class OptimizationAlgorithm: + """An abstract class for optimization algorithms in Qiskit Optimization. + """ + + @abstractmethod + def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + """Checks whether a given problem can be solved with the optimizer implementing this method. + + Args: + problem: The optization problem to check compatibility. + + Returns: + Returns ``None`` if the problem is compatible and else a string with the error message. + """ + pass + + @abstractmethod + def solve(self, problem: OptimizationProblem) -> OptimizationResult: + """Tries to solves the given problem using the optimizer. + + Runs the optimizer to try to solve the optimization problem. + + Args: + problem: The problem to be solved. + + Returns: + The result of the optimizer applied to the problem. + + Raises: + QiskitOptimizationError: If the problem is incompatible with the optimizer. + """ + pass diff --git a/qiskit/optimization/algorithms/recursive_ising_optimizer.py b/qiskit/optimization/algorithms/recursive_ising_optimizer.py new file mode 100644 index 0000000000..360f74209b --- /dev/null +++ b/qiskit/optimization/algorithms/recursive_ising_optimizer.py @@ -0,0 +1,49 @@ + +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +"""A recursive minimal eigen optimizer in Qiskit Optimization. + + Examples: + >>> problem = OptimizationProblem() + >>> # specify problem here + >>> # specify minimum eigen solver to be used, e.g., QAOA + >>> qaoa = QAOA(...) + >>> optimizer = RecursiveMinEigenOptimizer(qaoa) + >>> result = optimizer.solve(problem) +""" + +from qiskit.optimization.algorithms import OptimizationAlgorithm + + +class RecursiveIsingOptimizer(OptimizationAlgorithm): + """ + TODO + """ + + def __init__(self, ising_solver, mode='correlation', min_num_vars=0): + """ + TODO + """ + # TODO: should also allow function that maps problem to -correlators? + # --> would support efficient classical implementation for QAOA with depth p=1 + self._eigen_solver = eigen_solver # TODO: base on eigen_solver or ising_optimizer? + self._mode = mode + self._min_num_vars = min_num_vars + + def solve(self, problem): + # handle variable replacements via variable names + # --> allows to easily adjust problems and roll-out final results + # TODO + pass diff --git a/qiskit/optimization/applications/__init__.py b/qiskit/optimization/applications/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/qiskit/optimization/applications/ising/__init__.py b/qiskit/optimization/applications/ising/__init__.py new file mode 100644 index 0000000000..04813e82af --- /dev/null +++ b/qiskit/optimization/applications/ising/__init__.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + +""" +Ising Models (:mod:`qiskit.optimization.ising`) +=============================================== +Ising models for optimization problems + +.. currentmodule:: qiskit.optimization.ising + +Ising Models +============ + +.. autosummary:: + :toctree: ../stubs/ + :nosignatures: + + clique + exact_cover + graph_partition + max_cut + partition + set_packing + stable_set + tsp + vehicle_routing + vertex_cover + +Automatic Ising Model Generator from DoCPLEX Model +================================================== + +.. autosummary:: + :toctree: ../stubs/ + :nosignatures: + + docplex + +""" diff --git a/qiskit/optimization/applications/ising/clique.py b/qiskit/optimization/applications/ising/clique.py new file mode 100644 index 0000000000..c5bd89d6d6 --- /dev/null +++ b/qiskit/optimization/applications/ising/clique.py @@ -0,0 +1,200 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +""" +Convert clique instances into Pauli list +Deal with Gset format. See https://web.stanford.edu/~yyye/yyye/Gset/ +""" + +import logging +import warnings + +import numpy as np +from qiskit.quantum_info import Pauli + +from qiskit.aqua.operators import WeightedPauliOperator + +logger = logging.getLogger(__name__) + + +def get_operator(weight_matrix, K): # pylint: disable=invalid-name + r""" + Generate Hamiltonian for the clique. + + The goals is can we find a complete graph of size K? + + To build the Hamiltonian the following logic is applied. + + | Suppose Xv denotes whether v should appear in the clique (Xv=1 or 0)\n + | H = Ha + Hb\n + | Ha = (K-sum_{v}{Xv})\^2 + | Hb = K(K−1)/2 - sum_{(u,v)\in E}{XuXv} + + | Besides, Xv = (Zv+1)/2 + | By replacing Xv with Zv and simplifying it, we get what we want below. + + Note: in practice, we use H = A\*Ha + Bb, where A is a large constant such as 1000. + + A is like a huge penality over the violation of Ha, + which forces Ha to be 0, i.e., you have exact K vertices selected. + Under this assumption, Hb = 0 starts to make sense, + it means the subgraph constitutes a clique or complete graph. + Note the lowest possible value of Hb is 0. + + Without the above assumption, Hb may be negative (say you select all). + In this case, one needs to use Hb\^2 in the hamiltonian to minimize the difference. + + Args: + weight_matrix (numpy.ndarray) : adjacency matrix. + K (numpy.ndarray): K + + Returns: + tuple(WeightedPauliOperator, float): + The operator for the Hamiltonian and a constant shift for the obj function. + """ + # pylint: disable=invalid-name + num_nodes = len(weight_matrix) + pauli_list = [] + shift = 0 + + Y = K - 0.5 * num_nodes # Y = K-sum_{v}{1/2} + + A = 1000 + # Ha part: + shift += A * Y * Y + + for i in range(num_nodes): + for j in range(num_nodes): + if i != j: + xp = np.zeros(num_nodes, dtype=np.bool) + zp = np.zeros(num_nodes, dtype=np.bool) + zp[i] = True + zp[j] = True + pauli_list.append([A * 0.25, Pauli(zp, xp)]) + else: + shift += A * 0.25 + for i in range(num_nodes): + xp = np.zeros(num_nodes, dtype=np.bool) + zp = np.zeros(num_nodes, dtype=np.bool) + zp[i] = True + pauli_list.append([-A * Y, Pauli(zp, xp)]) + + shift += 0.5 * K * (K - 1) + + for i in range(num_nodes): + for j in range(i): + if weight_matrix[i, j] != 0: + xp = np.zeros(num_nodes, dtype=np.bool) + zp = np.zeros(num_nodes, dtype=np.bool) + zp[i] = True + zp[j] = True + pauli_list.append([-0.25, Pauli(zp, xp)]) + + zp2 = np.zeros(num_nodes, dtype=np.bool) + zp2[i] = True + pauli_list.append([-0.25, Pauli(zp2, xp)]) + + zp3 = np.zeros(num_nodes, dtype=np.bool) + zp3[j] = True + pauli_list.append([-0.25, Pauli(zp3, xp)]) + + shift += -0.25 + + return WeightedPauliOperator(paulis=pauli_list), shift + + +def satisfy_or_not(x, w, K): # pylint: disable=invalid-name + """Compute the value of a cut. + + Args: + x (numpy.ndarray): binary string as numpy array. + w (numpy.ndarray): adjacency matrix. + K (numpy.ndarray): K + + Returns: + float: value of the cut. + """ + # pylint: disable=invalid-name + X = np.outer(x, x) + w_01 = np.where(w != 0, 1, 0) + + return np.sum(w_01 * X) == K * (K - 1) # note sum() count the same edge twice + + +def get_graph_solution(x): + """Get graph solution from binary string. + + Args: + x (numpy.ndarray) : binary string as numpy array. + + Returns: + numpy.ndarray: graph solution as binary numpy array. + """ + return 1 - x + + +def random_graph(n, weight_range=10, edge_prob=0.3, savefile=None, seed=None): + """ random graph """ + # pylint: disable=import-outside-toplevel + from .common import random_graph as redirect_func + warnings.warn("random_graph function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(n=n, weight_range=weight_range, edge_prob=edge_prob, + savefile=savefile, seed=seed) + + +def parse_gset_format(filename): + """ parse gset format """ + # pylint: disable=import-outside-toplevel + from .common import parse_gset_format as redirect_func + warnings.warn("parse_gset_format function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(filename) + + +def sample_most_likely(n=None, state_vector=None): + """ sample most likely """ + # pylint: disable=import-outside-toplevel + from .common import sample_most_likely as redirect_func + if n is not None: + warnings.warn("n argument is not need and it will be removed after Aqua 0.7+", + DeprecationWarning) + warnings.warn("sample_most_likely function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(state_vector=state_vector) + + +def get_gset_result(x): + """ get gset result """ + # pylint: disable=import-outside-toplevel + from .common import get_gset_result as redirect_func + warnings.warn("get_gset_result function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(x) + + +def get_clique_qubitops(weight_matrix, K): # pylint: disable=invalid-name + """ get clique qubit ops """ + warnings.warn("get_clique_qubitops function has been changed to get_operator" + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return get_operator(weight_matrix, K) diff --git a/qiskit/optimization/applications/ising/common.py b/qiskit/optimization/applications/ising/common.py new file mode 100644 index 0000000000..12a35fb14d --- /dev/null +++ b/qiskit/optimization/applications/ising/common.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +""" common module """ + +from collections import OrderedDict + +import numpy as np + +from qiskit.aqua import aqua_globals + + +def random_graph(n, weight_range=10, edge_prob=0.3, negative_weight=True, + savefile=None, seed=None): + """Generate random Erdos-Renyi graph. + + Args: + n (int): number of nodes. + weight_range (int): weights will be smaller than this value, + in absolute value. range: [1, weight_range). + edge_prob (float): probability of edge appearing. + negative_weight (bool): allow to have edge with negative weights + savefile (str or None): name of file where to save graph. + seed (int or None): random seed - if None, will not initialize. + + Returns: + numpy.ndarray: adjacency matrix (with weights). + + """ + assert weight_range >= 0 + if seed: + aqua_globals.random_seed = seed + w = np.zeros((n, n)) + m = 0 + for i in range(n): + for j in range(i + 1, n): + if aqua_globals.random.rand() <= edge_prob: + w[i, j] = aqua_globals.random.randint(1, weight_range) + if aqua_globals.random.rand() >= 0.5 and negative_weight: + w[i, j] *= -1 + m += 1 + w += w.T + if savefile: + with open(savefile, 'w') as outfile: + outfile.write('{} {}\n'.format(n, m)) + for i in range(n): + for j in range(i + 1, n): + if w[i, j] != 0: + outfile.write('{} {} {}\n'.format(i + 1, j + 1, w[i, j])) + return w + + +def random_number_list(n, weight_range=100, savefile=None, seed=None): + """Generate a set of positive integers within the given range. + + Args: + n (int): size of the set of numbers. + weight_range (int): maximum absolute value of the numbers. + savefile (str or None): write numbers to this file. + seed (Union(int,None)): random seed - if None, will not initialize. + + Returns: + numpy.ndarray: the list of integer numbers. + """ + if seed: + aqua_globals.random_seed = seed + + number_list = aqua_globals.random.randint(low=1, high=(weight_range + 1), size=n) + if savefile: + with open(savefile, 'w') as outfile: + for i in range(n): + outfile.write('{}\n'.format(number_list[i])) + return number_list + + +def read_numbers_from_file(filename): + """Read numbers from a file + + Args: + filename (str): name of the file. + + Returns: + numpy.ndarray: list of numbers as a numpy.ndarray. + """ + numbers = [] + with open(filename) as infile: + for line in infile: + assert int(round(float(line))) == float(line) + numbers.append(int(round(float(line)))) + return np.array(numbers) + + +def parse_gset_format(filename): + """Read graph in Gset format from file. + + Args: + filename (str): name of the file. + + Returns: + numpy.ndarray: adjacency matrix as a 2D numpy array. + """ + n = -1 + with open(filename) as infile: + header = True + m = -1 + count = 0 + for line in infile: + v = map(lambda e: int(e), line.split()) # pylint: disable=unnecessary-lambda + if header: + n, m = v + w = np.zeros((n, n)) + header = False + else: + s__, t__, _ = v + s__ -= 1 # adjust 1-index + t__ -= 1 # ditto + w[s__, t__] = t__ + count += 1 + assert m == count + w += w.T + return w + + +def get_gset_result(x): + """Get graph solution in Gset format from binary string. + + Args: + x (numpy.ndarray) : binary string as numpy array. + + Returns: + Dict[int, int]: graph solution in Gset format. + """ + return {i + 1: 1 - x[i] for i in range(len(x))} + + +def sample_most_likely(state_vector): + """Compute the most likely binary string from state vector. + Args: + state_vector (numpy.ndarray or dict): state vector or counts. + + Returns: + numpy.ndarray: binary string as numpy.ndarray of ints. + """ + if isinstance(state_vector, (OrderedDict, dict)): + # get the binary string with the largest count + binary_string = sorted(state_vector.items(), key=lambda kv: kv[1])[-1][0] + x = np.asarray([int(y) for y in reversed(list(binary_string))]) + return x + else: + n = int(np.log2(state_vector.shape[0])) + k = np.argmax(np.abs(state_vector)) + x = np.zeros(n) + for i in range(n): + x[i] = k % 2 + k >>= 1 + return x diff --git a/qiskit/optimization/applications/ising/docplex.py b/qiskit/optimization/applications/ising/docplex.py new file mode 100644 index 0000000000..835a8985c3 --- /dev/null +++ b/qiskit/optimization/applications/ising/docplex.py @@ -0,0 +1,295 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + +""" +Automatically generate Ising Hamiltonians from general models of optimization problems. +This program converts general models of optimization problems into Ising Hamiltonian. +To write models of optimization problems, DOcplex (Python library for optimization problems) +is used in the program. +(https://cdn.rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html) + +It supports models that consist of the following elements now. + +- Binary variables. +- Linear or quadratic object function. +- Equality constraints. + + - Symbols in constraints have to be equal (==). + - Inequality constraints (e.g. x+y <= 5) are not allowed. + +The following is an example of use. + +.. code-block:: python + + # Create an instance of a model and variables with DOcplex. + mdl = Model(name='tsp') + x = {(i,p): mdl.binary_var(name='x_{0}_{1}'.format(i,p)) for i in range(num_node) + for p in range(num_node)} + + # Object function + tsp_func = mdl.sum(ins.w[i,j] * x[(i,p)] * x[(j,(p+1)%num_node)] for i in range(num_node) + for j in range(num_node) for p in range(num_node)) + mdl.minimize(tsp_func) + + # Constraints + for i in range(num_node): + mdl.add_constraint(mdl.sum(x[(i,p)] for p in range(num_node)) == 1) + for p in range(num_node): + mdl.add_constraint(mdl.sum(x[(i,p)] for i in range(num_node)) == 1) + + # Call the method to convert the model into Ising Hamiltonian. + qubitOp, offset = get_operator(mdl) + + # Calculate with the generated Ising Hamiltonian. + ee = ExactEigensolver(qubitOp, k=1) + result = ee.run() + print('get_operator') + print('tsp objective:', result['energy'] + offset) + +""" + +import logging +from math import fsum +import warnings + +import numpy as np +from docplex.mp.constants import ComparisonType +from docplex.mp.model import Model +from qiskit.quantum_info import Pauli + +from qiskit.aqua import AquaError +from qiskit.aqua.operators import WeightedPauliOperator + +logger = logging.getLogger(__name__) + + +def get_operator(mdl, auto_penalty=True, default_penalty=1e5): + """ + Generate Ising Hamiltonian from a model of DOcplex. + + Args: + mdl (docplex.mp.model.Model): A model of DOcplex for a optimization problem. + auto_penalty (bool): If true, the penalty coefficient is automatically defined + by "_auto_define_penalty()". + default_penalty (float): The default value of the penalty coefficient for the constraints. + This value is used if "auto_penalty" is False. + + Returns: + tuple(operators.WeightedPauliOperator, float): operator for the Hamiltonian and a + constant shift for the obj function. + """ + + _validate_input_model(mdl) + + # set the penalty coefficient by _auto_define_penalty() or manually. + if auto_penalty: + penalty = _auto_define_penalty(mdl, default_penalty) + else: + penalty = default_penalty + + # set a sign corresponding to a maximized or minimized problem. + # sign == 1 is for minimized problem. sign == -1 is for maximized problem. + sign = 1 + if mdl.is_maximized(): + sign = -1 + + # assign variables of the model to qubits. + q_d = {} + index = 0 + for i in mdl.iter_variables(): + if i in q_d: + continue + q_d[i] = index + index += 1 + + # initialize Hamiltonian. + num_nodes = len(q_d) + pauli_list = [] + shift = 0 + zero = np.zeros(num_nodes, dtype=np.bool) + + # convert a constant part of the object function into Hamiltonian. + shift += mdl.get_objective_expr().get_constant() * sign + + # convert linear parts of the object function into Hamiltonian. + l_itr = mdl.get_objective_expr().iter_terms() + for j in l_itr: + z_p = np.zeros(num_nodes, dtype=np.bool) + index = q_d[j[0]] + weight = j[1] * sign / 2 + z_p[index] = True + + pauli_list.append([-weight, Pauli(z_p, zero)]) + shift += weight + + # convert quadratic parts of the object function into Hamiltonian. + q_itr = mdl.get_objective_expr().iter_quads() + for i in q_itr: + index1 = q_d[i[0][0]] + index2 = q_d[i[0][1]] + weight = i[1] * sign / 4 + + if index1 == index2: + shift += weight + else: + z_p = np.zeros(num_nodes, dtype=np.bool) + z_p[index1] = True + z_p[index2] = True + pauli_list.append([weight, Pauli(z_p, zero)]) + + z_p = np.zeros(num_nodes, dtype=np.bool) + z_p[index1] = True + pauli_list.append([-weight, Pauli(z_p, zero)]) + + z_p = np.zeros(num_nodes, dtype=np.bool) + z_p[index2] = True + pauli_list.append([-weight, Pauli(z_p, zero)]) + + shift += weight + + # convert constraints into penalty terms. + for constraint in mdl.iter_constraints(): + constant = constraint.right_expr.get_constant() + + # constant parts of penalty*(Constant-func)**2: penalty*(Constant**2) + shift += penalty * constant ** 2 + + # linear parts of penalty*(Constant-func)**2: penalty*(-2*Constant*func) + for __l in constraint.left_expr.iter_terms(): + z_p = np.zeros(num_nodes, dtype=np.bool) + index = q_d[__l[0]] + weight = __l[1] + z_p[index] = True + + pauli_list.append([penalty * constant * weight, Pauli(z_p, zero)]) + shift += -penalty * constant * weight + + # quadratic parts of penalty*(Constant-func)**2: penalty*(func**2) + for __l in constraint.left_expr.iter_terms(): + for l_2 in constraint.left_expr.iter_terms(): + index1 = q_d[__l[0]] + index2 = q_d[l_2[0]] + weight1 = __l[1] + weight2 = l_2[1] + penalty_weight1_weight2 = penalty * weight1 * weight2 / 4 + + if index1 == index2: + shift += penalty_weight1_weight2 + else: + z_p = np.zeros(num_nodes, dtype=np.bool) + z_p[index1] = True + z_p[index2] = True + pauli_list.append([penalty_weight1_weight2, Pauli(z_p, zero)]) + + z_p = np.zeros(num_nodes, dtype=np.bool) + z_p[index1] = True + pauli_list.append([-penalty_weight1_weight2, Pauli(z_p, zero)]) + + z_p = np.zeros(num_nodes, dtype=np.bool) + z_p[index2] = True + pauli_list.append([-penalty_weight1_weight2, Pauli(z_p, zero)]) + + shift += penalty_weight1_weight2 + + # Remove paulis whose coefficients are zeros. + qubit_op = WeightedPauliOperator(paulis=pauli_list) + + return qubit_op, shift + + +def _validate_input_model(mdl): + """ + Check whether an input model is valid. If not, raise an AquaError + + Args: + mdl (docplex.mp.model.Model): A model of DOcplex for a optimization problem. + + Raises: + AquaError: Unsupported input model + """ + valid = True + + # validate an object type of the input. + if not isinstance(mdl, Model): + raise AquaError('An input model must be docplex.mp.model.Model.') + + # raise an error if the type of the variable is not a binary type. + for var in mdl.iter_variables(): + if not var.is_binary(): + logger.warning('The type of Variable %s is %s. It must be a binary variable. ', + var, var.vartype.short_name) + valid = False + + # raise an error if the constraint type is not an equality constraint. + for constraint in mdl.iter_constraints(): + if not constraint.sense == ComparisonType.EQ: + logger.warning('Constraint %s is not an equality constraint.', constraint) + valid = False + + if not valid: + raise AquaError('The input model has unsupported elements.') + + +def _auto_define_penalty(mdl, default_penalty=1e5): + """ + Automatically define the penalty coefficient. + This returns object function's (upper bound - lower bound + 1). + + + Args: + mdl (docplex.mp.model.Model): A model of DOcplex for a optimization problem. + default_penalty (float): The default value of the penalty coefficient for the constraints. + + Returns: + float: The penalty coefficient for the Hamiltonian. + """ + + # if a constraint has float coefficient, return 1e5 for the penalty coefficient. + terms = [] + for constraint in mdl.iter_constraints(): + terms.append(constraint.right_expr.get_constant()) + terms.extend(term[1] for term in constraint.left_expr.iter_terms()) + if any(isinstance(term, float) and not term.is_integer() for term in terms): + logger.warning('Using %f for the penalty coefficient because a float coefficient exists ' + 'in constraints. \nThe value could be too small. ' + 'If so, set the penalty coefficient manually.', default_penalty) + return default_penalty + + # (upper bound - lower bound) can be calculate as the sum of absolute value of coefficients + # Firstly, add 1 to guarantee that infeasible answers will be greater than upper bound. + penalties = [1] + # add linear terms of the object function. + penalties.extend(abs(i[1]) for i in mdl.get_objective_expr().iter_terms()) + # add quadratic terms of the object function. + penalties.extend(abs(i[1]) for i in mdl.get_objective_expr().iter_quads()) + + return fsum(penalties) + + +def sample_most_likely(state_vector): + """ sample most likely """ + # pylint: disable=import-outside-toplevel + from .common import sample_most_likely as redirect_func + warnings.warn("sample_most_likely function has been moved to qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(state_vector=state_vector) + + +def get_qubitops(mdl, auto_penalty=True, default_penalty=1e5): + """ get qubit ops """ + warnings.warn("get_qubitops function has been changed to get_operator." + " The method here will be removed after Aqua 0.7+", + DeprecationWarning) + return get_operator(mdl, auto_penalty, default_penalty) diff --git a/qiskit/optimization/applications/ising/exact_cover.py b/qiskit/optimization/applications/ising/exact_cover.py new file mode 100644 index 0000000000..21fd743ed3 --- /dev/null +++ b/qiskit/optimization/applications/ising/exact_cover.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +""" exact cover """ + +import logging +import warnings + +import numpy as np + +from qiskit.quantum_info import Pauli +from qiskit.aqua.operators import WeightedPauliOperator + +logger = logging.getLogger(__name__) + + +def get_operator(list_of_subsets): + """ + Construct the Hamiltonian for the exact solver problem. + + Note: + | Assumption: the union of the subsets contains all the elements to cover. + | The Hamiltonian is: + | sum_{each element e}{(1-sum_{every subset_i that contains e}{Xi})^2}, + | where Xi (Xi=1 or 0) means whether should include the subset i. + + Args: + list_of_subsets (list): list of lists (i.e., subsets) + + Returns: + tuple(WeightedPauliOperator, float): + operator for the Hamiltonian, a constant shift for the obj function. + """ + # pylint: disable=invalid-name + n = len(list_of_subsets) + + U = [] + for sub in list_of_subsets: + U.extend(sub) + U = np.unique(U) # U is the universe + + shift = 0 + pauli_list = [] + + for e in U: + # pylint: disable=simplifiable-if-expression + cond = [True if e in sub else False for sub in list_of_subsets] + indices_has_e = np.arange(n)[cond] + num_has_e = len(indices_has_e) + Y = 1 - 0.5 * num_has_e + shift += Y * Y + + for i in indices_has_e: + for j in indices_has_e: + if i != j: + w_p = np.zeros(n) + v_p = np.zeros(n) + v_p[i] = 1 + v_p[j] = 1 + pauli_list.append([0.25, Pauli(v_p, w_p)]) + else: + shift += 0.25 + + for i in indices_has_e: + w_p = np.zeros(n) + v_p = np.zeros(n) + v_p[i] = 1 + pauli_list.append([-Y, Pauli(v_p, w_p)]) + + return WeightedPauliOperator(paulis=pauli_list), shift + + +def get_solution(x): + """ + Args: + x (numpy.ndarray) : binary string as numpy array. + + Returns: + numpy.ndarray: graph solution as binary numpy array. + """ + return 1 - x + + +def check_solution_satisfiability(sol, list_of_subsets): + """ check solution satisfiability """ + # pylint: disable=invalid-name + n = len(list_of_subsets) + U = [] + for sub in list_of_subsets: + U.extend(sub) + U = np.unique(U) # U is the universe + + U2 = [] + selected_subsets = [] + for i in range(n): + if sol[i] == 1: + selected_subsets.append(list_of_subsets[i]) + U2.extend(list_of_subsets[i]) + + U2 = np.unique(U2) + if set(U) != set(U2): + return False + + tmplen = len(selected_subsets) + for i in range(tmplen): + for j in range(i): + L = selected_subsets[i] + R = selected_subsets[j] + + if set(L) & set(R): # should be empty + return False + + return True + + +def random_number_list(n, weight_range=100, savefile=None): + """ random number list """ + # pylint: disable=import-outside-toplevel + from .common import random_number_list as redirect_func + warnings.warn("random_number_list function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(n=n, weight_range=weight_range, savefile=savefile) + + +def read_numbers_from_file(filename): + """ read numbers from file """ + # pylint: disable=import-outside-toplevel + from .common import read_numbers_from_file as redirect_func + warnings.warn("read_numbers_from_file function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(filename) + + +def sample_most_likely(n=None, state_vector=None): + """ sample most likely """ + # pylint: disable=import-outside-toplevel + from .common import sample_most_likely as redirect_func + if n is not None: + warnings.warn("n argument is not need and it will be removed after Aqua 0.7+", + DeprecationWarning) + warnings.warn("sample_most_likely function has been moved to qiskit.aqua.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(state_vector=state_vector) + + +def get_exact_cover_qubitops(list_of_subsets): + """ get exact cover qubit ops """ + warnings.warn("get_exact_cover_qubitops function has been changed to get_operator" + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return get_operator(list_of_subsets) diff --git a/qiskit/optimization/applications/ising/graph_partition.py b/qiskit/optimization/applications/ising/graph_partition.py new file mode 100644 index 0000000000..0e404bed33 --- /dev/null +++ b/qiskit/optimization/applications/ising/graph_partition.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +""" +Convert graph partitioning instances into Pauli list +Deal with Gset format. See https://web.stanford.edu/~yyye/yyye/Gset/ +""" + +import logging +import warnings + +import numpy as np + +from qiskit.quantum_info import Pauli +from qiskit.aqua.operators import WeightedPauliOperator + +logger = logging.getLogger(__name__) + + +def get_operator(weight_matrix): + r"""Generate Hamiltonian for the graph partitioning + + Notes: + Goals: + 1 separate the vertices into two set of the same size + 2 make sure the number of edges between the two set is minimized. + Hamiltonian: + H = H_A + H_B + H_A = sum\_{(i,j)\in E}{(1-ZiZj)/2} + H_B = (sum_{i}{Zi})^2 = sum_{i}{Zi^2}+sum_{i!=j}{ZiZj} + H_A is for achieving goal 2 and H_B is for achieving goal 1. + + Args: + weight_matrix (numpy.ndarray) : adjacency matrix. + + Returns: + WeightedPauliOperator: operator for the Hamiltonian + float: a constant shift for the obj function. + """ + num_nodes = len(weight_matrix) + pauli_list = [] + shift = 0 + + for i in range(num_nodes): + for j in range(i): + if weight_matrix[i, j] != 0: + x_p = np.zeros(num_nodes, dtype=np.bool) + z_p = np.zeros(num_nodes, dtype=np.bool) + z_p[i] = True + z_p[j] = True + pauli_list.append([-0.5, Pauli(z_p, x_p)]) + shift += 0.5 + + for i in range(num_nodes): + for j in range(num_nodes): + if i != j: + x_p = np.zeros(num_nodes, dtype=np.bool) + z_p = np.zeros(num_nodes, dtype=np.bool) + z_p[i] = True + z_p[j] = True + pauli_list.append([1, Pauli(z_p, x_p)]) + else: + shift += 1 + return WeightedPauliOperator(paulis=pauli_list), shift + + +def objective_value(x, w): + """Compute the value of a cut. + + Args: + x (numpy.ndarray): binary string as numpy array. + w (numpy.ndarray): adjacency matrix. + + Returns: + float: value of the cut. + """ + # pylint: disable=invalid-name + X = np.outer(x, (1 - x)) + w_01 = np.where(w != 0, 1, 0) + return np.sum(w_01 * X) + + +def get_graph_solution(x): + """Get graph solution from binary string. + + Args: + x (numpy.ndarray) : binary string as numpy array. + + Returns: + numpy.ndarray: graph solution as binary numpy array. + """ + return 1 - x + + +def random_graph(n, weight_range=10, edge_prob=0.3, savefile=None, seed=None): + """ random graph """ + # pylint: disable=import-outside-toplevel + from .common import random_graph as redirect_func + warnings.warn("random_graph function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(n=n, weight_range=weight_range, edge_prob=edge_prob, + savefile=savefile, seed=seed) + + +def parse_gset_format(filename): + """ parse gset format """ + # pylint: disable=import-outside-toplevel + from .common import parse_gset_format as redirect_func + warnings.warn("parse_gset_format function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(filename) + + +def sample_most_likely(state_vector): + """ sample most likely """ + # pylint: disable=import-outside-toplevel + from .common import sample_most_likely as redirect_func + warnings.warn("sample_most_likely function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(state_vector=state_vector) + + +def get_gset_result(x): + """ get gset result """ + # pylint: disable=import-outside-toplevel + from .common import get_gset_result as redirect_func + warnings.warn("get_gset_result function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(x) + + +def get_graph_partition_qubitops(weight_matrix): + """ get graph partition qubit ops """ + warnings.warn("get_graph_partition_qubitops function has been changed to get_operator" + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return get_operator(weight_matrix) diff --git a/qiskit/optimization/applications/ising/max_cut.py b/qiskit/optimization/applications/ising/max_cut.py new file mode 100644 index 0000000000..9212d8e175 --- /dev/null +++ b/qiskit/optimization/applications/ising/max_cut.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +""" +Convert max-cut instances into Pauli list +Deal with Gset format. See https://web.stanford.edu/~yyye/yyye/Gset/ +Design the max-cut object `w` as a two-dimensional np.array +e.g., w[i, j] = x means that the weight of a edge between i and j is x +Note that the weights are symmetric, i.e., w[j, i] = x always holds. +""" + +import logging +import warnings + +import numpy as np + +from qiskit.quantum_info import Pauli +from qiskit.aqua.operators import WeightedPauliOperator + +logger = logging.getLogger(__name__) + + +def get_operator(weight_matrix): + """Generate Hamiltonian for the max-cut problem of a graph. + + Args: + weight_matrix (numpy.ndarray) : adjacency matrix. + + Returns: + WeightedPauliOperator: operator for the Hamiltonian + float: a constant shift for the obj function. + + """ + num_nodes = weight_matrix.shape[0] + pauli_list = [] + shift = 0 + for i in range(num_nodes): + for j in range(i): + if weight_matrix[i, j] != 0: + x_p = np.zeros(num_nodes, dtype=np.bool) + z_p = np.zeros(num_nodes, dtype=np.bool) + z_p[i] = True + z_p[j] = True + pauli_list.append([0.5 * weight_matrix[i, j], Pauli(z_p, x_p)]) + shift -= 0.5 * weight_matrix[i, j] + return WeightedPauliOperator(paulis=pauli_list), shift + + +def max_cut_value(x, w): + """Compute the value of a cut. + + Args: + x (numpy.ndarray): binary string as numpy array. + w (numpy.ndarray): adjacency matrix. + + Returns: + float: value of the cut. + """ + # pylint: disable=invalid-name + X = np.outer(x, (1 - x)) + return np.sum(w * X) + + +def get_graph_solution(x): + """Get graph solution from binary string. + + Args: + x (numpy.ndarray) : binary string as numpy array. + + Returns: + numpy.ndarray: graph solution as binary numpy array. + """ + return 1 - x + + +def random_graph(n, weight_range=10, edge_prob=0.3, savefile=None, seed=None): + """ random graph """ + # pylint: disable=import-outside-toplevel + from .common import random_graph as redirect_func + warnings.warn("random_graph function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(n=n, weight_range=weight_range, edge_prob=edge_prob, + savefile=savefile, seed=seed) + + +def parse_gset_format(filename): + """ parse gset format """ + # pylint: disable=import-outside-toplevel + from .common import parse_gset_format as redirect_func + warnings.warn("parse_gset_format function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(filename) + + +def sample_most_likely(state_vector): + """ sample most likely """ + # pylint: disable=import-outside-toplevel + from .common import sample_most_likely as redirect_func + warnings.warn("sample_most_likely function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(state_vector=state_vector) + + +def get_gset_result(x): + """ returns gset result """ + # pylint: disable=import-outside-toplevel + from .common import get_gset_result as redirect_func + warnings.warn("get_gset_result function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(x) + + +def get_max_cut_qubitops(weight_matrix): + """ returns max cut qubit ops """ + warnings.warn("get_max_cut_qubitops function has been changed to get_operator" + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return get_operator(weight_matrix) diff --git a/qiskit/optimization/applications/ising/partition.py b/qiskit/optimization/applications/ising/partition.py new file mode 100644 index 0000000000..77c73f803e --- /dev/null +++ b/qiskit/optimization/applications/ising/partition.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +""" +Generate Number Partitioning (Partition) instances, and convert them +into a Hamiltonian given as a Pauli list. +""" + +import logging +import warnings + +import numpy as np +from qiskit.quantum_info import Pauli + +from qiskit.aqua.operators import WeightedPauliOperator + +logger = logging.getLogger(__name__) + + +def get_operator(values): + """Construct the Hamiltonian for a given Partition instance. + + Given a list of numbers for the Number Partitioning problem, we + construct the Hamiltonian described as a list of Pauli gates. + + Args: + values (numpy.ndarray): array of values. + + Returns: + tuple(WeightedPauliOperator, float): operator for the Hamiltonian and a + constant shift for the obj function. + + """ + n = len(values) + # The Hamiltonian is: + # \sum_{i,j=1,\dots,n} ij z_iz_j + \sum_{i=1,\dots,n} i^2 + pauli_list = [] + for i in range(n): + for j in range(i): + x_p = np.zeros(n, dtype=np.bool) + z_p = np.zeros(n, dtype=np.bool) + z_p[i] = True + z_p[j] = True + pauli_list.append([2. * values[i] * values[j], Pauli(z_p, x_p)]) + return WeightedPauliOperator(paulis=pauli_list), sum(values * values) + + +def partition_value(x, number_list): + """Compute the value of a partition. + + Args: + x (numpy.ndarray): binary string as numpy array. + number_list (numpy.ndarray): list of numbers in the instance. + + Returns: + float: difference squared between the two sides of the number + partition. + """ + diff = np.sum(number_list[x == 0]) - np.sum(number_list[x == 1]) + return diff * diff + + +def random_number_list(n, weight_range=100, savefile=None): + """ random number list """ + # pylint: disable=import-outside-toplevel + from .common import random_number_list as redirect_func + warnings.warn("random_number_list function has been moved to " + "qiskit.optimization.ising.common,, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(n=n, weight_range=weight_range, savefile=savefile) + + +def read_numbers_from_file(filename): + """ read numbers from file """ + # pylint: disable=import-outside-toplevel + from .common import read_numbers_from_file as redirect_func + warnings.warn("read_numbers_from_file function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(filename) + + +def sample_most_likely(state_vector): + """ sample most likely """ + # pylint: disable=import-outside-toplevel + from .common import sample_most_likely as redirect_func + warnings.warn("sample_most_likely function has been moved " + "to qiskit.optimization.ising.common,, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(state_vector=state_vector) + + +def get_partition_qubitops(values): + """ get partition qubit ops """ + warnings.warn("get_partition_qubitops function has been changed to get_operator" + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return get_operator(values) diff --git a/qiskit/optimization/applications/ising/set_packing.py b/qiskit/optimization/applications/ising/set_packing.py new file mode 100644 index 0000000000..35e0a1b575 --- /dev/null +++ b/qiskit/optimization/applications/ising/set_packing.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +""" set packing module """ + +import logging +import warnings + +import numpy as np +from qiskit.quantum_info import Pauli + +from qiskit.aqua.operators import WeightedPauliOperator + +logger = logging.getLogger(__name__) + + +def get_operator(list_of_subsets): + """Construct the Hamiltonian for the set packing. + + Notes: + find the maximal number of subsets which are disjoint pairwise. + + Hamiltonian: + H = A Ha + B Hb + Ha = sum_{Si and Sj overlaps}{XiXj} + Hb = -sum_{i}{Xi} + + Ha is to ensure the disjoint condition, while Hb is to achieve the maximal number. + Ha is hard constraint that must be satisfied. Therefore A >> B. + In the following, we set A=10 and B = 1 + + where Xi = (Zi + 1)/2 + + Args: + list_of_subsets (list): list of lists (i.e., subsets) + + Returns: + tuple(WeightedPauliOperator, float): operator for the Hamiltonian, + a constant shift for the obj function. + """ + # pylint: disable=invalid-name + shift = 0 + pauli_list = [] + A = 10 + n = len(list_of_subsets) + for i in range(n): + for j in range(i): + if set(list_of_subsets[i]) & set(list_of_subsets[j]): + wp = np.zeros(n) + vp = np.zeros(n) + vp[i] = 1 + vp[j] = 1 + pauli_list.append([A * 0.25, Pauli(vp, wp)]) + + vp2 = np.zeros(n) + vp2[i] = 1 + pauli_list.append([A * 0.25, Pauli(vp2, wp)]) + + vp3 = np.zeros(n) + vp3[j] = 1 + pauli_list.append([A * 0.25, Pauli(vp3, wp)]) + + shift += A * 0.25 + + for i in range(n): + wp = np.zeros(n) + vp = np.zeros(n) + vp[i] = 1 + pauli_list.append([-0.5, Pauli(vp, wp)]) + shift += -0.5 + + return WeightedPauliOperator(paulis=pauli_list), shift + + +def get_solution(x): + """ + + Args: + x (numpy.ndarray) : binary string as numpy array. + + Returns: + numpy.ndarray: graph solution as binary numpy array. + """ + return 1 - x + + +def check_disjoint(sol, list_of_subsets): + """ check disjoint """ + # pylint: disable=invalid-name + n = len(list_of_subsets) + selected_subsets = [] + for i in range(n): + if sol[i] == 1: + selected_subsets.append(list_of_subsets[i]) + tmplen = len(selected_subsets) + for i in range(tmplen): + for j in range(i): + L = selected_subsets[i] + R = selected_subsets[j] + if set(L) & set(R): + return False + + return True + + +def random_number_list(n, weight_range=100, savefile=None): + """ random number list """ + # pylint: disable=import-outside-toplevel + from .common import random_number_list as redirect_func + warnings.warn("random_number_list function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(n=n, weight_range=weight_range, savefile=savefile) + + +def read_numbers_from_file(filename): + """ read numbers from file """ + # pylint: disable=import-outside-toplevel + from .common import read_numbers_from_file as redirect_func + warnings.warn("read_numbers_from_file function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(filename) + + +def sample_most_likely(n=None, state_vector=None): + """ sample most likely """ + # pylint: disable=import-outside-toplevel + from .common import sample_most_likely as redirect_func + if n is not None: + warnings.warn("n argument is not need and it will be removed after Aqua 0.7+", + DeprecationWarning) + warnings.warn("sample_most_likely function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(state_vector=state_vector) + + +def get_set_packing_qubitops(list_of_subsets): + """ get set packing qubit ops """ + warnings.warn("get_set_packing_qubitops function has been changed to get_operator" + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return get_operator(list_of_subsets) diff --git a/qiskit/optimization/applications/ising/stable_set.py b/qiskit/optimization/applications/ising/stable_set.py new file mode 100644 index 0000000000..370a6c0d02 --- /dev/null +++ b/qiskit/optimization/applications/ising/stable_set.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +""" +Convert stable set instances into Pauli list. We read instances in +the Gset format, see https://web.stanford.edu/~yyye/yyye/Gset/ , for +compatibility with the maxcut format, but the weights on the edges +as they are not really used and are always assumed to be 1. The +graph is represented by an adjacency matrix. +""" + +import logging +import warnings + +import numpy as np +from qiskit.quantum_info import Pauli + +from qiskit.aqua.operators import WeightedPauliOperator + +logger = logging.getLogger(__name__) + + +def get_operator(w): + """Generate Hamiltonian for the maximum stable set in a graph. + + Args: + w (numpy.ndarray) : adjacency matrix. + + Returns: + tuple(WeightedPauliOperator, float): operator for the Hamiltonian and a + constant shift for the obj function. + + """ + num_nodes = len(w) + pauli_list = [] + shift = 0 + for i in range(num_nodes): + for j in range(i + 1, num_nodes): + if w[i, j] != 0: + x_p = np.zeros(num_nodes, dtype=np.bool) + z_p = np.zeros(num_nodes, dtype=np.bool) + z_p[i] = True + z_p[j] = True + pauli_list.append([1.0, Pauli(z_p, x_p)]) + shift += 1 + for i in range(num_nodes): + degree = np.sum(w[i, :]) + x_p = np.zeros(num_nodes, dtype=np.bool) + z_p = np.zeros(num_nodes, dtype=np.bool) + z_p[i] = True + pauli_list.append([degree - 1 / 2, Pauli(z_p, x_p)]) + return WeightedPauliOperator(paulis=pauli_list), shift - num_nodes / 2 + + +def stable_set_value(x, w): + """Compute the value of a stable set, and its feasibility. + + Args: + x (numpy.ndarray): binary string in original format -- not + graph solution!. + w (numpy.ndarray): adjacency matrix. + + Returns: + tuple(float, bool): size of the stable set, and Boolean indicating + feasibility. + """ + assert len(x) == w.shape[0] + feasible = True + num_nodes = w.shape[0] + for i in range(num_nodes): + for j in range(i + 1, num_nodes): + if w[i, j] != 0 and x[i] == 0 and x[j] == 0: + feasible = False + break + return len(x) - np.sum(x), feasible + + +def get_graph_solution(x): + """Get graph solution from binary string. + + Args: + x (numpy.ndarray) : binary string as numpy array. + + Returns: + numpy.ndarray: graph solution as binary numpy array. + """ + return 1 - x + + +def random_graph(n, edge_prob=0.5, savefile=None, seed=None): + """ random graph """ + # pylint: disable=import-outside-toplevel + from .common import random_graph as redirect_func + warnings.warn("random_graph function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(n=n, weight_range=2, edge_prob=edge_prob, + negative_weight=False, savefile=savefile, seed=seed) + + +def parse_gset_format(filename): + """ parse gset format """ + # pylint: disable=import-outside-toplevel + from .common import parse_gset_format as redirect_func + warnings.warn("parse_gset_format function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(filename) + + +def sample_most_likely(state_vector): + """ sample most likely """ + # pylint: disable=import-outside-toplevel + from .common import sample_most_likely as redirect_func + warnings.warn("sample_most_likely function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(state_vector=state_vector) + + +def get_stable_set_qubitops(w): + """ get stable set qubit ops """ + warnings.warn("get_stable_set_qubitops function has been changed to get_operator" + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return get_operator(w) diff --git a/qiskit/optimization/applications/ising/tsp.py b/qiskit/optimization/applications/ising/tsp.py new file mode 100644 index 0000000000..0bcc7e2dac --- /dev/null +++ b/qiskit/optimization/applications/ising/tsp.py @@ -0,0 +1,288 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +""" Convert symmetric TSP instances into Pauli list +Deal with TSPLIB format. It supports only EUC_2D edge weight type. +See https://wwwproxy.iwr.uni-heidelberg.de/groups/comopt/software/TSPLIB95/ +and http://elib.zib.de/pub/mp-testdata/tsp/tsplib/tsp/index.html +Design the tsp object `w` as a two-dimensional np.array +e.g., w[i, j] = x means that the length of a edge between i and j is x +Note that the weights are symmetric, i.e., w[j, i] = x always holds. +""" + +import logging +import warnings +from collections import namedtuple + +import numpy as np +from qiskit.quantum_info import Pauli + +from qiskit.aqua import aqua_globals +from qiskit.aqua.operators import WeightedPauliOperator + +logger = logging.getLogger(__name__) + +"""Instance data of TSP""" +TspData = namedtuple('TspData', 'name dim coord w') + + +def calc_distance(coord, name='tmp'): + """ calculate distance """ + assert coord.shape[1] == 2 + dim = coord.shape[0] + w = np.zeros((dim, dim)) + for i in range(dim): + for j in range(i + 1, dim): + delta = coord[i] - coord[j] + w[i, j] = np.rint(np.hypot(delta[0], delta[1])) + w += w.T + return TspData(name=name, dim=dim, coord=coord, w=w) + + +def random_tsp(n, low=0, high=100, savefile=None, seed=None, name='tmp'): + """Generate a random instance for TSP. + + Args: + n (int): number of nodes. + low (float): lower bound of coordinate. + high (float): upper bound of coordinate. + savefile (str or None): name of file where to save graph. + seed (int or None): random seed - if None, will not initialize. + name (str): name of an instance + + Returns: + TspData: instance data. + + """ + assert n > 0 + if seed: + aqua_globals.random_seed = seed + + coord = aqua_globals.random.uniform(low, high, (n, 2)) + ins = calc_distance(coord, name) + if savefile: + with open(savefile, 'w') as outfile: + outfile.write('NAME : {}\n'.format(ins.name)) + outfile.write('COMMENT : random data\n') + outfile.write('TYPE : TSP\n') + outfile.write('DIMENSION : {}\n'.format(ins.dim)) + outfile.write('EDGE_WEIGHT_TYPE : EUC_2D\n') + outfile.write('NODE_COORD_SECTION\n') + for i in range(ins.dim): + x = ins.coord[i] + outfile.write('{} {:.4f} {:.4f}\n'.format(i + 1, x[0], x[1])) + return ins + + +def parse_tsplib_format(filename): + """Read graph in TSPLIB format from file. + + Args: + filename (str): name of the file. + + Returns: + TspData: instance data. + + """ + name = '' + coord = [] + with open(filename) as infile: + coord_section = False + for line in infile: + if line.startswith('NAME'): + name = line.split(':')[1] + name.strip() + elif line.startswith('TYPE'): + typ = line.split(':')[1] + typ.strip() + if typ != 'TSP': + logger.warning('This supports only "TSP" type. Actual: %s', typ) + elif line.startswith('DIMENSION'): + dim = int(line.split(':')[1]) + coord = np.zeros((dim, 2)) + elif line.startswith('EDGE_WEIGHT_TYPE'): + typ = line.split(':')[1] + typ.strip() + if typ != 'EUC_2D': + logger.warning('This supports only "EUC_2D" edge weight. Actual: %s', typ) + elif line.startswith('NODE_COORD_SECTION'): + coord_section = True + elif coord_section: + v = line.split() + index = int(v[0]) - 1 + coord[index][0] = float(v[1]) + coord[index][1] = float(v[2]) + return calc_distance(coord, name) + + +def get_operator(ins, penalty=1e5): + """Generate Hamiltonian for TSP of a graph. + + Args: + ins (TspData) : TSP data including coordinates and distances. + penalty (float) : Penalty coefficient for the constraints + + Returns: + tuple(WeightedPauliOperator, float): operator for the Hamiltonian and a + constant shift for the obj function. + + """ + num_nodes = ins.dim + num_qubits = num_nodes ** 2 + zero = np.zeros(num_qubits, dtype=np.bool) + pauli_list = [] + shift = 0 + for i in range(num_nodes): + for j in range(num_nodes): + if i == j: + continue + for p__ in range(num_nodes): + q = (p__ + 1) % num_nodes + shift += ins.w[i, j] / 4 + + z_p = np.zeros(num_qubits, dtype=np.bool) + z_p[i * num_nodes + p__] = True + pauli_list.append([-ins.w[i, j] / 4, Pauli(z_p, zero)]) + + z_p = np.zeros(num_qubits, dtype=np.bool) + z_p[j * num_nodes + q] = True + pauli_list.append([-ins.w[i, j] / 4, Pauli(z_p, zero)]) + + z_p = np.zeros(num_qubits, dtype=np.bool) + z_p[i * num_nodes + p__] = True + z_p[j * num_nodes + q] = True + pauli_list.append([ins.w[i, j] / 4, Pauli(z_p, zero)]) + + for i in range(num_nodes): + for p__ in range(num_nodes): + z_p = np.zeros(num_qubits, dtype=np.bool) + z_p[i * num_nodes + p__] = True + pauli_list.append([penalty, Pauli(z_p, zero)]) + shift += -penalty + + for p__ in range(num_nodes): + for i in range(num_nodes): + for j in range(i): + shift += penalty / 2 + + z_p = np.zeros(num_qubits, dtype=np.bool) + z_p[i * num_nodes + p__] = True + pauli_list.append([-penalty / 2, Pauli(z_p, zero)]) + + z_p = np.zeros(num_qubits, dtype=np.bool) + z_p[j * num_nodes + p__] = True + pauli_list.append([-penalty / 2, Pauli(z_p, zero)]) + + z_p = np.zeros(num_qubits, dtype=np.bool) + z_p[i * num_nodes + p__] = True + z_p[j * num_nodes + p__] = True + pauli_list.append([penalty / 2, Pauli(z_p, zero)]) + + for i in range(num_nodes): + for p__ in range(num_nodes): + for q in range(p__): + shift += penalty / 2 + + z_p = np.zeros(num_qubits, dtype=np.bool) + z_p[i * num_nodes + p__] = True + pauli_list.append([-penalty / 2, Pauli(z_p, zero)]) + + z_p = np.zeros(num_qubits, dtype=np.bool) + z_p[i * num_nodes + q] = True + pauli_list.append([-penalty / 2, Pauli(z_p, zero)]) + + z_p = np.zeros(num_qubits, dtype=np.bool) + z_p[i * num_nodes + p__] = True + z_p[i * num_nodes + q] = True + pauli_list.append([penalty / 2, Pauli(z_p, zero)]) + shift += 2 * penalty * num_nodes + return WeightedPauliOperator(paulis=pauli_list), shift + + +def tsp_value(z, w): + """Compute the TSP value of a solution. + + Args: + z (list[int]): list of cities. + w (numpy.ndarray): adjacency matrix. + + Returns: + float: value of the cut. + """ + ret = 0.0 + for i in range(len(z) - 1): + ret += w[z[i], z[i + 1]] + ret += w[z[-1], z[0]] + return ret + + +def tsp_feasible(x): + """Check whether a solution is feasible or not. + + Args: + x (numpy.ndarray) : binary string as numpy array. + + Returns: + bool: feasible or not. + """ + n = int(np.sqrt(len(x))) + y = np.zeros((n, n)) + for i in range(n): + for p__ in range(n): + y[i, p__] = x[i * n + p__] + for i in range(n): + if sum(y[i, p] for p in range(n)) != 1: + return False + for p__ in range(n): + if sum(y[i, p__] for i in range(n)) != 1: + return False + return True + + +def get_tsp_solution(x): + """Get graph solution from binary string. + + Args: + x (numpy.ndarray) : binary string as numpy array. + + Returns: + list[int]: sequence of cities to traverse. + """ + n = int(np.sqrt(len(x))) + z = [] + for p__ in range(n): + for i in range(n): + if x[i * n + p__] >= 0.999: + assert len(z) == p__ + z.append(i) + return z + + +def sample_most_likely(state_vector): + """ sample most likely """ + # pylint: disable=import-outside-toplevel + from .common import sample_most_likely as redirect_func + warnings.warn("sample_most_likely function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(state_vector=state_vector) + + +def get_tsp_qubitops(ins, penalty=1e5): + """ get tsp qubit ops """ + warnings.warn("get_tsp_qubitops function has been changed to get_operator" + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return get_operator(ins, penalty) diff --git a/qiskit/optimization/applications/ising/vehicle_routing.py b/qiskit/optimization/applications/ising/vehicle_routing.py new file mode 100644 index 0000000000..197a77d4d5 --- /dev/null +++ b/qiskit/optimization/applications/ising/vehicle_routing.py @@ -0,0 +1,204 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + +""" +Converts vehicle routing instances into a list of Paulis, +and provides some related routines (extracting a solution, +checking its objective function value). +""" + +import warnings + +import numpy as np +from qiskit.quantum_info import Pauli + +from qiskit.aqua.operators import WeightedPauliOperator + + +def get_vehiclerouting_matrices(instance, n, K): # pylint: disable=invalid-name + """Constructs auxiliary matrices from a vehicle routing instance, + which represent the encoding into a binary quadratic program. + This is used in the construction of the qubit ops and computation + of the solution cost. + + Args: + instance (numpy.ndarray) : a customers-to-customers distance matrix. + n (integer) : the number of customers. + K (integer) : the number of vehicles available. + + Returns: + tuple(numpy.ndarray, numpy.ndarray, float): + a matrix defining the interactions between variables. + a matrix defining the contribution from the individual variables. + the constant offset. + """ + # pylint: disable=invalid-name + # N = (n - 1) * n + A = np.max(instance) * 100 # A parameter of cost function + + # Determine the weights w + instance_vec = instance.reshape(n ** 2) + w_list = [instance_vec[x] for x in range(n ** 2) if instance_vec[x] > 0] + w = np.zeros(n * (n - 1)) + for i_i, _ in enumerate(w_list): + w[i_i] = w_list[i_i] + + # Some additional variables + id_n = np.eye(n) + im_n_1 = np.ones([n - 1, n - 1]) + iv_n_1 = np.ones(n) + iv_n_1[0] = 0 + iv_n = np.ones(n - 1) + neg_iv_n_1 = np.ones(n) - iv_n_1 + + v = np.zeros([n, n * (n - 1)]) + for i_i in range(n): + count = i_i - 1 + for j_j in range(n * (n - 1)): + + if j_j // (n - 1) == i_i: + count = i_i + + if j_j // (n - 1) != i_i and j_j % (n - 1) == count: + v[i_i][j_j] = 1. + + v_n = np.sum(v[1:], axis=0) + + # Q defines the interactions between variables + Q = A * (np.kron(id_n, im_n_1) + np.dot(v.T, v)) + + # g defines the contribution from the individual variables + g = w - 2 * A * (np.kron(iv_n_1, iv_n) + v_n.T) - \ + 2 * A * K * (np.kron(neg_iv_n_1, iv_n) + v[0].T) + + # c is the constant offset + c = 2 * A * (n - 1) + 2 * A * (K ** 2) + + return (Q, g, c) + + +def get_vehiclerouting_cost(instance, n, K, x_sol): # pylint: disable=invalid-name + """Computes the cost of a solution to an instance of a vehicle routing problem. + + Args: + instance (numpy.ndarray) : a customers-to-customers distance matrix. + n (integer) : the number of customers. + K (integer) : the number of vehicles available. + x_sol (numpy.ndarray): a solution, i.e., a path, in its binary representation. + + Returns: + float: objective function value. + """ + # pylint: disable=invalid-name + (Q, g, c) = get_vehiclerouting_matrices(instance, n, K) + + def fun(x): + return np.dot(np.around(x), np.dot(Q, np.around(x))) + np.dot(g, np.around(x)) + c + + cost = fun(x_sol) + return cost + + +def get_operator(instance, n, K): # pylint: disable=invalid-name + """Converts an instance of a vehicle routing problem into a list of Paulis. + + Args: + instance (numpy.ndarray) : a customers-to-customers distance matrix. + n (integer) : the number of customers. + K (integer) : the number of vehicles available. + + Returns: + WeightedPauliOperator: operator for the Hamiltonian. + """ + # pylint: disable=invalid-name + N = (n - 1) * n + (Q, g__, c) = get_vehiclerouting_matrices(instance, n, K) + + # Defining the new matrices in the Z-basis + i_v = np.ones(N) + q_z = (Q / 4) + g_z = (-g__ / 2 - np.dot(i_v, Q / 4) - np.dot(Q / 4, i_v)) + c_z = (c + np.dot(g__ / 2, i_v) + np.dot(i_v, np.dot(Q / 4, i_v))) + + c_z = c_z + np.trace(q_z) + q_z = q_z - np.diag(np.diag(q_z)) + + # Getting the Hamiltonian in the form of a list of Pauli terms + + pauli_list = [] + for i in range(N): + if g_z[i] != 0: + w_p = np.zeros(N) + v_p = np.zeros(N) + v_p[i] = 1 + pauli_list.append((g_z[i], Pauli(v_p, w_p))) + for i in range(N): + for j in range(i): + if q_z[i, j] != 0: + w_p = np.zeros(N) + v_p = np.zeros(N) + v_p[i] = 1 + v_p[j] = 1 + pauli_list.append((2 * q_z[i, j], Pauli(v_p, w_p))) + + pauli_list.append((c_z, Pauli(np.zeros(N), np.zeros(N)))) + return WeightedPauliOperator(paulis=pauli_list) + + +def get_vehiclerouting_solution(instance, n, K, result): # pylint: disable=invalid-name + """Tries to obtain a feasible solution (in vector form) of an instance + of vehicle routing from the results dictionary. + + Args: + instance (numpy.ndarray) : a customers-to-customers distance matrix. + n (integer) : the number of customers. + K (integer) : the number of vehicles available. + result (dictionary) : a dictionary obtained by QAOA.run or VQE.run containing key 'eigvecs'. + + Returns: + numpy.ndarray: a solution, i.e., a path, in its binary representation. + + #TODO: support statevector simulation, results should be a statevector or counts format, not + a result from algorithm run + """ + # pylint: disable=invalid-name + del instance, K # unused + v = result['eigvecs'][0] + N = (n - 1) * n + + index_value = [x for x in range(len(v)) if v[x] == max(v)][0] + string_value = "{0:b}".format(index_value) + + while len(string_value) < N: + string_value = '0' + string_value + + x_sol = list() + for elements in string_value: + if elements == '0': + x_sol.append(0) + else: + x_sol.append(1) + + x_sol = np.flip(x_sol, axis=0) + + return x_sol + + +def get_vehiclerouting_qubitops(instance, n, K): + """ get vehicle routing qubit ops """ + # pylint: disable=invalid-name + warnings.warn("get_vehiclerouting_qubitops function has been changed to get_operator" + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return get_operator(instance, n, K) diff --git a/qiskit/optimization/applications/ising/vertex_cover.py b/qiskit/optimization/applications/ising/vertex_cover.py new file mode 100644 index 0000000000..179c068747 --- /dev/null +++ b/qiskit/optimization/applications/ising/vertex_cover.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +""" +Convert vertex cover instances into Pauli list +Deal with Gset format. See https://web.stanford.edu/~yyye/yyye/Gset/ +""" + +import logging +import warnings + +import numpy as np +from qiskit.quantum_info import Pauli + +from qiskit.aqua.operators import WeightedPauliOperator + +logger = logging.getLogger(__name__) + + +def get_operator(weight_matrix): + r"""Generate Hamiltonian for the vertex cover + Args: + weight_matrix (numpy.ndarray) : adjacency matrix. + + Returns: + tuple(WeightedPauliOperator, float): operator for the Hamiltonian and a + constant shift for the obj function. + + Goals: + 1 color some vertices as red such that every edge is connected to some red vertex + 2 minimize the vertices to be colored as red + + Hamiltonian: + H = A * H_A + H_B + H_A = sum\_{(i,j)\in E}{(1-Xi)(1-Xj)} + H_B = sum_{i}{Zi} + + H_A is to achieve goal 1 while H_b is to achieve goal 2. + H_A is hard constraint so we place a huge penality on it. A=5. + Note Xi = (Zi+1)/2 + + """ + n = len(weight_matrix) + pauli_list = [] + shift = 0 + a__ = 5 + + for i in range(n): + for j in range(i): + if weight_matrix[i, j] != 0: + w_p = np.zeros(n) + v_p = np.zeros(n) + v_p[i] = 1 + v_p[j] = 1 + pauli_list.append([a__ * 0.25, Pauli(v_p, w_p)]) + + v_p2 = np.zeros(n) + v_p2[i] = 1 + pauli_list.append([-a__ * 0.25, Pauli(v_p2, w_p)]) + + v_p3 = np.zeros(n) + v_p3[j] = 1 + pauli_list.append([-a__ * 0.25, Pauli(v_p3, w_p)]) + + shift += a__ * 0.25 + + for i in range(n): + w_p = np.zeros(n) + v_p = np.zeros(n) + v_p[i] = 1 + pauli_list.append([0.5, Pauli(v_p, w_p)]) + shift += 0.5 + return WeightedPauliOperator(paulis=pauli_list), shift + + +def check_full_edge_coverage(x, w): + """ + Args: + x (numpy.ndarray): binary string as numpy array. + w (numpy.ndarray): adjacency matrix. + + Returns: + float: value of the cut. + """ + first = w.shape[0] + second = w.shape[1] + for i in range(first): + for j in range(second): + if w[i, j] != 0: + if x[i] != 1 and x[j] != 1: + return False + + return True + + +def get_graph_solution(x): + """Get graph solution from binary string. + + Args: + x (numpy.ndarray) : binary string as numpy array. + + Returns: + numpy.ndarray: graph solution as binary numpy array. + """ + return 1 - x + + +def random_graph(n, weight_range=10, edge_prob=0.3, savefile=None, seed=None): + """ random graph """ + # pylint: disable=import-outside-toplevel + from .common import random_graph as redirect_func + warnings.warn("random_graph function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(n=n, weight_range=weight_range, edge_prob=edge_prob, + savefile=savefile, seed=seed) + + +def parse_gset_format(filename): + """ parse gset format """ + # pylint: disable=import-outside-toplevel + from .common import parse_gset_format as redirect_func + warnings.warn("parse_gset_format function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(filename) + + +def sample_most_likely(n=None, state_vector=None): + """ sample most likely """ + # pylint: disable=import-outside-toplevel + from .common import sample_most_likely as redirect_func + if n is not None: + warnings.warn("n argument is not need and it will be removed after Aqua 0.7+", + DeprecationWarning) + warnings.warn("sample_most_likely function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(state_vector=state_vector) + + +def get_gset_result(x): + """ get gset result """ + # pylint: disable=import-outside-toplevel + from .common import get_gset_result as redirect_func + warnings.warn("get_gset_result function has been moved to " + "qiskit.optimization.ising.common, " + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return redirect_func(x) + + +def get_vertex_cover_qubitops(weight_matrix): + """ get vertex cover qubit ops """ + warnings.warn("get_vertex_cover_qubitops function has been changed to get_operator" + "the method here will be removed after Aqua 0.7+", + DeprecationWarning) + return get_operator(weight_matrix) diff --git a/qiskit/optimization/converters/__init__.py b/qiskit/optimization/converters/__init__.py new file mode 100644 index 0000000000..e018bb0222 --- /dev/null +++ b/qiskit/optimization/converters/__init__.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + +""" +======================================================== +Optimization stack for Aqua (:mod:`qiskit.optimization`) +======================================================== + +.. currentmodule:: qiskit.optimization.converters + +Structures for converting optimization problems +========== + +""" + +from qiskit.optimization.converters.inequality_to_equality_converter import\ + InequalityToEqualityConverter +from qiskit.optimization.converters.integer_to_binary_converter import\ + IntegerToBinaryConverter +from qiskit.optimization.converters.optimization_problem_to_operator import\ + OptimizationProblemToOperator +from qiskit.optimization.converters.penalize_linear_equality_constraints import\ + PenalizeLinearEqualityConstraints + +__all__ = [ + "InequalityToEqualityConverter", + "IntegerToBinaryConverter", + "OptimizationProblemToOperator", + "PenalizeLinearEqualityConstraints" +] diff --git a/qiskit/optimization/converters/inequality_to_equality_converter.py b/qiskit/optimization/converters/inequality_to_equality_converter.py new file mode 100644 index 0000000000..00051345ea --- /dev/null +++ b/qiskit/optimization/converters/inequality_to_equality_converter.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + + +import copy +from typing import List, Tuple, Dict + +import numpy as np + +from qiskit.optimization.problems.optimization_problem import OptimizationProblem +from qiskit.optimization.results.optimization_result import OptimizationResult +from qiskit.optimization.utils import QiskitOptimizationError + + +class InequalityToEqualityConverter: + """ Convert inequality constraints into equality constraints by introducing slack variables. + + Examples: + >>> problem = OptimizationProblem() + >>> # define a problem + >>> conv = InequalityToEqualityConverter() + >>> problem2 = conv.encode(problem) + """ + + _delimiter = '@' # users are supposed not to use this character in variable names + + def __init__(self): + """ Constructor. It initializes the internal data structure. No args. + """ + self._src = None + self._dst = None + self._conv: Dict[str, List[Tuple[str, int]]] = {} + # e.g., self._conv = {'c1': [('s@1', 1), ('s@2', 2)]} + + def encode(self, op: OptimizationProblem, name: str = None) -> OptimizationProblem: + """ Convert a problem with inequality constraints into new one with only equality + constraints. + + Args: + op: The problem to be solved, that may contain inequality constraints. + name: The name of the converted problem. + + Returns: + The converted problem, that contain only equality constraints. + + """ + self._src = copy.deepcopy(op) + self._dst = OptimizationProblem() + + # declare variables + names = self._src.variables.get_names() + types = self._src.variables.get_types() + lb = self._src.variables.get_lower_bounds() + ub = self._src.variables.get_upper_bounds() + + for i, name in enumerate(names): + typ = types[i] + if typ == 'B': + self._dst.variables.add(names=[name], types='B') + elif typ == 'C' or typ == 'I': + self._dst.variables.add(names=[name], types=typ, lb=[lb[i]], ub=[ub[i]]) + else: + raise QiskitOptimizationError('Variable type not supported: ' + typ) + + # set objective name + if name is None: + self._dst.objective.set_name(self._src.objective.get_name()) + else: + self._dst.objective.set_name(name) + + # set objective sense + self._dst.objective.set_sense(self._src.objective.get_sense()) + + # set objective offset + self._dst.objective.set_offset(self._src.objective.get_offset()) + + # set linear objective terms + for i, v in self._src.objective.get_linear().items(): + self._dst.objective.set_linear(i, v) + + # set quadratic objective terms + for i, vi in self._src.objective.get_quadratic().items(): + for j, v in vi.items(): + self._dst.objective.set_quadratic_coefficients(i, j, v) + + # set linear constraints + names = self._src.linear_constraints.get_names() + rows = self._src.linear_constraints.get_rows() + senses = self._src.linear_constraints.get_senses() + rhs = self._src.linear_constraints.get_rhs() + + for i, name in enumerate(names): + # Copy equality constraints into self._dst + if senses[i] == 'E': + self._dst.linear_constraints.add(lin_expr=[rows[i]], senses=[senses[i]], + rhs=[rhs[i]], names=[names[i]]) + # When the type of a constraint is L, make an equality constraint + # with slack variables which represent [lb, ub] = [0, constant - the lower bound of lhs] + elif senses[i] == 'L': + lhs_lb = 0 + for ind, val in zip(rows[i].ind, rows[i].val): + if self._dst.variables.get_types(ind) == 'B': + ub = 1 + else: + ub = self._dst.variables.get_upper_bounds(ind) + lb = self._dst.variables.get_lower_bounds(ind) + + lhs_lb += min(lb * val, ub * val) + + slack_vars = self._encode_var(name=name + '_slack', lb=0, ub=rhs[i] - lhs_lb) + self._dst.variables.add(names=[name for name, _ in slack_vars], + types='B' * len(slack_vars)) + self._conv[names[i]] = slack_vars + + new_ind = rows[i].ind + new_val = rows[i].val + + for name, coef in slack_vars: + new_ind.append(self._dst.variables._varsgetindex[name]) + new_val.append(coef) + self._dst.linear_constraints.add(lin_expr=[rows[i]], senses=['E'], rhs=[rhs[i]], + names=[names[i]]) + + # When the type of a constraint is G, make an equality constraint + # with slack variables which represent [lb, ub] = [0, the upper bound of lhs] + elif senses[i] == 'G': + lhs_ub = 0 + for ind, val in zip(rows[i].ind, rows[i].val): + if self._dst.variables.get_types(ind) == 'B': + ub = 1 + else: + ub = self._dst.variables.get_upper_bounds(ind) + lb = self._dst.variables.get_lower_bounds(ind) + + lhs_ub += max(lb * val, ub * val) + slack_vars = self._encode_var(name=name + '_slack', lb=0, ub=lhs_ub - rhs[i]) + self._dst.variables.add(names=[name for name, _ in slack_vars], + types='B' * len(slack_vars)) + self._conv[names[i]] = slack_vars + + new_ind = rows[i].ind + new_val = rows[i].val + + for name, coef in slack_vars: + new_ind.append(self._dst.variables._varsgetindex[name]) + new_val.append(-1 * coef) + self._dst.linear_constraints.add(lin_expr=[rows[i]], senses=['E'], rhs=[rhs[i]], + names=[names[i]]) + + else: + raise QiskitOptimizationError('Sense type not supported: ' + senses[i]) + + return self._dst + + def _encode_var(self, name: str, lb: int, ub: int) -> List[Tuple[str, int]]: + # bounded-coefficient encoding proposed in arxiv:1706.01945 (Eq. (5)) + var_range = ub - lb + power = int(np.log2(var_range)) + bounded_coef = var_range - (2 ** power - 1) + + lst = [] + for i in range(power): + coef = 2 ** i + new_name = name + self._delimiter + str(i) + lst.append((new_name, coef)) + + new_name = name + self._delimiter + str(power) + lst.append((new_name, bounded_coef)) + + return lst + + def decode(self, result: OptimizationResult) -> OptimizationResult: + """ Convert a result of a converted problem into that of the original problem. + + Args: + result: The result of the converted problem. + + Returns: + The result of the original problem. + + """ + new_result = OptimizationResult() + # convert the optimization result into that of the original problem + names = self._dst.variables.get_names() + vals = result.x + new_vals = self._decode_var(names, vals) + new_result.x = new_vals + new_result.fval = result.fval + return new_result + + def _decode_var(self, names, vals) -> List[int]: + # decode slack variables + sol = {name: vals[i] for i, name in enumerate(names)} + new_vals = [] + slack_var_names = [] + + for lst in self._conv.values(): + slack_var_names.extend(lst) + + for name in sol: + if name in slack_var_names: + pass + else: + new_vals.append(sol[name]) + return new_vals diff --git a/qiskit/optimization/converters/integer_to_binary_converter.py b/qiskit/optimization/converters/integer_to_binary_converter.py new file mode 100644 index 0000000000..a2ffd2e63d --- /dev/null +++ b/qiskit/optimization/converters/integer_to_binary_converter.py @@ -0,0 +1,225 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + + +import copy +from typing import List, Tuple, Dict + +import numpy as np +from cplex import SparsePair + +from qiskit.optimization.problems.optimization_problem import OptimizationProblem +from qiskit.optimization.results.optimization_result import OptimizationResult + + +class IntegerToBinaryConverter: + """ Convert an `OptimizationProblem` into new one by encoding integer variables + with binary variables. + + Examples: + >>> problem = OptimizationProblem() + >>> problem.variables.add(names=['x'], types=['I'], lb=[0], ub=[10]) + >>> conv = IntegerToBinaryConverter() + >>> problem2 = conv.encode(problem) + """ + + _delimiter = '@' # users are supposed not to use this character in variable names + + def __init__(self): + """ Constructor. It initializes the internal data structure. No args. + """ + self._src = None + self._dst = None + self._conv: Dict[str, List[Tuple[str, int]]] = {} + # e.g., self._conv = {'x': [('x@1', 1), ('x@2', 2)]} + + def encode(self, op: OptimizationProblem, name: str = None) -> OptimizationProblem: + """ Convert a problem into a new one by encoding integer variables + with binary variables. It does not change the input. + Args: + op: The problem to be solved, that may contain integer variables. + name: The name of the converted problem. + + Returns: + The converted problem, that contains no integer variables. + """ + + self._src = copy.deepcopy(op) + self._dst = OptimizationProblem() + if name is None: + self._dst.set_problem_name(self._src.get_problem_name()) + else: + self._dst.set_problem_name(name) + + # declare variables + names = self._src.variables.get_names() + types = self._src.variables.get_types() + lb = self._src.variables.get_lower_bounds() + ub = self._src.variables.get_upper_bounds() + for i, name in enumerate(names): + typ = types[i] + if typ == 'I': + new_vars: List[Tuple[str, int]] = self._encode_var(name=name, lb=lb[i], ub=ub[i]) + self._conv[name] = new_vars + self._dst.variables.add( + names=[name for name, _ in new_vars], types='B' * len(new_vars)) + else: + self._dst.variables.add(names=[name], types=typ) + + # replace integer variables with binary variables in the objective function + # self.objective.subs(self._conv) + + # replace integer variables with binary variables in the constrains + # self.linear_constraints.subs(self._conv) + # self.quadratic_constraints.subs(self._conv) + # note: `subs` substibutes variables with sets of auxiliary variables + + self._substitute_int_var() + + return self._dst + + def _encode_var(self, name: str, lb: int, ub: int) -> List[Tuple[str, int]]: + # bounded-coefficient encoding proposed in arxiv:1706.01945 (Eq. (5)) + var_range = ub - lb + power = int(np.log2(var_range)) + bounded_coef = var_range - (2 ** power - 1) + + lst = [] + for i in range(power): + coef = 2 ** i + new_name = name + self._delimiter + str(i) + lst.append((new_name, coef)) + + new_name = name + self._delimiter + str(power) + lst.append((new_name, bounded_coef)) + + return lst + + def _substitute_int_var(self): + # set objective name + self._dst.objective.set_name(self._src.objective.get_name()) + + # set the sense of the objective function + self._dst.objective.set_sense(self._src.objective.get_sense()) + + # set offset + self._dst.objective.set_offset(self._src.objective.get_offset()) + + # set linear terms of objective function + src_obj_linear = self._src.objective.get_linear() + + for src_var_index in src_obj_linear: + coef = src_obj_linear[src_var_index] + var_name = self._src.variables.get_names(src_var_index) + + if var_name in self._conv: + for converted_name, converted_coef in self._conv[var_name]: + self._dst.objective.set_linear(converted_name, coef * converted_coef) + + else: + self._dst.objective.set_linear(var_name, coef) + + # set quadratic terms of objective function + src_obj_quad = self._src.objective.get_quadratic() + + num_var = self._dst.variables.get_num() + new_quad = np.zeros((num_var, num_var)) + index_dict = self._dst.variables._varsgetindex + + for row in src_obj_quad: + for col in src_obj_quad[row]: + row_var_name = self._src.variables.get_names(row) + col_var_name = self._src.variables.get_names(col) + coef = src_obj_quad[row][col] + + if row_var_name in self._conv: + row_vars = self._conv[row_var_name] + else: + row_vars = [(row_var_name, 1)] + + if col_var_name in self._conv: + col_vars = self._conv[col_var_name] + else: + col_vars = [(col_var_name, 1)] + + for new_row, row_coef in row_vars: + for new_col, col_coef in col_vars: + row_index = index_dict[new_row] + col_index = index_dict[new_col] + new_quad[row_index, col_index] = coef * row_coef * col_coef + + ind = list(range(num_var)) + lst = [] + for i in ind: + sp = SparsePair(ind=ind, val=new_quad[i].tolist()) + lst.append(sp) + self._dst.objective.set_quadratic(lst) + + # set constraints whose integer variables are replaced with binary variables + linear_rows = self._src.linear_constraints.get_rows() + linear_sense = self._src.linear_constraints.get_senses() + linear_rhs = self._src.linear_constraints.get_rhs() + linear_ranges = self._src.linear_constraints.get_range_values() + linear_names = self._src.linear_constraints.get_names() + + lin_expr = [] + + for i, linear_row in enumerate(linear_rows): + sp = SparsePair() + for j, var_ind in enumerate(linear_row.ind): + coef = linear_row.val[j] + var_name = self._src.variables.get_names(var_ind) + + if var_name in self._conv: + for converted_name, converted_coef in self._conv[var_name]: + sp.ind.append(converted_name) + sp.val.append(converted_coef * coef) + else: + sp.ind.append(var_name) + sp.val.append(coef) + + lin_expr.append(sp) + + self._dst.linear_constraints.add( + lin_expr, linear_sense, linear_rhs, linear_ranges, linear_names) + + def decode(self, result: OptimizationResult) -> OptimizationResult: + """ Convert a result of a converted problem into that of the original problem + by decoding integer variables back. + + Args: + result: The result of the converted problem. + + Returns: + The result of the original problem. + """ + new_result = OptimizationResult() + names = self._dst.variables.get_names() + vals = result.x + new_vals = self._decode_var(names, vals) + new_result.x = new_vals + new_result.fval = result.fval + new_result.results = result.results + return new_result + + def _decode_var(self, names, vals) -> List[int]: + # decode integer values + sol = {name: int(vals[i]) for i, name in enumerate(names)} + new_vals = [] + for i, name in enumerate(self._src.variables.get_names()): + if name in self._conv: + new_vals.append(sum(sol[aux] * coef for aux, coef in self._conv[name])) + else: + new_vals.append(sol[name]) + return new_vals diff --git a/qiskit/optimization/converters/optimization_problem_to_operator.py b/qiskit/optimization/converters/optimization_problem_to_operator.py new file mode 100644 index 0000000000..7e6278ba82 --- /dev/null +++ b/qiskit/optimization/converters/optimization_problem_to_operator.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + + +from typing import Dict, Tuple + +import numpy as np +from qiskit.quantum_info import Pauli + +from qiskit.aqua.operators import WeightedPauliOperator +from qiskit.optimization import OptimizationProblem +from qiskit.optimization.utils import QiskitOptimizationError + + +class OptimizationProblemToOperator: + """ Convert an optimization problem into a qubit operator. + """ + + def __init__(self): + """ Constructor. Initialize the internal data structure. No args. + """ + self._src = None + self._q_d: Dict[int, int] = {} + # e.g., self._q_d = {0: 0} + + def encode(self, op: OptimizationProblem) -> Tuple[WeightedPauliOperator, float]: + """ Convert a problem into a qubit operator + + Args: + op: The problem to be solved, that is an unconstrained problem with only binary variables. + + Returns: + The qubit operator of the problem and the shift value. + + """ + + self._src = op + # if op has variables that are not binary, raise an error + var_list = self._src.variables.get_types() + if not all(var == 'B' for var in var_list): + raise QiskitOptimizationError('The type of variable must be a binary variable.') + + # if constraints exist, raise an error + linear_names = self._src.linear_constraints.get_names() + if len(linear_names) > 0: + raise QiskitOptimizationError('An constraint exists. ' + 'The method supports only model with no constraints.') + + # TODO: check for quadratic constraints as well + + # assign variables of the model to qubits. + _q_d = {} + qubit_index = 0 + for name in self._src.variables.get_names(): + var_index = self._src.variables._varsgetindex[name] + _q_d[var_index] = qubit_index + qubit_index += 1 + + # initialize Hamiltonian. + num_nodes = len(_q_d) + pauli_list = [] + shift = 0 + zero = np.zeros(num_nodes, dtype=np.bool) + + # set a sign corresponding to a maximized or minimized problem. + # sign == 1 is for minimized problem. sign == -1 is for maximized problem. + sense = self._src.objective.get_sense() + + # convert a constant part of the object function into Hamiltonian. + shift += self._src.objective.get_offset() * sense + + # convert linear parts of the object function into Hamiltonian. + for i, coef in self._src.objective.get_linear().items(): + z_p = np.zeros(num_nodes, dtype=np.bool) + qubit_index = _q_d[i] + weight = coef * sense / 2 + z_p[qubit_index] = True + + pauli_list.append([-weight, Pauli(z_p, zero)]) + shift += weight + + # convert quadratic parts of the object function into Hamiltonian. + for i, vi in self._src.objective.get_quadratic().items(): + for j, coef in vi.items(): + if j < i: + continue + qubit_index_1 = _q_d[i] + qubit_index_2 = _q_d[j] + if i == j: + coef = coef / 2 + weight = coef * sense / 4 + + if qubit_index_1 == qubit_index_2: + shift += weight + else: + z_p = np.zeros(num_nodes, dtype=np.bool) + z_p[qubit_index_1] = True + z_p[qubit_index_2] = True + pauli_list.append([weight, Pauli(z_p, zero)]) + + z_p = np.zeros(num_nodes, dtype=np.bool) + z_p[qubit_index_1] = True + pauli_list.append([-weight, Pauli(z_p, zero)]) + + z_p = np.zeros(num_nodes, dtype=np.bool) + z_p[qubit_index_2] = True + pauli_list.append([-weight, Pauli(z_p, zero)]) + + shift += weight + + # Remove paulis whose coefficients are zeros. + qubit_op = WeightedPauliOperator(paulis=pauli_list) + + return qubit_op, shift diff --git a/qiskit/optimization/converters/penalize_linear_equality_constraints.py b/qiskit/optimization/converters/penalize_linear_equality_constraints.py new file mode 100644 index 0000000000..452132aba9 --- /dev/null +++ b/qiskit/optimization/converters/penalize_linear_equality_constraints.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +import copy +from collections import defaultdict + +from qiskit.optimization.problems.optimization_problem import OptimizationProblem +from qiskit.optimization.utils import QiskitOptimizationError + + +class PenalizeLinearEqualityConstraints: + """ Convert a problem with only equality constraints into an unconstrained problem + with penalty terms associated with the constraints. + """ + + def __init__(self): + """ Constructor. Initialize the internal data structure. No args. + """ + self._src = None + self._dst = None + + def encode(self, op: OptimizationProblem, penalty_factor: float = 1e5, name: str = None): + """ Convert a problem with equality constraints into an unconstrained problem. + + Args: + op: The problem to be solved, that does not contain inequality constraints. + penalty_factor: Penalty terms in the objective function is multiplied with this factor. + name: The name of the converted problem. + + Returns: + The converted problem, that is an unconstrained problem. + + """ + + # TODO: test compatibility, how to react in case of incompatibility? + + # create empty OptimizationProblem model + self._src = copy.deepcopy(op) # deep copy + self._dst = OptimizationProblem() + + # set variables (obj is set via objective interface) + var_names = self._src.variables.get_names() + var_lbs = self._src.variables.get_lower_bounds() + var_ubs = self._src.variables.get_upper_bounds() + var_types = self._src.variables.get_types() + if var_names: + self._dst.variables.add(lb=var_lbs, ub=var_ubs, types=var_types, names=var_names) + + # set objective name + if name is None: + self._dst.set_problem_name(self._src.get_problem_name()) + else: + self._dst.set_problem_name(name) + + # set objective sense + self._dst.objective.set_sense(self._src.objective.get_sense()) + penalty_factor = self._src.objective.get_sense() * penalty_factor + + # store original objective offset + offset = self._src.objective.get_offset() + + # store original linear objective terms + linear_terms = defaultdict(int) + for i, v in self._src.objective.get_linear().items(): + linear_terms[i] = v + + # store original quadratic objective terms + quadratic_terms = defaultdict(lambda: defaultdict(int)) + for i, v in self._src.objective.get_quadratic().items(): + quadratic_terms[i].update(v) + + # get linear constraints' data + linear_rows = self._src.linear_constraints.get_rows() + linear_sense = self._src.linear_constraints.get_senses() + linear_rhs = self._src.linear_constraints.get_rhs() + linear_names = self._src.linear_constraints.get_names() + + # if inequality constraints exist, raise an error + if not all(ls == 'E' for ls in linear_sense): + raise QiskitOptimizationError('An inequality constraint exists. ' + 'The method supports only equality constraints.') + + # convert linear constraints into penalty terms + num_constraints = len(linear_names) + for i in range(num_constraints): + constant = linear_rhs[i] + row = linear_rows[i] + + # constant parts of penalty*(Constant-func)**2: penalty*(Constant**2) + offset += penalty_factor * constant ** 2 + + # linear parts of penalty*(Constant-func)**2: penalty*(-2*Constant*func) + for var_ind, coef in zip(row.ind, row.val): + # if var_ind already exisits in the linear terms dic, add a penalty term + # into existing value else create new key and value in the linear_term dict + linear_terms[var_ind] += penalty_factor * -2 * coef * constant + + # quadratic parts of penalty*(Constant-func)**2: penalty*(func**2) + for var_ind_1, coef_1 in zip(row.ind, row.val): + for var_ind_2, coef_2 in zip(row.ind, row.val): + # if var_ind_1 and var_ind_2 already exisit in the quadratic terms dic, + # add a penalty term into existing value + # else create new key and value in the quadratic term dict + + # according to implementation of quadratic terms in OptimizationModel, + # multiply by 2 + quadratic_terms[var_ind_1][var_ind_2] += penalty_factor * coef_1 * coef_2 * 2 + + # set objective offset + self._dst.objective.set_offset(offset) + + # set linear objective terms + for i, v in linear_terms.items(): + self._dst.objective.set_linear(i, v) + + # set quadratic objective terms + for i, vi in quadratic_terms.items(): + for j, v in vi.items(): + self._dst.objective.set_quadratic_coefficients(i, j, v) + + return self._dst diff --git a/qiskit/optimization/problems/__init__.py b/qiskit/optimization/problems/__init__.py new file mode 100755 index 0000000000..d6ba245f9d --- /dev/null +++ b/qiskit/optimization/problems/__init__.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + +""" +======================================================== +Optimization stack for Aqua (:mod:`qiskit.optimization`) +======================================================== + +.. currentmodule:: qiskit.optimization.problems + +Structures for defining an optimization problem and its solution +========== + +.. autosummary:: + :toctree: + + OptimizationProblem + VariablesInterface + ObjectiveInterface + LinearConstraintInterface + QuadraticConstraintInterface + +N.B. Additional classes LinearConstraintInterface, QuadraticConstraintInterface, ObjectiveInterface, and VariablesInterface +are not to be instantiated directly. Objects of those types are available within an instantiated OptimizationProblem. + +""" + +from qiskit.optimization.problems.optimization_problem import OptimizationProblem +from qiskit.optimization.problems.linear_constraint import LinearConstraintInterface +from qiskit.optimization.problems.objective import ObjSense, ObjectiveInterface + +__all__ = ["OptimizationProblem", "LinearConstraintInterface", "ObjSense", "ObjectiveInterface"] diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py new file mode 100755 index 0000000000..f610b4ab65 --- /dev/null +++ b/qiskit/optimization/problems/linear_constraint.py @@ -0,0 +1,901 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + +from collections.abc import Sequence + +import copy + +from qiskit.optimization.utils.base import BaseInterface +from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError +from qiskit.optimization.utils.helpers import init_list_args, validate_arg_lengths, listify, convert +from cplex import SparsePair + + +# TODO: can we delete these? +CPX_CON_LOWER_BOUND = 1 +CPX_CON_UPPER_BOUND = 2 +CPX_CON_LINEAR = 3 +CPX_CON_QUADRATIC = 4 +CPX_CON_SOS = 5 +CPX_CON_INDICATOR = 6 +CPX_CON_PWL = 7 +CPX_CON_ABS = 7 +CPX_CON_MINEXPR = 8 +CPX_CON_MAXEXPR = 9 +CPX_CON_LAST_CONTYPE = 10 + + +class LinearConstraintInterface(BaseInterface): + """Methods for adding, modifying, and querying linear constraints.""" + + def __init__(self, varsgetindexfunc=None): + """Creates a new LinearConstraintInterface. + + The linear constraints interface is exposed by the top-level + `OptimizationProblem` class as `OptimizationProblem.linear_constraints`. + This constructor is not meant to be used externally. + """ + super(LinearConstraintInterface, self).__init__() + self._rhs = [] + self._senses = [] + self._range_values = [] + self._names = [] + self._lin_expr = [] + self._linconsgetindex = {} + self._varsgetindexfunc = varsgetindexfunc + + def get_num(self): + """Returns the number of linear constraints. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.linear_constraints.add(names = ["c1", "c2", "c3"]) + >>> op.linear_constraints.get_num() + 3 + """ + return len(self._names) + + def _linconsgetindexfunc(self, item): + if item not in self._linconsgetindex: + self._linconsgetindex[item] = len(self._linconsgetindex) + return self._linconsgetindex[item] + + def _linconsrebuildindex(self): + self._linconsgetindex = {} + for (cnt, item) in enumerate(self._names): + self._linconsgetindex[item] = cnt + + def add(self, lin_expr=None, senses="", rhs=None, range_values=None, + names=None): + """Adds linear constraints to the problem. + + linear_constraints.add accepts the keyword arguments lin_expr, + senses, rhs, range_values, and names. + + If more than one argument is specified, all arguments must + have the same length. + + lin_expr may be either a list of SparsePair instances or a + matrix in list-of-lists format. + + Note + The entries of lin_expr must not contain duplicate indices. + If an entry of lin_expr references a variable more than + once, either by index, name, or a combination of index and + name, an exception will be raised. + + senses must be either a list of single-character strings or a + string containing the senses of the linear constraints. + Each entry must + be one of 'G', 'L', 'E', and 'R', indicating greater-than, + less-than, equality, and ranged constraints, respectively. + + rhs is a list of floats, specifying the righthand side of + each linear constraint. + + range_values is a list of floats, specifying the difference + between lefthand side and righthand side of each linear constraint. + If range_values[i] > 0 (zero) then the constraint i is defined as + rhs[i] <= rhs[i] + range_values[i]. If range_values[i] < 0 (zero) + then constraint i is defined as + rhs[i] + range_value[i] <= a*x <= rhs[i]. + + names is a list of strings. + + Returns an iterator containing the indices of the added linear + constraints. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = ["x1", "x2", "x3"]) + >>> indices = op.linear_constraints.add(\ + lin_expr = [SparsePair(ind = ["x1", "x3"], val = [1.0, -1.0]),\ + SparsePair(ind = ["x1", "x2"], val = [1.0, 1.0]),\ + SparsePair(ind = ["x1", "x2", "x3"], val = [-1.0] * 3),\ + SparsePair(ind = ["x2", "x3"], val = [10.0, -2.0])],\ + senses = ["E", "L", "G", "R"],\ + rhs = [0.0, 1.0, -1.0, 2.0],\ + range_values = [0.0, 0.0, 0.0, -10.0],\ + names = ["c0", "c1", "c2", "c3"]) + >>> op.linear_constraints.get_rhs() + [0.0, 1.0, -1.0, 2.0] + """ + + arg_list = init_list_args(lin_expr, senses, rhs, range_values, names) + arg_lengths = [len(x) for x in arg_list] + if len(arg_lengths) == 0: + return range(0) + max_length = max(arg_lengths) + for arg_length in arg_lengths: + if arg_length > 0 and arg_length != max_length: + raise QiskitOptimizationError("inconsistent arguments in linear_constraints.add().") + + if max_length > 0: + + if not rhs: + rhs = [0.0] * max_length + self._rhs.extend(rhs) + + if not senses: + senses = "E" * max_length + self._senses.extend(senses) + + if not range_values: + range_values = [0.0] * max_length + self._range_values.extend(range_values) + + if not names: + names = ["c" + str(cnt) for cnt in range(len(self._names), + len(self._names) + max_length)] + self._names.extend(names) + for name in names: + self._linconsgetindexfunc(name) + + if not lin_expr: + lin_expr = [SparsePair()] * max_length + if all(isinstance(el, SparsePair) for el in lin_expr): + for sp in lin_expr: + lin_expr_dict = {} + for i, val in zip(sp.ind, sp.val): + i = convert(i, self._varsgetindexfunc) + if i in lin_expr_dict: + raise QiskitOptimizationError( + 'Variables should only appear once in linear constraint.') + lin_expr_dict[i] = val + self._lin_expr += [lin_expr_dict] + elif all(isinstance(el, Sequence) for el in lin_expr): + for l in lin_expr: + lin_expr_dict = {} + for i, val in zip(l[0], l[1]): + i = convert(i, self._varsgetindexfunc) + if i in lin_expr_dict: + raise QiskitOptimizationError( + 'Variables should only appear once in linear constraint.') + lin_expr_dict[i] = val + self._lin_expr += [lin_expr_dict] + else: + raise QiskitOptimizationError( + 'Invalid lin_expr format in linear_constraint.add().') + + return range(len(self._names) - max_length, len(self._names)) + + def delete(self, *args): + """Removes linear constraints from the problem. + + There are four forms by which linear_constraints.delete may be + called. + + linear_constraints.delete() + deletes all linear constraints from the problem. + + linear_constraints.delete(i) + i must be a linear constraint name or index. Deletes the + linear constraint whose index or name is i. + + linear_constraints.delete(s) + s must be a sequence of linear constraint names or indices. + Deletes the linear constraints with names or indices contained + within s. Equivalent to [linear_constraints.delete(i) for i in s]. + + linear_constraints.delete(begin, end) + begin and end must be linear constraint indices or linear + constraint names. Deletes the linear constraints with indices + between begin and end, inclusive of end. Equivalent to + linear_constraints.delete(range(begin, end + 1)). This will + give the best performance when deleting batches of linear + constraints. + + See CPXdelrows in the Callable Library Reference Manual for + more detail. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.linear_constraints.add(names=[str(i) for i in range(10)]) + >>> op.linear_constraints.get_num() + 10 + >>> op.linear_constraints.delete(8) + >>> op.linear_constraints.get_names() + ['0', '1', '2', '3', '4', '5', '6', '7', '9'] + >>> op.linear_constraints.delete("1", 3) + >>> op.linear_constraints.get_names() + ['0', '4', '5', '6', '7', '9'] + >>> op.linear_constraints.delete([2, "0", 5]) + >>> op.linear_constraints.get_names() + ['4', '6', '7'] + >>> op.linear_constraints.delete() + >>> op.linear_constraints.get_names() + [] + """ + + # TODO: delete does not update the index to find constraints by name etc. + + def _delete(i): + del self._rhs[i] + del self._senses[i] + del self._names[i] + del self._lin_expr[i] + del self._range_values[i] + + if len(args) == 0: + # Delete All: + self._rhs = [] + self._senses = [] + self._names = [] + self._lin_expr = [] + self._range_values = [] + self._linconsgetindex = {} + elif len(args) == 1: + # Delete all items from a possibly unordered list of mixed types: + args = listify(convert(args[0], self._linconsgetindexfunc)) + args = sorted(args) + for i, j in enumerate(args): + _delete(j - i) + self._linconsrebuildindex() + elif len(args) == 2: + # Delete range from arg[0] to arg[1]: + start = convert(args[0], self._linconsgetindexfunc) + end = convert(args[1], self._linconsgetindexfunc) + self.delete(range(start, end + 1)) + self._linconsrebuildindex() + else: + raise QiskitOptimizationError("Wrong number of arguments.") + + def set_rhs(self, *args): + """Sets the righthand side of a set of linear constraints. + + There are two forms by which linear_constraints.set_rhs may be + called. + + linear_constraints.set_rhs(i, rhs) + i must be a row name or index and rhs must be a real number. + Sets the righthand side of the row whose index or name is + i to rhs. + + linear_constraints.set_rhs(seq_of_pairs) + seq_of_pairs must be a list or tuple of (i, rhs) pairs, each + of which consists of a row name or index and a real + number. Sets the righthand side of the specified rows to + the corresponding values. Equivalent to + [linear_constraints.set_rhs(pair[0], pair[1]) for pair in seq_of_pairs]. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) + >>> op.linear_constraints.get_rhs() + [0.0, 0.0, 0.0, 0.0] + >>> op.linear_constraints.set_rhs("c1", 1.0) + >>> op.linear_constraints.get_rhs() + [0.0, 1.0, 0.0, 0.0] + >>> op.linear_constraints.set_rhs([("c3", 2.0), (2, -1.0)]) + >>> op.linear_constraints.get_rhs() + [0.0, 1.0, -1.0, 2.0] + """ + + def _set(i, v): + self._rhs[convert(i, self._linconsgetindexfunc)] = v + + if len(args) == 2: + _set(args[0], args[1]) + elif len(args) == 1: + args = listify(args[0]) + for (i, v) in args: + _set(i, v) + else: + raise QiskitOptimizationError("Wrong number of arguments.") + + def set_names(self, *args): + """Sets the name of a linear constraint or set of linear constraints. + + There are two forms by which linear_constraints.set_names may be + called. + + linear_constraints.set_names(i, name) + i must be a linear constraint name or index and name must be a + string. + + linear_constraints.set_names(seq_of_pairs) + seq_of_pairs must be a list or tuple of (i, name) pairs, + each of which consists of a linear constraint name or index and a + string. Sets the name of the specified linear constraints to the + corresponding strings. Equivalent to + [linear_constraints.set_names(pair[0], pair[1]) for pair in seq_of_pairs]. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) + >>> op.linear_constraints.set_names("c1", "second") + >>> op.linear_constraints.get_names(1) + 'second' + >>> op.linear_constraints.set_names([("c3", "last"), (2, "middle")]) + >>> op.linear_constraints.get_names() + ['c0', 'second', 'middle', 'last'] + """ + + def _set(i, v): + self._names[convert(i, self._linconsgetindexfunc)] = v + + if len(args) == 2: + _set(args[0], args[1]) + elif len(args) == 1: + args = listify(args[0]) + for (i, v) in args: + _set(i, v) + else: + raise QiskitOptimizationError("Wrong number of arguments.") + + def set_senses(self, *args): + """Sets the sense of a linear constraint or set of linear constraints. + + There are two forms by which linear_constraints.set_senses may be + called. + + linear_constraints.set_senses(i, type) + i must be a row name or index and name must be a + single-character string. + + linear_constraints.set_senses(seq_of_pairs) + seq_of_pairs must be a list or tuple of (i, sense) pairs, + each of which consists of a row name or index and a + single-character string. Sets the sense of the specified + rows to the corresponding strings. Equivalent to + [linear_constraints.set_senses(pair[0], pair[1]) for pair in seq_of_pairs]. + + The senses of the constraints must be one of 'G', 'L', 'E', + and 'R', indicating greater-than, less-than, equality, and + ranged constraints, respectively. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) + >>> op.linear_constraints.get_senses() + ['E', 'E', 'E', 'E'] + >>> op.linear_constraints.set_senses("c1", "G") + >>> op.linear_constraints.get_senses(1) + 'G' + >>> op.linear_constraints.set_senses([("c3", "L"), (2, "R")]) + >>> op.linear_constraints.get_senses() + ['E', 'G', 'R', 'L'] + """ + + def _set(i, v): + v = v.upper().strip() + if v in ["G", "L", "E", "R"]: + self._senses[convert(i, getindexfunc=self._linconsgetindexfunc)] = v + else: + raise QiskitOptimizationError("Wrong sense!") + + if len(args) == 2: + _set(args[0], args[1]) + elif len(args) == 1: + args = listify(args[0]) + for (i, v) in args: + _set(i, v) + else: + raise QiskitOptimizationError("Wrong number of arguments.") + + def set_linear_components(self, *args): + """Sets a linear constraint or set of linear constraints. + + There are two forms by which this method may be called: + + linear_constraints.set_linear_components(i, lin) + i must be a row name or index and lin must be either a + SparsePair or a pair of sequences, the first of which + consists of variable names or indices, the second of which + consists of floats. + + linear_constraints.set_linear_components(seq_of_pairs) + seq_of_pairs must be a list or tuple of (i, lin) pairs, + each of which consists of a row name or index and a vector + as described above. Sets the specified rows + to the corresponding vector. Equivalent to + [linear_constraints.set_linear_components(pair[0], pair[1]) for pair in seq_of_pairs]. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) + >>> indices = op.variables.add(names = ["x0", "x1"]) + >>> op.linear_constraints.set_linear_components("c0", [["x0"], [1.0]]) + >>> op.linear_constraints.get_rows("c0") + SparsePair(ind = [0], val = [1.0]) + >>> op.linear_constraints.set_linear_components([("c3", SparsePair(ind = ["x1"], val = [-1.0])),\ + (2, [[0, 1], [-2.0, 3.0]])]) + >>> op.linear_constraints.get_rows() + [SparsePair(ind = [0], val = [1.0]), SparsePair(ind = [], val = []), SparsePair(ind = [0, 1], val = [-2.0, 3.0]), SparsePair(ind = [1], val = [-1.0])] + """ + + def _set(i, v): + i = convert(i, self._linconsgetindexfunc) + if isinstance(v, SparsePair): + ind, val = SparsePair.unpack(v) + for j, w in zip(ind, val): + j = convert(j, self._varsgetindexfunc) + self._lin_expr[i][j] = w + elif isinstance(v, Sequence): + if len(v) != 2: + raise QiskitOptimizationError( + "Wrong linear expression. A SparsePair or a pair of indices and values is expected!") + for j, w in zip(v[0], v[1]): + j = convert(j, self._varsgetindexfunc) + self._lin_expr[i][j] = w + else: + raise QiskitOptimizationError("Wrong linear expression. A SparsePair is expected!") + + if len(args) == 2: + _set(args[0], args[1]) + elif len(args) == 1: + args = listify(args[0]) + for (i, v) in args: + _set(i, v) + else: + raise QiskitOptimizationError("Wrong number of arguments.") + + def set_range_values(self, *args): + """Sets the range values for a set of linear constraints. + + That is, this method sets the lefthand side (lhs) for each ranged + constraint of the form lhs <= lin_expr <= rhs. + + The range values are a list of floats, specifying the difference + between lefthand side and righthand side of each linear constraint. + If range_values[i] > 0 (zero) then the constraint i is defined as + rhs[i] <= rhs[i] + range_values[i]. If range_values[i] < 0 (zero) + then constraint i is defined as + rhs[i] + range_value[i] <= a*x <= rhs[i]. + + Note that changing the range values will not change the sense of a + constraint; you must call the method set_senses() of the class + LinearConstraintInterface to change the sense of a ranged row if + the previous range value was 0 (zero) and the constraint sense was not + 'R'. Similarly, changing the range coefficient from a nonzero value to + 0 (zero) will not change the constraint sense from 'R" to "E"; an + additional call of setsenses() is required to accomplish that. + + There are two forms by which linear_constraints.set_range_values may be + called. + + linear_constraints.set_range_values(i, range) + i must be a row name or index and range must be a real + number. Sets the range value of the row whose index or + name is i to range. + + linear_constraints.set_range_values(seq_of_pairs) + seq_of_pairs must be a list or tuple of (i, range) pairs, each + of which consists of a row name or index and a real + number. Sets the range values for the specified rows to + the corresponding values. Equivalent to + [linear_constraints.set_range_values(pair[0], pair[1]) for pair in seq_of_pairs]. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) + >>> op.linear_constraints.set_range_values("c1", 1.0) + >>> op.linear_constraints.get_range_values() + [0.0, 1.0, 0.0, 0.0] + >>> op.linear_constraints.set_range_values([("c3", 2.0), (2, -1.0)]) + >>> op.linear_constraints.get_range_values() + [0.0, 1.0, -1.0, 2.0] + """ + + def _set(i, v): + self._range_values[convert(i, getindexfunc=self._linconsgetindexfunc)] = v + # TODO: raise QiskitOptimizationError("Wrong range!") + + if len(args) == 2: + _set(args[0], args[1]) + elif len(args) == 1: + args = listify(args[0]) + for (i, v) in args: + _set(i, v) + else: + raise QiskitOptimizationError("Wrong number of arguments.") + + def set_coefficients(self, *args): + """Sets individual coefficients of the linear constraint matrix. + + There are two forms by which + linear_constraints.set_coefficients may be called. + + linear_constraints.set_coefficients(row, col, val) + row and col must be indices or names of a linear constraint + and variable, respectively. The corresponding coefficient + is set to val. + + linear_constraints.set_coefficients(coefficients) + coefficients must be a list of (row, col, val) triples as + described above. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) + >>> indices = op.variables.add(names = ["x0", "x1"]) + >>> op.linear_constraints.set_coefficients("c0", "x1", 1.0) + >>> op.linear_constraints.get_rows(0) + SparsePair(ind = [1], val = [1.0]) + >>> op.linear_constraints.set_coefficients([("c2", "x0", 2.0),\ + ("c2", "x1", -1.0)]) + >>> op.linear_constraints.get_rows("c2") + SparsePair(ind = [0, 1], val = [2.0, -1.0]) + """ + if len(args) == 3: + arg_list = [args] + elif len(args) == 1: + arg_list = listify(args[0]) + else: + raise QiskitOptimizationError("Wrong number of arguments") + for ijv in arg_list: + i = convert(ijv[0], self._linconsgetindexfunc) + j = convert(ijv[1], self._varsgetindexfunc) + self._lin_expr[i][j] = ijv[2] + + def get_rhs(self, *args): + """Returns the righthand side of constraints from the problem. + + Can be called by four forms. + + linear_constraints.get_rhs() + return the righthand side of all linear constraints from + the problem. + + linear_constraints.get_rhs(i) + i must be a linear constraint name or index. Returns the + righthand side of the linear constraint whose index or + name is i. + + linear_constraints.get_rhs(s) + s must be a sequence of linear constraint names or indices. + Returns the righthand side of the linear constraints with + indices the members of s. Equivalent to + [linear_constraints.get_rhs(i) for i in s] + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.linear_constraints.add(rhs = [1.5 * i for i in range(10)],\ + names = [str(i) for i in range(10)]) + >>> op.linear_constraints.get_num() + 10 + >>> op.linear_constraints.get_rhs(8) + 12.0 + >>> op.linear_constraints.get_rhs([2,"0",5]) + [3.0, 0.0, 7.5] + >>> op.linear_constraints.get_rhs() + [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] + """ + + def _get(i): + i = convert(i, self._linconsgetindexfunc) + return self._rhs[i] + + out = [] + if len(args) == 0: + return copy.deepcopy(self._rhs) + elif len(args) == 1: + if isinstance(args[0], Sequence): + for i in args[0]: + out.append(_get(i)) + else: + return _get(args[0]) + else: + for i in args: + out.append(_get(i)) + return out + + def get_senses(self, *args): + """Returns the senses of constraints from the problem. + + Can be called by four forms. + + linear_constraints.get_senses() + return the senses of all linear constraints from the + problem. + + linear_constraints.get_senses(i) + i must be a linear constraint name or index. Returns the + sense of the linear constraint whose index or name is i. + + linear_constraints.get_senses(s) + s must be a sequence of linear constraint names or indices. + Returns the senses of the linear constraints with indices + the members of s. Equivalent to + [linear_constraints.get_senses(i) for i in s] + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.linear_constraints.add( + ... senses=["E", "G", "L", "R"], + ... names=[str(i) for i in range(4)]) + >>> op.linear_constraints.get_num() + 4 + >>> op.linear_constraints.get_senses(1) + 'G' + >>> op.linear_constraints.get_senses([2,"0",1]) + ['L', 'E', 'G'] + >>> op.linear_constraints.get_senses() + ['E', 'G', 'L', 'R'] + """ + + def _get(i): + i = convert(i, self._linconsgetindexfunc) + return self._senses[i] + + out = [] + if len(args) == 0: + return copy.deepcopy(self._senses) + elif len(args) == 1: + if isinstance(args[0], Sequence): + for i in args[0]: + out.append(_get(i)) + else: + return _get(args[0]) + else: + for i in args: + out.append(_get(i)) + return out + + def get_range_values(self, *args): + """Returns the range values of linear constraints from the problem. + + That is, this method returns the lefthand side (lhs) for each + ranged constraint of the form lhs <= lin_expr <= rhs. This method + makes sense only for ranged constraints, that is, linear constraints + of sense 'R'. + + The range values are a list of floats, specifying the difference + between lefthand side and righthand side of each linear constraint. + If range_values[i] > 0 (zero) then the constraint i is defined as + rhs[i] <= rhs[i] + range_values[i]. If range_values[i] < 0 (zero) + then constraint i is defined as + rhs[i] + range_value[i] <= a*x <= rhs[i]. + + Can be called by four forms. + + linear_constraints.get_range_values() + return the range values of all linear constraints from the + problem. + + linear_constraints.get_range_values(i) + i must be a linear constraint name or index. Returns the + range value of the linear constraint whose index or name is i. + + linear_constraints.get_range_values(s) + s must be a sequence of linear constraint names or indices. + Returns the range values of the linear constraints with + indices the members of s. Equivalent to + [linear_constraints.get_range_values(i) for i in s] + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.linear_constraints.add(\ + range_values = [1.5 * i for i in range(10)],\ + senses = ["R"] * 10,\ + names = [str(i) for i in range(10)]) + >>> op.linear_constraints.get_num() + 10 + >>> op.linear_constraints.get_range_values(8) + 12.0 + >>> op.linear_constraints.get_range_values([2,"0",5]) + [3.0, 0.0, 7.5] + >>> op.linear_constraints.get_range_values() + [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] + """ + + def _get(i): + i = convert(i, self._linconsgetindexfunc) + return self._range_values[i] + + out = [] + if len(args) == 0: + return copy.deepcopy(self._range_values) + elif len(args) == 1: + if isinstance(args[0], Sequence): + for i in args[0]: + out.append(_get(i)) + else: + return _get(args[0]) + else: + for i in args: + out.append(_get(i)) + return out + + def get_coefficients(self, *args): + """Returns coefficients by row, column coordinates. + + There are three forms by which + linear_constraints.get_coefficients may be called. + + Without arguments, it returns a dictionary indexed + first by constraints and second by variables. + + With two arguments, + linear_constraints.get_coefficients(row, col) + returns the coefficient. + + With one argument, + linear_constraints.get_coefficients(sequence_of_pairs) + returns a list of coefficients. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = ["x0", "x1"]) + >>> indices = op.linear_constraints.add(\ + names = ["c0", "c1"],\ + lin_expr = [[[1], [1.0]], [[0, 1], [2.0, -1.0]]]) + >>> op.linear_constraints.get_coefficients("c0", "x1") + 1.0 + >>> op.linear_constraints.get_coefficients([("c1", "x0"), ("c1", "x1")]) + [2.0, -1.0] + """ + + def _get(i, j): + i = convert(i, self._linconsgetindexfunc) + j = convert(j, self._varsgetindexfunc) + return self._lin_expr[i].get(j, 0) + + if len(args) == 0: + return copy.deepcopy(self._lin_expr) + elif len(args) == 1: + if isinstance(args[0], Sequence): + out = [] + for (i, j) in args[0]: + out.append(_get(i, j)) + return out + else: + raise QiskitOptimizationError( + "Wrong type of arguments. Single argument must be of list type.") + elif len(args) == 2: + return _get(args[0], args[1]) + else: + raise QiskitOptimizationError( + "Wrong number of arguments. Please use 2 or one list of pairs.") + + def get_rows(self, *args): + """Returns a set of rows of the linear constraint matrix. + + Returns a list of SparsePair instances or a single SparsePair + instance, depending on the form by which it was called. + + There are four forms by which linear_constraints.get_rows may be called. + + linear_constraints.get_rows() + return the entire linear constraint matrix. + + linear_constraints.get_rows(i) + i must be a row name or index. Returns the ith row of + the linear constraint matrix. + + linear_constraints.get_rows(s) + s must be a sequence of row names or indices. Returns the + rows of the linear constraint matrix indexed by the members + of s. Equivalent to + [linear_constraints.get_rows(i) for i in s] + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = ["x1", "x2", "x3"]) + >>> indices = op.linear_constraints.add(\ + names = ["c0", "c1", "c2", "c3"],\ + lin_expr = [SparsePair(ind = ["x1", "x3"], val = [1.0, -1.0]),\ + SparsePair(ind = ["x1", "x2"], val = [1.0, 1.0]),\ + SparsePair(ind = ["x1", "x2", "x3"], val = [-1.0] * 3),\ + SparsePair(ind = ["x2", "x3"], val = [10.0, -2.0])]) + >>> op.linear_constraints.get_rows(0) + SparsePair(ind = [0, 2], val = [1.0, -1.0]) + >>> op.linear_constraints.get_rows(["c2", 0]) + [SparsePair(ind = [0, 1, 2], val = [-1.0, -1.0, -1.0]), SparsePair(ind = [0, 2], val = [1.0, -1.0])] + >>> op.linear_constraints.get_rows() + [SparsePair(ind = [0, 2], val = [1.0, -1.0]), SparsePair(ind = [0, 1], val = [1.0, 1.0]), SparsePair(ind = [0, 1, 2], val = [-1.0, -1.0, -1.0]), SparsePair(ind = [1, 2], val = [10.0, -2.0])] + """ + + def _get(i): + i = convert(i, self._linconsgetindexfunc) + keys = self._lin_expr[i].keys() + keys = sorted(keys) + values = [self._lin_expr[i][k] for k in keys] + s = SparsePair(keys, values) + return s + + out = [] + if len(args) == 0: + for i in range(len(self._lin_expr)): + out.append(_get(i)) + return out + elif len(args) == 1: + if isinstance(args[0], str): + return _get(args[0]) + if isinstance(args[0], Sequence): + for i in args[0]: + out.append(_get(i)) + return out + else: + return _get(args[0]) + else: + raise QiskitOptimizationError( + "Wrong number of arguments. Please use 0 or 1. If there is 1, it can be iterable.") + + def get_num_nonzeros(self): + """Returns the number of nonzeros in the linear constraint matrix. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = ["x1", "x2", "x3"]) + >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"],\ + lin_expr = [SparsePair(ind = ["x1", "x3"], val = [1.0, -1.0]),\ + SparsePair(ind = ["x1", "x2"], val = [1.0, 1.0]),\ + SparsePair(ind = ["x1", "x2", "x3"], val = [-1.0] * 3),\ + SparsePair(ind = ["x2", "x3"], val = [10.0, -2.0])]) + >>> op.linear_constraints.get_num_nonzeros() + 9 + """ + nnz = 0 + for c in self._lin_expr: + for v in c.values(): + nnz += 1 * (v != 0.0) + return nnz + + def get_names(self, *args): + """Returns the names of linear constraints from the problem. + + There are four forms by which linear_constraints.get_names may be called. + + linear_constraints.get_names() + return the names of all linear constraints from the problem. + + linear_constraints.get_names(i) + i must be a linear constraint index. Returns the name of row i. + + linear_constraints.get_names(s) + s must be a sequence of row indices. Returns the names of + the linear constraints with indices the members of s. + Equivalent to [linear_constraints.get_names(i) for i in s] + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.linear_constraints.add(names = ["c" + str(i) for i in range(10)]) + >>> op.linear_constraints.get_num() + 10 + >>> op.linear_constraints.get_names(8) + 'c8' + >>> op.linear_constraints.get_names([2, 0, 5]) + ['c2', 'c0', 'c5'] + >>> op.linear_constraints.get_names() + ['c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9'] + """ + + def _get(i): + i = convert(i, self._linconsgetindexfunc) + return self._names[i] + + out = [] + if len(args) == 0: + return copy.deepcopy(self._names) + elif len(args) == 1: + if isinstance(args[0], Sequence): + for i in args[0]: + out.append(_get(i)) + else: + return _get(args[0]) + else: + for i in args: + out.append(_get(i)) + return out diff --git a/qiskit/optimization/problems/objective.py b/qiskit/optimization/problems/objective.py new file mode 100755 index 0000000000..863ea15f5d --- /dev/null +++ b/qiskit/optimization/problems/objective.py @@ -0,0 +1,552 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + +from collections.abc import Sequence +import copy +import numbers + +from qiskit.optimization.utils import BaseInterface, QiskitOptimizationError +from qiskit.optimization.utils.helpers import listify, convert +from cplex import SparsePair + +CPX_MAX = -1 +CPX_MIN = 1 + + +class ObjSense(object): + """Constants defining the sense of the objective function.""" + maximize = CPX_MAX + minimize = CPX_MIN + + def __getitem__(self, item): + """Converts a constant to a string. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> op.objective.sense.minimize + 1 + >>> op.objective.sense[1] + 'minimize' + """ + if item == CPX_MAX: + return 'maximize' + if item == CPX_MIN: + return 'minimize' + + +class ObjectiveInterface(BaseInterface): + """Contains methods for querying and modifying the objective function.""" + + sense = ObjSense() # See `ObjSense()` + + def __init__(self, varsgetindexfunc=None): + super(ObjectiveInterface, self).__init__() + self._linear = {} + self._quadratic = {} + self._name = None + self._sense = ObjSense.minimize + self._offset = 0.0 + + def defaultvarsgetindexfunc(i): + if isinstance(i, int): + return i + else: + raise ("Cannot convert variable names without appropriate vargetindexfunc!") + + if varsgetindexfunc: + self._varsgetindexfunc = varsgetindexfunc + else: + self._varsgetindexfunc = defaultvarsgetindexfunc + + def set_linear(self, *args): + """Changes the linear part of the objective function. + + Can be called by two forms: + + objective.set_linear(var, value) + var must be a variable index or name and value must be a + float. Changes the coefficient of the variable identified + by var to value. + + objective.set_linear(sequence) + sequence is a sequence of pairs (var, value) as described + above. Changes the coefficients for the specified + variables to the given values. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = [str(i) for i in range(4)]) + >>> op.objective.get_linear() + [0.0, 0.0, 0.0, 0.0] + >>> op.objective.set_linear(0, 1.0) + >>> op.objective.get_linear() + [1.0, 0.0, 0.0, 0.0] + >>> op.objective.set_linear("3", -1.0) + >>> op.objective.get_linear() + [1.0, 0.0, 0.0, -1.0] + >>> op.objective.set_linear([("2", 2.0), (1, 0.5)]) + >>> op.objective.get_linear() + [1.0, 0.5, 2.0, -1.0] + """ + + def _set(i, v): + try: + v = v + 0.0 + if v == 0.0 and i in self._linear: + self._linear.pop(i) + else: + self._linear[convert(i, self._varsgetindexfunc)] = v + except: + raise QiskitOptimizationError( + "Value of a coefficient needs to allow for addition of a float!") + + # check for all elements in args whether they are types + if len(args) == 1 and all(hasattr(el, '__len__') and len(el) == 2 for el in args[0]): + for el in args[0]: + _set(*el) + elif len(args) == 2: + _set(*args) + else: + raise QiskitOptimizationError("Invalid arguments to set set_linear!") + + def set_quadratic(self, *args): + """Sets the quadratic part of the objective function. + + Call this method with a list with length equal to the number + of variables in the problem. + + If the quadratic objective function is separable, the entries + of the list must all be of type float, int, or long. + + If the quadratic objective function is not separable, the + entries of the list must be either SparsePair instances or + lists of two lists, the first of which contains variable + indices or names, the second of which contains the values that + those variables take. + + Note + Successive calls to set_quadratic will overwrite any previous + quadratic objective function. To modify only part of the + quadratic objective function, use the method + set_quadratic_coefficients. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = [str(i) for i in range(3)]) + >>> op.objective.set_quadratic([SparsePair(ind = [0, 1, 2], val = [1.0, -2.0, 0.5]),\ + SparsePair(ind = [0, 1], val = [-2.0, -1.0]),\ + SparsePair(ind = [0, 2], val = [0.5, -3.0])]) + >>> op.objective.get_quadratic() + [SparsePair(ind = [0, 1, 2], val = [1.0, -2.0, 0.5]), + SparsePair(ind = [0, 1], val = [-2.0, -1.0]), + SparsePair(ind = [0, 2], val = [0.5, -3.0])] + >>> op.objective.set_quadratic([1.0, 2.0, 3.0]) + >>> op.objective.get_quadratic() + [SparsePair(ind = [0], val = [1.0]), SparsePair(ind = [1], val = [2.0]), + SparsePair(ind = [2], val = [3.0])] + """ + + # clear data + self._quadratic = {} + + def _set(i, j, val): + i = convert(i, self._varsgetindexfunc) + j = convert(j, self._varsgetindexfunc) + i_vals = self._quadratic.setdefault(i, {}) + j_vals = self._quadratic.setdefault(j, {}) + # NOTE: The following check is not necessary, considering we clear the _quadratic first + # if i in j_vals.keys() and j_vals[i] != val or j in i_vals.keys() and i_vals[j] != val: + # raise QiskitOptimizationError('Q is not symmetric in set_quadratic.') + i_vals[j] = val + j_vals[i] = val + + if len(args) != 1: + raise QiskitOptimizationError("set_quadratic expects one argument, which is a list") + if args[0] and isinstance(args[0][0], numbers.Number): + for i, val in enumerate(args[0]): + _set(i, i, val) + else: + for i, sp in enumerate(args[0]): + if isinstance(sp, SparsePair): + for j, val in zip(sp.ind, sp.val): + _set(i, j, val) + elif isinstance(sp, Sequence) and len(sp) == 2: + for i, (j, val) in enumerate(zip(sp[0], sp[1])): + _set(i, j, val) + else: + QiskitOptimizationError( + "set_quadratic expects a list of the length equal to the number of " + + "variables, where each entry has a pair of the indices of the other " + + "variables and values, or the corresponding SparsePair") + + def set_quadratic_coefficients(self, *args): + """Sets coefficients of the quadratic component of the objective function. + + To set a single coefficient, call this method as + + objective.set_quadratic_coefficients(v1, v2, val) + + where v1 and v2 are names or indices of variables and val is + the value for the coefficient. + + To set multiple coefficients, call this method as + + objective.set_quadratic_coefficients(sequence) + + where sequence is a list or tuple of triples (v1, v2, val) as + described above. + + Note + Since the quadratic objective function must be symmetric, each + triple in which v1 is different from v2 is used to set both + the (v1, v2) coefficient and the (v2, v1) coefficient. If + (v1, v2) and (v2, v1) are set with a single call, the second + value is stored. + + Note + Attempting to set many coefficients with set_quadratic_coefficients + can be time consuming. Instead, use the method set_quadratic to set + the quadratic part of the objective efficiently. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = [str(i) for i in range(3)]) + >>> op.objective.set_quadratic_coefficients(0, 1, 1.0) + >>> op.objective.get_quadratic() + [SparsePair(ind = [1], val = [1.0]), SparsePair(ind = [0], val = [1.0]), + SparsePair(ind = [], val = [])] + >>> op.objective.set_quadratic_coefficients([(1, 1, 2.0), (0, 2, 3.0)]) + >>> op.objective.get_quadratic() + [SparsePair(ind = [1, 2], val = [1.0, 3.0]), SparsePair(ind = [0, 1], val = [1.0, 2.0]), + SparsePair(ind = [0], val = [3.0])] + >>> op.objective.set_quadratic_coefficients([(0, 1, 4.0), (1, 0, 5.0)]) + >>> op.objective.get_quadratic() + [SparsePair(ind = [1, 2], val = [5.0, 3.0]), SparsePair(ind = [0, 1], val = [5.0, 2.0]), + SparsePair(ind = [0], val = [3.0])] + """ + + def _set(i, j, val): + i = convert(i, self._varsgetindexfunc) + j = convert(j, self._varsgetindexfunc) + i_vals = self._quadratic.setdefault(i, {}) + j_vals = self._quadratic.setdefault(j, {}) + if val == 0: + if j in i_vals: + i_vals.pop(j) + if i != j and i in j_vals: + j_vals.pop(i) + else: + i_vals[j] = val + j_vals[i] = val + + if len(args) not in (3, 1): + raise QiskitOptimizationError("Wrong number of arguments") + if isinstance(args[0], (str, int)): + arg_list = [args] + else: + arg_list = args[0] + for i, j, val in arg_list: + _set(i, j, val) + + def set_sense(self, sense): + """Sets the sense of the objective function. + + The argument to this method must be either + objective.sense.minimize or objective.sense.maximize. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> op.objective.sense[op.objective.get_sense()] + 'minimize' + >>> op.objective.set_sense(op.objective.sense.maximize) + >>> op.objective.sense[op.objective.get_sense()] + 'maximize' + >>> op.objective.set_sense(op.objective.sense.minimize) + >>> op.objective.sense[op.objective.get_sense()] + 'minimize' + """ + if sense in [CPX_MAX, CPX_MIN]: + self._sense = sense + else: + raise QiskitOptimizationError( + "sense should be one of [CPX_MAX, CPX_MIN], i.e., objective.sense.minimize or " + + "objective.sense.maximize.") + + def set_name(self, name): + """Sets the name of the objective function. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> op.objective.set_name("cost") + >>> op.objective.get_name() + 'cost' + """ + self._name = name + + def get_linear(self, *args): + """Returns the linear coefficients of a set of variables. + + Can be called by four forms. + + objective.get_linear() + return the linear objective coefficients of all variables + from the problem. + + objective.get_linear(i) + i must be a variable name or index. Returns the linear + objective coefficient of the variable whose index or name + is i. + + objective.get_linear(s) + s must be a sequence of variable names or indices. Returns + the linear objective coefficient of the variables with + indices the members of s. Equivalent to + [objective.get_linear(i) for i in s] + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(obj = [1.5 * i for i in range(10)],\ + names = [str(i) for i in range(10)]) + >>> op.variables.get_num() + 10 + >>> op.objective.get_linear(8) + 12.0 + >>> op.objective.get_linear([2,"0",5]) + [3.0, 0.0, 7.5] + >>> op.objective.get_linear() + [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] + """ + + def _get(i): + i = convert(i, self._varsgetindexfunc) + return self._linear.get(i, 0.0) + + out = [] + if len(args) == 0: + return copy.deepcopy(self._linear) + elif len(args) == 1: + if isinstance(args[0], str): + return _get(args[0]) + elif isinstance(args[0], Sequence): + for i in args[0]: + out.append(_get(i)) + else: + return _get(args[0]) + else: + for i in args: + out.append(_get(i)) + return out + + def get_quadratic(self, *args): + """Returns a set of columns of the quadratic component of the objective function. + + Returns a SparsePair instance or a list of SparsePair instances. + + Can be called by four forms. + + objective.get_quadratic() + return the entire quadratic objective function. + + objective.get_quadratic(i) + i must be a variable name or index. Returns the column of + the quadratic objective function associated with the + variable whose index or name is i. + + objective.get_quadratic(s) + s must be a sequence of variable names or indices. Returns + the columns of the quadratic objective function associated + with the variables with indices the members of s. + Equivalent to [objective.get_quadratic(i) for i in s] + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = [str(i) for i in range(10)]) + >>> op.variables.get_num() + 10 + >>> op.objective.set_quadratic([1.5 * i for i in range(10)]) + >>> op.objective.get_quadratic(8) + SparsePair(ind = [8], val = [12.0]) + >>> op.objective.get_quadratic([3,"1",5]) + [SparsePair(ind = [3], val = [4.5]), SparsePair(ind = [1], val = [1.5]), + SparsePair(ind = [5], val = [7.5])] + >>> op.objective.get_quadratic() + [SparsePair(ind = [], val = []), SparsePair(ind = [1], val = [1.5]), + SparsePair(ind = [2], val = [3.0]), SparsePair(ind = [3], val = [4.5]), + SparsePair(ind = [4], val = [6.0]), SparsePair(ind = [5], val = [7.5]), + SparsePair(ind = [6], val = [9.0]), SparsePair(ind = [7], val = [10.5]), + SparsePair(ind = [8], val = [12.0]), SparsePair(ind = [9], val = [13.5])] + """ + + def _get(i): + i = convert(i, self._varsgetindexfunc) + return SparsePair(list(self._quadratic[i].keys()), list(self._quadratic[i].values())) + + out = [] + if len(args) == 0: + return copy.deepcopy(self._quadratic) + elif len(args) == 1: + if isinstance(args[0], str): + return _get(args[0]) + elif isinstance(args[0], Sequence): + for i in args[0]: + out.append(_get(i)) + else: + return _get(args[0]) + else: + # NOTE: This is not documented, but perhaps useful. + for i in args: + out.append(_get(i)) + return out + + def get_quadratic_coefficients(self, *args): + """Returns individual coefficients from the quadratic objective function. + + To query a single coefficient, call this as + + objective.get_quadratic_coefficients(v1, v2) + + where v1 and v2 are indices or names of variables. + + To query multiple coefficients, call this method as + + objective.get_quadratic_coefficients(sequence) + + where sequence is a list or tuple of pairs (v1, v2) as + described above. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = [str(i) for i in range(3)]) + >>> op.objective.set_quadratic_coefficients(0, 1, 1.0) + >>> op.objective.get_quadratic_coefficients("1", 0) + 1.0 + >>> op.objective.set_quadratic_coefficients([(1, 1, 2.0), (0, 2, 3.0), (1, 0, 5.0)]) + >>> op.objective.get_quadratic_coefficients([(1, 0), (1, "1"), (2, "0")]) + [5.0, 2.0, 3.0] + """ + + def _get(i, j): + i = convert(i, self._varsgetindexfunc) + j = convert(j, self._varsgetindexfunc) + return self._quadratic.get(i, {}).get(j, 0) + + out = [] + if len(args) == 0: + return copy.deepcopy(self._quadratic) + elif len(args) == 2: + return _get(args[0], args[1]) + elif len(args) == 1: + if isinstance(args[0], str): + raise QiskitOptimizationError('Incompatible type: %s' % args[0]) + elif isinstance(args[0], Sequence): + for (i, j) in args[0]: + out.append(_get(i, j)) + return out + else: + raise QiskitOptimizationError( + "get_quadratic_coefficients passed a single argument that is not iterable.") + else: + raise QiskitOptimizationError( + "get_quadratic_coefficients expects either values two indices as two arguments, " + + "or a sequence of tuples.") + + def get_sense(self): + """Returns the sense of the objective function. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> op.objective.sense[op.objective.get_sense()] + 1 + >>> op.objective.set_sense(op.objective.sense.maximize) + >>> op.objective.sense[op.objective.get_sense()] + -1 + >>> op.objective.set_sense(op.objective.sense.minimize) + >>> op.objective.sense[op.objective.get_sense()] + 1 + """ + return self._sense + + def get_name(self): + """Returns the name of the objective function. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> op.objective.set_name("cost") + >>> op.objective.get_name() + 'cost' + """ + return self._name + + def get_num_quadratic_variables(self): + """Returns the number of variables with quadratic coefficients. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = [str(i) for i in range(3)]) + >>> op.objective.set_quadratic_coefficients(0, 1, 1.0) + >>> op.objective.get_num_quadratic_variables() + 2 + >>> op.objective.set_quadratic([1.0, 0.0, 0.0]) + >>> op.objective.get_num_quadratic_variables() + 1 + >>> op.objective.set_quadratic_coefficients([(1, 1, 2.0), (0, 2, 3.0)]) + >>> op.objective.get_num_quadratic_variables() + 3 + """ + return len(self._quadratic.items()) + + def get_num_quadratic_nonzeros(self): + """Returns the number of nonzeros in the quadratic objective function. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = [str(i) for i in range(3)]) + >>> op.objective.set_quadratic_coefficients(0, 1, 1.0) + >>> op.objective.get_num_quadratic_nonzeros() + 2 + >>> op.objective.set_quadratic_coefficients([(1, 1, 2.0), (0, 2, 3.0)]) + >>> op.objective.get_num_quadratic_nonzeros() + 5 + >>> op.objective.set_quadratic_coefficients([(0, 1, 4.0), (1, 0, 0.0)]) + >>> op.objective.get_num_quadratic_nonzeros() + 3 + """ + nnz = 0 + for (i, v) in self._quadratic.items(): + nnz += len(v) + return nnz + + def get_offset(self): + """Returns the constant offset of the objective function for a problem. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> offset = op.objective.get_offset() + >>> abs(offset - 0.0) < 1e-6 + True + """ + return self._offset + + def set_offset(self, offset): + """Sets the constant offset of the objective function for a problem. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> op.objective.set_offset(3.14) + >>> offset = op.objective.get_offset() + >>> abs(offset - 3.14) < 1e-6 + True + """ + self._offset = offset diff --git a/qiskit/optimization/problems/optimization_problem.py b/qiskit/optimization/problems/optimization_problem.py new file mode 100755 index 0000000000..5460371aed --- /dev/null +++ b/qiskit/optimization/problems/optimization_problem.py @@ -0,0 +1,554 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + + +from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError +from qiskit.optimization.utils.helpers import convert +from qiskit.optimization.problems.variables import VariablesInterface +from qiskit.optimization.problems.linear_constraint import LinearConstraintInterface +from qiskit.optimization.problems.quadratic_constraint import QuadraticConstraintInterface +from qiskit.optimization.problems.objective import ObjectiveInterface +from qiskit.optimization.problems.problem_type import ProblemType +from qiskit.optimization.results.solution import SolutionInterface +from cplex import Cplex, SparsePair +from cplex.exceptions import CplexSolverError + + +class OptimizationProblem(object): + """A class encapsulating an optimization problem, modelled after + Python CPLEX API. + + An instance of the OptimizationProblem class provides methods for creating, + modifying, and querying an optimization problem, solving it, and + querying aspects of the solution. + """ + + def __init__(self, *args): + """Constructor of the OptimizationProblem class. + + The OptimizationProblem constructor accepts four types of argument lists. + + op = qiskit.optimization.OptimizationProblem() + op is a new problem with no data + + op = qiskit.optimization.OptimizationProblem("filename") + op is a new problem containing the data in filename. If + filename does not exist, an exception is raised. + + The OptimizationProblem object is a context manager and can be used, like so: + + with qiskit.optimization.OptimizationProblem() as op: + # do stuff + op.solve() + + When the with block is finished, the end() method will be called + automatically. + """ + + if len(args) > 1: + raise QiskitOptimizationError("Too many arguments to OptimizationProblem()") + self._disposed = False + self._name = None + + self.variables = VariablesInterface() + """See `qiskit.optimization.VariablesInterface()` """ + + self.linear_constraints = LinearConstraintInterface( + varsgetindexfunc=self.variables._varsgetindexfunc) + """See `qiskit.optimization.LinearConstraintInterface()` """ + + self.quadratic_constraints = QuadraticConstraintInterface() + """See `qiskit.optimization.QuadraticConstraintInterface()` """ + + # pylint: disable=unexpected-keyword-arg + self.objective = ObjectiveInterface(varsgetindexfunc=self.variables._varsgetindexfunc) + """See `qiskit.optimization.ObjectiveInterface()` """ + + self.solution = SolutionInterface() + """See `qiskit.optimization.SolutionInterface()` """ + + self.problem_type = ProblemType() + """See `qiskit.optimization.ProblemType()` -- + essentially conversions from integers to strings and back""" + self.my_problem_type = 0 + + # read from file in case filename is given + if len(args) == 1: + try: + self.read(args[0]) + except CplexSolverError: + raise QiskitOptimizationError('Could not load file: %s' % args[0]) + + def from_cplex(self, op): + + # make sure current problem is clean + self._disposed = False + self._name = None + self.variables = VariablesInterface() + self.linear_constraints = LinearConstraintInterface( + varsgetindexfunc=self.variables._varsgetindexfunc) + self.quadratic_constraints = QuadraticConstraintInterface() + self.objective = ObjectiveInterface(varsgetindexfunc=self.variables._varsgetindexfunc) + self.solution = SolutionInterface() + + # set problem name + if op.get_problem_name(): + self.set_problem_name(op.get_problem_name()) + + # TODO: how to choose problem type? + # set problem type + if op.get_problem_type(): + self.set_problem_type(op.get_problem_type()) + + # TODO: There seems to be a bug in CPLEX, it raises a "Not a MIP (3003)"-error + # if the problem never had a non-cts. variable + idx = op.variables.add(types='B') + op.variables.delete(idx[0]) + + # set variables (obj is set via objective interface) + var_names = op.variables.get_names() + var_lbs = op.variables.get_lower_bounds() + var_ubs = op.variables.get_upper_bounds() + var_types = op.variables.get_types() + self.variables.add(lb=var_lbs, ub=var_ubs, types=var_types, names=var_names) + + # set objective sense + self.objective.set_sense(op.objective.get_sense()) + + # set objective name + try: + self.objective.set_name(op.objective.get_name()) + except: + pass + + # set linear objective terms + for i, v in enumerate(op.objective.get_linear()): + self.objective.set_linear(i, v) + + # set quadratic objective terms + for i, sp in enumerate(op.objective.get_quadratic()): + for j, v in zip(sp.ind, sp.val): + self.objective.set_quadratic_coefficients(i, j, v) + + # set objective offset + self.objective.set_offset(op.objective.get_offset()) + + # set linear constraints + linear_rows = op.linear_constraints.get_rows() + linear_sense = op.linear_constraints.get_senses() + linear_rhs = op.linear_constraints.get_rhs() + linear_ranges = op.linear_constraints.get_range_values() + linear_names = op.linear_constraints.get_names() + self.linear_constraints.add(linear_rows, linear_sense, + linear_rhs, linear_ranges, linear_names) + + # TODO: add quadratic constraints + + def from_docplex(self, op): + self.from_cplex(op.get_cplex()) + + def to_cplex(self): + + # create empty CPLEX model + op = Cplex() + if self.get_problem_name() is not None: + op.set_problem_name(self.get_problem_name()) + # TODO: what about problem type? + + # set variables (obj is set via objective interface) + var_names = self.variables.get_names() + var_lbs = self.variables.get_lower_bounds() + var_ubs = self.variables.get_upper_bounds() + var_types = self.variables.get_types() + # TODO: what about columns? + op.variables.add(lb=var_lbs, ub=var_ubs, types=var_types, names=var_names) + + # set objective sense + op.objective.set_sense(self.objective.get_sense()) + + # set objective name + op.objective.set_name(self.objective.get_name()) + + # set linear objective terms + for i, v in self.objective.get_linear().items(): + op.objective.set_linear(i, v) + + # set quadratic objective terms + for i, vi in self.objective.get_quadratic().items(): + for j, v in vi.items(): + op.objective.set_quadratic_coefficients(i, j, v) + + # set objective offset + op.objective.set_offset(self.objective.get_offset()) + + # set linear constraints + linear_rows = self.linear_constraints.get_rows() + linear_sense = self.linear_constraints.get_senses() + linear_rhs = self.linear_constraints.get_rhs() + linear_ranges = self.linear_constraints.get_range_values() + linear_names = self.linear_constraints.get_names() + op.linear_constraints.add(linear_rows, linear_sense, linear_rhs, + linear_ranges, linear_names) + + # TODO: add quadratic constraints + + return op + + def end(self): + """Releases the OptimizationProblem object. + """ + if self._disposed: + return + self._disposed = True + + def __del__(self): + """non-public""" + self.end() + + def __enter__(self): + """ To implement a ContextManager, as in Cplex. """ + return self + + def __exit__(self, *exc): + """ To implement a ContextManager, as in Cplex. """ + return False + + def read(self, filename, filetype=""): + """Reads a problem from file. + + The first argument is a string specifying the filename from + which the problem will be read. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> op.read("lpex.mps") + """ + cplex = Cplex() + cplex.read(filename, filetype) + self.from_cplex(cplex) + + def write(self, filename, filetype=""): + """Writes a problem to file. + + The first argument is a string specifying the filename to + which the problem will be written. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names=['x1', 'x2', 'x3']) + >>> op.write("example.lp") + """ + cplex = self.to_cplex() + cplex.write(filename, filetype) + + def write_to_stream(self, stream, filetype='LP', comptype=''): + """Writes a problem to a file-like object in the given file format. + + The filetype argument can be any of "sav" (a binary format), "lp" + (the default), "mps", "rew", "rlp", or "alp" (see `OptimizationProblem.write` + for an explanation of these). + + If comptype is "bz2" (for BZip2) or "gz" (for GNU Zip), a + compressed file is written. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names=['x1', 'x2', 'x3']) + >>> class NoOpStream(object): + ... def __init__(self): + ... self.was_called = False + ... def write(self, bytes): + ... self.was_called = True + ... pass + ... def flush(self): + ... pass + >>> stream = NoOpStream() + >>> op.write_to_stream(stream) + >>> stream.was_called + True + """ + try: + callable(stream.write) + except AttributeError: + raise QiskitOptimizationError("stream must have a write method") + try: + callable(stream.flush) + except AttributeError: + raise QiskitOptimizationError("stream must have a flush method") + op = self.to_cplex() + return op.write_to_stream(stream, filetype, comptype) + + def write_as_string(self, filetype='LP', comptype=''): + """Writes a problem as a string in the given file format. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names=['x1', 'x2', 'x3']) + >>> lp_str = op.write_as_string("lp") + >>> len(lp_str) > 0 + True + """ + op = self.to_cplex() + return op.write_as_string(filetype, comptype) + + def get_problem_type(self): + """Returns the problem type. + + The return value is an attribute of self.problem_type. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> op.read("lpex.mps") + >>> op.get_problem_type() + 0 + >>> op.problem_type[op.get_problem_type()] + 'LP' + """ + # TODO: A better option would be to scan the variables to check their types, etc. + return self.my_problem_type + + def set_problem_type(self, _type): + """Changes the problem type. + + If only one argument is given, that argument specifies the new + problem type. It must be one of the following: + + qiskit.optimization.problem_type.LP + qiskit.optimization.problem_type.MILP + qiskit.optimization.problem_type.fixed_MILP + qiskit.optimization.problem_type.QP + qiskit.optimization.problem_type.MIQP + qiskit.optimization.problem_type.fixed_MIQP + qiskit.optimization.problem_type.QCP + qiskit.optimization.problem_type.MIQCP + """ + self.my_problem_type = _type + + def solve(self): + """Solves the problem. + + Note + The solve method returning normally does not necessarily mean + that an optimal or feasible solution has been found. Use + OptimizationProblem.solution.get_status() to query the status of the current + solution. + """ + None + # TODO: Implement me + + def set_problem_name(self, name): + self._name = name + + def get_problem_name(self): + return self._name + + def substitute_variables(self, constants=None, variables=None): + """ + constants: SparsePair (replace variable by constant) + variables: SparseTriple (replace variables by weighted other variable + need to copy everything using name reference to make sure that indices are matched correctly + """ + + # guarantee that there is no overlap between variables to be replaced and combine input + vars_to_be_replaced = {} + if constants is not None: + for i, v in zip(constants.ind, constants.val): + i = convert(i, self.variables._varsgetindexfunc) + name = self.variables.get_names(i) + if i in vars_to_be_replaced: + raise QiskitOptimizationError('cannot substitute the same variable twice') + vars_to_be_replaced[name] = [v] + if variables is not None: + for i, j, v in zip(variables.ind1, variables.ind2, variables.val): + i = convert(i, self.variables._varsgetindexfunc) + j = convert(j, self.variables._varsgetindexfunc) + name1 = self.variables.get_names(i) + name2 = self.variables.get_names(j) + if name1 in vars_to_be_replaced: + raise QiskitOptimizationError('Cannot substitute the same variable twice') + if name2 in vars_to_be_replaced.keys(): + raise QiskitOptimizationError( + 'Cannot substitute by variable that gets substituted it self.') + vars_to_be_replaced[name1] = [name2, v] + + # get variables to be kept + vars_to_be_kept = set() + for name in self.variables.get_names(): + if name not in vars_to_be_replaced: + vars_to_be_kept.add(name) + + # construct new problem + op = OptimizationProblem() + + # set problem name + op.set_problem_name(self.get_problem_name()) + + # copy variables that are not replaced + # TODO: what about columns? + for name, var_type, lb, ub in zip( + self.variables.get_names(), + self.variables.get_types(), + self.variables.get_lower_bounds(), + self.variables.get_upper_bounds(), + ): + if name not in vars_to_be_replaced: + op.variables.add(lb=[lb], ub=[ub], types=[var_type], names=[name]) + else: + # check that replacement satisfies bounds + repl = vars_to_be_replaced[name] + if len(repl) == 1: + if not (lb <= repl[0] <= ub): + raise QiskitOptimizationError('Infeasible substitution for variable') + + # initialize offset + offset = self.objective.get_offset() + + # construct linear part of objective + for i, v in self.objective.get_linear().items(): + i = convert(i, self.variables._varsgetindexfunc) + i_name = self.variables.get_names(i) + i_repl = vars_to_be_replaced.get(i_name, None) + if i_repl is not None: + w_i = self.objective.get_linear(i_name) + if len(i_repl) == 1: + offset += i_repl[0] * w_i + else: # len == 2 + w_i = i_repl[1] * w_i + op.objective.get_linear(i_repl[0]) + op.objective.set_linear(i_repl[0], w_i) + else: + w_i = self.objective.get_linear(i_name) + op.objective.get_linear(i_name) + op.objective.set_linear(i_name, w_i) + + # construct quadratic part of objective + for i, vi in self.objective.get_quadratic().items(): + for j, v in vi.items(): + i = convert(i, self.variables._varsgetindexfunc) + j = convert(j, self.variables._varsgetindexfunc) + i_name = self.variables.get_names(i) + j_name = self.variables.get_names(j) + i_repl = vars_to_be_replaced.get(i_name, None) + j_repl = vars_to_be_replaced.get(j_name, None) + w_ij = self.objective.get_quadratic_coefficients(i_name, j_name) + if i_repl is not None and j_repl is None: + if len(i_repl) == 1: + # if x_i is replaced, the term needs to be added to the linear part of x_j + w_j = op.objective.get_linear(j_name) + w_j += i_repl[0] * w_ij / 2 + op.objective.set_linear(j_name, w_j) + else: # len == 2 + k = convert(i_repl[0], self.variables._varsgetindexfunc) + k_name = self.variables.get_names(k) + if k_name in vars_to_be_replaced.keys(): + raise QiskitOptimizationError( + 'Cannot substitute by variable that gets substituted itself.') + w_jk = op.objective.get_quadratic_coefficients(j_name, k_name) + w_jk += i_repl[1] * w_ij + op.objective.set_quadratic_coefficients(j_name, k_name, w_jk) + elif i_repl is None and j_repl is not None: + if len(j_repl) == 1: + # if x_j is replaced, the term needs to be added to the linear part of x_i + w_i = op.objective.get_linear(i_name) + w_i += j_repl[0] * w_ij / 2 + op.objective.set_linear(i_name, w_i) + else: # len == 2 + k = convert(j_repl[0], self.variables._varsgetindexfunc) + k_name = self.variables.get_names(k) + if k_name in vars_to_be_replaced.keys(): + raise QiskitOptimizationError( + 'Cannot substitute by variable that gets substituted itself.') + w_ik = op.objective.get_quadratic_coefficients(i_name, k_name) + w_ik += j_repl[1] * w_ij + op.objective.set_quadratic_coefficients(i_name, k_name, w_ik) + elif i_repl is not None and j_repl is not None: + if len(i_repl) == 1 and len(j_repl) == 1: + offset += w_ij * i_repl[0] * j_repl[0] / 2 + elif len(i_repl) == 1 and len(j_repl) == 2: + k = convert(j_repl[0], self.variables._varsgetindexfunc) + k_name = self.variables.get_names(k) + if k_name in vars_to_be_replaced.keys(): + raise QiskitOptimizationError( + 'Cannot substitute by variable that gets substituted itself.') + w_k = op.objective.get_linear(k_name) + w_k += w_ij * i_repl[0] * j_repl[1] / 2 + op.objective.set_linear(k_name, w_k) + elif len(i_repl) == 2 and len(j_repl) == 1: + k = convert(i_repl[0], self.variables._varsgetindexfunc) + k_name = self.variables.get_names(k) + if k_name in vars_to_be_replaced.keys(): + raise QiskitOptimizationError( + 'Cannot substitute by variable that gets substituted itself.') + w_k = op.objective.get_linear(k_name) + w_k += w_ij * j_repl[0] * i_repl[1] / 2 + op.objective.set_linear(k_name, w_k) + else: # both len(repl) == 2 + k = convert(i_repl[0], self.variables._varsgetindexfunc) + k_name = self.variables.get_names(k) + if k_name in vars_to_be_replaced.keys(): + raise QiskitOptimizationError( + 'Cannot substitute by variable that gets substituted itself.') + l = convert(j_repl[0], self.variables._varsgetindexfunc) + l_name = self.variables.get_names(l) + if l_name in vars_to_be_replaced.keys(): + raise QiskitOptimizationError( + 'Cannot substitute by variable that gets substituted itself.') + w_kl = op.objective.get_quadratic_coefficients(k_name, l_name) + w_kl += w_ij * i_repl[1] * j_repl[1] + op.objective.set_quadratic_coefficients(k_name, l_name, w_kl) + else: + # nothing to be replaced, just copy coefficients + if i == j: + w_ij = self.objective.get_quadratic_coefficients( + i_name, j_name) + op.objective.get_quadratic_coefficients(i_name, j_name) + else: + w_ij = self.objective.get_quadratic_coefficients( + i_name, j_name)/2 + op.objective.get_quadratic_coefficients(i_name, j_name) + op.objective.set_quadratic_coefficients(i_name, j_name, w_ij) + + # set offset + op.objective.set_offset(offset) + + # construct linear constraints + for name, row, rhs, sense, range_value in zip( + self.linear_constraints.get_names(), + self.linear_constraints.get_rows(), + self.linear_constraints.get_rhs(), + self.linear_constraints.get_senses(), + self.linear_constraints.get_range_values() + ): + # print(name, row, rhs, sense, range_value) + new_vals = {} + for i, v in zip(row.ind, row.val): + i = convert(i, self.variables._varsgetindexfunc) + i_name = self.variables.get_names(i) + i_repl = vars_to_be_replaced.get(i_name, None) + if i_repl is not None: + if len(i_repl) == 1: + rhs -= v*i_repl[0] + else: + j = convert(i_repl[0], self.variables._varsgetindexfunc) + j_name = self.variables.get_names(j) + new_vals[j_name] = v*i_repl[1] + new_vals.get(i_name, 0) + else: + # nothing to replace, just add value + new_vals[i_name] = v + new_vals.get(i_name, 0) + new_ind = list(new_vals.keys()) + new_val = [new_vals[i] for i in new_ind] + new_row = SparsePair(new_ind, new_val) + op.linear_constraints.add(lin_expr=[new_row], senses=[sense], rhs=[ + rhs], range_values=[range_value], names=[name]) + + # TODO: quadratic constraints + + # TODO: amend self.my_problem_type + + return op diff --git a/qiskit/optimization/problems/problem_type.py b/qiskit/optimization/problems/problem_type.py new file mode 100755 index 0000000000..25c14d8136 --- /dev/null +++ b/qiskit/optimization/problems/problem_type.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + +CPXPROB_LP = 0 +CPXPROB_MILP = 1 +CPXPROB_FIXEDMILP = 3 +CPXPROB_NODELP = 4 +CPXPROB_QP = 5 +CPXPROB_MIQP = 7 +CPXPROB_FIXEDMIQP = 8 +CPXPROB_NODEQP = 9 +CPXPROB_QCP = 10 +CPXPROB_MIQCP = 11 +CPXPROB_NODEQCP = 12 + + +class ProblemType(object): + """ + Types of problems the OptimizationProblem class can encapsulate. + These types are compatible with those of IBM ILOG CPLEX. + For explanations of the problem types, see those topics in the + CPLEX User's Manual in the topic titled Continuous Optimization + for LP, QP, and QCP or the topic titled Discrete Optimization + for MILP, FIXEDMILP, NODELP, NODEQP, MIQCP, NODEQCP. + """ + LP = CPXPROB_LP + MILP = CPXPROB_MILP + fixed_MILP = CPXPROB_FIXEDMILP + node_LP = CPXPROB_NODELP + QP = CPXPROB_QP + MIQP = CPXPROB_MIQP + fixed_MIQP = CPXPROB_FIXEDMIQP + node_QP = CPXPROB_NODEQP + QCP = CPXPROB_QCP + MIQCP = CPXPROB_MIQCP + node_QCP = CPXPROB_NODEQCP + + def __getitem__(self, item): + """Converts a constant to a string. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> op.problem_type.LP + 0 + >>> op.problem_type[0] + 'LP' + """ + if item == CPXPROB_LP: + return 'LP' + if item == CPXPROB_MILP: + return 'MILP' + if item == CPXPROB_FIXEDMILP: + return 'fixed_MILP' + if item == CPXPROB_NODELP: + return 'node_LP' + if item == CPXPROB_QP: + return 'QP' + if item == CPXPROB_MIQP: + return 'MIQP' + if item == CPXPROB_FIXEDMIQP: + return 'fixed_MIQP' + if item == CPXPROB_NODEQP: + return 'node_QP' + if item == CPXPROB_QCP: + return 'QCP' + if item == CPXPROB_MIQCP: + return 'MIQCP' + if item == CPXPROB_NODEQCP: + return 'node_QCP' diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py new file mode 100755 index 0000000000..a327ec7c11 --- /dev/null +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -0,0 +1,491 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + +from qiskit.optimization.utils.base import BaseInterface +from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError +from qiskit.optimization.utils.helpers import unpack_pair, unpack_triple +from cplex import SparsePair, SparseTriple + + +class QuadraticConstraintInterface(BaseInterface): + """Methods for adding, modifying, and querying quadratic constraints.""" + + def __init__(self): + """Creates a new QuadraticConstraintInterface. + + The quadratic constraints interface is exposed by the top-level + `OptimizationProblem` class as `OptimizationProblem.quadratic_constraints`. This constructor + is not meant to be used externally. + """ + super(QuadraticConstraintInterface, self).__init__() + + def get_num(self): + """Returns the number of quadratic constraints. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = ['x','y']) + >>> l = SparsePair(ind = ['x'], val = [1.0]) + >>> q = SparseTriple(ind1 = ['x'], ind2 = ['y'], val = [1.0]) + >>> [op.quadratic_constraints.add(name=str(i), lin_expr=l, quad_expr=q) + ... for i in range(10)] + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> op.quadratic_constraints.get_num() + 10 + """ + None + + def _add(self, lin_expr, quad_expr, sense, rhs, name): + """non-public""" + ind, val = unpack_pair(lin_expr) + if len(val) == 1 and val[0] == 0.0: + ind = [] + val = [] + ind1, ind2, qval = unpack_triple(quad_expr) + varcache = {} + None + + def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): + """Adds a quadratic constraint to the problem. + + Takes up to five keyword arguments: + + lin_expr : either a SparsePair or a list of two lists specifying + the linear component of the constraint. + + Note + lin_expr must not contain duplicate indices. If lin_expr + references a variable more than once, either by index, name, + or a combination of index and name, an exception will be + raised. + + quad_expr : either a SparseTriple or a list of three lists + specifying the quadratic component of the constraint. + + Note + quad_expr must not contain duplicate indices. If quad_expr + references a matrix entry more than once, either by indices, + names, or a combination of indices and names, an exception + will be raised. + + sense : either "L", "G", or "E" + + rhs : a float specifying the righthand side of the constraint. + + name : the name of the constraint. + + Returns the index of the added quadratic constraint. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = ['x','y']) + >>> l = SparsePair(ind = ['x'], val = [1.0]) + >>> q = SparseTriple(ind1 = ['x'], ind2 = ['y'], val = [1.0]) + >>> op.quadratic_constraints.add(name = "my_quad", + ... lin_expr = l, + ... quad_expr = q, + ... rhs = 1.0, + ... sense = "G") + 0 + """ + if lin_expr is None: + lin_expr = SparsePair([0], [0.0]) + if quad_expr is None: + quad_expr = SparseTriple([0], [0], [0.0]) + # We only ever create one quadratic constraint at a time. + return self._add_single(self.get_num, self._add, + lin_expr, quad_expr, sense, rhs, name) + + def delete(self, *args): + """Deletes quadratic constraints from the problem. + + There are four forms by which quadratic_constraints.delete may be + called. + + quadratic_constraints.delete() + deletes all quadratic constraints from the problem. + + quadratic_constraints.delete(i) + i must be a quadratic constraint name or index. Deletes + the quadratic constraint whose index or name is i. + + quadratic_constraints.delete(s) + s must be a sequence of quadratic constraint names or + indices. Deletes the quadratic constraints with names or + indices contained within s. Equivalent to + [quadratic_constraints.delete(i) for i in s]. + + quadratic_constraints.delete(begin, end) + begin and end must be quadratic constraint indices or quadratic + constraint names. Deletes the quadratic constraints with + indices between begin and end, inclusive of end. Equivalent to + quadratic_constraints.delete(range(begin, end + 1)). This will + give the best performance when deleting batches of quadratic + constraints. + + See CPXdelqconstrs in the Callable Library Reference Manual for + more detail. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names=['x', 'y']) + >>> l = SparsePair(ind=['x'], val=[1.0]) + >>> q = SparseTriple(ind1=['x'], ind2=['y'], val=[1.0]) + >>> [op.quadratic_constraints.add( + ... name=str(i), lin_expr=l, quad_expr=q) + ... for i in range(10)] + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> op.quadratic_constraints.get_num() + 10 + >>> op.quadratic_constraints.delete(8) + >>> op.quadratic_constraints.get_names() + ['0', '1', '2', '3', '4', '5', '6', '7', '9'] + >>> op.quadratic_constraints.delete("1", 3) + >>> op.quadratic_constraints.get_names() + ['0', '4', '5', '6', '7', '9'] + >>> op.quadratic_constraints.delete([2, "0", 5]) + >>> op.quadratic_constraints.get_names() + ['4', '6', '7'] + >>> op.quadratic_constraints.delete() + >>> op.quadratic_constraints.get_names() + [] + """ + None + + def get_rhs(self, *args): + """Returns the righthand side of a set of quadratic constraints. + + Can be called by four forms. + + quadratic_constraints.get_rhs() + return the righthand side of all quadratic constraints + from the problem. + + quadratic_constraints.get_rhs(i) + i must be a quadratic constraint name or index. Returns the + righthand side of the quadratic constraint whose index or + name is i. + + quadratic_constraints.get_rhs(s) + s must be a sequence of quadratic constraint names or + indices. Returns the righthand side of the quadratic + constraints with indices the members of s. Equivalent to + [quadratic_constraints.get_rhs(i) for i in s] + + quadratic_constraints.get_rhs(begin, end) + begin and end must be quadratic constraint indices or quadratic + constraint names. Returns the righthand side of the quadratic + constraints with indices between begin and end, inclusive of + end. Equivalent to + quadratic_constraints.get_rhs(range(begin, end + 1)). + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = [str(i) for i in range(10)]) + >>> [op.quadratic_constraints.add(rhs=1.5 * i, name=str(i)) + ... for i in range(10)] + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> op.quadratic_constraints.get_num() + 10 + >>> op.quadratic_constraints.get_rhs(8) + 12.0 + >>> op.quadratic_constraints.get_rhs("1",3) + [1.5, 3.0, 4.5] + >>> op.quadratic_constraints.get_rhs([2,"0",5]) + [3.0, 0.0, 7.5] + >>> op.quadratic_constraints.get_rhs() + [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] + """ + return [] + + def get_senses(self, *args): + """Returns the senses of a set of quadratic constraints. + + Can be called by four forms. + + quadratic_constraints.get_senses() + return the senses of all quadratic constraints from the + problem. + + quadratic_constraints.get_senses(i) + i must be a quadratic constraint name or index. Returns the + sense of the quadratic constraint whose index or name is i. + + quadratic_constraints.get_senses(s) + s must be a sequence of quadratic constraint names or + indices. Returns the senses of the quadratic constraints + with indices the members of s. Equivalent to + [quadratic_constraints.get_senses(i) for i in s] + + quadratic_constraints.get_senses(begin, end) + begin and end must be quadratic constraint indices or quadratic + constraint names. Returns the senses of the quadratic + constraints with indices between begin and end, inclusive of + end. Equivalent to + quadratic_constraints.get_senses(range(begin, end + 1)). + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = ["x0"]) + >>> [op.quadratic_constraints.add(name=str(i), sense=j) + ... for i, j in enumerate("GGLL")] + [0, 1, 2, 3] + >>> op.quadratic_constraints.get_num() + 4 + >>> op.quadratic_constraints.get_senses(1) + 'G' + >>> op.quadratic_constraints.get_senses("1",3) + ['G', 'L', 'L'] + >>> op.quadratic_constraints.get_senses([2,"0",1]) + ['L', 'G', 'G'] + >>> op.quadratic_constraints.get_senses() + ['G', 'G', 'L', 'L'] + """ + return [] + + def get_linear_num_nonzeros(self, *args): + """Returns the number of nonzeros in the linear part of a set of quadratic constraints. + + Can be called by four forms. + + quadratic_constraints.get_linear_num_nonzeros() + return the number of nonzeros in all quadratic constraints + from the problem. + + quadratic_constraints.get_linear_num_nonzeros(i) + i must be a quadratic constraint name or index. Returns the + number of nonzeros in the quadratic constraint whose index + or name is i. + + quadratic_constraints.get_linear_num_nonzeros(s) + s must be a sequence of quadratic constraint names or + indices. Returns the number of nonzeros in the quadratic + constraints with indices the members of s. Equivalent to + [quadratic_constraints.get_linear_num_nonzeros(i) for i in s] + + quadratic_constraints.get_linear_num_nonzeros(begin, end) + begin and end must be quadratic constraint indices or quadratic + constraint names. Returns the number of nonzeros in the + quadratic constraints with indices between begin and end, + inclusive of end. Equivalent to + quadratic_constraints.get_linear_num_nonzeros(range(begin, end + 1)). + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = [str(i) for i in range(11)], types = "B" * 11) + >>> [op.quadratic_constraints.add( + ... name = str(i), + ... lin_expr = [range(i), [1.0 * (j+1.0) for j in range(i)]]) + ... for i in range(10)] + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> op.quadratic_constraints.get_num() + 10 + >>> op.quadratic_constraints.get_linear_num_nonzeros(8) + 8 + >>> op.quadratic_constraints.get_linear_num_nonzeros("1",3) + [1, 2, 3] + >>> op.quadratic_constraints.get_linear_num_nonzeros([2,"0",5]) + [2, 0, 5] + >>> op.quadratic_constraints.get_linear_num_nonzeros() + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + """ + [] + + def get_linear_components(self, *args): + """Returns the linear part of a set of quadratic constraints. + + Returns a list of SparsePair instances or one SparsePair instance. + + Can be called by four forms. + + quadratic_constraints.get_linear_components() + return the linear components of all quadratic constraints + from the problem. + + quadratic_constraints.get_linear_components(i) + i must be a quadratic constraint name or index. Returns the + linear component of the quadratic constraint whose index or + name is i. + + quadratic_constraints.get_linear_components(s) + s must be a sequence of quadratic constraint names or + indices. Returns the linear components of the quadratic + constraints with indices the members of s. Equivalent to + [quadratic_constraints.get_linear_components(i) for i in s] + + quadratic_constraints.get_linear_components(begin, end) + begin and end must be quadratic constraint indices or quadratic + constraint names. Returns the linear components of the + quadratic constraints with indices between begin and end, + inclusive of end. Equivalent to + quadratic_constraints.get_linear_components(range(begin, end + 1)). + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = [str(i) for i in range(11)], types = "B" * 11) + >>> [op.quadratic_constraints.add( + ... name = str(i), + ... lin_expr = [range(i), [1.0 * (j+1.0) for j in range(i)]]) + ... for i in range(10)] + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> op.quadratic_constraints.get_num() + 10 + >>> op.quadratic_constraints.get_linear_components(8) + SparsePair(ind = [0, 1, 2, 3, 4, 5, 6, 7], val = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]) + >>> op.quadratic_constraints.get_linear_components("1",3) + [SparsePair(ind = [0], val = [1.0]), SparsePair(ind = [0, 1], val = [1.0, 2.0]), SparsePair(ind = [0, 1, 2], val = [1.0, 2.0, 3.0])] + >>> op.quadratic_constraints.get_linear_components([2,"0",5]) + [SparsePair(ind = [0, 1], val = [1.0, 2.0]), SparsePair(ind = [], val = []), SparsePair(ind = [0, 1, 2, 3, 4], val = [1.0, 2.0, 3.0, 4.0, 5.0])] + >>> op.quadratic_constraints.delete(4,9) + >>> op.quadratic_constraints.get_linear_components() + [SparsePair(ind = [], val = []), SparsePair(ind = [0], val = [1.0]), SparsePair(ind = [0, 1], val = [1.0, 2.0]), SparsePair(ind = [0, 1, 2], val = [1.0, 2.0, 3.0])] + """ + return [] + + def get_quad_num_nonzeros(self, *args): + """Returns the number of nonzeros in the quadratic part of a set of quadratic constraints. + + Can be called by four forms. + + quadratic_constraints.get_quad_num_nonzeros() + Returns the number of nonzeros in all quadratic constraints + from the problem. + + quadratic_constraints.get_quad_num_nonzeros(i) + i must be a quadratic constraint name or index. Returns the + number of nonzeros in the quadratic constraint whose index + or name is i. + + quadratic_constraints.get_quad_num_nonzeros(s) + s must be a sequence of quadratic constraint names or + indices. Returns the number of nonzeros in the quadratic + constraints with indices the members of s. Equivalent to + [quadratic_constraints.get_quad_num_nonzeros(i) for i in s] + + quadratic_constraints.get_quad_num_nonzeros(begin, end) + begin and end must be quadratic constraint indices or quadratic + constraint names. Returns the number of nonzeros in the + quadratic constraints with indices between begin and end, + inclusive of end. Equivalent to + quadratic_constraints.get_quad_num_nonzeros(range(begin, end + 1)). + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = [str(i) for i in range(11)]) + >>> [op.quadratic_constraints.add( + ... name = str(i), + ... quad_expr = [range(i), range(i), [1.0 * (j+1.0) for j in range(i)]]) + ... for i in range(1, 11)] + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> op.quadratic_constraints.get_num() + 10 + >>> op.quadratic_constraints.get_quad_num_nonzeros(8) + 9 + >>> op.quadratic_constraints.get_quad_num_nonzeros("1",2) + [1, 2, 3] + >>> op.quadratic_constraints.get_quad_num_nonzeros([2,"1",5]) + [3, 1, 6] + >>> op.quadratic_constraints.get_quad_num_nonzeros() + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + """ + return [] + + def get_quadratic_components(self, *args): + """Returns the quadratic part of a set of quadratic constraints. + + Can be called by four forms. + + quadratic_constraints.get_quadratic_components() + return the quadratic components of all quadratic constraints + from the problem. + + quadratic_constraints.get_quadratic_components(i) + i must be a quadratic constraint name or index. Returns the + quadratic component of the quadratic constraint whose index or + name is i. + + quadratic_constraints.get_quadratic_components(s) + s must be a sequence of quadratic constraint names or + indices. Returns the quadratic components of the quadratic + constraints with indices the members of s. Equivalent to + [quadratic_constraints.get_quadratic_components(i) for i in s] + + quadratic_constraints.get_quadratic_components(begin, end) + begin and end must be quadratic constraint indices or quadratic + constraint names. Returns the quadratic components of the + quadratic constraints with indices between begin and end, + inclusive of end. Equivalent to + quadratic_constraints.get_quadratic_components(range(begin, end + 1)). + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = [str(i) for i in range(11)], types = "B" * 11) + >>> [op.quadratic_constraints.add( + ... name = str(i), + ... quad_expr = [range(i), range(i), [1.0 * (j+1.0) for j in range(i)]]) + ... for i in range(1, 11)] + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> op.quadratic_constraints.get_num() + 10 + >>> op.quadratic_constraints.get_quadratic_components(8) + SparseTriple(ind1 = [0, 1, 2, 3, 4, 5, 6, 7, 8], ind2 = [0, 1, 2, 3, 4, 5, 6, 7, 8], val = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]) + >>> op.quadratic_constraints.get_quadratic_components("1",3) + [SparseTriple(ind1 = [0], ind2 = [0], val = [1.0]), SparseTriple(ind1 = [0, 1], ind2 = [0, 1], val = [1.0, 2.0]), SparseTriple(ind1 = [0, 1, 2], ind2 = [0, 1, 2], val = [1.0, 2.0, 3.0]), SparseTriple(ind1 = [0, 1, 2, 3], ind2 = [0, 1, 2, 3], val = [1.0, 2.0, 3.0, 4.0])] + >>> op.quadratic_constraints.get_quadratic_components([2,"1",5]) + [SparseTriple(ind1 = [0, 1, 2], ind2 = [0, 1, 2], val = [1.0, 2.0, 3.0]), SparseTriple(ind1 = [0], ind2 = [0], val = [1.0]), SparseTriple(ind1 = [0, 1, 2, 3, 4, 5], ind2 = [0, 1, 2, 3, 4, 5], val = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0])] + >>> op.quadratic_constraints.delete(4,9) + >>> op.quadratic_constraints.get_quadratic_components() + [SparseTriple(ind1 = [0], ind2 = [0], val = [1.0]), SparseTriple(ind1 = [0, 1], ind2 = [0, 1], val = [1.0, 2.0]), SparseTriple(ind1 = [0, 1, 2], ind2 = [0, 1, 2], val = [1.0, 2.0, 3.0]), SparseTriple(ind1 = [0, 1, 2, 3], ind2 = [0, 1, 2, 3], val = [1.0, 2.0, 3.0, 4.0])] + """ + return [] + + def get_names(self, *args): + """Returns the names of a set of quadratic constraints. + + Can be called by four forms. + + quadratic_constraints.get_names() + return the names of all quadratic constraints from the + problem. + + quadratic_constraints.get_names(i) + i must be a quadratic constraint index. Returns the name + of constraint i. + + quadratic_constraints.get_names(s) + s must be a sequence of quadratic constraint indices. + Returns the names of the quadratic constraints with indices + the members of s. Equivalent to + [quadratic_constraints.get_names(i) for i in s] + + quadratic_constraints.get_names(begin, end) + begin and end must be quadratic constraint indices. Returns + the names of the quadratic constraints with indices between + begin and end, inclusive of end. Equivalent to + quadratic_constraints.get_names(range(begin, end + 1)). + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = [str(i) for i in range(11)]) + >>> [op.quadratic_constraints.add( + ... name = "q" + str(i), + ... quad_expr = [range(i), range(i), [1.0 * (j+1.0) for j in range(i)]]) + ... for i in range(1, 11)] + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> op.quadratic_constraints.get_num() + 10 + >>> op.quadratic_constraints.get_names(8) + 'q9' + >>> op.quadratic_constraints.get_names(1, 3) + ['q2', 'q3', 'q4'] + >>> op.quadratic_constraints.get_names([2, 0, 5]) + ['q3', 'q1', 'q6'] + >>> op.quadratic_constraints.get_names() + ['q1', 'q2', 'q3', 'q4', 'q5', 'q6', 'q7', 'q8', 'q9', 'q10'] + """ + None diff --git a/qiskit/optimization/problems/variables.py b/qiskit/optimization/problems/variables.py new file mode 100755 index 0000000000..5184620fe2 --- /dev/null +++ b/qiskit/optimization/problems/variables.py @@ -0,0 +1,761 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + +from collections.abc import Sequence +import copy + +from qiskit.optimization.utils.helpers import ( + init_list_args, validate_arg_lengths, max_arg_length, listify, convert) +from qiskit.optimization.utils.base import BaseInterface +from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError +from qiskit.optimization import infinity + +from cplex.exceptions import CplexSolverError + +CPX_CONTINUOUS = 'C' +CPX_BINARY = 'B' +CPX_INTEGER = 'I' +CPX_SEMICONT = 'S' +CPX_SEMIINT = 'N' + + +class VarTypes(object): + """Constants defining variable types + + These constants are compatible with IBM ILOG CPLEX. + For a definition of each type, see those topics in the CPLEX User's + Manual. + """ + continuous = CPX_CONTINUOUS + binary = CPX_BINARY + integer = CPX_INTEGER + semi_integer = CPX_SEMIINT + semi_continuous = CPX_SEMICONT + + def __getitem__(self, item): + """Converts a constant to a string. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> op.variables.type.binary + 'B' + >>> op.variables.type['B'] + 'binary' + """ + if item == CPX_CONTINUOUS: + return 'continuous' + if item == CPX_BINARY: + return 'binary' + if item == CPX_INTEGER: + return 'integer' + if item == CPX_SEMIINT: + return 'semi_integer' + if item == CPX_SEMICONT: + return 'semi_continuous' + + +class VariablesInterface(BaseInterface): + """Methods for adding, querying, and modifying variables. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = ["x0", "x1", "x2"]) + >>> # default values for lower_bounds are 0.0 + >>> op.variables.get_lower_bounds() + [0.0, 0.0, 0.0] + >>> # values can be set either one at a time or many at a time + >>> op.variables.set_lower_bounds(0, 1.0) + >>> op.variables.set_lower_bounds([("x1", -1.0), (2, 3.0)]) + >>> # values can be queried as a sequence in arbitrary order + >>> op.variables.get_lower_bounds(["x1", "x2", 0]) + [-1.0, 3.0, 1.0] + >>> # can query the number of variables + >>> op.variables.get_num() + 3 + >>> op.variables.set_types(0, op.variables.type.binary) + >>> op.variables.get_num_binary() + 1 + """ + + type = VarTypes() + """See `VarTypes()` """ + + def __init__(self): + """Creates a new VariablesInterface. + + The variables interface is exposed by the top-level `OptimizationProblem` class + as `OptimizationProblem.variables`. This constructor is not meant to be used + externally. + """ + super(VariablesInterface, self).__init__() + self._names = [] + self._lb = [] + self._ub = [] + self._types = [] + # self._obj = [] + # self._columns = [] + self._varsgetindex = {} + + def _varsgetindexfunc(self, item): + if item not in self._varsgetindex: + self._varsgetindex[item] = len(self._varsgetindex) + return self._varsgetindex[item] + + def _varsrebuildindex(self): + self._varsgetindex = {} + for (cnt, item) in enumerate(self._names): + self._varsgetindex[item] = cnt + + def get_num(self): + """Returns the number of variables in the problem. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> t = op.variables.type + >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) + >>> op.variables.get_num() + 3 + """ + return len(self._names) + + def get_num_continuous(self): + """Returns the number of continuous variables in the problem. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> t = op.variables.type + >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) + >>> op.variables.get_num_continuous() + 1 + """ + return self._types.count(VarTypes().continuous) + + def get_num_integer(self): + """Returns the number of integer variables in the problem. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> t = op.variables.type + >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) + >>> op.variables.get_num_integer() + 1 + """ + return self._types.count(VarTypes().integer) + + def get_num_binary(self): + """Returns the number of binary variables in the problem. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> t = op.variables.type + >>> indices = op.variables.add(types = [t.semi_continuous, t.binary, t.integer]) + >>> op.variables.get_num_binary() + 1 + """ + return self._types.count(VarTypes().binary) + + def get_num_semicontinuous(self): + """Returns the number of semi-continuous variables in the problem. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> t = op.variables.type + >>> indices = op.variables.add(types = [t.semi_continuous, t.semi_integer, t.semi_integer]) + >>> op.variables.get_num_semicontinuous() + 1 + """ + return self._types.count(VarTypes().semi_continuous) + + def get_num_semiinteger(self): + """Returns the number of semi-integer variables in the problem. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> t = op.variables.type + >>> indices = op.variables.add(types = [t.semi_continuous, t.semi_integer, t.semi_integer]) + >>> op.variables.get_num_semiinteger() + 2 + """ + return self._types.count(VarTypes().semi_integer) + + def add(self, obj=None, lb=None, ub=None, types="", names=None, + columns=None): + """Adds variables and related data to the problem. + + variables.add accepts the keyword arguments obj, lb, ub, + types, names, and columns. + + If more than one argument is specified, all arguments must + have the same length. + + obj is a list of floats specifying the linear objective + coefficients of the variables. + + lb is a list of floats specifying the lower bounds on the + variables. + + ub is a list of floats specifying the upper bounds on the + variables. + + types must be either a list of single-character strings or a + string containing the types of the variables. + + Note + If types is specified, the problem type will be a MIP, even if + all variables are specified to be continuous. + + names is a list of strings. + + columns may be either a list of sparse vectors or a matrix in + list-of-lists format. + + Note + The entries of columns must not contain duplicate indices. + If an entry of columns references a row more than once, + either by index, name, or a combination of index and name, + an exception will be raised. + + Returns an iterator containing the indices of the added variables. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2"]) + >>> indices = op.variables.add(obj = [1.0, 2.0, 3.0],\ + types = [op.variables.type.integer] * 3) + >>> indices = op.variables.add(obj = [1.0, 2.0, 3.0],\ + lb = [-1.0, 1.0, 0.0],\ + ub = [100.0, infinity, infinity],\ + types = [op.variables.type.integer] * 3,\ + names = ["0", "1", "2"],\ + columns = [SparsePair(ind = ['c0', 2], val = [1.0, -1.0]),\ + [['c2'],[2.0]],\ + SparsePair(ind = [0, 1], val = [3.0, 4.0])]) + + >>> op.variables.get_lower_bounds() + [0.0, 0.0, 0.0, -1.0, 1.0, 0.0] + >>> op.variables.get_cols("1") + SparsePair(ind = [2], val = [2.0]) + """ + if obj: + raise QiskitOptimizationError("Please use ObjectiveInterface instead of obj.") + if columns: + raise QiskitOptimizationError( + "Please use LinearConstraintInterface instead of columns.") + + arg_list = init_list_args(lb, ub, types, names) + arg_lengths = [len(x) for x in arg_list] + if len(arg_lengths) == 0: + return range(0) + max_length = max(arg_lengths) + for arg_length in arg_lengths: + if arg_length > 0 and arg_length != max_length: + raise QiskitOptimizationError("inconsistent arguments") + + if not lb: + lb = [0.0] * max_length + self._lb.extend(lb) + + if not ub: + ub = [infinity] * max_length + self._ub.extend(ub) + + if not types: + types = [VarTypes().continuous] * max_length + self._types.extend(types) + + if not names: + names = ["x" + str(cnt) + for cnt in range(len(self._names), len(self._names) + max_length)] + self._names.extend(names) + + for name in names: + self._varsgetindexfunc(name) + + return range(len(self._names) - max_length, len(self._names)) + + def delete(self, *args): + """Deletes variables from the problem. + + There are four forms by which variables.delete may be called. + + variables.delete() + deletes all variables from the problem. + + variables.delete(i) + i must be a variable name or index. Deletes the variable + whose index or name is i. + + variables.delete(s) + s must be a sequence of variable names or indices. Deletes + the variables with names or indices contained within s. + Equivalent to [variables.delete(i) for i in s]. + + variables.delete(begin, end) + begin and end must be variable indices or variable names. + Deletes the variables with indices between begin and end, + inclusive of end. Equivalent to + variables.delete(range(begin, end + 1)). This will give the + best performance when deleting batches of variables. + + See CPXdelcols in the Callable Library Reference Manual for + more detail. + + Example usage: + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names=[str(i) for i in range(10)]) + >>> op.variables.get_num() + 10 + >>> op.variables.delete(8) + >>> op.variables.get_names() + ['0', '1', '2', '3', '4', '5', '6', '7', '9'] + >>> op.variables.delete([2, "0", 5]) + >>> op.variables.get_names() + ['1', '3', '5', '6', '7', '9'] + >>> op.variables.delete() + >>> op.variables.get_names() + [] + """ + + # TODO: does not update varsgetindexfunc + + def _delete(i): + del self._names[i] + del self._ub[i] + del self._lb[i] + del self._types[i] + + if len(args) == 0: + # Delete All: + self._names = [] + self._ub = [] + self._lb = [] + self._types = [] + # self._columns = [] + self._varsgetindex = {} + elif len(args) == 1: + # Delete all items from a possibly unordered list of mixed types: + args = listify(args[0]) + for i in args: + i = convert(i, self._varsgetindexfunc) + _delete(i) + self._varsrebuildindex() + elif len(args) == 2: + # Delete range from arg[0] to arg[1]: + for i in reversed(range(convert(args[0], self._varsgetindexfunc), + convert(args[1], self._varsgetindexfunc))): + _delete(i) + self._varsrebuildindex() + else: + raise QiskitOptimizationError("Wrong number of arguments.") + + def set_lower_bounds(self, *args): + """Sets the lower bound for a variable or set of variables. + + There are two forms by which variables.set_lower_bounds may be + called. + + variables.set_lower_bounds(i, lb) + i must be a variable name or index and lb must be a real + number. Sets the lower bound of the variable whose index + or name is i to lb. + + variables.set_lower_bounds(seq_of_pairs) + seq_of_pairs must be a list or tuple of (i, lb) pairs, each + of which consists of a variable name or index and a real + number. Sets the lower bound of the specified variables to + the corresponding values. Equivalent to + [variables.set_lower_bounds(pair[0], pair[1]) for pair in seq_of_pairs]. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = ["x0", "x1", "x2"]) + >>> op.variables.set_lower_bounds(0, 1.0) + >>> op.variables.get_lower_bounds() + [1.0, 0.0, 0.0] + >>> op.variables.set_lower_bounds([(2, 3.0), ("x1", -1.0)]) + >>> op.variables.get_lower_bounds() + [1.0, -1.0, 3.0] + """ + + def _set(i, v): + try: + v = v + 0.0 + self._lb[convert(i, self._varsgetindexfunc)] = v + except CplexSolverError: + raise QiskitOptimizationError("Lower bound must support addition of a float.") + + # check for all elements in args whether they are types + if len(args) == 1 and all(hasattr(el, '__len__') and len(el) == 2 for el in args[0]): + for el in args[0]: + _set(*el) + elif len(args) == 2: + _set(*args) + else: + raise QiskitOptimizationError("Invalid arguments to set_lower_bounds!") + + def set_upper_bounds(self, *args): + """Sets the upper bound for a variable or set of variables. + + There are two forms by which variables.set_upper_bounds may be + called. + + variables.set_upper_bounds(i, ub) + i must be a variable name or index and ub must be a real + number. Sets the upper bound of the variable whose index + or name is i to ub. + + variables.set_upper_bounds(seq_of_pairs) + seq_of_pairs must be a list or tuple of (i, ub) pairs, each + of which consists of a variable name or index and a real + number. Sets the upper bound of the specified variables to + the corresponding values. Equivalent to + [variables.set_upper_bounds(pair[0], pair[1]) for pair in seq_of_pairs]. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = ["x0", "x1", "x2"]) + >>> op.variables.set_upper_bounds(0, 1.0) + >>> op.variables.set_upper_bounds([("x1", 10.0), (2, 3.0)]) + >>> op.variables.get_upper_bounds() + [1.0, 10.0, 3.0] + """ + + def _set(i, v): + try: + v = v + 0.0 + self._ub[convert(i, self._varsgetindexfunc)] = v + except CplexSolverError: + raise QiskitOptimizationError("Upper bound must support addition of a float.") + + # check for all elements in args whether they are types + if len(args) == 1 and all(hasattr(el, '__len__') and len(el) == 2 for el in args[0]): + for el in args[0]: + _set(*el) + elif len(args) == 2: + _set(*args) + else: + raise QiskitOptimizationError("Invalid arguments to set_upper_bounds!") + + def set_names(self, *args): + """Sets the name of a variable or set of variables. + + There are two forms by which variables.set_names may be + called. + + variables.set_names(i, name) + i must be a variable name or index and name must be a + string. + + variables.set_names(seq_of_pairs) + seq_of_pairs must be a list or tuple of (i, name) pairs, + each of which consists of a variable name or index and a + string. Sets the name of the specified variables to the + corresponding strings. Equivalent to + [variables.set_names(pair[0], pair[1]) for pair in seq_of_pairs]. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> t = op.variables.type + >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) + >>> op.variables.set_names(0, "first") + >>> op.variables.set_names([(2, "third"), (1, "second")]) + >>> op.variables.get_names() + ['first', 'second', 'third'] + """ + + def _set(i, v): + try: + v = v + "" + i = convert(i, self._varsgetindexfunc) + old_name = self._names[i] + self._names[i] = v + self._varsgetindex[v] = i + self._varsgetindex.pop(old_name) + except CplexSolverError: + raise QiskitOptimizationError( + "First argument must refer to currently defined variable, second to string.") + + if len(args) == 2: + _set(args[0], args[1]) + elif len(args) == 1: + args = listify(args[0]) + for iv in args: + _set(iv[0], iv[1]) + else: + raise QiskitOptimizationError("Wrong number of arguments.") + + def set_types(self, *args): + """Sets the type of a variable or set of variables. + + There are two forms by which variables.set_types may be + called. + + variables.set_types(i, type) + i must be a variable name or index and name must be a + single-character string. + + variables.set_types(seq_of_pairs) + seq_of_pairs must be a list or tuple of (i, type) pairs, + each of which consists of a variable name or index and a + single-character string. Sets the type of the specified + variables to the corresponding strings. Equivalent to + [variables.set_types(pair[0], pair[1]) for pair in seq_of_pairs]. + + Note + If the types are set, the problem will be treated as a MIP, + even if all variable types are continuous. + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = [str(i) for i in range(5)]) + >>> op.variables.set_types(0, op.variables.type.continuous) + >>> op.variables.set_types([("1", op.variables.type.integer),\ + ("2", op.variables.type.binary),\ + ("3", op.variables.type.semi_continuous),\ + ("4", op.variables.type.semi_integer)]) + >>> op.variables.get_types() + ['C', 'I', 'B', 'S', 'N'] + >>> op.variables.type[op.variables.get_types(0)] + 'continuous' + """ + + def _set(i, v): + try: + if v not in [CPX_CONTINUOUS, CPX_BINARY, CPX_INTEGER, CPX_SEMICONT, CPX_SEMIINT]: + raise QiskitOptimizationError( + "Second argument must be a string, as per VarTypes constants.") + i = convert(i, self._varsgetindexfunc) + self._types[i] = v + except CplexSolverError: + raise QiskitOptimizationError( + "First argument must be index of a currently defined variable.") + + if len(args) == 2: + _set(args[0], args[1]) + elif len(args) == 1: + args = listify(args[0]) + for (i, v) in args: + _set(i, v) + else: + raise QiskitOptimizationError("Wrong number of arguments.") + + def get_lower_bounds(self, *args): + """Returns the lower bounds on variables from the problem. + + There are four forms by which variables.get_lower_bounds may be called. + + variables.get_lower_bounds() + return the lower bounds on all variables from the problem. + + variables.get_lower_bounds(i) + i must be a variable name or index. Returns the lower + bound on the variable whose index or name is i. + + variables.get_lower_bounds(s) + s must be a sequence of variable names or indices. Returns + the lower bounds on the variables with indices the members + of s. Equivalent to + [variables.get_lower_bounds(i) for i in s] + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(lb = [1.5 * i for i in range(10)],\ + names = [str(i) for i in range(10)]) + >>> op.variables.get_num() + 10 + >>> op.variables.get_lower_bounds(8) + 12.0 + >>> op.variables.get_lower_bounds([2,"0",5]) + [3.0, 0.0, 7.5] + >>> op.variables.get_lower_bounds() + [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] + """ + + def _get(i): + i = convert(i, self._varsgetindexfunc) + return self._lb[i] + + out = [] + if len(args) == 0: + return copy.deepcopy(self._lb) + elif len(args) == 1: + if isinstance(args[0], Sequence): + for i in args[0]: + out.append(_get(i)) + else: + return _get(args[0]) + else: + for i in args: + out.append(_get(i)) + return out + + def get_upper_bounds(self, *args): + """Returns the upper bounds on variables from the problem. + + There are four forms by which variables.get_upper_bounds may be called. + + variables.get_upper_bounds() + return the upper bounds on all variables from the problem. + + variables.get_upper_bounds(i) + i must be a variable name or index. Returns the upper + bound on the variable whose index or name is i. + + variables.get_upper_bounds(s) + s must be a sequence of variable names or indices. Returns + the upper bounds on the variables with indices the members + of s. Equivalent to + [variables.get_upper_bounds(i) for i in s] + + variables.get_upper_bounds(begin, end) + begin and end must be variable indices or variable names. + Returns the upper bounds on the variables with indices between + begin and end, inclusive of end. Equivalent to + variables.get_upper_bounds(range(begin, end + 1)). + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(ub = [(1.5 * i) + 1.0 for i in range(10)],\ + names = [str(i) for i in range(10)]) + >>> op.variables.get_num() + 10 + >>> op.variables.get_upper_bounds(8) + 13.0 + >>> op.variables.get_upper_bounds([2,"0",5]) + [4.0, 1.0, 8.5] + >>> op.variables.get_upper_bounds() + [1.0, 2.5, 4.0, 5.5, 7.0, 8.5, 10.0, 11.5, 13.0, 14.5] + """ + + def _get(i): + i = convert(i, self._varsgetindexfunc) + return self._ub[i] + + out = [] + if len(args) == 0: + return copy.deepcopy(self._ub) + elif len(args) == 1: + if isinstance(args[0], Sequence): + for i in args[0]: + out.append(_get(i)) + else: + return _get(args[0]) + else: + for i in args: + out.append(_get(i)) + return out + + def get_names(self, *args): + """Returns the names of variables from the problem. + + There are four forms by which variables.get_names may be called. + + variables.get_names() + return the names of all variables from the problem. + + variables.get_names(i) + i must be a variable index. Returns the name of variable i. + + variables.get_names(s) + s must be a sequence of variable indices. Returns the + names of the variables with indices the members of s. + Equivalent to [variables.get_names(i) for i in s] + + >>> op = qiskit.optimization.OptimizationProblem() + >>> indices = op.variables.add(names = ['x' + str(i) for i in range(10)]) + >>> op.variables.get_num() + 10 + >>> op.variables.get_names(8) + 'x8' + >>> op.variables.get_names([2,0,5]) + ['x2', 'x0', 'x5'] + >>> op.variables.get_names() + ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9'] + """ + + def _get(i): + i = convert(i, self._varsgetindexfunc) + return self._names[i] + + out = [] + if len(args) == 0: + return copy.deepcopy(self._names) + elif len(args) == 1: + if isinstance(args[0], Sequence): + for i in args[0]: + out.append(_get(i)) + else: + return _get(args[0]) + else: + for i in args: + out.append(_get(i)) + return out + + def get_types(self, *args): + """Returns the types of variables from the problem. + + There are four forms by which variables.types may be called. + + variables.types() + return the types of all variables from the problem. + + variables.types(i) + i must be a variable name or index. Returns the type of + the variable whose index or name is i. + + variables.types(s) + s must be a sequence of variable names or indices. Returns + the types of the variables with indices the members of s. + Equivalent to [variables.get_types(i) for i in s] + + >>> op = qiskit.optimization.OptimizationProblem() + >>> t = op.variables.type + >>> indices = op.variables.add(names = [str(i) for i in range(5)],\ + types = [t.continuous, t.integer,\ + t.binary, t.semi_continuous, t.semi_integer]) + >>> op.variables.get_num() + 5 + >>> op.variables.get_types(3) + 'S' + >>> op.variables.get_types([2,0,4]) + ['B', 'C', 'N'] + >>> op.variables.get_types() + ['C', 'I', 'B', 'S', 'N'] + """ + + def _get(i): + i = convert(i, self._varsgetindexfunc) + return self._types[i] + + out = [] + if len(args) == 0: + return copy.deepcopy(self._types) + elif len(args) == 1: + if isinstance(args[0], Sequence): + for i in args[0]: + out.append(_get(i)) + else: + return _get(args[0]) + else: + for i in args: + out.append(_get(i)) + return out + + def get_cols(self, *args): + raise QiskitOptimizationError("Please use LinearConstraintInterface instead.") + + def get_obj(self, *args): + raise QiskitOptimizationError("Please use ObjectiveInterface instead.") diff --git a/qiskit/optimization/results/__init__.py b/qiskit/optimization/results/__init__.py new file mode 100755 index 0000000000..d5c1802e42 --- /dev/null +++ b/qiskit/optimization/results/__init__.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + +""" +======================================================== +Optimization stack for Aqua (:mod:`qiskit.optimization`) +======================================================== + +.. currentmodule:: qiskit.optimization.solutions + +Structures for defining a solution with metrics of its quality etc +========== + +.. autosummary:: + :toctree: + + SolutionStatus + QualityMetrics + +""" + +from qiskit.optimization.results.quality_metrics import QualityMetrics +from qiskit.optimization.results.solution import SolutionInterface +from qiskit.optimization.results.solution_status import SolutionStatus +from qiskit.optimization.results.optimization_result import OptimizationResult + +__all__ = ["SolutionStatus", "QualityMetrics", "SolutionStatus", "OptimizationResult"] diff --git a/qiskit/optimization/results/optimization_result.py b/qiskit/optimization/results/optimization_result.py new file mode 100644 index 0000000000..3b0404d447 --- /dev/null +++ b/qiskit/optimization/results/optimization_result.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +"""This file implements the OptimizationResult, returned by the optimization algorithms.""" + +from typing import Any, Optional + + +class OptimizationResult: + """The optimization result class. + + The optimization algorithms return an object of the type `OptimizationResult`, which enforces + providing the following attributes. + + Attributes: + x: The optimal value found in the optimization algorithm. + fval: The function value corresponding to the optimal value. + results: The original results object returned from the optimization algorithm. This can + contain more information than only the optimal value and function value. + """ + + def __init__(self, x: Optional[Any] = None, fval: Optional[Any] = None, + results: Optional[Any] = None) -> None: + """Initialize the optimization result.""" + self._x = x + self._fval = fval + self._results = results + + def __repr__(self): + return '([%s] / %s)' % (','.join([str(x_) for x_ in self.x]), self.fval) + + @property + def x(self) -> Any: + """Returns the optimal value found in the optimization. + + Returns: + The optimal value found in the optimization. + """ + return self._x + + @property + def fval(self) -> Any: + """Returns the optimal function value. + + Returns: + The function value corresponding to the optimal value found in the optimization. + """ + return self._fval + + @property + def results(self) -> Any: + """Return the original results object from the algorithm. + + Currently a dump for any leftovers. + + Returns: + Additional result information of the optimization algorithm. + """ + return self._results + + @x.setter + def x(self, x: Any) -> None: + """Set a new optimal value. + + Args: + x: The new optimal value. + """ + self._x = x + + @fval.setter + def fval(self, fval: Any) -> None: + """Set a new optimal function value. + + Args: + fval: The new optimal function value. + """ + self._fval = fval + + @results.setter + def results(self, results: Any) -> None: + """Set results. + + Args: + results: The new additional results of the optimization. + """ + self._results = results diff --git a/qiskit/optimization/results/quality_metrics.py b/qiskit/optimization/results/quality_metrics.py new file mode 100755 index 0000000000..7bdf2807f2 --- /dev/null +++ b/qiskit/optimization/results/quality_metrics.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + + +from qiskit.optimization.utils.base import BaseInterface +from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError + + +class QualityMetrics(object): + """A class containing measures of the quality of a solution. + + The __str__ method of this class prints all available measures of + the quality of the solution in human readable form. + + This class may have a different set of data members depending on + the optimization algorithm used and the quality metrics that are + available. + + An instance of this class always has the member quality_type, + which is one of the following strings: + + "quadratically_constrained" + "MIP" + + If self.quality_type is "quadratically_constrained" this instance + has the following members: + + objective + norm_total + norm_max + error_Ax_b_total + error_Ax_b_max + error_xQx_dx_f_total + error_xQx_dx_f_max + x_bound_error_total + x_bound_error_max + slack_bound_error_total + slack_bound_error_max + quadratic_slack_bound_error_total + quadratic_slack_bound_error_max + normalized_error_max + + If self.quality_type is "MIP" and this instance was generated for + the incumbent solution, it has the members: + + solver + objective + x_norm_total + x_norm_max + error_Ax_b_total + error_Ax_b_max + x_bound_error_total + x_bound_error_max + integrality_error_total + integrality_error_max + slack_bound_error_total + slack_bound_error_max + + If solver is "MIQCP" this instance also has the members: + + error_xQx_dx_f_total + error_xQx_dx_f_max + quadratic_slack_bound_error_total + quadratic_slack_bound_error_max + """ + + def __init__(self, soln=-1): + self._tostring = "" + + def __str__(self): + # See __init__ (above) to see how this is constructed. + return self._tostring diff --git a/qiskit/optimization/results/solution.py b/qiskit/optimization/results/solution.py new file mode 100755 index 0000000000..6bf4b47f54 --- /dev/null +++ b/qiskit/optimization/results/solution.py @@ -0,0 +1,253 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + + +from qiskit.optimization.results.quality_metrics import QualityMetrics +from qiskit.optimization.results.solution_status import SolutionStatus + +from qiskit.optimization.utils.base import BaseInterface +from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError + + +class SolutionInterface(BaseInterface): + """Methods for querying the solution to an optimization problem.""" + + # method = SolutionMethod() + # """See `SolutionMethod()` """ + # quality_metric = QualityMetric() + # """See `QualityMetric()` """ + status = SolutionStatus() + """See `SolutionStatus()` """ + + def __init__(self): + """Creates a new SolutionInterface. + + The solution interface is exposed by the top-level `Cplex` class + as Cplex.solution. This constructor is not meant to be used + externally. + """ + super(SolutionInterface, self).__init__() + # self.progress = ProgressInterface(self) + # """See `ProgressInterface()` """ + # self.MIP = MIPSolutionInterface(self) + # """See `MIPSolutionInterface()` """ + # self.pool = SolnPoolInterface(self) + # """See `SolnPoolInterface()` """ + + def get_status(self): + """Returns the status of the solution. + + Returns an attribute of Cplex.solution.status. + For interpretations of the status codes, see the + reference manual of the CPLEX Callable Library, + especially the group optim.cplex.callable.solutionstatus + + >>> import cplex + >>> c = cplex.Cplex() + >>> out = c.set_results_stream(None) + >>> out = c.set_log_stream(None) + >>> c.read("example.mps") + >>> c.solve() + >>> c.solution.get_status() + 1 + """ + return None + + def get_method(self): + """Returns the method used to solve the problem. + + Returns an attribute of Cplex.solution.method. + + >>> import cplex + >>> c = cplex.Cplex() + >>> out = c.set_results_stream(None) + >>> out = c.set_log_stream(None) + >>> c.read("example.mps") + >>> c.solve() + >>> c.solution.get_method() + 2 + """ + return None + + def get_status_string(self, status_code=None): + """Returns a string describing the status of the solution. + + Example usage: + + >>> import cplex + >>> c = cplex.Cplex() + >>> out = c.set_results_stream(None) + >>> out = c.set_log_stream(None) + >>> c.read("example.mps") + >>> c.solve() + >>> c.solution.get_status_string() + 'optimal' + """ + # if status_code is None: + # status_code = self.get_status() + return None + + def get_objective_value(self): + """Returns the value of the objective function. + + Example usage: + + >>> import cplex + >>> c = cplex.Cplex() + >>> out = c.set_results_stream(None) + >>> out = c.set_log_stream(None) + >>> c.read("example.mps") + >>> c.solve() + >>> c.solution.get_objective_value() + -202.5 + """ + return None + + def get_values(self, *args): + """Returns the values of a set of variables at the solution. + + Can be called by four forms. + + solution.get_values() + return the values of all variables from the problem. + + solution.get_values(i) + i must be a variable name or index. Returns the value of + the variable whose index or name is i. + + solution.get_values(s) + s must be a sequence of variable names or indices. Returns + the values of the variables with indices the members of s. + Equivalent to [solution.get_values(i) for i in s] + + solution.get_values(begin, end) + begin and end must be variable indices or variable names. + Returns the values of the variables with indices between begin + and end, inclusive of end. Equivalent to + solution.get_values(range(begin, end + 1)). + + >>> import cplex + >>> c = cplex.Cplex() + >>> out = c.set_results_stream(None) + >>> out = c.set_log_stream(None) + >>> c.read("lpex.mps") + >>> c.solve() + >>> c.solution.get_values([0, 4, 5]) + [25.5, 0.0, 80.0] + """ + return None + + def get_integer_quality(self, which): + """Returns a measure of the quality of the solution. + + The measure of the quality of a solution can be a single attribute of + solution.quality_metrics or a sequence of such + attributes. + + >>> import cplex + >>> c = cplex.Cplex() + >>> out = c.set_results_stream(None) + >>> out = c.set_log_stream(None) + >>> c.read("lpex.mps") + >>> c.solve() + >>> m = c.solution.quality_metric + >>> c.solution.get_integer_quality([m.max_x, m.max_dual_infeasibility]) + [18, -1] + """ + if isinstance(which, int): + return None + else: + return [None for a in which] + + def get_float_quality(self, which): + """Returns a measure of the quality of the solution. + + The measure of the quality of a solution can be a single attribute of + solution.quality_metrics or a sequence of such attributes. + + Note + This corresponds to the CPLEX callable library function + CPXgetdblquality. + + >>> import cplex + >>> c = cplex.Cplex() + >>> out = c.set_results_stream(None) + >>> out = c.set_log_stream(None) + >>> c.read("lpex.mps") + >>> c.solve() + >>> m = c.solution.quality_metric + >>> c.solution.get_float_quality([m.max_x, m.max_dual_infeasibility]) + [500.0, 0.0] + """ + if isinstance(which, int): + return None + else: + return [None for a in which] + + def get_solution_type(self): + """Returns the type of the solution. + + Returns an attribute of Cplex.solution.type. + + >>> import cplex + >>> c = cplex.Cplex() + >>> out = c.set_results_stream(None) + >>> out = c.set_log_stream(None) + >>> c.read("lpex.mps") + >>> c.solve() + >>> c.solution.get_solution_type() + 1 + """ + return None + + def is_primal_feasible(self): + """Returns whether or not the solution is known to be primal feasible. + + Note + Returning False does not necessarily mean that the problem is + not primal feasible, only that it is not proved to be primal + feasible. + + >>> import cplex + >>> c = cplex.Cplex() + >>> out = c.set_results_stream(None) + >>> out = c.set_log_stream(None) + >>> c.read("lpex.mps") + >>> c.solve() + >>> c.solution.is_primal_feasible() + True + """ + return None + + def get_quality_metrics(self): + """Returns an object containing measures of the solution quality. + + See `QualityMetrics` + """ + return QualityMetrics() + + def write(self, filename): + """Writes the incumbent solution to a file. + + Example usage: + + >>> import cplex + >>> c = cplex.Cplex() + >>> out = c.set_results_stream(None) + >>> out = c.set_log_stream(None) + >>> c.read("lpex.mps") + >>> c.solve() + >>> c.solution.write("lpex.sol") + """ + None diff --git a/qiskit/optimization/results/solution_status.py b/qiskit/optimization/results/solution_status.py new file mode 100755 index 0000000000..088fe03132 --- /dev/null +++ b/qiskit/optimization/results/solution_status.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + + +from qiskit.optimization.utils.base import BaseInterface +from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError + +CPX_STAT_ABORT_DETTIME_LIM = 25 +CPX_STAT_ABORT_IT_LIM = 10 +CPX_STAT_ABORT_OBJ_LIM = 12 +CPX_STAT_ABORT_TIME_LIM = 11 +CPX_STAT_ABORT_USER = 13 +CPX_STAT_FEASIBLE = 23 +CPX_STAT_INFEASIBLE = 3 +CPX_STAT_INForUNBD = 4 +CPX_STAT_OPTIMAL = 1 +CPX_STAT_UNBOUNDED = 2 + +CPXMIP_OPTIMAL = 101 +CPXMIP_INFEASIBLE = 103 +CPXMIP_TIME_LIM_FEAS = 107 +CPXMIP_TIME_LIM_INFEAS = 108 +CPXMIP_FAIL_FEAS = 109 +CPXMIP_FAIL_INFEAS = 110 +CPXMIP_MEM_LIM_FEAS = 111 +CPXMIP_MEM_LIM_INFEAS = 112 +CPXMIP_ABORT_FEAS = 113 +CPXMIP_ABORT_INFEAS = 114 +CPXMIP_UNBOUNDED = 118 +CPXMIP_INForUNBD = 119 +CPXMIP_FEASIBLE = 127 +CPXMIP_DETTIME_LIM_FEAS = 131 +CPXMIP_DETTIME_LIM_INFEAS = 132 + + +class SolutionStatus(object): + """Solution status codes. + + For documentation of each status code, see the reference manual + of the CPLEX Callable Library, especially the group + optim.cplex.callable.solutionstatus. + """ + unknown = 0 # There is no constant for this. + optimal = CPX_STAT_OPTIMAL + unbounded = CPX_STAT_UNBOUNDED + infeasible = CPX_STAT_INFEASIBLE + feasible = CPX_STAT_FEASIBLE + infeasible_or_unbounded = CPX_STAT_INForUNBD + abort_obj_limit = CPX_STAT_ABORT_OBJ_LIM + abort_iteration_limit = CPX_STAT_ABORT_IT_LIM + abort_time_limit = CPX_STAT_ABORT_TIME_LIM + abort_dettime_limit = CPX_STAT_ABORT_DETTIME_LIM + abort_user = CPX_STAT_ABORT_USER + fail_feasible = CPXMIP_FAIL_FEAS + fail_infeasible = CPXMIP_FAIL_INFEAS + mem_limit_feasible = CPXMIP_MEM_LIM_FEAS + mem_limit_infeasible = CPXMIP_MEM_LIM_INFEAS + + MIP_optimal = CPXMIP_OPTIMAL + MIP_infeasible = CPXMIP_INFEASIBLE + MIP_time_limit_feasible = CPXMIP_TIME_LIM_FEAS + MIP_time_limit_infeasible = CPXMIP_TIME_LIM_INFEAS + MIP_dettime_limit_feasible = CPXMIP_DETTIME_LIM_FEAS + MIP_dettime_limit_infeasible = CPXMIP_DETTIME_LIM_INFEAS + MIP_abort_feasible = CPXMIP_ABORT_FEAS + MIP_abort_infeasible = CPXMIP_ABORT_INFEAS + MIP_unbounded = CPXMIP_UNBOUNDED + MIP_infeasible_or_unbounded = CPXMIP_INForUNBD + MIP_feasible = CPXMIP_FEASIBLE + + def __getitem__(self, item): + """Converts a constant to a string. + + Example usage: + + >>> import cplex + >>> c = cplex.Cplex() + >>> c.solution.status.optimal + 1 + >>> c.solution.status[1] + 'optimal' + """ + if item == 0: + return 'unknown' + if item == CPX_STAT_OPTIMAL: + return 'optimal' + if item == CPX_STAT_UNBOUNDED: + return 'unbounded' + if item == CPX_STAT_INFEASIBLE: + return 'infeasible' + if item == CPX_STAT_FEASIBLE: + return 'feasible' + if item == CPX_STAT_INForUNBD: + return 'infeasible_or_unbounded' + if item == CPX_STAT_ABORT_OBJ_LIM: + return 'abort_obj_limit' + if item == CPX_STAT_ABORT_IT_LIM: + return 'abort_iteration_limit' + if item == CPX_STAT_ABORT_TIME_LIM: + return 'abort_time_limit' + if item == CPX_STAT_ABORT_DETTIME_LIM: + return 'abort_dettime_limit' + if item == CPX_STAT_ABORT_USER: + return 'abort_user' + if item == CPXMIP_FAIL_FEAS: + return 'fail_feasible' + if item == CPXMIP_FAIL_INFEAS: + return 'fail_infeasible' + if item == CPXMIP_MEM_LIM_FEAS: + return 'mem_limit_feasible' + if item == CPXMIP_MEM_LIM_INFEAS: + return 'mem_limit_infeasible' + if item == CPXMIP_OPTIMAL: + return 'MIP_optimal' + if item == CPXMIP_INFEASIBLE: + return 'MIP_infeasible' + if item == CPXMIP_TIME_LIM_FEAS: + return 'MIP_time_limit_feasible' + if item == CPXMIP_TIME_LIM_INFEAS: + return 'MIP_time_limit_infeasible' + if item == CPXMIP_DETTIME_LIM_FEAS: + return 'MIP_dettime_limit_feasible' + if item == CPXMIP_DETTIME_LIM_INFEAS: + return 'MIP_dettime_limit_infeasible' + if item == CPXMIP_ABORT_FEAS: + return 'MIP_abort_feasible' + if item == CPXMIP_ABORT_INFEAS: + return 'MIP_abort_infeasible' + if item == CPXMIP_UNBOUNDED: + return 'MIP_unbounded' + if item == CPXMIP_INForUNBD: + return 'MIP_infeasible_or_unbounded' + if item == CPXMIP_FEASIBLE: + return 'MIP_feasible' + raise QiskitOptimizationError("Unexpected solution status code!") diff --git a/qiskit/optimization/utils/__init__.py b/qiskit/optimization/utils/__init__.py new file mode 100755 index 0000000000..d983674705 --- /dev/null +++ b/qiskit/optimization/utils/__init__.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. +""" +======================================================== +Optimization stack for Aqua (:mod:`qiskit.optimization`) +======================================================== + +.. currentmodule:: qiskit.optimization.utils + +Utility classes and functions +========== + +.. autosummary:: + :toctree: + + QiskitOptimizationError + BaseInterface + SparsePair + SparseTriple + +N.B. Utility functions in .aux are intended for internal use. + +""" + +from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError +from qiskit.optimization.utils.base import BaseInterface +from qiskit.optimization.utils.eigenvector_to_solutions import eigenvector_to_solutions + +__all__ = ["QiskitOptimizationError", "BaseInterface", "eigenvector_to_solutions"] diff --git a/qiskit/optimization/utils/base.py b/qiskit/optimization/utils/base.py new file mode 100755 index 0000000000..bb35c3dc28 --- /dev/null +++ b/qiskit/optimization/utils/base.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + + +from qiskit.optimization.utils.helpers import convert, _defaultgetindexfunc + + +class BaseInterface(object): + """Common methods for sub-interfaces within Qiskit Optimization.""" + + def __init__(self): + """Creates a new BaseInterface. + + This class is not meant to be instantiated directly nor used + externally. + """ + if type(self) == BaseInterface: + raise TypeError("BaseInterface must be sub-classed") + + def _conv(self, name, cache=None): + """Converts from names to indices as necessary.""" + return convert(name, self._get_index, cache) + + @staticmethod + def _add_iter(getnumfun, addfun, *args, **kwargs): + """non-public""" + old = getnumfun() + addfun(*args, **kwargs) + return range(old, getnumfun()) + + @staticmethod + def _add_single(getnumfun, addfun, *args, **kwargs): + """non-public""" + addfun(*args, **kwargs) + return getnumfun() - 1 # minus one for zero-based indices + + def _get_index(self, name): + return _defaultgetindexfunc(name) + + def get_indices(self, name): + """Converts from names to indices. + + If name is a string, get_indices returns the index of the + object with that name. If no such object exists, an + exception is raised. + + If name is a sequence of strings, get_indices returns a list + of the indices corresponding to the strings in name. + Equivalent to map(self.get_indices, name). + + If the subclass does not provide an index function (i.e., the + interface is not indexed), then a NotImplementedError is raised. + + Example usage: + + >>> import cplex + >>> c = cplex.Cplex() + >>> indices = c.variables.add(names=["a", "b"]) + >>> c.variables.get_indices("a") + 0 + >>> c.variables.get_indices(["a", "b"]) + [0, 1] + """ + if _defaultgetindexfunc is None: + raise NotImplementedError("This is not an indexed interface") + if isinstance(name, str): + return self._get_index(name) + else: + return [self._get_index(x) for x in name] diff --git a/qiskit/optimization/utils/eigenvector_to_solutions.py b/qiskit/optimization/utils/eigenvector_to_solutions.py new file mode 100644 index 0000000000..d4424fb0af --- /dev/null +++ b/qiskit/optimization/utils/eigenvector_to_solutions.py @@ -0,0 +1,97 @@ + +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +"""Auxilliary methods to translate eigenvectors into optimization results.""" + +from qiskit.aqua.operators import MatrixOperator +from typing import Union, List, Tuple +from qiskit import QuantumCircuit, BasicAer, execute +from qiskit.aqua import QuantumInstance +from qiskit.aqua.operators import WeightedPauliOperator, MatrixOperator +import numpy + + +def eigenvector_to_solutions(eigenvector: Union[dict, numpy.ndarray], + operator: Union[WeightedPauliOperator, MatrixOperator], + min_probability: float = 1e-6) -> List[Tuple[str, float, float]]: + """Convert the eigenvector to the bitstrings and corresponding eigenvalues. + + Examples: + >>> op = MatrixOperator(numpy.array([[1, 1], [1, -1]]) / numpy.sqrt(2)) + >>> eigenvectors = {'0': 12, '1': 1} + >>> print(eigenvector_to_solutions(eigenvectors, op)) + [('0', 0.7071067811865475, 0.9230769230769231), ('1', -0.7071067811865475, 0.07692307692307693)] + + >>> op = MatrixOperator(numpy.array([[1, 1], [1, -1]]) / numpy.sqrt(2)) + >>> eigenvectors = numpy.array([1, 1] / numpy.sqrt(2), dtype=complex) + >>> print(eigenvector_to_solutions(eigenvectors, op)) + [('0', 0.7071067811865475, 0.4999999999999999), ('1', -0.7071067811865475, 0.4999999999999999)] + + Returns: + For each computational basis state contained in the eigenvector, return the basis + state as bitstring along with the operator evaluated at that bitstring and the + probability of sampling this bitstring from the eigenvector. + """ + solutions = [] + if isinstance(eigenvector, dict): + all_counts = sum(eigenvector.values()) + # iterate over all samples + for bitstr, count in eigenvector.items(): + sampling_probability = count / all_counts + + # add the bitstring, if the sampling probability exceeds the threshold + if sampling_probability >= min_probability: + value = eval_operator_at_bitstring(operator, bitstr) + solutions += [(bitstr, value, sampling_probability)] + + elif isinstance(eigenvector, numpy.ndarray): + num_qubits = int(numpy.log2(eigenvector.size)) + probabilities = numpy.abs(eigenvector * eigenvector.conj()) + + # iterate over all states and their sampling probabilities + for i, sampling_probability in enumerate(probabilities): + + # add the i-th state if the sampling probability exceeds the threshold + if sampling_probability >= min_probability: + bitstr = '{:b}'.format(i).rjust(num_qubits, '0') + value = eval_operator_at_bitstring(operator, bitstr) + solutions += [(bitstr, value, sampling_probability)] + + else: + raise TypeError('Unsupported format of eigenvector. Provide a dict or numpy.ndarray.') + + return solutions + + +def eval_operator_at_bitstring(operator: Union[WeightedPauliOperator, MatrixOperator], + bitstr: str) -> float: + """ + TODO + """ + + # TODO check that operator size and bitstr are compatible + circuit = QuantumCircuit(len(bitstr)) + for i, bit in enumerate(bitstr): + # TODO in which order to iterate over the bitstring??? + if bit == '1': + circuit.x(i) + + # simulate the circuit + result = execute(circuit, BasicAer.get_backend('statevector_simulator')).result() + + # evaluate the operator + value = numpy.real(operator.evaluate_with_statevector(result.get_statevector())[0]) + + return value diff --git a/qiskit/optimization/utils/helpers.py b/qiskit/optimization/utils/helpers.py new file mode 100755 index 0000000000..c0419ec53a --- /dev/null +++ b/qiskit/optimization/utils/helpers.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + + +from qiskit.optimization.utils import QiskitOptimizationError + +from collections.abc import Sequence + + +_defaultgetindex = {} + + +def _defaultgetindexfunc(item): + if item not in _defaultgetindex: + _defaultgetindex[item] = len(_defaultgetindex) + return _defaultgetindex[item] + + +def _cachelookup(item, getindexfunc, cache): + if item not in cache: + cache[item] = getindexfunc(item) + return cache[item] + + +def _convert_sequence(seq, getindexfunc, cache): + results = [] + for item in seq: + if isinstance(item, str): + idx = _cachelookup(item, getindexfunc, cache) + results.append(idx) + else: + results.append(item) + return results + + +def listify(x): + """Returns [x] if x isn't already a list. + + This is used to wrap arguments for functions that require lists. + """ + # Assumes name to index conversions already done. + assert not isinstance(x, str) + if isinstance(x, Sequence): + return x + else: + return [x] + + +def convert(name, getindexfunc=_defaultgetindexfunc, cache=None): + """Converts from names to indices as necessary. + + If name is a string, an index is returned. + + If name is a sequence, a sequence of indices is returned. + + If name is neither (i.e., it's an integer), then that is returned + as is. + + getindexfunc is a function that takes a name and returns an index. + + The optional cache argument allows for further localized + caching (e.g., within a loop). + """ + # In some cases, it can be beneficial to cache lookups. + if cache is None: + cache = {} + if isinstance(name, str): + return _cachelookup(name, getindexfunc, cache) + elif isinstance(name, Sequence): + # It's tempting to use a recursive solution here, but that kills + # performance for the case where all indices are passed in (i.e., + # no names). This is due to the fact that we end up doing the + # extra check for sequence types over and over (above). + return _convert_sequence(name, getindexfunc, cache) + else: + return name + + +def init_list_args(*args): + """Initialize default arguments with empty lists if necessary.""" + return tuple([] if a is None else a for a in args) + + +if __debug__: + + # With non-optimzied bytecode, validate_arg_lengths actually does + # something. + def validate_arg_lengths(arg_list, allow_empty=True): + """Checks for equivalent argument lengths. + + If allow_empty is True (the default), then empty arguments are not + checked against the max length of non-empty arguments. Some functions + allow NULL arguments in the Callable Library, for example. + """ + arg_lengths = [len(x) for x in arg_list] + if allow_empty: + arg_lengths = [x for x in arg_lengths if x > 0] + if len(arg_lengths) == 0: + return + max_length = max(arg_lengths) + for arg_length in arg_lengths: + if arg_length != max_length: + raise QiskitOptimizationError("inconsistent arguments") + +else: + + # A no-op if using python -O or the PYTHONOPTIMIZE environment + # variable is defined as a non-empty string. + def validate_arg_lengths(arg_list, allow_empty=True): + pass + + +def unpack_pair(item): + """Extracts the indices and values from an object. + + The argument item can either be an instance of SparsePair or a + sequence of length two. + + Example usage: + + >>> sp = SparsePair() + >>> ind, val = unpack_pair(sp) + >>> lin_expr = [[], []] + >>> ind, val = unpack_pair(lin_expr) + """ + try: + assert item.isvalid() + ind, val = item.unpack() + except AttributeError: + ind, val = item[0:2] + validate_arg_lengths([ind, val]) + return ind, val + + +def unpack_triple(item): + """Extracts the indices and values from an object. + + The argument item can either be an instance of SparseTriple or a + sequence of length three. + + Example usage: + + >>> st = SparseTriple() + >>> ind1, ind2, val = unpack_triple(st) + >>> quad_expr = [[], [], []] + >>> ind1, ind2, val = unpack_triple(quad_expr) + """ + try: + assert item.isvalid() + ind1, ind2, val = item.unpack() + except AttributeError: + ind1, ind2, val = item[0:3] + validate_arg_lengths([ind1, ind2, val]) + return ind1, ind2, val + + +def max_arg_length(arg_list): + """Returns the max length of the arguments in arg_list.""" + return max(len(x) for x in arg_list) diff --git a/qiskit/optimization/utils/qiskit_optimization_error.py b/qiskit/optimization/utils/qiskit_optimization_error.py new file mode 100644 index 0000000000..cca56e9150 --- /dev/null +++ b/qiskit/optimization/utils/qiskit_optimization_error.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + + +class QiskitOptimizationError(Exception): + """Class for errors returned by Qiskit Optimization libraries functions. + + self.args[0] : A string describing the error. + """ + + def __str__(self): + # Note: this is actually ok. Exception does have subscriptable args. + # pylint: disable=unsubscriptable-object + return self.args[0] diff --git a/test/optimization/common.py b/test/optimization/common.py new file mode 100755 index 0000000000..cbb6006698 --- /dev/null +++ b/test/optimization/common.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +"""Shared functionality and helpers for the unit tests.""" + +from enum import Enum +import inspect +import logging +import os +import unittest +import time + +from qiskit.optimization import __path__ as qiskit_optimization_path + + +class Path(Enum): + """Helper with paths commonly used during the tests.""" + # Main SDK path: qiskit/optimization/ + SDK = qiskit_optimization_path[0] + # test.python path: test/optimization + TEST = os.path.dirname(__file__) + + +class QiskitOptimizationTestCase(unittest.TestCase): + """Helper class that contains common functionality.""" + + def setUp(self): + self._started_at = time.time() + + def tearDown(self): + elapsed = time.time() - self._started_at + if elapsed > 5.0: + print('({:.2f}s)'.format(round(elapsed, 2)), flush=True) + + @classmethod + def setUpClass(cls): + cls.moduleName = os.path.splitext(inspect.getfile(cls))[0] + cls.log = logging.getLogger(cls.__name__) + + # Set logging to file and stdout if the LOG_LEVEL environment variable + # is set. + if os.getenv('LOG_LEVEL'): + # Set up formatter. + log_fmt = ('{}.%(funcName)s:%(levelname)s:%(asctime)s:' + ' %(message)s'.format(cls.__name__)) + formatter = logging.Formatter(log_fmt) + + # Set up the file handler. + log_file_name = '%s.log' % cls.moduleName + file_handler = logging.FileHandler(log_file_name) + file_handler.setFormatter(formatter) + cls.log.addHandler(file_handler) + + # Set the logging level from the environment variable, defaulting + # to INFO if it is not a valid level. + level = logging._nameToLevel.get(os.getenv('LOG_LEVEL'), + logging.INFO) + cls.log.setLevel(level) + + @staticmethod + def _get_resource_path(filename, path=Path.TEST): + """ Get the absolute path to a resource. + Args: + filename (string): filename or relative path to the resource. + path (Path): path used as relative to the filename. + Returns: + str: the absolute path to the resource. + """ + return os.path.normpath(os.path.join(path.value, filename)) + + def assertNoLogs(self, logger=None, level=None): + """The opposite to assertLogs. + """ + # pylint: disable=invalid-name + return _AssertNoLogsContext(self, logger, level) + + +class _AssertNoLogsContext(unittest.case._AssertLogsContext): + """A context manager used to implement TestCase.assertNoLogs().""" + + LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s" + + # pylint: disable=inconsistent-return-statements + def __exit__(self, exc_type, exc_value, tb): + """ + This is a modified version of unittest.case._AssertLogsContext.__exit__(...) + """ + self.logger.handlers = self.old_handlers + self.logger.propagate = self.old_propagate + self.logger.setLevel(self.old_level) + if exc_type is not None: + # let unexpected exceptions pass through + return False + for record in self.watcher.records: + self._raiseFailure( + "Something was logged in the logger %s by %s:%i" % + (record.name, record.pathname, record.lineno)) diff --git a/test/optimization/resources/op_ip1.lp b/test/optimization/resources/op_ip1.lp new file mode 100644 index 0000000000..13a8bc3b94 --- /dev/null +++ b/test/optimization/resources/op_ip1.lp @@ -0,0 +1,13 @@ +\ENCODING=ISO-8859-1 +\Problem name: op_ip1 + +Minimize + obj1: 2 x0 + 2 x1 + [ 4 x0 * x0 + x1 * x1 + 0.5 x0 * x1 + 0.5 x0 * x1 ] / 2 +Subject To + constraint: x0 + x1 = 2 +Bounds + 0 <= x0 <= 4 + 0 <= x1 <= 4 +Generals + x0 x1 +End diff --git a/test/optimization/resources/op_ip2.lp b/test/optimization/resources/op_ip2.lp new file mode 100644 index 0000000000..3a9923848f --- /dev/null +++ b/test/optimization/resources/op_ip2.lp @@ -0,0 +1,12 @@ +\ENCODING=ISO-8859-1 +\Problem name: op_ip2 + +Maximize + obj: x1 + 2 x2 + 3 x3 + [ - 33 x1 ^2 + 12 x1 * x2 - 22 x2 ^2 + 23 x2 * x3 + - 11 x3 ^2 ] / 2 +Subject To + c1: - x1 + x2 + x3 <= 20 + c2: x1 - 3 x2 + x3 <= 30 +Bounds + 0 <= x1 <= 40 +End \ No newline at end of file diff --git a/test/optimization/resources/op_lp1.lp b/test/optimization/resources/op_lp1.lp new file mode 100644 index 0000000000..56979102ad --- /dev/null +++ b/test/optimization/resources/op_lp1.lp @@ -0,0 +1,11 @@ +\ENCODING=ISO-8859-1 +\Problem name: op_ip1 + +Minimize + obj: 2 x0 + 2 x1 + [ 4 x0 * x0 + x1 * x1 + 0.5 x0 * x1 + 0.5 x0 * x1 ] / 2 +Subject To + constraint: x0 + x1 = 2 +Bounds + 0 <= x0 <= 4 + 0 <= x1 <= 4 +End diff --git a/test/optimization/resources/op_mip1.lp b/test/optimization/resources/op_mip1.lp new file mode 100644 index 0000000000..9d26bd32d4 --- /dev/null +++ b/test/optimization/resources/op_mip1.lp @@ -0,0 +1,13 @@ +\ENCODING=ISO-8859-1 +\Problem name: op_ip1 + +Minimize + obj1: x0 + 2 x1 + 4 x2 + [ 4 x0 * x0 + x1 * x1 + 0.5 x0 * x1 + 0.5 x0 * x1 ] / 2 +Subject To + constraint: x0 + x1 + x2 = 2 +Bounds + 0 <= x0 <= 4 + 0 <= x1 <= 4 +Generals + x0 x1 +End diff --git a/test/optimization/test_admm_algorithm.py b/test/optimization/test_admm_algorithm.py new file mode 100644 index 0000000000..f57cf2549f --- /dev/null +++ b/test/optimization/test_admm_algorithm.py @@ -0,0 +1,66 @@ +from cplex import SparsePair +from qiskit.optimization.problems.optimization_problem import OptimizationProblem +from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer + + +def create_simple_admm_problem(): + op = OptimizationProblem() + op.variables.add(names=["x0_1"], types=["B"]) + op.variables.add(names=["x0_2"], types=["B"]) + op.variables.add(names=["u_1"], lb=[0]) + op.variables.add(names=["u_2"], lb=[0]) + + # a list with length equal to the number of variables + # list of SparsePairs + q_terms = [SparsePair(ind=(0, 1, 2, 3), val=[11, 12, 0, 0]), + SparsePair(ind=(0, 1, 2, 3), val=[12, 22, 0, 0]), + SparsePair(ind=(0, 1, 2, 3), val=[0, 0, 33, 34]), + SparsePair(ind=(0, 1, 2, 3), val=[0, 0, 34, 44])] + op.objective.set_quadratic(q_terms) + + op.objective.set_linear("x0_1", 1) + op.objective.set_linear("x0_2", 2) + + op.objective.set_linear("u_1", 3) + op.objective.set_linear("u_2", 4) + + # A0, vector b0 + op.linear_constraints.add(lin_expr=[SparsePair(ind=[0, 1], val=[1, 11])], senses="E", rhs=[11.1], + names=["constraint1"]) + op.linear_constraints.add(lin_expr=[SparsePair(ind=[0, 1], val=[2, 22])], senses="E", rhs=[22.22], + names=["constraint2"]) + + # matrix A1, vector b1 + op.linear_constraints.add(lin_expr=[SparsePair(ind=[0, 1], val=[3, 33])], senses="L", rhs=[33.33], + names=["constraint3"]) + op.linear_constraints.add(lin_expr=[SparsePair(ind=[0, 1], val=[4, 44])], senses="L", rhs=[44.44], + names=["constraint4"]) + + # matrix A2, matrix A3 + # original one + # op.linear_constraints.add(lin_expr=[SparsePair(ind=[0, 1, 2, 3], val=[5, 55, 555, 5555])], senses="L", rhs=[55.55], + # names=["constraint5"]) + op.linear_constraints.add(lin_expr=[SparsePair(ind=[0, 2, 1, 3], val=[5, 555, 55, 5555])], senses="L", rhs=[55.55], + names=["constraint5"]) + op.linear_constraints.add(lin_expr=[SparsePair(ind=[0, 1, 2, 3], val=[6, 66, 666, 6666])], senses="L", rhs=[66.66], + names=["constraint6"]) + op.linear_constraints.add(lin_expr=[SparsePair(ind=[2, 3, 0], val=[777, 7777, 7])], senses="L", rhs=[77.77], + names=["constraint7"]) + + # matrix A4, vector b3 + op.linear_constraints.add(lin_expr=[SparsePair(ind=[2, 3], val=[777, 7777])], senses="L", rhs=[77.77], + names=["constraint8"]) + op.linear_constraints.add(lin_expr=[SparsePair(ind=[2, 3], val=[888, 8888])], senses="L", rhs=[88.88], + names=["constraint9"]) + + op.write("admm-sample.lp") + + return op + + +if __name__ == '__main__': + op = create_simple_admm_problem() + solver = ADMMOptimizer() + compatible = solver.is_compatible(op) + print("Problem is compatible: {}".format(compatible)) + solution = solver.solve(op) diff --git a/test/optimization/test_admm_bpp.py b/test/optimization/test_admm_bpp.py new file mode 100644 index 0000000000..8d8e5bde50 --- /dev/null +++ b/test/optimization/test_admm_bpp.py @@ -0,0 +1,361 @@ +""" +Created: 2020-02-19 +@author Claudio Gambella [claudio.gambella1@ie.ibm.com] +""" +import math +import os +import sys +import numpy as np + +from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer +from qiskit.optimization.problems import OptimizationProblem + + + +try: + import cplex + from cplex.exceptions import CplexError +except ImportError: + print("Failed to import cplex.") + sys.exit(1) +from cplex import SparsePair + +def create_folder(folder: str): + if not os.path.exists(folder): + os.makedirs(folder) +def nm(stem, index1, index2=None, index3=None): + """A method to return a string representing the name of a decision variable or a constraint, given its indices. + Attributes: + stem: Element name. + index1, index2, index3: Element indices. + """ + if index2==None: return stem + "(" + str(index1) + ")" + if index3==None: return stem + "(" + str(index1) + "," + str(index2) + ")" + return stem + "(" + str(index1) + "," + str(index2) + "," + str(index3) + ")" + + +def __dump_arrays(Q0, Q1, c0, c1, A0, b0, A1, b1, A4, b3, A2, A3, b2): + """ + For debugging purposes + :param Q0: + :param Q1: + :param c0: + :param c1: + :param A0: + :param b0: + :param A1: + :param b1: + :param A4: + :param b3: + :param A2: + :param A3: + :param b2: + :return: + """ + print("Claudio's implementation") + print("Q0") + print(Q0) + print("Q0 shape") + print(Q0.shape) + print("Q1") + print(Q1) + print("Q1 shape") + print(Q1.shape) + + print("c0") + print(c0) + print("c0 shape") + print(c0.shape) + print("c1") + print(c1) + print("c1 shape") + print(c1.shape) + + print("A0") + print(A0) + print("A0 shape") + print(A0.shape) + print("b0") + print(b0) + print("b0 shape") + print(b0.shape) + + print("A1") + print(A1) + print("A1 shape") + print(A1.shape) + print("b1") + print(b1) + print("b1 shape") + print(b1.shape) + + print("A4") + print(A4) + print("A4 shape") + print(A4.shape) + print("b3") + print(b3) + print("b3 shape") + print(b3.shape) + + print("A2") + print(A2) + print("A2 shape") + print(A2.shape) + print("A3") + print(A3) + print("A3 shape") + print(A3.shape) + print("b2") + print(b2) + print("b2 shape") + print(b2.shape) + + print("End of Claudio's implementation") + + +def get_instance_params(): + """ + - Populate a Bin Packing Problem (BPP) instance. + - Create the instance parameters needed to apply ADMM. + NOTE: The x decision variables are thought of as array parameters. + + n number of binary dec vars x0 (and z) + m number of continuous dec vars u + + :return: n, m, Q0, c0, Q1, c1, A0, b0, A1, b1, A2, A3, b2, lb, ub ADMM parameters + """ + # + + n_items = 2 + n_bins = n_items + n_x_vars = n_items * n_bins + C = 40 + w = [35, 31] + + n = n_x_vars + n_bins # number of binary dec vars x0 (and z) + m = 0 # number of continuous dec vars u + + # Parameters of the Quadratic Programming problem in the standard form for ADMM. + Q0 = np.zeros((n, n)) + c0 = np.hstack((np.zeros(n_x_vars), np.ones(n_bins))) + + # A0 x0 = b0 --> \sum _i x_{ij} = 1 + A0 = np.tile(np.eye(n_items), n_bins) + A0 = np.hstack((A0, np.zeros((n_items, n_items + )))) + b0 = np.ones(n_items) + + Q1 = np.zeros((m, m)) + c1 = np.zeros(m) + + # A1 z \leq b1 + A1 = np.zeros((n_bins, n)) + block1 = np.zeros((n_bins, n_x_vars)) + for i in range(n_bins): + block1[i, i*n_items:(i+1)*n_items] = np.asarray(w) + A1 = np.hstack((block1, -C * np.eye(n_bins))) + b1 = np.zeros(n_bins) + + dummy_dim = 1 + A2 = np.zeros((dummy_dim, n)) + A3 = np.zeros((dummy_dim, m)) + b2 = np.zeros(dummy_dim) + + A4 = np.zeros((1,m)) + b3 = np.zeros(1) + + __dump_arrays(Q0, Q1, c0, c1, A0, b0, A1, b1, A4, b3, A2, A3, b2) + + return n_items, n_bins, C, w + +class Bpp: + + def __init__(self, n_items, n_bins, C, w, verbose=False, relativeGap=0.0): + """ + Constructor method of the class. + :param n: number of items + :param m: number of bins + :param C: capacity of the bins + :param w: weight of items + :param verbose: + :param relativeGap: + """ + self.C = C + self.relativeGap = relativeGap + self.verbose = verbose + self.w = w + self.n = n_items + self.m = n_bins + self.lp_folder = "./outputs/lps/bpp/" + + def create_params(self, save_vars=False): + self.range_n = range(self.n) + self.range_m = range(self.m) + self.range_mn = [(i,j) for i in self.range_m for j in self.range_n] + self.nm = self.n*self.m + + # make sure instance params are floats + self.w = [float(item) for item in self.w] + self.C = float(self.C) + + if save_vars: + self.l = self.get_lb_bins() + self.n_x = self.m*self.n-self.m + self.n_y = self.m-self.l + + self.range_x_vars = [(i, j) for i in self.range_m for j in self.range_n if + j != 0] # item j=0 is automatically assigned to bin 0 + self.range_y_vars = range(self.l, self.m) # y_0, ..., y_{l-1} are not needed + else: + self.l = 0 + + self.n_x = self.m * self.n + self.n_y = self.m + + self.range_x_vars = self.range_mn + self.range_y_vars = self.range_m + + def get_lb_bins(self) -> int: + """ + Return lower bound on the number of bins needed, (see, e.g., Martello and Toth 1990) + :return: + """ + return math.ceil(sum(self.w)/self.C) + + + def create_vars(self, x_var=True): + """ + + :param x_var: + :return: + """ + + if x_var: + self.op.variables.add( + types=["B"] * self.n_x, + names=[nm("x", i, j) for i,j in self.range_x_vars]) + + self.op.variables.add( + types=["B"] * self.n_y, + names=[nm("y", i) for i in self.range_y_vars]) + + + def create_cons_eq(self, save_vars=False): + + if save_vars: + self.items_to_assign = range(1, self.n) + else: + self.items_to_assign = self.range_n + + self.op.linear_constraints.add( + lin_expr=[ + SparsePair( + ind=[ + nm("x", i, j) for i in self.range_m + ], + val=[1.0] * self.m) + for j in self.items_to_assign + ], + senses="E" * len(self.items_to_assign), + rhs=[1.0] * len(self.items_to_assign), + names=[nm("ASSIGN_ITEM", j) for j in self.items_to_assign]) + + def create_cons_ineq(self, save_vars=False): + + if not save_vars: + self.op.linear_constraints.add( + lin_expr=[ + SparsePair( + ind=[ + nm("x", i, j) for j in self.range_n + ] +[nm("y", i)] + , + val=self.w + [-self.C]) + for i in self.range_m + ], + senses="L" * self.m, + rhs=[0.0] * self.m, + names=[nm("CAPACITY", i) for i in self.range_m]) + else: + #This is because x(0,0) is assigned + self.op.linear_constraints.add( + lin_expr=[ + SparsePair( + ind=[ + nm("x", 0, j) for j in self.range_n if j != 0 + ] + , + val=self.w[1:]) + ], + senses="L", + rhs=[self.C-self.w[0]], + names=[nm("CAPACITY_l", 0)]) + # Bins from 1 to l + self.op.linear_constraints.add( + lin_expr=[ + SparsePair( + ind=[ + nm("x", i, j) for j in self.range_n if j != 0 + ] + , + val=self.w[1:]) for i in range(1, self.l) + ], + senses="L" * (self.l-1), + rhs=[self.C] * (self.l-1), + names=[nm("CAPACITY_l", i) for i in range(1, self.l)]) + self.op.linear_constraints.add( + lin_expr=[ + SparsePair( + ind=[ + nm("x", i, j) for j in self.range_n if j != 0 + ] +[nm("y", i)] + , + val=self.w[1:] + [-self.C]) + for i in self.range_y_vars + ], + senses="L" * (self.m - self.l), + rhs=[0.0] * (self.m - self.l), + names=[nm("CAPACITY", i) for i in self.range_y_vars]) + + def create_cons(self, save_vars=False): + """Method to populate the constraints""" + self.create_cons_eq(save_vars) + self.create_cons_ineq(save_vars) + + def create_obj(self): + self.op.objective.set_linear([(nm("y", i), 1.0) for i in self.range_y_vars]) + + def run_op(self, save_vars=False): + """Main method, which populates and solve the mathematical model via Python Cplex API + """ + + self.op = OptimizationProblem() + + self.create_params(save_vars=save_vars) + self.create_vars() + self.create_obj() + self.create_cons(save_vars=save_vars) + + # Save the model + create_folder(self.lp_folder) + + self.op.write(self.lp_folder + "bpp.lp") + + + solver = ADMMOptimizer() + solution = solver.solve(self.op) + + return solution + + +def toy_op(): + n_items, n_bins, C, w = get_instance_params() + pb = Bpp(n_items, n_bins, C, w) + out = pb.run_op() + + +if __name__ == '__main__': + # toy_cplex_api() + toy_op() + diff --git a/test/optimization/test_admm_miskp.py b/test/optimization/test_admm_miskp.py new file mode 100644 index 0000000000..e12dd7878d --- /dev/null +++ b/test/optimization/test_admm_miskp.py @@ -0,0 +1,346 @@ +""" +Created: 2020-01-24 +@author Claudio Gambella [claudio.gambella1@ie.ibm.com] + +This solves Mixed-Integer Setup Knapsack Problem via ADMM +References: Exact and heuristic solution approaches for the mixed integer setup knapsack problem, Altay et. al, EJOR, 2008. +Gambella, C., & Simonetto, A. (2020). Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical +and Quantum Computers. arXiv preprint arXiv:2001.02069. +""" +import os +import sys +import time +import numpy as np + +from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer +from qiskit.optimization.problems import OptimizationProblem + + +try: + import cplex + from cplex.exceptions import CplexError +except ImportError: + print("Failed to import cplex.") + sys.exit(1) +from cplex import SparsePair + +def create_folder(folder: str): + if not os.path.exists(folder): + os.makedirs(folder) +def nm(stem, index1, index2=None, index3=None): + """A method to return a string representing the name of a decision variable or a constraint, given its indices. + Attributes: + stem: Element name. + index1, index2, index3: Element indices. + """ + if index2==None: return stem + "(" + str(index1) + ")" + if index3==None: return stem + "(" + str(index1) + "," + str(index2) + ")" + return stem + "(" + str(index1) + "," + str(index2) + "," + str(index3) + ")" + +def __dump_arrays(Q0, Q1, c0, c1, A0, b0, A1, b1, A4, b3, A2, A3, b2): + """ + For debugging purposes + :param Q0: + :param Q1: + :param c0: + :param c1: + :param A0: + :param b0: + :param A1: + :param b1: + :param A4: + :param b3: + :param A2: + :param A3: + :param b2: + :return: + """ + print("Claudio's implementation") + print("Q0") + print(Q0) + print("Q0 shape") + print(Q0.shape) + print("Q1") + print(Q1) + print("Q1 shape") + print(Q1.shape) + + print("c0") + print(c0) + print("c0 shape") + print(c0.shape) + print("c1") + print(c1) + print("c1 shape") + print(c1.shape) + + print("A0") + print(A0) + print("A0 shape") + print(A0.shape) + print("b0") + print(b0) + print("b0 shape") + print(b0.shape) + + print("A1") + print(A1) + print("A1 shape") + print(A1.shape) + print("b1") + print(b1) + print("b1 shape") + print(b1.shape) + + print("A4") + print(A4) + print("A4 shape") + print(A4.shape) + print("b3") + print(b3) + print("b3 shape") + print(b3.shape) + + print("A2") + print(A2) + print("A2 shape") + print(A2.shape) + print("A3") + print(A3) + print("A3 shape") + print(A3.shape) + print("b2") + print(b2) + print("b2 shape") + print(b2.shape) + + print("End of Claudio's implementation") + + +def get_instance_params(): + """ + Get parameters for a Mixed Integer Setup Knapsack Problem (MISKP) instance. + + :return: + """ + # + + K = 2 + T = 10 + P = 45.10 + S = np.asarray([75.61, 75.54]) + + D = np.asarray([9.78, 8.81, 9.08, 2.03, 8.9, 9, 4.12, 8.16, 6.55, 4.84, 3.78, 5.13, + 4.72, 7.6, 2.83, 1.44, 2.45, 2.24, 6.3, 5.02]).reshape((K, T)) + + C = np.asarray([-11.78, -10.81, -11.08, -4.03, -10.90, -11.00, -6.12, -10.16, -8.55, -6.84, + -5.78, -7.13, -6.72, -9.60, -4.83, -3.44, -4.45, -4.24, -8.30, -7.02]).reshape((K, T)) + + n = K # number of binary dec vars x0 (and z) + m = K * T # number of continuous dec vars u + # size of x1 is then n+m + + # Parameters of the Quadratic Programming problem in the standard form for ADMM. + ## Objective function + Q0 = np.zeros((n, n)) + c0 = S.reshape(n) + + A0 = np.zeros((1, n)) # we set here one dummy equality constraint + b0 = np.zeros(1) # we set here one dummy equality constraint + + Q1 = np.zeros((m, m)) + c1 = C.reshape(m) + + ## Constraints + # we set here one dummy A1 x0 \leq b1 constraint + A1 = np.zeros((1, n)) + b1 = np.zeros(1) + + # A_2 z + A_3 u \leq b_2 -- > - y_k <= x_{k,t} + A2 = np.zeros((m, n)) + for i in range(K): + A2[T * i:T * (i + 1), i] = - np.ones(T) + A3 = np.eye(m) + b2 = np.zeros(m) + + # A_4 u <= b_3 --> sum_k sum_t D_{kt} x_{kt} \leq P, -x_{kt} \leq 0 + block1 = D.reshape((1, m)) + block2 = -np.eye(m) + A4 = np.block([[block1], [block2]]) + b3 = np.hstack([P, np.zeros(m)]) + + __dump_arrays(Q0, Q1, c0, c1, A0, b0, A1, b1, A4, b3, A2, A3, b2) + + return K, T, P, S, D, C + + +class Miskp: + + def __init__(self, K, T, P, S, D: np.ndarray, C: np.ndarray, verbose=False, relativeGap=0.0, + pairwise_incomp=0, multiple_choice=0): + """ + Constructor method of the class. + :param K: number of families + :param T: number of items in each family + :param C: value of including item t in family k in the knapsack + :param D: resources consumed if item t in family k is included in the knapsack + :param S: setup cost to include family k in the knapsack + :param P: capacity of the knapsack + :param verbose: + :param relativeGap: + """ + + self.multiple_choice = multiple_choice + self.pairwise_incomp = pairwise_incomp + self.P = P + self.S = S + self.D = D + self.C = C + self.T = T + self.K = K + self.relativeGap = relativeGap + self.verbose = verbose + self.lp_folder = "./lps/" + + def create_params(self): + self.range_K = range(self.K) + self.range_T = range(self.T) + + # make sure instance params are floats + + self.S = [float(val) for val in self.S] + self.C = self.C.astype(float) + self.D = self.D.astype(float) + + self.n_x_vars = self.K * self.T + self.n_y_vars = self.K + + self.range_x_vars = [(k, t) for k in self.range_K for t in self.range_T] + self.range_y_vars = self.range_K + + + def create_vars(self): + + self.op.variables.add( + lb=[0.0] * self.n_x_vars, + names=[nm("x", i, j) for i,j in self.range_x_vars]) + + self.op.variables.add( + # lb=[0.0] * self.n_y_vars, + # ub=[1.0] * self.n_y_vars, + types=["B"] * self.n_y_vars, + names=[nm("y", i) for i in self.range_y_vars]) + + def create_cons_capacity(self): + self.op.linear_constraints.add( + lin_expr=[ + SparsePair( + ind=[ + nm("x", i, j) for i,j in self.range_x_vars + ] + , + val=[self.D[i,j] for i,j in self.range_x_vars]) + ], + senses="L", + rhs=[self.P], + names=["CAPACITY"]) + + def create_cons_allocation(self): + self.op.linear_constraints.add( + lin_expr=[ + SparsePair( + ind=[nm("x", k, t)]+[nm("y", k)], + val=[1.0, -1.0]) + for k,t in self.range_x_vars + ], + senses="L" * self.n_x_vars, + rhs=[0.0] * self.n_x_vars, + names=[nm("ALLOCATION", k, t) for k, t in self.range_x_vars]) + + + def create_cons(self): + """Method to populate the constraints""" + + self.create_cons_capacity() + self.create_cons_allocation() + + def create_obj(self): + self.op.objective.set_linear([(nm("y", k), self.S[k]) for k in self.range_K] + + [(nm("x", k, t), self.C[k, t]) for k, t in self.range_x_vars] + ) + + def run_cplex_api(self): + """Main method, which populates and solve the mathematical model via Python Cplex API + + """ + + # Creation of the Cplex object + self.op = OptimizationProblem() + + + self.create_params() + self.create_vars() + self.create_obj() + start_time = time.time() + self.create_cons() + constraints_time = time.time() - start_time + if self.verbose: + print ("Time to populate constraints:", constraints_time) + + # Save the model + create_folder(self.lp_folder) + + self.op.write(self.lp_folder + "miskp.lp") + + out = 0 + return out + + def run_op(self): + """Main method, which populates and solve the OptimizationProblem model via OptimizationAlgorithm + + """ + + self.op = OptimizationProblem() + + self.create_params() + self.create_vars() + self.create_obj() + start_time = time.time() + self.create_cons() + constraints_time = time.time() - start_time + if self.verbose: + print ("Time to populate constraints:", constraints_time) + + # Save the model + create_folder(self.lp_folder) + + self.op.write(self.lp_folder + "miskp.lp") + + solver = ADMMOptimizer() + solution = solver.solve(self.op) + + return solution + + +def toy_cplex_api(): + + K, T, P, S, D, C = get_instance_params() + pb = Miskp(K, T, P, S, D, C) + out = pb.run_cplex_api() + +def toy_op(): + + K, T, P, S, D, C = get_instance_params() + pb = Miskp(K, T, P, S, D, C) + out = pb.run_op() + + +if __name__ == '__main__': + # toy_cplex_api() + toy_op() + + + + + + diff --git a/test/optimization/test_cobyla_optimizer.py b/test/optimization/test_cobyla_optimizer.py new file mode 100644 index 0000000000..a2b16bc1f9 --- /dev/null +++ b/test/optimization/test_cobyla_optimizer.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +""" Test Cobyla Optimizer """ + +from test.optimization.common import QiskitOptimizationTestCase +import numpy as np + +from qiskit.optimization.algorithms import CobylaOptimizer +from qiskit.optimization.problems import OptimizationProblem + +from ddt import ddt, data + + +@ddt +class TestCobylaOptimizer(QiskitOptimizationTestCase): + """Cobyla Optimizer Tests.""" + + def setUp(self): + super().setUp() + + self.resource_path = './test/optimization/resources/' + self.cobyla_optimizer = CobylaOptimizer() + + @data( + ('op_lp1.lp', 5.8750) + ) + def test_cobyla_optimizer(self, config): + """ Cobyla Optimizer Test """ + + # unpack configuration + filename, fval = config + + # load optimization problem + problem = OptimizationProblem() + problem.read(self.resource_path + filename) + + # solve problem with cobyla + result = self.cobyla_optimizer.solve(problem) + + # analyze results + self.assertAlmostEqual(result.fval, fval) diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py new file mode 100644 index 0000000000..cd8f454c91 --- /dev/null +++ b/test/optimization/test_converters.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" Test Converters """ + +from cplex import SparsePair + +from qiskit.optimization import OptimizationProblem, QiskitOptimizationError +from qiskit.optimization.converters import InequalityToEqualityConverter, \ + OptimizationProblemToOperator, IntegerToBinaryConverter, PenalizeLinearEqualityConstraints +from test.optimization.common import QiskitOptimizationTestCase + + +class TestConverters(QiskitOptimizationTestCase): + """Test Converters""" + + def setUp(self): + super().setUp() + + def test_empty_problem(self): + op = OptimizationProblem() + conv = InequalityToEqualityConverter() + op = conv.encode(op) + conv = IntegerToBinaryConverter() + op = conv.encode(op) + conv = PenalizeLinearEqualityConstraints() + op = conv.encode(op) + conv = OptimizationProblemToOperator() + qubitop, shift = conv.encode(op) + self.assertEqual(shift, 0.0) + + def test_inequality_binary(self): + op = OptimizationProblem() + op.variables.add(names=['x', 'y', 'z'], types='B' * 3) + op.linear_constraints.add( + lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), + SparsePair(ind=['y', 'z'], val=[1, -1]), + SparsePair(ind=['z', 'x'], val=[1, 2])], + senses=['E', 'L', 'G'], + rhs=[1, 2, 3], + names=['xy', 'yz', 'zx'] + ) + conv = InequalityToEqualityConverter() + op2 = conv.encode(op) + self.assertEqual(op.get_problem_name(), op2.get_problem_name()) + self.assertEqual(op.get_problem_type(), op2.get_problem_type()) + cst = op2.linear_constraints + self.assertListEqual(cst.get_names(), ['xy', 'yz', 'zx']) + self.assertListEqual(cst.get_senses(), ['E', 'E', 'E']) + self.assertListEqual(cst.get_rhs(), [1, 2, 3]) + + def test_inequality_integer(self): + op = OptimizationProblem() + op.variables.add(names=['x', 'y', 'z'], types='I' * 3, lb=[-3] * 3, ub=[3] * 3) + op.linear_constraints.add( + lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), + SparsePair(ind=['y', 'z'], val=[1, -1]), + SparsePair(ind=['z', 'x'], val=[1, 2])], + senses=['E', 'L', 'G'], + rhs=[1, 2, 3], + names=['xy', 'yz', 'zx'] + ) + conv = InequalityToEqualityConverter() + op2 = conv.encode(op) + self.assertEqual(op.get_problem_name(), op2.get_problem_name()) + self.assertEqual(op.get_problem_type(), op2.get_problem_type()) + cst = op2.linear_constraints + self.assertListEqual(cst.get_names(), ['xy', 'yz', 'zx']) + self.assertListEqual(cst.get_senses(), ['E', 'E', 'E']) + self.assertListEqual(cst.get_rhs(), [1, 2, 3]) + + def test_penalize_sense(self): + op = OptimizationProblem() + op.variables.add(names=['x', 'y', 'z'], types='B' * 3) + op.linear_constraints.add( + lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), + SparsePair(ind=['y', 'z'], val=[1, -1]), + SparsePair(ind=['z', 'x'], val=[1, 2])], + senses=['E', 'L', 'G'], + rhs=[1, 2, 3], + names=['xy', 'yz', 'zx'] + ) + self.assertEqual(op.linear_constraints.get_num(), 3) + conv = PenalizeLinearEqualityConstraints() + self.assertRaises(QiskitOptimizationError, lambda: conv.encode(op)) + + def test_penalize_binary(self): + op = OptimizationProblem() + op.variables.add(names=['x', 'y', 'z'], types='B' * 3) + op.linear_constraints.add( + lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), + SparsePair(ind=['y', 'z'], val=[1, -1])], + senses=['E', 'E'], + rhs=[1, 2], + names=['xy', 'yz'] + ) + self.assertEqual(op.linear_constraints.get_num(), 2) + conv = PenalizeLinearEqualityConstraints() + op2 = conv.encode(op) + self.assertEqual(op2.linear_constraints.get_num(), 0) + + def test_penalize_integer(self): + op = OptimizationProblem() + op.variables.add(names=['x', 'y', 'z'], types='I' * 3, lb=[-3] * 3, ub=[3] * 3) + op.linear_constraints.add( + lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), + SparsePair(ind=['y', 'z'], val=[1, -1])], + senses=['E', 'E'], + rhs=[1, 2], + names=['xy', 'yz'] + ) + self.assertEqual(op.linear_constraints.get_num(), 2) + conv = PenalizeLinearEqualityConstraints() + op2 = conv.encode(op) + self.assertEqual(op2.linear_constraints.get_num(), 0) + + def test_integer_to_binary(self): + op = OptimizationProblem() + op.variables.add(names=['x', 'y', 'z'], types='BIC', lb=[0, 0, 0], ub=[1, 5, 10]) + self.assertEqual(op.variables.get_num(), 3) + conv = IntegerToBinaryConverter() + op2 = conv.encode(op) + print(op2.variables.get_num()) + names = op2.variables.get_names() + self.assertIn('x', names) + self.assertIn('z', names) + vars = op2.variables + self.assertEqual(vars.get_lower_bounds('x')[0], 0.0) + self.assertEqual(vars.get_lower_bounds('z')[0], 0.0) + self.assertEqual(vars.get_upper_bounds('x')[0], 1.0) + self.assertEqual(vars.get_upper_bounds('z')[0], 10.0) + self.assertListEqual(vars.get_types(['x', 'z']), ['B', 'C']) diff --git a/test/optimization/test_cplex_optimizer.py b/test/optimization/test_cplex_optimizer.py new file mode 100644 index 0000000000..373bfeb799 --- /dev/null +++ b/test/optimization/test_cplex_optimizer.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +""" Test Cplex Optimizer """ + +from test.optimization.common import QiskitOptimizationTestCase +import numpy as np + +from qiskit.optimization.algorithms import CplexOptimizer +from qiskit.optimization.problems import OptimizationProblem + +from ddt import ddt, data + + +@ddt +class TestCplexOptimizer(QiskitOptimizationTestCase): + """Cplex Optimizer Tests.""" + + def setUp(self): + super().setUp() + + self.resource_path = './test/optimization/resources/' + self.cplex_optimizer = CplexOptimizer() + + @data( + ('op_ip1.lp', [0, 2], 6), + ('op_mip1.lp', [1, 1, 0], 6), + ('op_lp1.lp', [0.25, 1.75], 5.8750) + ) + def test_cplex_optimizer(self, config): + """ Cplex Optimizer Test """ + + # unpack configuration + filename, x, fval = config + + # load optimization problem + problem = OptimizationProblem() + problem.read(self.resource_path + filename) + + # solve problem with cplex + result = self.cplex_optimizer.solve(problem) + + # analyze results + self.assertAlmostEqual(result.fval, fval) + self.assertAlmostEqual(result.x, x) diff --git a/test/optimization/test_linear_constraints.py b/test/optimization/test_linear_constraints.py new file mode 100644 index 0000000000..6f73bd1e27 --- /dev/null +++ b/test/optimization/test_linear_constraints.py @@ -0,0 +1,268 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" Test LinearConstraintInterface """ + +import numpy as np +import os.path +import tempfile +from cplex import SparsePair +from qiskit.optimization.problems import OptimizationProblem +from cplex import infinity +from test.optimization.common import QiskitOptimizationTestCase +from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError + + +class TestLinearConstraints(QiskitOptimizationTestCase): + """Test LinearConstraintInterface.""" + + def setUp(self): + super().setUp() + + def test_initial1(self): + op = OptimizationProblem() + op.linear_constraints.add(names=["c1", "c2", "c3"]) + self.assertEqual(op.linear_constraints.get_num(), 3) + + def test_initial2(self): + rhs = [0.0, 1.0, -1.0, 2.0] + op = OptimizationProblem() + op.variables.add(names=["x1", "x2", "x3"]) + op.linear_constraints.add( + lin_expr=[SparsePair(ind=["x1", "x3"], val=[1.0, -1.0]), + SparsePair(ind=["x1", "x2"], val=[1.0, 1.0]), + SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), + SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])], + senses=["E", "L", "G", "R"], + rhs=rhs, + range_values=[0.0, 0.0, 0.0, -10.0], + names=["c0", "c1", "c2", "c3"]) + self.assertEqual(len(op.linear_constraints.get_rhs()), 4) + for i, v in enumerate(rhs): + self.assertAlmostEqual(op.linear_constraints.get_rhs(i), v) + self.assertAlmostEqual(op.linear_constraints.get_rhs()[i], v) + + def test_initial3(self): + op = OptimizationProblem() + op.linear_constraints.add(names=[str(i) for i in range(10)]) + self.assertEqual(op.linear_constraints.get_num(), 10) + op.linear_constraints.delete(8) + self.assertEqual(len(op.linear_constraints.get_names()), 9) + self.assertEqual(op.linear_constraints.get_names()[0], '0') + # ['0', '1', '2', '3', '4', '5', '6', '7', '9'] + op.linear_constraints.delete("1", 3) + self.assertEqual(len(op.linear_constraints.get_names()), 6) + # ['0', '4', '5', '6', '7', '9'] + op.linear_constraints.delete([2, "0", 5]) + self.assertEqual(len(op.linear_constraints.get_names()), 3) + self.assertEqual(op.linear_constraints.get_names()[0], '4') + # ['4', '6', '7'] + op.linear_constraints.delete() + self.assertEqual(len(op.linear_constraints.get_names()), 0) + # [] + + def test_rhs1(self): + op = OptimizationProblem() + op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) + self.assertEqual(len(op.linear_constraints.get_rhs()), 4) + self.assertAlmostEqual(op.linear_constraints.get_rhs()[0], 0.0) + # [0.0, 0.0, 0.0, 0.0] + op.linear_constraints.set_rhs("c1", 1.0) + self.assertEqual(len(op.linear_constraints.get_rhs()), 4) + self.assertAlmostEqual(op.linear_constraints.get_rhs()[1], 1.0) + # [0.0, 1.0, 0.0, 0.0] + op.linear_constraints.set_rhs([("c3", 2.0), (2, -1.0)]) + self.assertEqual(len(op.linear_constraints.get_rhs()), 4) + self.assertAlmostEqual(op.linear_constraints.get_rhs()[2], -1.0) + self.assertAlmostEqual(op.linear_constraints.get_rhs()[3], 2.0) + # [0.0, 1.0, -1.0, 2.0] + + def test_names(self): + op = OptimizationProblem() + op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) + op.linear_constraints.set_names("c1", "second") + self.assertEqual(op.linear_constraints.get_names(1), 'second') + op.linear_constraints.set_names([("c3", "last"), (2, "middle")]) + op.linear_constraints.get_names() + self.assertEqual(len(op.linear_constraints.get_names()), 4) + self.assertEqual(op.linear_constraints.get_names()[0], 'c0') + self.assertEqual(op.linear_constraints.get_names()[1], 'second') + self.assertEqual(op.linear_constraints.get_names()[2], 'middle') + self.assertEqual(op.linear_constraints.get_names()[3], 'last') + # ['c0', 'second', 'middle', 'last'] + + def test_senses1(self): + op = OptimizationProblem() + op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) + op.linear_constraints.get_senses() + self.assertEqual(len(op.linear_constraints.get_senses()), 4) + self.assertEqual(op.linear_constraints.get_senses()[0], 'E') + # ['E', 'E', 'E', 'E'] + op.linear_constraints.set_senses("c1", "G") + self.assertEqual(op.linear_constraints.get_senses(1), 'G') + op.linear_constraints.set_senses([("c3", "L"), (2, "R")]) + # ['E', 'G', 'R', 'L'] + self.assertEqual(op.linear_constraints.get_senses()[0], 'E') + self.assertEqual(op.linear_constraints.get_senses()[1], 'G') + self.assertEqual(op.linear_constraints.get_senses()[2], 'R') + self.assertEqual(op.linear_constraints.get_senses()[3], 'L') + + def test_linear_components(self): + op = OptimizationProblem() + op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) + op.variables.add(names=["x0", "x1"]) + op.linear_constraints.set_linear_components("c0", [["x0"], [1.0]]) + self.assertEqual(op.linear_constraints.get_rows("c0").ind[0], 0) + self.assertAlmostEqual(op.linear_constraints.get_rows("c0").val[0], 1.0) + # SparsePair(ind = [0], val = [1.0]) + op.linear_constraints.set_linear_components([("c3", SparsePair(ind=["x1"], val=[-1.0])), + (2, [[0, 1], [-2.0, 3.0]])]) + op.linear_constraints.get_rows() + # [SparsePair(ind = [0], val = [1.0]), + # SparsePair(ind = [], val = []), + # SparsePair(ind = [0, 1], val = [-2.0, 3.0]), + # SparsePair(ind = [1], val = [-1.0])] + self.assertEqual(op.linear_constraints.get_rows()[0].ind[0], 0) + self.assertAlmostEqual(op.linear_constraints.get_rows()[0].val[0], 1.0) + self.assertEqual(op.linear_constraints.get_rows()[2].ind[0], 0) + self.assertAlmostEqual(op.linear_constraints.get_rows()[2].val[0], -2.0) + + def test_linear_components_ranges(self): + op = OptimizationProblem() + op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) + op.linear_constraints.set_range_values("c1", 1.0) + self.assertEqual(len(op.linear_constraints.get_range_values()), 4) + self.assertAlmostEqual(op.linear_constraints.get_range_values()[0], 0.0) + # [0.0, 1.0, 0.0, 0.0] + op.linear_constraints.set_range_values([("c3", 2.0), (2, -1.0)]) + # [0.0, 1.0, -1.0, 2.0] + self.assertEqual(len(op.linear_constraints.get_range_values()), 4) + self.assertAlmostEqual(op.linear_constraints.get_range_values()[2], -1.0) + self.assertAlmostEqual(op.linear_constraints.get_range_values()[3], 2.0) + + def test_rows(self): + op = OptimizationProblem() + op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) + op.variables.add(names=["x0", "x1"]) + op.linear_constraints.set_coefficients("c0", "x1", 1.0) + self.assertEqual(op.linear_constraints.get_rows(0).ind[0], 1) + self.assertAlmostEqual(op.linear_constraints.get_rows(0).val[0], 1.0) + # SparsePair(ind = [1], val = [1.0]) + op.linear_constraints.set_coefficients([("c2", "x0", 2.0), + ("c2", "x1", -1.0)]) + # SparsePair(ind = [0, 1], val = [2.0, -1.0]) + self.assertEqual(op.linear_constraints.get_rows("c2").ind[0], 0) + self.assertAlmostEqual(op.linear_constraints.get_rows("c2").val[0], 2.0) + self.assertEqual(op.linear_constraints.get_rows("c2").ind[1], 1) + self.assertAlmostEqual(op.linear_constraints.get_rows("c2").val[1], -1.0) + + def test_rhs2(self): + op = OptimizationProblem() + op.linear_constraints.add(rhs=[1.5 * i for i in range(10)], + names=[str(i) for i in range(10)]) + self.assertEqual(op.linear_constraints.get_num(), 10) + self.assertAlmostEqual(op.linear_constraints.get_rhs(8), 12.0) + self.assertAlmostEqual(op.linear_constraints.get_rhs([2, "0", 5])[0], 3.0) + self.assertAlmostEqual(op.linear_constraints.get_rhs([2, "0", 5])[1], 0.0) + self.assertAlmostEqual(op.linear_constraints.get_rhs([2, "0", 5])[2], 7.5) + # [3.0, 0.0, 7.5] + self.assertEqual(len(op.linear_constraints.get_rhs()), 10) + self.assertEqual(sum(op.linear_constraints.get_rhs()), 67.5) + # [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] + + def test_senses2(self): + op = OptimizationProblem() + op.linear_constraints.add( + senses=["E", "G", "L", "R"], + names=[str(i) for i in range(4)]) + self.assertEqual(op.linear_constraints.get_num(), 4) + self.assertEqual(op.linear_constraints.get_senses(1), 'G') + self.assertEqual(op.linear_constraints.get_senses([2, "0", 1])[0], 'L') + self.assertEqual(op.linear_constraints.get_senses([2, "0", 1])[1], 'E') + self.assertEqual(op.linear_constraints.get_senses([2, "0", 1])[2], 'G') + # ['L', 'E', 'G'] + self.assertEqual(op.linear_constraints.get_senses()[0], 'E') + self.assertEqual(op.linear_constraints.get_senses()[1], 'G') + # ['E', 'G', 'L', 'R'] + + def test_range_values(self): + op = OptimizationProblem() + op.linear_constraints.add( + range_values=[1.5 * i for i in range(10)], + senses=["R"] * 10, + names=[str(i) for i in range(10)]) + self.assertEqual(op.linear_constraints.get_num(), 10) + self.assertAlmostEqual(op.linear_constraints.get_range_values(8), 12.0) + self.assertAlmostEqual(sum(op.linear_constraints.get_range_values([2, "0", 5])), 10.5) + # [3.0, 0.0, 7.5] + self.assertAlmostEqual(sum(op.linear_constraints.get_range_values()), 67.5) + # [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] + + def test_coefficients(self): + op = OptimizationProblem() + op.variables.add(names=["x0", "x1"]) + op.linear_constraints.add( + names=["c0", "c1"], + lin_expr=[[[1], [1.0]], [[0, 1], [2.0, -1.0]]]) + self.assertAlmostEqual(op.linear_constraints.get_coefficients("c0", "x1"), 1.0) + self.assertAlmostEqual(op.linear_constraints.get_coefficients( + [("c1", "x0"), ("c1", "x1")])[0], 2.0) + self.assertAlmostEqual(op.linear_constraints.get_coefficients( + [("c1", "x0"), ("c1", "x1")])[1], -1.0) + + def test_rows2(self): + op = OptimizationProblem() + op.variables.add(names=["x1", "x2", "x3"]) + op.linear_constraints.add( + names=["c0", "c1", "c2", "c3"], + lin_expr=[SparsePair(ind=["x1", "x3"], val=[1.0, -1.0]), + SparsePair(ind=["x1", "x2"], val=[1.0, 1.0]), + SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), + SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])]) + self.assertEqual(op.linear_constraints.get_rows(0).ind[0], 0) + self.assertAlmostEqual(op.linear_constraints.get_rows(0).val[0], 1.0) + self.assertEqual(op.linear_constraints.get_rows(0).ind[1], 2) + self.assertAlmostEqual(op.linear_constraints.get_rows(0).val[1], -1.0) + # SparsePair(ind = [0, 2], val = [1.0, -1.0]) + self.assertEqual(op.linear_constraints.get_rows("c2").ind[0], 0) + self.assertAlmostEqual(op.linear_constraints.get_rows("c2").val[0], -1.0) + self.assertEqual(op.linear_constraints.get_rows("c2").ind[1], 1) + self.assertAlmostEqual(op.linear_constraints.get_rows("c2").val[1], -1.0) + # [SparsePair(ind = [0, 1, 2], val = [-1.0, -1.0, -1.0]), + # SparsePair(ind = [0, 2], val = [1.0, -1.0])] + self.assertEqual(len(op.linear_constraints.get_rows()), 4) + # [SparsePair(ind = [0, 2], val = [1.0, -1.0]), + # SparsePair(ind = [0, 1], val = [1.0, 1.0]), + # SparsePair(ind = [0, 1, 2], val = [-1.0, -1.0, -1.0]), + # SparsePair(ind = [1, 2], val = [10.0, -2.0])] + + def test_nnz(self): + op = OptimizationProblem() + op.variables.add(names=["x1", "x2", "x3"]) + op.linear_constraints.add(names=["c0", "c1", "c2", "c3"], + lin_expr=[SparsePair(ind=["x1", "x3"], val=[1.0, -1.0]), + SparsePair(ind=["x1", "x2"], val=[1.0, 1.0]), + SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), + SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])]) + self.assertEqual(op.linear_constraints.get_num_nonzeros(), 9) + + def test_names2(self): + op = OptimizationProblem() + op.linear_constraints.add(names=["c" + str(i) for i in range(10)]) + self.assertEqual(op.linear_constraints.get_num(), 10) + self.assertEqual(op.linear_constraints.get_names(8), 'c8') + self.assertEqual(op.linear_constraints.get_names([2, 0, 5])[0], 'c2') + # ['c2', 'c0', 'c5'] + self.assertEqual(len(op.linear_constraints.get_names()), 10) + # ['c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9'] diff --git a/test/optimization/test_min_eigen_optimizer.py b/test/optimization/test_min_eigen_optimizer.py new file mode 100644 index 0000000000..f55155139a --- /dev/null +++ b/test/optimization/test_min_eigen_optimizer.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +""" Test Min Eigen Optimizer """ + +from test.optimization.common import QiskitOptimizationTestCase +import numpy as np +from qiskit import BasicAer + +from qiskit.aqua import QuantumInstance +from qiskit.aqua.algorithms import ExactEigensolver +from qiskit.aqua.algorithms import QAOA +from qiskit.aqua.components.optimizers import COBYLA + +from qiskit.optimization.algorithms import MinEigenOptimizer, CplexOptimizer +from qiskit.optimization.problems import OptimizationProblem + +from ddt import ddt, data + + +@ddt +class TestMinEigenOptimizer(QiskitOptimizationTestCase): + """Min Eigen Optimizer Tests.""" + + def setUp(self): + super().setUp() + + self.resource_path = './test/optimization/resources/' + + # setup minimum eigen solvers + self.min_eigen_solvers = {} + + # exact eigen solver + self.min_eigen_solvers['exact'] = ExactEigensolver() + + # QAOA + optimizer = COBYLA() + self.min_eigen_solvers['qaoa'] = QAOA(optimizer=optimizer) + + @data( + ('exact', None, 'op_ip1.lp'), + ('qaoa', 'statevector_simulator', 'op_ip1.lp'), + ('qaoa', 'qasm_simulator', 'op_ip1.lp') + ) + def test_min_eigen_optimizer(self, config): + """ Min Eigen Optimizer Test """ + + # unpack configuration + min_eigen_solver_name, backend, filename = config + + # get minimum eigen solver + min_eigen_solver = self.min_eigen_solvers[min_eigen_solver_name] + if backend: + min_eigen_solver._quantum_instance = BasicAer.get_backend(backend) + + # construct minimum eigen optimizer + min_eigen_optimizer = MinEigenOptimizer(min_eigen_solver) + + # load optimization problem + problem = OptimizationProblem() + problem.read(self.resource_path + filename) + + # solve problem with cplex + cplex = CplexOptimizer() + cplex_result = cplex.solve(problem) + + # solve problem + result = min_eigen_optimizer.solve(problem) + + # analyze results + self.assertAlmostEqual(cplex_result.fval, result.fval) diff --git a/test/optimization/test_objective.py b/test/optimization/test_objective.py new file mode 100644 index 0000000000..05ee460d8a --- /dev/null +++ b/test/optimization/test_objective.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" Test ObjectiveInterface """ + +from qiskit.optimization import OptimizationProblem +from test.optimization.common import QiskitOptimizationTestCase + + +class TestObjective(QiskitOptimizationTestCase): + """Test ObjectiveInterface""" + + def setUp(self): + super().setUp() + + def test_set_empty_quadratic(self): + op = OptimizationProblem() + op.objective.set_quadratic([]) diff --git a/test/optimization/test_optimization_problem.py b/test/optimization/test_optimization_problem.py new file mode 100644 index 0000000000..0f297139d0 --- /dev/null +++ b/test/optimization/test_optimization_problem.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" Test OptimizationProblem """ + +import numpy as np +import os.path +import tempfile +import qiskit.optimization.problems.optimization_problem +from test.optimization.common import QiskitOptimizationTestCase +from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError + + +class TestOptimizationProblem(QiskitOptimizationTestCase): + """Test OptimizationProblem without the members that have separate test classes + (VariablesInterface, etc).""" + + def setUp(self): + super().setUp() + self.resource_file = './test/optimization/resources/op_ip2.lp' + + def test_constructor1(self): + op = qiskit.optimization.OptimizationProblem() + op.variables.add(names=['x1', 'x2', 'x3']) + self.assertEqual(op.variables.get_num(), 3) + + def test_constructor2(self): + with self.assertRaises(QiskitOptimizationError): + op = qiskit.optimization.OptimizationProblem("unknown") + # If filename does not exist, an exception is raised. + + def test_constructor3(self): + # we can pass at most one argument + with self.assertRaises(QiskitOptimizationError): + op = qiskit.optimization.OptimizationProblem("test", "west") + + def test_constructor_context(self): + with qiskit.optimization.OptimizationProblem() as op: + op.variables.add(names=['x1', 'x2', 'x3']) + self.assertEqual(op.variables.get_num(), 3) + + # def test_end(self): + # op = qiskit.optimization.OptimizationProblem() + # op.end() + # TODO: we do not need to release the object + # with self.assertRaises(QiskitOptimizationError): + # op.variables.add(names=['x1', 'x2', 'x3']) + + def test_read1(self): + op = qiskit.optimization.OptimizationProblem() + op.read(self.resource_file) + self.assertEqual(op.variables.get_num(), 3) + + def test_write1(self): + op = qiskit.optimization.OptimizationProblem() + op.variables.add(names=['x1', 'x2', 'x3']) + f, fn = tempfile.mkstemp(suffix='.lp') + os.close(f) + op.write(fn) + assert os.path.exists(fn) == 1 + + def test_write2(self): + op1 = qiskit.optimization.OptimizationProblem() + op1.variables.add(names=['x1', 'x2', 'x3']) + f, fn = tempfile.mkstemp(suffix='.lp') + os.close(f) + op1.write(fn) + op2 = qiskit.optimization.OptimizationProblem() + op2.read(fn) + self.assertEqual(op2.variables.get_num(), 3) + + def test_write3(self): + op = qiskit.optimization.OptimizationProblem() + op.variables.add(names=['x1', 'x2', 'x3']) + + class NoOpStream(object): + def __init__(self): + self.was_called = False + + def write(self, bytes): + self.was_called = True + pass + + def flush(self): + pass + stream = NoOpStream() + op.write_to_stream(stream) + self.assertEqual(stream.was_called, True) + with self.assertRaises(QiskitOptimizationError): + op.write_to_stream("this-is-no-stream") + + def test_write4(self): + # Writes a problem as a string in the given file format. + op = qiskit.optimization.OptimizationProblem() + op.variables.add(names=['x1', 'x2', 'x3']) + lp_str = op.write_as_string("lp") + self.assertGreater(len(lp_str), 0) + + def test_problem_type1(self): + op = qiskit.optimization.OptimizationProblem() + op.read(self.resource_file) + self.assertEqual(op.get_problem_type(), + qiskit.optimization.problems.problem_type.CPXPROB_QP) + self.assertEqual(op.problem_type[op.get_problem_type()], 'QP') + + def test_problem_type2(self): + op = qiskit.optimization.OptimizationProblem() + op.set_problem_type(op.problem_type.LP) + self.assertEqual(op.get_problem_type(), + qiskit.optimization.problems.problem_type.CPXPROB_LP) + self.assertEqual(op.problem_type[op.get_problem_type()], 'LP') + + def test_problem_name(self): + op = qiskit.optimization.OptimizationProblem() + op.set_problem_name("test") + # test + self.assertEqual(op.get_problem_name(), "test") diff --git a/test/optimization/test_variables.py b/test/optimization/test_variables.py new file mode 100644 index 0000000000..1d77a9eb7d --- /dev/null +++ b/test/optimization/test_variables.py @@ -0,0 +1,219 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" Test VariablesInterface """ + +import numpy as np +import os.path +import tempfile +from cplex import SparsePair +import qiskit.optimization.problems.optimization_problem +from cplex import infinity +from test.optimization.common import QiskitOptimizationTestCase +from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError + + +class TestVariables(QiskitOptimizationTestCase): + """Test VariablesInterface.""" + + def setUp(self): + super().setUp() + + def test_type(self): + op = qiskit.optimization.OptimizationProblem() + op.variables.type.binary + op.variables.type['B'] + + def test_initial(self): + op = qiskit.optimization.OptimizationProblem() + op.variables.add(names=["x0", "x1", "x2"]) + # default values for lower_bounds are 0.0 + self.assertAlmostEqual(sum(op.variables.get_lower_bounds()), 0.0) + # values can be set either one at a time or many at a time + op.variables.set_lower_bounds(0, 1.0) + self.assertAlmostEqual(sum(op.variables.get_lower_bounds()), 1.0) + op.variables.set_lower_bounds([("x1", -1.0), (2, 3.0)]) + self.assertAlmostEqual(sum(op.variables.get_lower_bounds()), 3.0) + # values can be queried as a sequence in arbitrary order + self.assertAlmostEqual(op.variables.get_lower_bounds(["x1", "x2", 0])[0], -1.0) + self.assertAlmostEqual(op.variables.get_lower_bounds(["x1", "x2", 0])[1], 3.0) + self.assertAlmostEqual(op.variables.get_lower_bounds(["x1", "x2", 0])[2], 1.0) + # can query the number of variables + self.assertEqual(op.variables.get_num(), 3) + op.variables.set_types(0, op.variables.type.binary) + self.assertEqual(op.variables.get_num_binary(), 1) + + def test_get_num(self): + op = qiskit.optimization.OptimizationProblem() + t = op.variables.type + op.variables.add(types=[t.continuous, t.binary, t.integer]) + self.assertEqual(op.variables.get_num(), 3) + + def test_get_num_continuous(self): + op = qiskit.optimization.OptimizationProblem() + t = op.variables.type + op.variables.add(types=[t.continuous, t.binary, t.integer]) + self.assertEqual(op.variables.get_num_continuous(), 1) + + def test_get_num_integer(self): + op = qiskit.optimization.OptimizationProblem() + t = op.variables.type + op.variables.add(types=[t.continuous, t.binary, t.integer]) + self.assertEqual(op.variables.get_num_integer(), 1) + + def test_get_num_binary(self): + op = qiskit.optimization.OptimizationProblem() + t = op.variables.type + op.variables.add(types=[t.semi_continuous, t.binary, t.integer]) + self.assertEqual(op.variables.get_num_binary(), 1) + + def test_get_num_semicontinuous(self): + op = qiskit.optimization.OptimizationProblem() + t = op.variables.type + op.variables.add(types=[t.semi_continuous, t.semi_integer, t.semi_integer]) + self.assertEqual(op.variables.get_num_semicontinuous(), 1) + + def test_get_num_semiinteger(self): + op = qiskit.optimization.OptimizationProblem() + t = op.variables.type + op.variables.add(types=[t.semi_continuous, t.semi_integer, t.semi_integer]) + self.assertEqual(op.variables.get_num_semiinteger(), 2) + + def test_delete1(self): + op = qiskit.optimization.OptimizationProblem() + op.variables.add(names=[str(i) for i in range(10)]) + self.assertEqual(op.variables.get_num(), 10) + op.variables.delete(8) + self.assertEqual(len(op.variables.get_names()), 9) + self.assertEqual(op.variables.get_names(), ['0', '1', '2', '3', '4', '5', '6', '7', '9']) + op.variables.delete([2, "0", 5]) + self.assertEqual(len(op.variables.get_names()), 6) + self.assertEqual(op.variables.get_names(), ['1', '3', '4', '5', '6', '9']) + op.variables.delete() + self.assertEqual(len(op.variables.get_names()), 0) + + def test_lower_bounds(self): + op = qiskit.optimization.OptimizationProblem() + op.variables.add(names=["x0", "x1", "x2"]) + op.variables.set_lower_bounds(0, 1.0) + op.variables.get_lower_bounds() + # [1.0, 0.0, 0.0] + self.assertEqual(len(op.variables.get_lower_bounds()), 3) + self.assertAlmostEqual(sum(op.variables.get_lower_bounds()), 1.0) + op.variables.set_lower_bounds([(2, 3.0), ("x1", -1.0)]) + op.variables.get_lower_bounds() + # [1.0, -1.0, 3.0] + self.assertEqual(len(op.variables.get_lower_bounds()), 3) + self.assertAlmostEqual(sum(op.variables.get_lower_bounds()), 3.0) + + def test_upper_bounds(self): + op = qiskit.optimization.OptimizationProblem() + op.variables.add(names=["x0", "x1", "x2"]) + op.variables.set_upper_bounds(0, 1.0) + op.variables.set_upper_bounds([("x1", 10.0), (2, 3.0)]) + # [1.0, 10.0, 3.0] + self.assertEqual(len(op.variables.get_upper_bounds()), 3) + self.assertAlmostEqual(sum(op.variables.get_upper_bounds()), 14.0) + + def test_names(self): + op = qiskit.optimization.OptimizationProblem() + t = op.variables.type + op.variables.add(types=[t.continuous, t.binary, t.integer]) + op.variables.set_names(0, "first") + op.variables.set_names([(2, "third"), (1, "second")]) + self.assertEqual(len(op.variables.get_names()), 3) + # ['first', 'second', 'third'] + + def test_lower_bounds1(self): + op = qiskit.optimization.OptimizationProblem() + op.variables.add(names=[str(i) for i in range(5)]) + op.variables.set_types(0, op.variables.type.continuous) + op.variables.set_types([("1", op.variables.type.integer), + ("2", op.variables.type.binary), + ("3", op.variables.type.semi_continuous), + ("4", op.variables.type.semi_integer)]) + self.assertEqual(len(op.variables.get_types()), 5) + # ['C', 'I', 'B', 'S', 'N'] + self.assertEqual(op.variables.get_types(0), 'C') + + def test_lower_bounds2(self): + op = qiskit.optimization.OptimizationProblem() + op.variables.add(lb=[1.5 * i for i in range(10)], + names=[str(i) for i in range(10)]) + self.assertEqual(op.variables.get_num(), 10) + self.assertAlmostEqual(op.variables.get_lower_bounds(8), 12.0) + self.assertEqual(len(op.variables.get_lower_bounds([2, "0", 5])), 3) + self.assertAlmostEqual(op.variables.get_lower_bounds([2, "0", 5])[0], 3.0) + self.assertAlmostEqual(op.variables.get_lower_bounds([2, "0", 5])[1], 0.0) + self.assertAlmostEqual(op.variables.get_lower_bounds([2, "0", 5])[2], 7.5) + self.assertEqual(len(op.variables.get_lower_bounds()), 10) + # [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] + self.assertAlmostEqual(op.variables.get_lower_bounds()[0], 0.0) + + def test_upper_bounds2(self): + op = qiskit.optimization.OptimizationProblem() + op.variables.add(ub=[(1.5 * i) + 1.0 for i in range(10)], + names=[str(i) for i in range(10)]) + self.assertEqual(op.variables.get_num(), 10) + self.assertAlmostEqual(op.variables.get_upper_bounds(8), 13.0) + self.assertEqual(len(op.variables.get_upper_bounds([2, "0", 5])), 3) + self.assertAlmostEqual(op.variables.get_upper_bounds([2, "0", 5])[0], 4.0) + self.assertEqual(len(op.variables.get_upper_bounds()), 10) + # [1.0, 2.5, 4.0, 5.5, 7.0, 8.5, 10.0, 11.5, 13.0, 14.5] + self.assertAlmostEqual(op.variables.get_upper_bounds()[0], 1.0) + + def test_names2(self): + op = qiskit.optimization.OptimizationProblem() + op.variables.add(names=['x' + str(i) for i in range(10)]) + self.assertAlmostEqual(op.variables.get_num(), 10) + self.assertEqual(op.variables.get_names(8), 'x8') + self.assertEqual(len(op.variables.get_names([2, 0, 5])), 3) + self.assertEqual(op.variables.get_names([2, 0, 5])[0], 'x2') + # ['x2', 'x0', 'x5'] + self.assertEqual(len(op.variables.get_names()), 10) + # ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9'] + + def test_types(self): + op = qiskit.optimization.OptimizationProblem() + t = op.variables.type + op.variables.add(names=[str(i) for i in range(5)], + types=[t.continuous, t.integer, + t.binary, t.semi_continuous, t.semi_integer]) + self.assertEqual(op.variables.get_num(), 5) + self.assertEqual(op.variables.get_types(3), 'S') + types = op.variables.get_types([2,0,4]) + # ['B', 'C', 'N'] + self.assertEqual(len(types), 3) + self.assertEqual(types[0], 'B') + self.assertEqual(types[1], 'C') + self.assertEqual(types[2], 'N') + + types = op.variables.get_types() + #['C', 'I', 'B', 'S', 'N'] + self.assertEqual(len(types), 5) + self.assertEqual(types[0], 'C') + self.assertEqual(types[1], 'I') + self.assertEqual(types[2], 'B') + self.assertEqual(types[3], 'S') + self.assertEqual(types[4], 'N') + + def test_cols(self): + op = qiskit.optimization.OptimizationProblem() + with self.assertRaises(QiskitOptimizationError): + op.variables.get_cols() + + def test_obj(self): + op = qiskit.optimization.OptimizationProblem() + with self.assertRaises(QiskitOptimizationError): + op.variables.get_obj() From ff0038c6643ecb5f1ae7e10c3242cb4daaef0433 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Tue, 3 Mar 2020 15:27:23 +0100 Subject: [PATCH 002/323] update tests --- qiskit/optimization/__init__.py | 12 ++++++++-- test/optimization/__init__.py | 2 +- test/optimization/common.py | 2 +- test/optimization/optimization_test_case.py | 25 --------------------- test/optimization/test_clique.py | 4 ++-- test/optimization/test_cplex_ising.py | 4 ++-- test/optimization/test_docplex.py | 2 +- test/optimization/test_exact_cover.py | 4 ++-- test/optimization/test_graph_partition.py | 4 ++-- test/optimization/test_partition.py | 4 ++-- test/optimization/test_qaoa.py | 4 ++-- test/optimization/test_set_packing.py | 4 ++-- test/optimization/test_vehicle_routing.py | 2 +- test/optimization/test_vertex_cover.py | 6 ++--- 14 files changed, 31 insertions(+), 48 deletions(-) mode change 100644 => 100755 qiskit/optimization/__init__.py mode change 100644 => 100755 test/optimization/__init__.py delete mode 100644 test/optimization/optimization_test_case.py mode change 100644 => 100755 test/optimization/test_clique.py mode change 100644 => 100755 test/optimization/test_cplex_ising.py mode change 100644 => 100755 test/optimization/test_docplex.py mode change 100644 => 100755 test/optimization/test_exact_cover.py mode change 100644 => 100755 test/optimization/test_graph_partition.py mode change 100644 => 100755 test/optimization/test_partition.py mode change 100644 => 100755 test/optimization/test_qaoa.py mode change 100644 => 100755 test/optimization/test_set_packing.py mode change 100644 => 100755 test/optimization/test_vehicle_routing.py mode change 100644 => 100755 test/optimization/test_vertex_cover.py diff --git a/qiskit/optimization/__init__.py b/qiskit/optimization/__init__.py old mode 100644 new mode 100755 index f94b1c5d36..7e73e54191 --- a/qiskit/optimization/__init__.py +++ b/qiskit/optimization/__init__.py @@ -25,12 +25,20 @@ .. autosummary:: :toctree: - ising + OptimizationProblem """ +CPX_INFBOUND = 1.0E+20 +infinity = CPX_INFBOUND + +from qiskit.optimization.utils import QiskitOptimizationError +from qiskit.optimization.problems.linear_constraint import LinearConstraintInterface +from qiskit.optimization.problems.objective import ObjSense, ObjectiveInterface +from qiskit.optimization.problems.optimization_problem import OptimizationProblem from ._logging import (get_qiskit_optimization_logging, set_qiskit_optimization_logging) -__all__ = ['get_qiskit_optimization_logging', +__all__ = ["OptimizationProblem", "QiskitOptimizationError", "LinearConstraintInterface", + "ObjSense", "ObjectiveInterface", "infinity", 'get_qiskit_optimization_logging', 'set_qiskit_optimization_logging'] diff --git a/test/optimization/__init__.py b/test/optimization/__init__.py old mode 100644 new mode 100755 index 53208963d7..8b498a9dc7 --- a/test/optimization/__init__.py +++ b/test/optimization/__init__.py @@ -14,6 +14,6 @@ """ Optimization test packages """ -from .optimization_test_case import QiskitOptimizationTestCase +from .common import QiskitOptimizationTestCase __all__ = ['QiskitOptimizationTestCase'] diff --git a/test/optimization/common.py b/test/optimization/common.py index cbb6006698..6c488f6603 100755 --- a/test/optimization/common.py +++ b/test/optimization/common.py @@ -69,7 +69,7 @@ def setUpClass(cls): cls.log.setLevel(level) @staticmethod - def _get_resource_path(filename, path=Path.TEST): + def get_resource_path(filename, path=Path.TEST): """ Get the absolute path to a resource. Args: filename (string): filename or relative path to the resource. diff --git a/test/optimization/optimization_test_case.py b/test/optimization/optimization_test_case.py deleted file mode 100644 index f147c88ed2..0000000000 --- a/test/optimization/optimization_test_case.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Optimization Test Case""" - -from test import QiskitBaseTestCase - - -class QiskitOptimizationTestCase(QiskitBaseTestCase): - """Optimization Test Case""" - - def setUp(self) -> None: - super().setUp() - self._class_location = __file__ diff --git a/test/optimization/test_clique.py b/test/optimization/test_clique.py old mode 100644 new mode 100755 index b0bad4af99..5731d841e2 --- a/test/optimization/test_clique.py +++ b/test/optimization/test_clique.py @@ -19,8 +19,8 @@ from qiskit import BasicAer from qiskit.aqua import aqua_globals, QuantumInstance -from qiskit.optimization.ising import clique -from qiskit.optimization.ising.common import random_graph, sample_most_likely +from qiskit.optimization.applications.ising import clique +from qiskit.optimization.applications.ising.common import random_graph, sample_most_likely from qiskit.aqua.algorithms import ExactEigensolver, VQE from qiskit.aqua.components.optimizers import COBYLA from qiskit.aqua.components.variational_forms import RY diff --git a/test/optimization/test_cplex_ising.py b/test/optimization/test_cplex_ising.py old mode 100644 new mode 100755 index 0397d91847..9bde9e487f --- a/test/optimization/test_cplex_ising.py +++ b/test/optimization/test_cplex_ising.py @@ -18,8 +18,8 @@ import numpy as np from qiskit.aqua import aqua_globals -from qiskit.optimization.ising import max_cut -from qiskit.optimization.ising.common import random_graph +from qiskit.optimization.applications.ising import max_cut +from qiskit.optimization.applications.ising.common import random_graph from qiskit.aqua.algorithms import CPLEX_Ising diff --git a/test/optimization/test_docplex.py b/test/optimization/test_docplex.py old mode 100644 new mode 100755 index 1da8705a26..fe290b579f --- a/test/optimization/test_docplex.py +++ b/test/optimization/test_docplex.py @@ -24,7 +24,7 @@ from qiskit.aqua import AquaError, aqua_globals from qiskit.aqua.algorithms import ExactEigensolver -from qiskit.optimization.ising import docplex, tsp +from qiskit.optimization.applications.ising import docplex, tsp from qiskit.aqua.operators import WeightedPauliOperator # Reference operators and offsets for maxcut and tsp. diff --git a/test/optimization/test_exact_cover.py b/test/optimization/test_exact_cover.py old mode 100644 new mode 100755 index 98af9c4084..bf10801991 --- a/test/optimization/test_exact_cover.py +++ b/test/optimization/test_exact_cover.py @@ -20,8 +20,8 @@ from qiskit import BasicAer from qiskit.aqua import aqua_globals, QuantumInstance -from qiskit.optimization.ising import exact_cover -from qiskit.optimization.ising.common import sample_most_likely +from qiskit.optimization.applications.ising import exact_cover +from qiskit.optimization.applications.ising.common import sample_most_likely from qiskit.aqua.algorithms import ExactEigensolver, VQE from qiskit.aqua.components.optimizers import COBYLA from qiskit.aqua.components.variational_forms import RYRZ diff --git a/test/optimization/test_graph_partition.py b/test/optimization/test_graph_partition.py old mode 100644 new mode 100755 index 2f206c3739..524769c197 --- a/test/optimization/test_graph_partition.py +++ b/test/optimization/test_graph_partition.py @@ -18,8 +18,8 @@ import numpy as np from qiskit import BasicAer from qiskit.aqua import aqua_globals, QuantumInstance -from qiskit.optimization.ising import graph_partition -from qiskit.optimization.ising.common import random_graph, sample_most_likely +from qiskit.optimization.applications.ising import graph_partition +from qiskit.optimization.applications.ising.common import random_graph, sample_most_likely from qiskit.aqua.algorithms import ExactEigensolver, VQE from qiskit.aqua.components.variational_forms import RY from qiskit.aqua.components.optimizers import SPSA diff --git a/test/optimization/test_partition.py b/test/optimization/test_partition.py old mode 100644 new mode 100755 index a502b84847..25e3a02c3d --- a/test/optimization/test_partition.py +++ b/test/optimization/test_partition.py @@ -18,8 +18,8 @@ import numpy as np from qiskit import BasicAer from qiskit.aqua import aqua_globals, QuantumInstance -from qiskit.optimization.ising import partition -from qiskit.optimization.ising.common import read_numbers_from_file, sample_most_likely +from qiskit.optimization.applications.ising import partition +from qiskit.optimization.applications.ising.common import read_numbers_from_file, sample_most_likely from qiskit.aqua.algorithms import ExactEigensolver, VQE from qiskit.aqua.components.optimizers import SPSA from qiskit.aqua.components.variational_forms import RY diff --git a/test/optimization/test_qaoa.py b/test/optimization/test_qaoa.py old mode 100644 new mode 100755 index 7e25d1170d..0cefb7f383 --- a/test/optimization/test_qaoa.py +++ b/test/optimization/test_qaoa.py @@ -21,8 +21,8 @@ from ddt import ddt, idata, unpack from qiskit import BasicAer -from qiskit.optimization.ising import max_cut -from qiskit.optimization.ising.common import sample_most_likely +from qiskit.optimization.applications.ising import max_cut +from qiskit.optimization.applications.ising.common import sample_most_likely from qiskit.aqua.components.optimizers import COBYLA from qiskit.aqua.algorithms import QAOA from qiskit.aqua import QuantumInstance, aqua_globals diff --git a/test/optimization/test_set_packing.py b/test/optimization/test_set_packing.py old mode 100644 new mode 100755 index 42c01c25e2..751cf45e07 --- a/test/optimization/test_set_packing.py +++ b/test/optimization/test_set_packing.py @@ -18,8 +18,8 @@ from test.optimization import QiskitOptimizationTestCase import numpy as np -from qiskit.optimization.ising import set_packing -from qiskit.optimization.ising.common import sample_most_likely +from qiskit.optimization.applications.ising import set_packing +from qiskit.optimization.applications.ising.common import sample_most_likely from qiskit.aqua import QuantumInstance, aqua_globals from qiskit.aqua.algorithms import ExactEigensolver, VQE from qiskit.aqua.components.optimizers import SPSA diff --git a/test/optimization/test_vehicle_routing.py b/test/optimization/test_vehicle_routing.py old mode 100644 new mode 100755 index 8834a00e0c..89fd4f23fd --- a/test/optimization/test_vehicle_routing.py +++ b/test/optimization/test_vehicle_routing.py @@ -20,7 +20,7 @@ from qiskit.quantum_info import Pauli from qiskit.aqua import aqua_globals from qiskit.aqua.algorithms import ExactEigensolver -from qiskit.optimization.ising.vehicle_routing import get_operator +from qiskit.optimization.applications.ising.vehicle_routing import get_operator # To run only this test, issue: diff --git a/test/optimization/test_vertex_cover.py b/test/optimization/test_vertex_cover.py old mode 100644 new mode 100755 index 130f12c376..d1490ff123 --- a/test/optimization/test_vertex_cover.py +++ b/test/optimization/test_vertex_cover.py @@ -12,15 +12,15 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" Text Vertex Cover """ +""" Test Vertex Cover """ from test.optimization import QiskitOptimizationTestCase import numpy as np from qiskit import BasicAer from qiskit.aqua import aqua_globals, QuantumInstance -from qiskit.optimization.ising import vertex_cover -from qiskit.optimization.ising.common import random_graph, sample_most_likely +from qiskit.optimization.applications.ising import vertex_cover +from qiskit.optimization.applications.ising.common import random_graph, sample_most_likely from qiskit.aqua.algorithms import ExactEigensolver, VQE from qiskit.aqua.components.variational_forms import RYRZ from qiskit.aqua.components.optimizers import SPSA From d5ece9f6a2d21d07af3a6df1b5571e11f08d4c16 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Tue, 3 Mar 2020 16:00:04 +0100 Subject: [PATCH 003/323] add min eigen solver interface --- .../eigen_solvers/exact_eigen_solver.py | 63 ++++++++++--- .../minimum_eigen_solvers/min_eigen_solver.py | 36 +++++++ .../minimum_eigen_solvers/qaoa/qaoa.py | 21 ++++- .../minimum_eigen_solvers/qaoa/var_form.py | 0 .../algorithms/minimum_eigen_solvers/vqe.py | 67 +++++++++++-- qiskit/aqua/algorithms/vq_algorithm.py | 3 +- test/aqua/test_compute_min_eigenvalue.py | 94 +++++++++++++++++++ 7 files changed, 262 insertions(+), 22 deletions(-) mode change 100644 => 100755 qiskit/aqua/algorithms/eigen_solvers/exact_eigen_solver.py create mode 100644 qiskit/aqua/algorithms/minimum_eigen_solvers/min_eigen_solver.py mode change 100644 => 100755 qiskit/aqua/algorithms/minimum_eigen_solvers/qaoa/qaoa.py mode change 100644 => 100755 qiskit/aqua/algorithms/minimum_eigen_solvers/qaoa/var_form.py mode change 100644 => 100755 qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py create mode 100755 test/aqua/test_compute_min_eigenvalue.py diff --git a/qiskit/aqua/algorithms/eigen_solvers/exact_eigen_solver.py b/qiskit/aqua/algorithms/eigen_solvers/exact_eigen_solver.py old mode 100644 new mode 100755 index cfc7192dfb..9d8212c02b --- a/qiskit/aqua/algorithms/eigen_solvers/exact_eigen_solver.py +++ b/qiskit/aqua/algorithms/eigen_solvers/exact_eigen_solver.py @@ -19,8 +19,9 @@ import numpy as np from scipy import sparse as scisparse +from qiskit.aqua.algorithms import MinEigenSolver from qiskit.aqua.algorithms import ClassicalAlgorithm -from qiskit.aqua.operators import op_converter +from qiskit.aqua.operators import MatrixOperator, op_converter # pylint: disable=unused-import from qiskit.aqua.operators import BaseOperator from qiskit.aqua.utils.validation import validate_min @@ -29,7 +30,7 @@ # pylint: disable=invalid-name -class ExactEigensolver(ClassicalAlgorithm): +class ExactEigensolver(ClassicalAlgorithm, MinEigenSolver): r""" The Exact Eigensolver algorithm. @@ -42,7 +43,7 @@ class ExactEigensolver(ClassicalAlgorithm): operator size, mostly in terms of number of qubits it represents, gets larger. """ - def __init__(self, operator: BaseOperator, k: int = 1, + def __init__(self, operator: Optional[BaseOperator] = None, k: int = 1, aux_operators: Optional[List[BaseOperator]] = None) -> None: """ Args: @@ -53,7 +54,11 @@ def __init__(self, operator: BaseOperator, k: int = 1, validate_min('k', k, 1) super().__init__() - self._operator = op_converter.to_matrix_operator(operator) + if operator: + self._operator = op_converter.to_matrix_operator(operator) + else: + self._operator = None + if aux_operators is None: self._aux_operators = [] else: @@ -62,22 +67,38 @@ def __init__(self, operator: BaseOperator, k: int = 1, self._aux_operators = \ [op_converter.to_matrix_operator(aux_op) for aux_op in aux_operators] self._k = k - if self._k > self._operator.matrix.shape[0]: + if self._operator and self._k > self._operator.matrix.shape[0]: self._k = self._operator.matrix.shape[0] logger.debug("WARNING: Asked for %s eigenvalues but max possible is %s.", k, self._k) self._ret = {} + @property + def operator(self): + """Return the operator.""" + return self._operator + + @operator.setter + def operator(self, operator): + self._operator = op_converter.to_matrix_operator(operator) + def _solve(self): - if self._operator.dia_matrix is None: - if self._k >= self._operator.matrix.shape[0] - 1: + if self._operator is None: + raise ValueError('The operator has not been set!') + + operator = self._operator + if not isinstance(operator, MatrixOperator): + operator = op_converter.to_matrix_operator(operator) + + if operator.dia_matrix is None: + if self._k >= operator.matrix.shape[0] - 1: logger.debug("Scipy doesn't support to get all eigenvalues, using numpy instead.") - eigval, eigvec = np.linalg.eig(self._operator.matrix.toarray()) + eigval, eigvec = np.linalg.eig(operator.matrix.toarray()) else: - eigval, eigvec = scisparse.linalg.eigs(self._operator.matrix, k=self._k, which='SR') + eigval, eigvec = scisparse.linalg.eigs(operator.matrix, k=self._k, which='SR') else: - eigval = np.sort(self._operator.matrix.data)[:self._k] - temp = np.argsort(self._operator.matrix.data)[:self._k] - eigvec = np.zeros((self._operator.matrix.shape[0], self._k)) + eigval = np.sort(operator.matrix.data)[:self._k] + temp = np.argsort(operator.matrix.data)[:self._k] + eigvec = np.zeros((operator.matrix.shape[0], self._k)) for i, idx in enumerate(temp): eigvec[idx, i] = 1.0 if self._k > 1: @@ -116,6 +137,24 @@ def _eval_aux_operators(self, wavefn, threshold=1e-12): values.append((value, 0)) return np.asarray(values) + def compute_min_eigenvalue(self, operator: Optional[BaseOperator] = None + ) -> Tuple[List[float], float]: + # if operator is None, set it to the one given in the initializer + # if it is still None, raise an error + operator = operator or self._operator + if operator is None: + raise AquaError('Provide an operator either in the initializer or this method.') + + # run the algorithm with the operator passed in + # (bit hacky w/o the QuantumAlgorithm refactor) + current_operator = self._operator + self._operator = operator + ret = self.run(self._quantum_instance) + self._operator = current_operator + + # return the eigenvector corresponding to the lowest eigenvalue + return ret['wavefunction'][0] if self._k == 1 else ret['wavefunction'][0], ret['energy'] + def _run(self): """ Run the algorithm to compute up to the requested k number of eigenvalues. diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/min_eigen_solver.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/min_eigen_solver.py new file mode 100644 index 0000000000..fdb29498cc --- /dev/null +++ b/qiskit/aqua/algorithms/minimum_eigen_solvers/min_eigen_solver.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +"""TODO""" + +from abc import abstractmethod + + +class MinEigenSolver: + """Base class for algorithms that search the minimal eigenvalue of an operator.""" + + @abstractmethod + def __init__(self, operator=None): + self._operator = operator + + @abstractmethod + def compute_min_eigenvalue(self, operator=None): + raise NotImplementedError() + + # Cannot implement this, since ExactEigenSolver and VQE both inherit from + # QuantumAlgorithm which has the signature `run(self, quantum_instance, kwargs)` + # and not `run(self, operator)`. + # @abstractmethod + # def run(self, operator): + # raise NotImplementedError() diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/qaoa/qaoa.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/qaoa/qaoa.py old mode 100644 new mode 100755 index bc6817a5ef..231a2c9c84 --- a/qiskit/aqua/algorithms/minimum_eigen_solvers/qaoa/qaoa.py +++ b/qiskit/aqua/algorithms/minimum_eigen_solvers/qaoa/qaoa.py @@ -17,6 +17,7 @@ from typing import List, Callable, Optional import logging import numpy as np +from qiskit.aqua import QuantumInstance from qiskit.aqua.operators import BaseOperator from qiskit.aqua.components.initial_states import InitialState from qiskit.aqua.components.optimizers import Optimizer @@ -57,12 +58,14 @@ class QAOA(VQE): be supplied. """ - def __init__(self, operator: BaseOperator, optimizer: Optimizer, p: int = 1, + def __init__(self, operator: Optional[BaseOperator] = None, + optimizer: Optional[Optimizer] = None, p: int = 1, initial_state: Optional[InitialState] = None, mixer: Optional[BaseOperator] = None, initial_point: Optional[np.ndarray] = None, max_evals_grouped: int = 1, aux_operators: Optional[List[BaseOperator]] = None, callback: Optional[Callable[[int, np.ndarray, float, float], None]] = None, - auto_conversion: bool = True) -> None: + auto_conversion: bool = True, quantum_instance: Optional[QuantumInstance] = None + ) -> None: """ Args: operator: Qubit operator @@ -105,4 +108,16 @@ def __init__(self, operator: BaseOperator, optimizer: Optimizer, p: int = 1, mixer_operator=mixer) super().__init__(operator, var_form, optimizer, initial_point=initial_point, max_evals_grouped=max_evals_grouped, aux_operators=aux_operators, - callback=callback, auto_conversion=auto_conversion) + callback=callback, auto_conversion=auto_conversion, + quantum_instance=quantum_instance) + + def compute_min_eigenvalue(self, operator=None): + + if operator is None: + operator = self._operator + + var_form = QAOAVarForm(operator.copy(), self._p, initial_state=self._initial_state, + mixer_operator=self._mixer) + self.var_form = var_form + + return super().compute_min_eigenvalue(operator) diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/qaoa/var_form.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/qaoa/var_form.py old mode 100644 new mode 100755 diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py old mode 100644 new mode 100755 index 4ade1452b4..3af67e4540 --- a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py @@ -27,6 +27,8 @@ from qiskit import ClassicalRegister, QuantumCircuit from qiskit.circuit import ParameterVector +from qiskit.aqua import QuantumInstance +from qiskit.aqua.algorithms import MinEigenSolver from qiskit.aqua.algorithms import VQAlgorithm from qiskit.aqua import AquaError from qiskit.aqua.operators import (TPBGroupedWeightedPauliOperator, WeightedPauliOperator, @@ -40,7 +42,7 @@ logger = logging.getLogger(__name__) -class VQE(VQAlgorithm): +class VQE(VQAlgorithm, MinEigenSolver): r""" The Variational Quantum Eigensolver algorithm. @@ -75,11 +77,13 @@ class VQE(VQAlgorithm): as the upper bound, the default value will be :math:`2\pi`. """ - def __init__(self, operator: BaseOperator, var_form: VariationalForm, optimizer: Optimizer, + def __init__(self, operator: Optional[BaseOperator] = None, + var_form: Optional[VariationalForm] = None, optimizer: Optional[Optimizer] = None, initial_point: Optional[np.ndarray] = None, max_evals_grouped: int = 1, aux_operators: Optional[List[BaseOperator]] = None, callback: Optional[Callable[[int, np.ndarray, float, float], None]] = None, - auto_conversion: bool = True) -> None: + auto_conversion: bool = True, quantum_instance: Optional[QuantumInstance] = None + ) -> None: """ Args: @@ -115,16 +119,22 @@ def __init__(self, operator: BaseOperator, var_form: VariationalForm, optimizer: - for *qasm simulator or real backend:* :class:`~qiskit.aqua.operators.TPBGroupedWeightedPauliOperator` """ + + # TODO add setter and remove this error + if optimizer is None: + raise ValueError('No optimizer provided.') + super().__init__(var_form=var_form, optimizer=optimizer, cost_fn=self._energy_evaluation, initial_point=initial_point) + self._quantum_instance = quantum_instance self._use_simulator_snapshot_mode = None self._ret = None self._eval_time = None self._optimizer.set_max_evals_grouped(max_evals_grouped) self._callback = callback - if initial_point is None: + if initial_point is None and var_form is not None: self._initial_point = var_form.preferred_init_points self._operator = operator self._eval_count = 0 @@ -136,10 +146,18 @@ def __init__(self, operator: BaseOperator, var_form: VariationalForm, optimizer: self._aux_operators.append(aux_op) self._auto_conversion = auto_conversion logger.info(self.print_settings()) - self._var_form_params = ParameterVector('θ', self._var_form.num_parameters) self._parameterized_circuits = None + @property + def operator(self): + """Return the operator.""" + return self._operator + + @operator.setter + def operator(self, operator): + self._operator = operator + @property def setting(self): """Prepare the setting of VQE as a string.""" @@ -166,7 +184,10 @@ def print_settings(self): self.__class__.__name__) ret += "{}".format(self.setting) ret += "===============================================================\n" - ret += "{}".format(self._var_form.setting) + if self.var_form is not None: + ret += "{}".format(self._var_form.setting) + else: + ret += "var_form not set." ret += "===============================================================\n" ret += "{}".format(self._optimizer.setting) ret += "===============================================================\n" @@ -269,6 +290,37 @@ def _eval_aux_ops(self, threshold=1e-12, params=None): aux_op_vals[0, :] = np.asarray(values) self._ret['aux_ops'] = aux_op_vals + def compute_min_eigenvalue(self, operator=None): + """Compute the minimal eigenvalue along with the eigenvector. + + Args: + operator (BaseOperator): The operator of which to compute the minimal + eigenvalue + eigenvector. + + Returns: + Union[Tuple[np.array, float], Tuple[dict, float]]: Eigenvector (either as np.array, + if statevector simulation is used or otherwise as dictionary) and the Eigenvalue. + """ + # keep track of the current state of the operator + current_operator = self._operator + + # if operator is None, set it to the one given in the initializer + # if it is still None, raise an error + operator = operator or self._operator + if operator is None: + raise AquaError('Provide an operator either in the initializer or this method.') + + if self._quantum_instance is None: + raise AquaError('Provide a QuantumInstance or BaseBackend to the initializer!') + + # run the algorithm with the operator passed in + # (bit hacky w/o the QuantumAlgorithm refactor) + self._operator = operator + ret = self.run(self._quantum_instance) + self._operator = current_operator + + return ret['min_vector'], ret['min_val'] + def _run(self): """ Run the algorithm to compute the minimum eigenvalue. @@ -279,6 +331,9 @@ def _run(self): Raises: AquaError: wrong setting of operator and backend. """ + if self.operator is None: + raise ValueError('The operator has not been set!') + if self._auto_conversion: self._operator = \ self._config_the_best_mode(self._operator, self._quantum_instance.backend) diff --git a/qiskit/aqua/algorithms/vq_algorithm.py b/qiskit/aqua/algorithms/vq_algorithm.py index 00af9bc664..40f589e5d5 100644 --- a/qiskit/aqua/algorithms/vq_algorithm.py +++ b/qiskit/aqua/algorithms/vq_algorithm.py @@ -54,7 +54,7 @@ def __init__(self, super().__init__() if var_form is None: raise AquaError('Missing variational form.') - self._var_form = var_form + self.var_form = var_form if optimizer is None: raise AquaError('Missing optimizer.') @@ -196,6 +196,7 @@ def var_form(self): def var_form(self, new_value): """ sets var forms """ self._var_form = new_value + self._var_form_params = ParameterVector('θ', new_value.num_parameters) @property def optimizer(self): diff --git a/test/aqua/test_compute_min_eigenvalue.py b/test/aqua/test_compute_min_eigenvalue.py new file mode 100755 index 0000000000..159b751529 --- /dev/null +++ b/test/aqua/test_compute_min_eigenvalue.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +""" Test VQE """ + +import unittest +import os +from test.aqua.common import QiskitAquaTestCase +import numpy as np +from parameterized import parameterized +from qiskit import BasicAer + +from qiskit.aqua import QuantumInstance, aqua_globals +from qiskit.aqua.operators import WeightedPauliOperator, MatrixOperator +from qiskit.aqua.components.variational_forms import RY, RYRZ +from qiskit.aqua.components.optimizers import L_BFGS_B, COBYLA, SPSA, SLSQP +from qiskit.aqua.components.initial_states import Zero +from qiskit.aqua.algorithms import VQE, ExactEigensolver + + +class TestComputeMinEigenvalue(QiskitAquaTestCase): + """ Test VQE """ + + def setUp(self): + super().setUp() + self.seed = 50 + aqua_globals.random_seed = self.seed + pauli_dict = { + 'paulis': [{"coeff": {"imag": 0.0, "real": -1.052373245772859}, "label": "II"}, + {"coeff": {"imag": 0.0, "real": 0.39793742484318045}, "label": "IZ"}, + {"coeff": {"imag": 0.0, "real": -0.39793742484318045}, "label": "ZI"}, + {"coeff": {"imag": 0.0, "real": -0.01128010425623538}, "label": "ZZ"}, + {"coeff": {"imag": 0.0, "real": 0.18093119978423156}, "label": "XX"} + ] + } + self.qubit_op = WeightedPauliOperator.from_dict(pauli_dict) + + def test_vqe(self): + """ VQE test """ + quantum_instance = QuantumInstance(BasicAer.get_backend('statevector_simulator'), + basis_gates=['u1', 'u2', 'u3', 'cx', 'id'], + coupling_map=[[0, 1]], + seed_simulator=aqua_globals.random_seed, + seed_transpiler=aqua_globals.random_seed) + + dummy_operator = MatrixOperator([]) + vqe = VQE(dummy_operator, RYRZ(self.qubit_op.num_qubits), L_BFGS_B(), + quantum_instance=quantum_instance) + + output = vqe.compute_min_eigenvalue(self.qubit_op) + + vector, energy = output + self.assertAlmostEqual(energy, -1.85727503) + + def test_vqe_qasm(self): + """ VQE QASM test """ + backend = BasicAer.get_backend('qasm_simulator') + num_qubits = self.qubit_op.num_qubits + var_form = RY(num_qubits, 3) + optimizer = SPSA(max_trials=300, last_avg=5) + quantum_instance = QuantumInstance(backend, shots=10000, + seed_simulator=self.seed, + seed_transpiler=self.seed) + dummy_operator = MatrixOperator([]) + vqe = VQE(dummy_operator, var_form, optimizer, max_evals_grouped=1, + quantum_instance=quantum_instance) + + output = vqe.compute_min_eigenvalue(self.qubit_op) + + vector, energy = output + self.assertAlmostEqual(energy, -1.85727503, places=2) + + def test_ee(self): + """ EE test """ + dummy_operator = MatrixOperator([[1]]) + ee = ExactEigensolver(self.qubit_op, k=1, aux_operators=[]) + eigenvector, eigenvalue = ee.compute_min_eigenvalue(self.qubit_op) + + self.assertAlmostEqual(eigenvalue, -1.85727503) + + +if __name__ == '__main__': + unittest.main() From 7497ed92ab32e1a1902125f91ba490616f465d00 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Tue, 3 Mar 2020 16:30:06 +0100 Subject: [PATCH 004/323] update min eigen solver --- qiskit/aqua/algorithms/__init__.py | 4 +++- .../eigen_solvers/exact_eigen_solver.py | 6 +++--- .../algorithms/minimum_eigen_solvers/__init__.py | 4 +++- .../algorithms/minimum_eigen_solvers/qaoa/qaoa.py | 12 ++++++++++-- .../aqua/algorithms/minimum_eigen_solvers/vqe.py | 2 +- qiskit/aqua/algorithms/vq_algorithm.py | 15 +++++++-------- test/aqua/test_compute_min_eigenvalue.py | 7 ++----- test/optimization/test_min_eigen_optimizer.py | 4 ++-- 8 files changed, 31 insertions(+), 23 deletions(-) diff --git a/qiskit/aqua/algorithms/__init__.py b/qiskit/aqua/algorithms/__init__.py index cde2fea67d..b34e02a27f 100644 --- a/qiskit/aqua/algorithms/__init__.py +++ b/qiskit/aqua/algorithms/__init__.py @@ -109,7 +109,8 @@ from .eigen_solvers import ExactEigensolver from .factorizers import Shor from .linear_solvers import HHL, ExactLSsolver -from .minimum_eigen_solvers import VQE, QAOA, IQPE, QPE, CPLEX_Ising, ExactMinimumEigensolver +from .minimum_eigen_solvers import (VQE, QAOA, IQPE, QPE, CPLEX_Ising, ExactMinimumEigensolver, + MinEigenSolver) from .education import EOH, Simon, DeutschJozsa, BernsteinVazirani __all__ = [ @@ -123,6 +124,7 @@ 'ExactEigensolver', 'ExactLSsolver', 'ExactMinimumEigensolver', + 'MinEigenSolver', 'SVM_Classical', 'CPLEX_Ising', 'EOH', diff --git a/qiskit/aqua/algorithms/eigen_solvers/exact_eigen_solver.py b/qiskit/aqua/algorithms/eigen_solvers/exact_eigen_solver.py index 9d8212c02b..33a7193d9b 100755 --- a/qiskit/aqua/algorithms/eigen_solvers/exact_eigen_solver.py +++ b/qiskit/aqua/algorithms/eigen_solvers/exact_eigen_solver.py @@ -13,13 +13,13 @@ # that they have been altered from the originals. """The Exact Eigensolver algorithm.""" -from typing import List, Optional +from typing import List, Optional, Tuple import logging import numpy as np from scipy import sparse as scisparse -from qiskit.aqua.algorithms import MinEigenSolver +from qiskit.aqua.algorithms.minimum_eigen_solvers.min_eigen_solver import MinEigenSolver from qiskit.aqua.algorithms import ClassicalAlgorithm from qiskit.aqua.operators import MatrixOperator, op_converter # pylint: disable=unused-import from qiskit.aqua.operators import BaseOperator @@ -143,7 +143,7 @@ def compute_min_eigenvalue(self, operator: Optional[BaseOperator] = None # if it is still None, raise an error operator = operator or self._operator if operator is None: - raise AquaError('Provide an operator either in the initializer or this method.') + raise ValueError('Provide an operator either in the initializer or this method.') # run the algorithm with the operator passed in # (bit hacky w/o the QuantumAlgorithm refactor) diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/__init__.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/__init__.py index b33da68ec8..68eff485b0 100644 --- a/qiskit/aqua/algorithms/minimum_eigen_solvers/__init__.py +++ b/qiskit/aqua/algorithms/minimum_eigen_solvers/__init__.py @@ -20,6 +20,7 @@ from .qpe import QPE from .cplex import CPLEX_Ising from .exact_minimum_eigen_solver import ExactMinimumEigensolver +from .min_eigen_solver import MinEigenSolver __all__ = [ 'VQE', @@ -27,5 +28,6 @@ 'IQPE', 'QPE', 'CPLEX_Ising', - 'ExactMinimumEigensolver' + 'ExactMinimumEigensolver', + 'MinEigenSolver' ] diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/qaoa/qaoa.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/qaoa/qaoa.py index 231a2c9c84..fb85fde114 100755 --- a/qiskit/aqua/algorithms/minimum_eigen_solvers/qaoa/qaoa.py +++ b/qiskit/aqua/algorithms/minimum_eigen_solvers/qaoa/qaoa.py @@ -104,17 +104,25 @@ def __init__(self, operator: Optional[BaseOperator] = None, :class:`~qiskit.aqua.operators.TPBGroupedWeightedPauliOperator` """ validate_min('p', p, 1) - var_form = QAOAVarForm(operator.copy(), p, initial_state=initial_state, - mixer_operator=mixer) + if operator: + var_form = QAOAVarForm(operator.copy(), p, initial_state=initial_state, + mixer_operator=mixer) + else: + var_form = None super().__init__(operator, var_form, optimizer, initial_point=initial_point, max_evals_grouped=max_evals_grouped, aux_operators=aux_operators, callback=callback, auto_conversion=auto_conversion, quantum_instance=quantum_instance) + self._p = p + self._mixer = mixer + self._initial_state = initial_state def compute_min_eigenvalue(self, operator=None): if operator is None: operator = self._operator + if operator is None: + raise ValueError('Operator needs to be set in constructor, setter, or here!') var_form = QAOAVarForm(operator.copy(), self._p, initial_state=self._initial_state, mixer_operator=self._mixer) diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py index 3af67e4540..0e22cb6f80 100755 --- a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py @@ -28,7 +28,7 @@ from qiskit.circuit import ParameterVector from qiskit.aqua import QuantumInstance -from qiskit.aqua.algorithms import MinEigenSolver +from qiskit.aqua.algorithms.minimum_eigen_solvers.min_eigen_solver import MinEigenSolver from qiskit.aqua.algorithms import VQAlgorithm from qiskit.aqua import AquaError from qiskit.aqua.operators import (TPBGroupedWeightedPauliOperator, WeightedPauliOperator, diff --git a/qiskit/aqua/algorithms/vq_algorithm.py b/qiskit/aqua/algorithms/vq_algorithm.py index 40f589e5d5..11933f4062 100644 --- a/qiskit/aqua/algorithms/vq_algorithm.py +++ b/qiskit/aqua/algorithms/vq_algorithm.py @@ -30,8 +30,7 @@ import logging from abc import abstractmethod import numpy as np - -from qiskit.aqua import AquaError +from qiskit.circuit import ParameterVector from qiskit.aqua.algorithms import QuantumAlgorithm from qiskit.aqua.components.optimizers import Optimizer from qiskit.aqua.components.variational_forms import VariationalForm @@ -47,17 +46,16 @@ class VQAlgorithm(QuantumAlgorithm): """ def __init__(self, - var_form: VariationalForm, - optimizer: Optimizer, + var_form: Optional[VariationalForm] = None, + optimizer: Optional[Optimizer] = None, cost_fn: Optional[Callable] = None, initial_point: Optional[np.ndarray] = None) -> None: super().__init__() - if var_form is None: - raise AquaError('Missing variational form.') + self.var_form = var_form if optimizer is None: - raise AquaError('Missing optimizer.') + raise ValueError('Missing optimizer.') self._optimizer = optimizer self._cost_fn = cost_fn @@ -196,7 +194,8 @@ def var_form(self): def var_form(self, new_value): """ sets var forms """ self._var_form = new_value - self._var_form_params = ParameterVector('θ', new_value.num_parameters) + if new_value: + self._var_form_params = ParameterVector('θ', new_value.num_parameters) @property def optimizer(self): diff --git a/test/aqua/test_compute_min_eigenvalue.py b/test/aqua/test_compute_min_eigenvalue.py index 159b751529..9a1995a29f 100755 --- a/test/aqua/test_compute_min_eigenvalue.py +++ b/test/aqua/test_compute_min_eigenvalue.py @@ -15,16 +15,13 @@ """ Test VQE """ import unittest -import os -from test.aqua.common import QiskitAquaTestCase -import numpy as np -from parameterized import parameterized +from test.aqua.aqua_test_case import QiskitAquaTestCase from qiskit import BasicAer from qiskit.aqua import QuantumInstance, aqua_globals from qiskit.aqua.operators import WeightedPauliOperator, MatrixOperator from qiskit.aqua.components.variational_forms import RY, RYRZ -from qiskit.aqua.components.optimizers import L_BFGS_B, COBYLA, SPSA, SLSQP +from qiskit.aqua.components.optimizers import L_BFGS_B, SPSA, SLSQP from qiskit.aqua.components.initial_states import Zero from qiskit.aqua.algorithms import VQE, ExactEigensolver diff --git a/test/optimization/test_min_eigen_optimizer.py b/test/optimization/test_min_eigen_optimizer.py index f55155139a..adb4d1a2c5 100644 --- a/test/optimization/test_min_eigen_optimizer.py +++ b/test/optimization/test_min_eigen_optimizer.py @@ -16,6 +16,8 @@ from test.optimization.common import QiskitOptimizationTestCase import numpy as np +from ddt import ddt, data + from qiskit import BasicAer from qiskit.aqua import QuantumInstance @@ -26,8 +28,6 @@ from qiskit.optimization.algorithms import MinEigenOptimizer, CplexOptimizer from qiskit.optimization.problems import OptimizationProblem -from ddt import ddt, data - @ddt class TestMinEigenOptimizer(QiskitOptimizationTestCase): From b45673e919bcd036b3ce6ffb70fa1da72c19e483 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 4 Mar 2020 10:06:12 +0100 Subject: [PATCH 005/323] remove ising translators (are now in applications folder) --- qiskit/optimization/ising/__init__.py | 49 ---- qiskit/optimization/ising/clique.py | 143 ---------- qiskit/optimization/ising/common.py | 167 ----------- qiskit/optimization/ising/docplex.py | 277 ------------------- qiskit/optimization/ising/exact_cover.py | 123 -------- qiskit/optimization/ising/graph_partition.py | 102 ------- qiskit/optimization/ising/max_cut.py | 83 ------ qiskit/optimization/ising/partition.py | 70 ----- qiskit/optimization/ising/set_packing.py | 113 -------- qiskit/optimization/ising/stable_set.py | 97 ------- qiskit/optimization/ising/tsp.py | 268 ------------------ qiskit/optimization/ising/vehicle_routing.py | 192 ------------- qiskit/optimization/ising/vertex_cover.py | 115 -------- 13 files changed, 1799 deletions(-) delete mode 100644 qiskit/optimization/ising/__init__.py delete mode 100644 qiskit/optimization/ising/clique.py delete mode 100644 qiskit/optimization/ising/common.py delete mode 100644 qiskit/optimization/ising/docplex.py delete mode 100644 qiskit/optimization/ising/exact_cover.py delete mode 100644 qiskit/optimization/ising/graph_partition.py delete mode 100644 qiskit/optimization/ising/max_cut.py delete mode 100644 qiskit/optimization/ising/partition.py delete mode 100644 qiskit/optimization/ising/set_packing.py delete mode 100644 qiskit/optimization/ising/stable_set.py delete mode 100644 qiskit/optimization/ising/tsp.py delete mode 100644 qiskit/optimization/ising/vehicle_routing.py delete mode 100644 qiskit/optimization/ising/vertex_cover.py diff --git a/qiskit/optimization/ising/__init__.py b/qiskit/optimization/ising/__init__.py deleted file mode 100644 index 04813e82af..0000000000 --- a/qiskit/optimization/ising/__init__.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019. -# -# 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. - -""" -Ising Models (:mod:`qiskit.optimization.ising`) -=============================================== -Ising models for optimization problems - -.. currentmodule:: qiskit.optimization.ising - -Ising Models -============ - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - clique - exact_cover - graph_partition - max_cut - partition - set_packing - stable_set - tsp - vehicle_routing - vertex_cover - -Automatic Ising Model Generator from DoCPLEX Model -================================================== - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - docplex - -""" diff --git a/qiskit/optimization/ising/clique.py b/qiskit/optimization/ising/clique.py deleted file mode 100644 index 56773d8019..0000000000 --- a/qiskit/optimization/ising/clique.py +++ /dev/null @@ -1,143 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -""" -Convert clique instances into Pauli list -Deal with Gset format. See https://web.stanford.edu/~yyye/yyye/Gset/ -""" - -import logging - -import numpy as np -from qiskit.quantum_info import Pauli - -from qiskit.aqua.operators import WeightedPauliOperator - -logger = logging.getLogger(__name__) - - -def get_operator(weight_matrix, K): # pylint: disable=invalid-name - r""" - Generate Hamiltonian for the clique. - - The goals is can we find a complete graph of size K? - - To build the Hamiltonian the following logic is applied. - - | Suppose Xv denotes whether v should appear in the clique (Xv=1 or 0)\n - | H = Ha + Hb\n - | Ha = (K-sum_{v}{Xv})\^2 - | Hb = K(K−1)/2 - sum_{(u,v)\in E}{XuXv} - - | Besides, Xv = (Zv+1)/2 - | By replacing Xv with Zv and simplifying it, we get what we want below. - - Note: in practice, we use H = A\*Ha + Bb, where A is a large constant such as 1000. - - A is like a huge penality over the violation of Ha, - which forces Ha to be 0, i.e., you have exact K vertices selected. - Under this assumption, Hb = 0 starts to make sense, - it means the subgraph constitutes a clique or complete graph. - Note the lowest possible value of Hb is 0. - - Without the above assumption, Hb may be negative (say you select all). - In this case, one needs to use Hb\^2 in the hamiltonian to minimize the difference. - - Args: - weight_matrix (numpy.ndarray) : adjacency matrix. - K (numpy.ndarray): K - - Returns: - tuple(WeightedPauliOperator, float): - The operator for the Hamiltonian and a constant shift for the obj function. - """ - # pylint: disable=invalid-name - num_nodes = len(weight_matrix) - pauli_list = [] - shift = 0 - - Y = K - 0.5*num_nodes # Y = K-sum_{v}{1/2} - - A = 1000 - # Ha part: - shift += A*Y*Y - - for i in range(num_nodes): - for j in range(num_nodes): - if i != j: - xp = np.zeros(num_nodes, dtype=np.bool) - zp = np.zeros(num_nodes, dtype=np.bool) - zp[i] = True - zp[j] = True - pauli_list.append([A*0.25, Pauli(zp, xp)]) - else: - shift += A*0.25 - for i in range(num_nodes): - xp = np.zeros(num_nodes, dtype=np.bool) - zp = np.zeros(num_nodes, dtype=np.bool) - zp[i] = True - pauli_list.append([-A*Y, Pauli(zp, xp)]) - - shift += 0.5*K*(K-1) - - for i in range(num_nodes): - for j in range(i): - if weight_matrix[i, j] != 0: - xp = np.zeros(num_nodes, dtype=np.bool) - zp = np.zeros(num_nodes, dtype=np.bool) - zp[i] = True - zp[j] = True - pauli_list.append([-0.25, Pauli(zp, xp)]) - - zp2 = np.zeros(num_nodes, dtype=np.bool) - zp2[i] = True - pauli_list.append([-0.25, Pauli(zp2, xp)]) - - zp3 = np.zeros(num_nodes, dtype=np.bool) - zp3[j] = True - pauli_list.append([-0.25, Pauli(zp3, xp)]) - - shift += -0.25 - - return WeightedPauliOperator(paulis=pauli_list), shift - - -def satisfy_or_not(x, w, K): # pylint: disable=invalid-name - """Compute the value of a cut. - - Args: - x (numpy.ndarray): binary string as numpy array. - w (numpy.ndarray): adjacency matrix. - K (numpy.ndarray): K - - Returns: - float: value of the cut. - """ - # pylint: disable=invalid-name - X = np.outer(x, x) - w_01 = np.where(w != 0, 1, 0) - - return np.sum(w_01 * X) == K*(K-1) # note sum() count the same edge twice - - -def get_graph_solution(x): - """Get graph solution from binary string. - - Args: - x (numpy.ndarray) : binary string as numpy array. - - Returns: - numpy.ndarray: graph solution as binary numpy array. - """ - return 1 - x diff --git a/qiskit/optimization/ising/common.py b/qiskit/optimization/ising/common.py deleted file mode 100644 index 83f09ed13b..0000000000 --- a/qiskit/optimization/ising/common.py +++ /dev/null @@ -1,167 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2019. -# -# 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. - -""" common module """ - -from collections import OrderedDict - -import numpy as np - -from qiskit.aqua import aqua_globals - - -def random_graph(n, weight_range=10, edge_prob=0.3, negative_weight=True, - savefile=None, seed=None): - """Generate random Erdos-Renyi graph. - - Args: - n (int): number of nodes. - weight_range (int): weights will be smaller than this value, - in absolute value. range: [1, weight_range). - edge_prob (float): probability of edge appearing. - negative_weight (bool): allow to have edge with negative weights - savefile (str or None): name of file where to save graph. - seed (int or None): random seed - if None, will not initialize. - - Returns: - numpy.ndarray: adjacency matrix (with weights). - - """ - assert weight_range >= 0 - if seed: - aqua_globals.random_seed = seed - w = np.zeros((n, n)) - m = 0 - for i in range(n): - for j in range(i+1, n): - if aqua_globals.random.rand() <= edge_prob: - w[i, j] = aqua_globals.random.randint(1, weight_range) - if aqua_globals.random.rand() >= 0.5 and negative_weight: - w[i, j] *= -1 - m += 1 - w += w.T - if savefile: - with open(savefile, 'w') as outfile: - outfile.write('{} {}\n'.format(n, m)) - for i in range(n): - for j in range(i+1, n): - if w[i, j] != 0: - outfile.write('{} {} {}\n'.format(i + 1, j + 1, w[i, j])) - return w - - -def random_number_list(n, weight_range=100, savefile=None, seed=None): - """Generate a set of positive integers within the given range. - - Args: - n (int): size of the set of numbers. - weight_range (int): maximum absolute value of the numbers. - savefile (str or None): write numbers to this file. - seed (Union(int,None)): random seed - if None, will not initialize. - - Returns: - numpy.ndarray: the list of integer numbers. - """ - if seed: - aqua_globals.random_seed = seed - - number_list = aqua_globals.random.randint(low=1, high=(weight_range+1), size=n) - if savefile: - with open(savefile, 'w') as outfile: - for i in range(n): - outfile.write('{}\n'.format(number_list[i])) - return number_list - - -def read_numbers_from_file(filename): - """Read numbers from a file - - Args: - filename (str): name of the file. - - Returns: - numpy.ndarray: list of numbers as a numpy.ndarray. - """ - numbers = [] - with open(filename) as infile: - for line in infile: - assert int(round(float(line))) == float(line) - numbers.append(int(round(float(line)))) - return np.array(numbers) - - -def parse_gset_format(filename): - """Read graph in Gset format from file. - - Args: - filename (str): name of the file. - - Returns: - numpy.ndarray: adjacency matrix as a 2D numpy array. - """ - n = -1 - with open(filename) as infile: - header = True - m = -1 - count = 0 - for line in infile: - v = map(lambda e: int(e), line.split()) # pylint: disable=unnecessary-lambda - if header: - n, m = v - w = np.zeros((n, n)) - header = False - else: - s__, t__, _ = v - s__ -= 1 # adjust 1-index - t__ -= 1 # ditto - w[s__, t__] = t__ - count += 1 - assert m == count - w += w.T - return w - - -def get_gset_result(x): - """Get graph solution in Gset format from binary string. - - Args: - x (numpy.ndarray) : binary string as numpy array. - - Returns: - Dict[int, int]: graph solution in Gset format. - """ - return {i + 1: 1 - x[i] for i in range(len(x))} - - -def sample_most_likely(state_vector): - """Compute the most likely binary string from state vector. - Args: - state_vector (numpy.ndarray or dict): state vector or counts. - - Returns: - numpy.ndarray: binary string as numpy.ndarray of ints. - """ - if isinstance(state_vector, (OrderedDict, dict)): - # get the binary string with the largest count - binary_string = sorted(state_vector.items(), key=lambda kv: kv[1])[-1][0] - x = np.asarray([int(y) for y in reversed(list(binary_string))]) - return x - else: - n = int(np.log2(state_vector.shape[0])) - k = np.argmax(np.abs(state_vector)) - x = np.zeros(n) - for i in range(n): - x[i] = k % 2 - k >>= 1 - return x diff --git a/qiskit/optimization/ising/docplex.py b/qiskit/optimization/ising/docplex.py deleted file mode 100644 index a94956b2c5..0000000000 --- a/qiskit/optimization/ising/docplex.py +++ /dev/null @@ -1,277 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020 -# -# 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. - -""" -Automatically generate Ising Hamiltonians from general models of optimization problems. -This program converts general models of optimization problems into Ising Hamiltonian. -To write models of optimization problems, DOcplex (Python library for optimization problems) -is used in the program. -(https://cdn.rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html) - -It supports models that consist of the following elements now. - -- Binary variables. -- Linear or quadratic object function. -- Equality constraints. - - - Symbols in constraints have to be equal (==). - - Inequality constraints (e.g. x+y <= 5) are not allowed. - -The following is an example of use. - -.. code-block:: python - - # Create an instance of a model and variables with DOcplex. - mdl = Model(name='tsp') - x = {(i,p): mdl.binary_var(name='x_{0}_{1}'.format(i,p)) for i in range(num_node) - for p in range(num_node)} - - # Object function - tsp_func = mdl.sum(ins.w[i,j] * x[(i,p)] * x[(j,(p+1)%num_node)] for i in range(num_node) - for j in range(num_node) for p in range(num_node)) - mdl.minimize(tsp_func) - - # Constraints - for i in range(num_node): - mdl.add_constraint(mdl.sum(x[(i,p)] for p in range(num_node)) == 1) - for p in range(num_node): - mdl.add_constraint(mdl.sum(x[(i,p)] for i in range(num_node)) == 1) - - # Call the method to convert the model into Ising Hamiltonian. - qubitOp, offset = get_operator(mdl) - - # Calculate with the generated Ising Hamiltonian. - ee = ExactEigensolver(qubitOp, k=1) - result = ee.run() - print('get_operator') - print('tsp objective:', result['energy'] + offset) - -""" - -import logging -from math import fsum -from typing import Tuple - -import numpy as np -from docplex.mp.constants import ComparisonType -from docplex.mp.model import Model -from qiskit.quantum_info import Pauli - -from qiskit.aqua import AquaError -from qiskit.aqua.operators import WeightedPauliOperator - -logger = logging.getLogger(__name__) - - -def get_operator(mdl: Model, auto_penalty: bool = True, - default_penalty: float = 1e5) -> Tuple[WeightedPauliOperator, float]: - """ - Generate Ising Hamiltonian from a model of DOcplex. - - Args: - mdl : A model of DOcplex for a optimization problem. - auto_penalty : If true, the penalty coefficient is automatically defined - by "_auto_define_penalty()". - default_penalty : The default value of the penalty coefficient for the constraints. - This value is used if "auto_penalty" is False. - - Returns: - A WeightedPauliOperator for the Hamiltonian and a constant shift for the obj function. - """ - - _validate_input_model(mdl) - - # set the penalty coefficient by _auto_define_penalty() or manually. - if auto_penalty: - penalty = _auto_define_penalty(mdl, default_penalty) - else: - penalty = default_penalty - - # set a sign corresponding to a maximized or minimized problem. - # sign == 1 is for minimized problem. sign == -1 is for maximized problem. - sign = 1 - if mdl.is_maximized(): - sign = -1 - - # assign variables of the model to qubits. - q_d = {} - index = 0 - for i in mdl.iter_variables(): - if i in q_d: - continue - q_d[i] = index - index += 1 - - # initialize Hamiltonian. - num_nodes = len(q_d) - pauli_list = [] - shift = 0 - zero = np.zeros(num_nodes, dtype=np.bool) - - # convert a constant part of the object function into Hamiltonian. - shift += mdl.get_objective_expr().get_constant() * sign - - # convert linear parts of the object function into Hamiltonian. - l_itr = mdl.get_objective_expr().iter_terms() - for j in l_itr: - z_p = np.zeros(num_nodes, dtype=np.bool) - index = q_d[j[0]] - weight = j[1] * sign / 2 - z_p[index] = True - - pauli_list.append([-weight, Pauli(z_p, zero)]) - shift += weight - - # convert quadratic parts of the object function into Hamiltonian. - q_itr = mdl.get_objective_expr().iter_quads() - for i in q_itr: - index1 = q_d[i[0][0]] - index2 = q_d[i[0][1]] - weight = i[1] * sign / 4 - - if index1 == index2: - shift += weight - else: - z_p = np.zeros(num_nodes, dtype=np.bool) - z_p[index1] = True - z_p[index2] = True - pauli_list.append([weight, Pauli(z_p, zero)]) - - z_p = np.zeros(num_nodes, dtype=np.bool) - z_p[index1] = True - pauli_list.append([-weight, Pauli(z_p, zero)]) - - z_p = np.zeros(num_nodes, dtype=np.bool) - z_p[index2] = True - pauli_list.append([-weight, Pauli(z_p, zero)]) - - shift += weight - - # convert constraints into penalty terms. - for constraint in mdl.iter_constraints(): - constant = constraint.right_expr.get_constant() - - # constant parts of penalty*(Constant-func)**2: penalty*(Constant**2) - shift += penalty * constant ** 2 - - # linear parts of penalty*(Constant-func)**2: penalty*(-2*Constant*func) - for __l in constraint.left_expr.iter_terms(): - z_p = np.zeros(num_nodes, dtype=np.bool) - index = q_d[__l[0]] - weight = __l[1] - z_p[index] = True - - pauli_list.append([penalty * constant * weight, Pauli(z_p, zero)]) - shift += -penalty * constant * weight - - # quadratic parts of penalty*(Constant-func)**2: penalty*(func**2) - for __l in constraint.left_expr.iter_terms(): - for l_2 in constraint.left_expr.iter_terms(): - index1 = q_d[__l[0]] - index2 = q_d[l_2[0]] - weight1 = __l[1] - weight2 = l_2[1] - penalty_weight1_weight2 = penalty * weight1 * weight2 / 4 - - if index1 == index2: - shift += penalty_weight1_weight2 - else: - z_p = np.zeros(num_nodes, dtype=np.bool) - z_p[index1] = True - z_p[index2] = True - pauli_list.append([penalty_weight1_weight2, Pauli(z_p, zero)]) - - z_p = np.zeros(num_nodes, dtype=np.bool) - z_p[index1] = True - pauli_list.append([-penalty_weight1_weight2, Pauli(z_p, zero)]) - - z_p = np.zeros(num_nodes, dtype=np.bool) - z_p[index2] = True - pauli_list.append([-penalty_weight1_weight2, Pauli(z_p, zero)]) - - shift += penalty_weight1_weight2 - - # Remove paulis whose coefficients are zeros. - qubit_op = WeightedPauliOperator(paulis=pauli_list) - - return qubit_op, shift - - -def _validate_input_model(mdl: Model): - """ - Check whether an input model is valid. If not, raise an AquaError - - Args: - mdl : A model of DOcplex for a optimization problem. - - Raises: - AquaError: Unsupported input model - """ - valid = True - - # validate an object type of the input. - if not isinstance(mdl, Model): - raise AquaError('An input model must be docplex.mp.model.Model.') - - # raise an error if the type of the variable is not a binary type. - for var in mdl.iter_variables(): - if not var.is_binary(): - logger.warning('The type of Variable %s is %s. It must be a binary variable. ', - var, var.vartype.short_name) - valid = False - - # raise an error if the constraint type is not an equality constraint. - for constraint in mdl.iter_constraints(): - if not constraint.sense == ComparisonType.EQ: - logger.warning('Constraint %s is not an equality constraint.', constraint) - valid = False - - if not valid: - raise AquaError('The input model has unsupported elements.') - - -def _auto_define_penalty(mdl: Model, default_penalty: float = 1e5) -> float: - """ - Automatically define the penalty coefficient. - This returns object function's (upper bound - lower bound + 1). - - - Args: - mdl : A model of DOcplex for a optimization problem. - default_penalty : The default value of the penalty coefficient for the constraints. - - Returns: - The penalty coefficient for the Hamiltonian. - """ - - # if a constraint has float coefficient, return 1e5 for the penalty coefficient. - terms = [] - for constraint in mdl.iter_constraints(): - terms.append(constraint.right_expr.get_constant()) - terms.extend(term[1] for term in constraint.left_expr.iter_terms()) - if any(isinstance(term, float) and not term.is_integer() for term in terms): - logger.warning('Using %f for the penalty coefficient because a float coefficient exists ' - 'in constraints. \nThe value could be too small. ' - 'If so, set the penalty coefficient manually.', default_penalty) - return default_penalty - - # (upper bound - lower bound) can be calculate as the sum of absolute value of coefficients - # Firstly, add 1 to guarantee that infeasible answers will be greater than upper bound. - penalties = [1] - # add linear terms of the object function. - penalties.extend(abs(i[1]) for i in mdl.get_objective_expr().iter_terms()) - # add quadratic terms of the object function. - penalties.extend(abs(i[1]) for i in mdl.get_objective_expr().iter_quads()) - - return fsum(penalties) diff --git a/qiskit/optimization/ising/exact_cover.py b/qiskit/optimization/ising/exact_cover.py deleted file mode 100644 index 39f2928bf2..0000000000 --- a/qiskit/optimization/ising/exact_cover.py +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -""" exact cover """ - -import logging - -import numpy as np - -from qiskit.quantum_info import Pauli -from qiskit.aqua.operators import WeightedPauliOperator - -logger = logging.getLogger(__name__) - - -def get_operator(list_of_subsets): - """ - Construct the Hamiltonian for the exact solver problem. - - Note: - | Assumption: the union of the subsets contains all the elements to cover. - | The Hamiltonian is: - | sum_{each element e}{(1-sum_{every subset_i that contains e}{Xi})^2}, - | where Xi (Xi=1 or 0) means whether should include the subset i. - - Args: - list_of_subsets (list): list of lists (i.e., subsets) - - Returns: - tuple(WeightedPauliOperator, float): - operator for the Hamiltonian, a constant shift for the obj function. - """ - # pylint: disable=invalid-name - n = len(list_of_subsets) - - U = [] - for sub in list_of_subsets: - U.extend(sub) - U = np.unique(U) # U is the universe - - shift = 0 - pauli_list = [] - - for e in U: - # pylint: disable=simplifiable-if-expression - cond = [True if e in sub else False for sub in list_of_subsets] - indices_has_e = np.arange(n)[cond] - num_has_e = len(indices_has_e) - Y = 1-0.5*num_has_e - shift += Y*Y - - for i in indices_has_e: - for j in indices_has_e: - if i != j: - w_p = np.zeros(n) - v_p = np.zeros(n) - v_p[i] = 1 - v_p[j] = 1 - pauli_list.append([0.25, Pauli(v_p, w_p)]) - else: - shift += 0.25 - - for i in indices_has_e: - w_p = np.zeros(n) - v_p = np.zeros(n) - v_p[i] = 1 - pauli_list.append([-Y, Pauli(v_p, w_p)]) - - return WeightedPauliOperator(paulis=pauli_list), shift - - -def get_solution(x): - """ - Args: - x (numpy.ndarray) : binary string as numpy array. - - Returns: - numpy.ndarray: graph solution as binary numpy array. - """ - return 1 - x - - -def check_solution_satisfiability(sol, list_of_subsets): - """ check solution satisfiability """ - # pylint: disable=invalid-name - n = len(list_of_subsets) - U = [] - for sub in list_of_subsets: - U.extend(sub) - U = np.unique(U) # U is the universe - - U2 = [] - selected_subsets = [] - for i in range(n): - if sol[i] == 1: - selected_subsets.append(list_of_subsets[i]) - U2.extend(list_of_subsets[i]) - - U2 = np.unique(U2) - if set(U) != set(U2): - return False - - tmplen = len(selected_subsets) - for i in range(tmplen): - for j in range(i): - L = selected_subsets[i] - R = selected_subsets[j] - - if set(L) & set(R): # should be empty - return False - - return True diff --git a/qiskit/optimization/ising/graph_partition.py b/qiskit/optimization/ising/graph_partition.py deleted file mode 100644 index c805783cfe..0000000000 --- a/qiskit/optimization/ising/graph_partition.py +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -""" -Convert graph partitioning instances into Pauli list -Deal with Gset format. See https://web.stanford.edu/~yyye/yyye/Gset/ -""" - -import logging - -import numpy as np - -from qiskit.quantum_info import Pauli -from qiskit.aqua.operators import WeightedPauliOperator - -logger = logging.getLogger(__name__) - - -def get_operator(weight_matrix): - r"""Generate Hamiltonian for the graph partitioning - - Notes: - Goals: - 1 separate the vertices into two set of the same size - 2 make sure the number of edges between the two set is minimized. - Hamiltonian: - H = H_A + H_B - H_A = sum\_{(i,j)\in E}{(1-ZiZj)/2} - H_B = (sum_{i}{Zi})^2 = sum_{i}{Zi^2}+sum_{i!=j}{ZiZj} - H_A is for achieving goal 2 and H_B is for achieving goal 1. - - Args: - weight_matrix (numpy.ndarray) : adjacency matrix. - - Returns: - WeightedPauliOperator: operator for the Hamiltonian - float: a constant shift for the obj function. - """ - num_nodes = len(weight_matrix) - pauli_list = [] - shift = 0 - - for i in range(num_nodes): - for j in range(i): - if weight_matrix[i, j] != 0: - x_p = np.zeros(num_nodes, dtype=np.bool) - z_p = np.zeros(num_nodes, dtype=np.bool) - z_p[i] = True - z_p[j] = True - pauli_list.append([-0.5, Pauli(z_p, x_p)]) - shift += 0.5 - - for i in range(num_nodes): - for j in range(num_nodes): - if i != j: - x_p = np.zeros(num_nodes, dtype=np.bool) - z_p = np.zeros(num_nodes, dtype=np.bool) - z_p[i] = True - z_p[j] = True - pauli_list.append([1, Pauli(z_p, x_p)]) - else: - shift += 1 - return WeightedPauliOperator(paulis=pauli_list), shift - - -def objective_value(x, w): - """Compute the value of a cut. - - Args: - x (numpy.ndarray): binary string as numpy array. - w (numpy.ndarray): adjacency matrix. - - Returns: - float: value of the cut. - """ - # pylint: disable=invalid-name - X = np.outer(x, (1-x)) - w_01 = np.where(w != 0, 1, 0) - return np.sum(w_01 * X) - - -def get_graph_solution(x): - """Get graph solution from binary string. - - Args: - x (numpy.ndarray) : binary string as numpy array. - - Returns: - numpy.ndarray: graph solution as binary numpy array. - """ - return 1 - x diff --git a/qiskit/optimization/ising/max_cut.py b/qiskit/optimization/ising/max_cut.py deleted file mode 100644 index 4bef9da2e8..0000000000 --- a/qiskit/optimization/ising/max_cut.py +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -""" -Convert max-cut instances into Pauli list -Deal with Gset format. See https://web.stanford.edu/~yyye/yyye/Gset/ -Design the max-cut object `w` as a two-dimensional np.array -e.g., w[i, j] = x means that the weight of a edge between i and j is x -Note that the weights are symmetric, i.e., w[j, i] = x always holds. -""" - -import logging - -import numpy as np - -from qiskit.quantum_info import Pauli -from qiskit.aqua.operators import WeightedPauliOperator - -logger = logging.getLogger(__name__) - - -def get_operator(weight_matrix): - """Generate Hamiltonian for the max-cut problem of a graph. - - Args: - weight_matrix (numpy.ndarray) : adjacency matrix. - - Returns: - WeightedPauliOperator: operator for the Hamiltonian - float: a constant shift for the obj function. - - """ - num_nodes = weight_matrix.shape[0] - pauli_list = [] - shift = 0 - for i in range(num_nodes): - for j in range(i): - if weight_matrix[i, j] != 0: - x_p = np.zeros(num_nodes, dtype=np.bool) - z_p = np.zeros(num_nodes, dtype=np.bool) - z_p[i] = True - z_p[j] = True - pauli_list.append([0.5 * weight_matrix[i, j], Pauli(z_p, x_p)]) - shift -= 0.5 * weight_matrix[i, j] - return WeightedPauliOperator(paulis=pauli_list), shift - - -def max_cut_value(x, w): - """Compute the value of a cut. - - Args: - x (numpy.ndarray): binary string as numpy array. - w (numpy.ndarray): adjacency matrix. - - Returns: - float: value of the cut. - """ - # pylint: disable=invalid-name - X = np.outer(x, (1 - x)) - return np.sum(w * X) - - -def get_graph_solution(x): - """Get graph solution from binary string. - - Args: - x (numpy.ndarray) : binary string as numpy array. - - Returns: - numpy.ndarray: graph solution as binary numpy array. - """ - return 1 - x diff --git a/qiskit/optimization/ising/partition.py b/qiskit/optimization/ising/partition.py deleted file mode 100644 index 396d3083b7..0000000000 --- a/qiskit/optimization/ising/partition.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -""" -Generate Number Partitioning (Partition) instances, and convert them -into a Hamiltonian given as a Pauli list. -""" - -import logging - -import numpy as np -from qiskit.quantum_info import Pauli - -from qiskit.aqua.operators import WeightedPauliOperator - -logger = logging.getLogger(__name__) - - -def get_operator(values): - """Construct the Hamiltonian for a given Partition instance. - - Given a list of numbers for the Number Partitioning problem, we - construct the Hamiltonian described as a list of Pauli gates. - - Args: - values (numpy.ndarray): array of values. - - Returns: - tuple(WeightedPauliOperator, float): operator for the Hamiltonian and a - constant shift for the obj function. - - """ - n = len(values) - # The Hamiltonian is: - # \sum_{i,j=1,\dots,n} ij z_iz_j + \sum_{i=1,\dots,n} i^2 - pauli_list = [] - for i in range(n): - for j in range(i): - x_p = np.zeros(n, dtype=np.bool) - z_p = np.zeros(n, dtype=np.bool) - z_p[i] = True - z_p[j] = True - pauli_list.append([2. * values[i] * values[j], Pauli(z_p, x_p)]) - return WeightedPauliOperator(paulis=pauli_list), sum(values*values) - - -def partition_value(x, number_list): - """Compute the value of a partition. - - Args: - x (numpy.ndarray): binary string as numpy array. - number_list (numpy.ndarray): list of numbers in the instance. - - Returns: - float: difference squared between the two sides of the number - partition. - """ - diff = np.sum(number_list[x == 0]) - np.sum(number_list[x == 1]) - return diff * diff diff --git a/qiskit/optimization/ising/set_packing.py b/qiskit/optimization/ising/set_packing.py deleted file mode 100644 index 436930a1b8..0000000000 --- a/qiskit/optimization/ising/set_packing.py +++ /dev/null @@ -1,113 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -""" set packing module """ - -import logging - -import numpy as np -from qiskit.quantum_info import Pauli - -from qiskit.aqua.operators import WeightedPauliOperator - -logger = logging.getLogger(__name__) - - -def get_operator(list_of_subsets): - """Construct the Hamiltonian for the set packing. - - Notes: - find the maximal number of subsets which are disjoint pairwise. - - Hamiltonian: - H = A Ha + B Hb - Ha = sum_{Si and Sj overlaps}{XiXj} - Hb = -sum_{i}{Xi} - - Ha is to ensure the disjoint condition, while Hb is to achieve the maximal number. - Ha is hard constraint that must be satisfied. Therefore A >> B. - In the following, we set A=10 and B = 1 - - where Xi = (Zi + 1)/2 - - Args: - list_of_subsets (list): list of lists (i.e., subsets) - - Returns: - tuple(WeightedPauliOperator, float): operator for the Hamiltonian, - a constant shift for the obj function. - """ - # pylint: disable=invalid-name - shift = 0 - pauli_list = [] - A = 10 - n = len(list_of_subsets) - for i in range(n): - for j in range(i): - if set(list_of_subsets[i]) & set(list_of_subsets[j]): - wp = np.zeros(n) - vp = np.zeros(n) - vp[i] = 1 - vp[j] = 1 - pauli_list.append([A*0.25, Pauli(vp, wp)]) - - vp2 = np.zeros(n) - vp2[i] = 1 - pauli_list.append([A*0.25, Pauli(vp2, wp)]) - - vp3 = np.zeros(n) - vp3[j] = 1 - pauli_list.append([A*0.25, Pauli(vp3, wp)]) - - shift += A*0.25 - - for i in range(n): - wp = np.zeros(n) - vp = np.zeros(n) - vp[i] = 1 - pauli_list.append([-0.5, Pauli(vp, wp)]) - shift += -0.5 - - return WeightedPauliOperator(paulis=pauli_list), shift - - -def get_solution(x): - """ - - Args: - x (numpy.ndarray) : binary string as numpy array. - - Returns: - numpy.ndarray: graph solution as binary numpy array. - """ - return 1 - x - - -def check_disjoint(sol, list_of_subsets): - """ check disjoint """ - # pylint: disable=invalid-name - n = len(list_of_subsets) - selected_subsets = [] - for i in range(n): - if sol[i] == 1: - selected_subsets.append(list_of_subsets[i]) - tmplen = len(selected_subsets) - for i in range(tmplen): - for j in range(i): - L = selected_subsets[i] - R = selected_subsets[j] - if set(L) & set(R): - return False - - return True diff --git a/qiskit/optimization/ising/stable_set.py b/qiskit/optimization/ising/stable_set.py deleted file mode 100644 index 28e0859c9b..0000000000 --- a/qiskit/optimization/ising/stable_set.py +++ /dev/null @@ -1,97 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -""" -Convert stable set instances into Pauli list. We read instances in -the Gset format, see https://web.stanford.edu/~yyye/yyye/Gset/ , for -compatibility with the maxcut format, but the weights on the edges -as they are not really used and are always assumed to be 1. The -graph is represented by an adjacency matrix. -""" - -import logging - -import numpy as np -from qiskit.quantum_info import Pauli - -from qiskit.aqua.operators import WeightedPauliOperator - -logger = logging.getLogger(__name__) - - -def get_operator(w): - """Generate Hamiltonian for the maximum stable set in a graph. - - Args: - w (numpy.ndarray) : adjacency matrix. - - Returns: - tuple(WeightedPauliOperator, float): operator for the Hamiltonian and a - constant shift for the obj function. - - """ - num_nodes = len(w) - pauli_list = [] - shift = 0 - for i in range(num_nodes): - for j in range(i+1, num_nodes): - if w[i, j] != 0: - x_p = np.zeros(num_nodes, dtype=np.bool) - z_p = np.zeros(num_nodes, dtype=np.bool) - z_p[i] = True - z_p[j] = True - pauli_list.append([1.0, Pauli(z_p, x_p)]) - shift += 1 - for i in range(num_nodes): - degree = np.sum(w[i, :]) - x_p = np.zeros(num_nodes, dtype=np.bool) - z_p = np.zeros(num_nodes, dtype=np.bool) - z_p[i] = True - pauli_list.append([degree - 1/2, Pauli(z_p, x_p)]) - return WeightedPauliOperator(paulis=pauli_list), shift - num_nodes/2 - - -def stable_set_value(x, w): - """Compute the value of a stable set, and its feasibility. - - Args: - x (numpy.ndarray): binary string in original format -- not - graph solution!. - w (numpy.ndarray): adjacency matrix. - - Returns: - tuple(float, bool): size of the stable set, and Boolean indicating - feasibility. - """ - assert len(x) == w.shape[0] - feasible = True - num_nodes = w.shape[0] - for i in range(num_nodes): - for j in range(i+1, num_nodes): - if w[i, j] != 0 and x[i] == 0 and x[j] == 0: - feasible = False - break - return len(x) - np.sum(x), feasible - - -def get_graph_solution(x): - """Get graph solution from binary string. - - Args: - x (numpy.ndarray) : binary string as numpy array. - - Returns: - numpy.ndarray: graph solution as binary numpy array. - """ - return 1 - x diff --git a/qiskit/optimization/ising/tsp.py b/qiskit/optimization/ising/tsp.py deleted file mode 100644 index 32a2886f54..0000000000 --- a/qiskit/optimization/ising/tsp.py +++ /dev/null @@ -1,268 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -""" Convert symmetric TSP instances into Pauli list -Deal with TSPLIB format. It supports only EUC_2D edge weight type. -See https://wwwproxy.iwr.uni-heidelberg.de/groups/comopt/software/TSPLIB95/ -and http://elib.zib.de/pub/mp-testdata/tsp/tsplib/tsp/index.html -Design the tsp object `w` as a two-dimensional np.array -e.g., w[i, j] = x means that the length of a edge between i and j is x -Note that the weights are symmetric, i.e., w[j, i] = x always holds. -""" - -import logging -from collections import namedtuple - -import numpy as np -from qiskit.quantum_info import Pauli - -from qiskit.aqua import aqua_globals -from qiskit.aqua.operators import WeightedPauliOperator - -logger = logging.getLogger(__name__) - -"""Instance data of TSP""" -TspData = namedtuple('TspData', 'name dim coord w') - - -def calc_distance(coord, name='tmp'): - """ calculate distance """ - assert coord.shape[1] == 2 - dim = coord.shape[0] - w = np.zeros((dim, dim)) - for i in range(dim): - for j in range(i + 1, dim): - delta = coord[i] - coord[j] - w[i, j] = np.rint(np.hypot(delta[0], delta[1])) - w += w.T - return TspData(name=name, dim=dim, coord=coord, w=w) - - -def random_tsp(n, low=0, high=100, savefile=None, seed=None, name='tmp'): - """Generate a random instance for TSP. - - Args: - n (int): number of nodes. - low (float): lower bound of coordinate. - high (float): upper bound of coordinate. - savefile (str or None): name of file where to save graph. - seed (int or None): random seed - if None, will not initialize. - name (str): name of an instance - - Returns: - TspData: instance data. - - """ - assert n > 0 - if seed: - aqua_globals.random_seed = seed - - coord = aqua_globals.random.uniform(low, high, (n, 2)) - ins = calc_distance(coord, name) - if savefile: - with open(savefile, 'w') as outfile: - outfile.write('NAME : {}\n'.format(ins.name)) - outfile.write('COMMENT : random data\n') - outfile.write('TYPE : TSP\n') - outfile.write('DIMENSION : {}\n'.format(ins.dim)) - outfile.write('EDGE_WEIGHT_TYPE : EUC_2D\n') - outfile.write('NODE_COORD_SECTION\n') - for i in range(ins.dim): - x = ins.coord[i] - outfile.write('{} {:.4f} {:.4f}\n'.format(i + 1, x[0], x[1])) - return ins - - -def parse_tsplib_format(filename): - """Read graph in TSPLIB format from file. - - Args: - filename (str): name of the file. - - Returns: - TspData: instance data. - - """ - name = '' - coord = [] - with open(filename) as infile: - coord_section = False - for line in infile: - if line.startswith('NAME'): - name = line.split(':')[1] - name.strip() - elif line.startswith('TYPE'): - typ = line.split(':')[1] - typ.strip() - if typ != 'TSP': - logger.warning('This supports only "TSP" type. Actual: %s', typ) - elif line.startswith('DIMENSION'): - dim = int(line.split(':')[1]) - coord = np.zeros((dim, 2)) - elif line.startswith('EDGE_WEIGHT_TYPE'): - typ = line.split(':')[1] - typ.strip() - if typ != 'EUC_2D': - logger.warning('This supports only "EUC_2D" edge weight. Actual: %s', typ) - elif line.startswith('NODE_COORD_SECTION'): - coord_section = True - elif coord_section: - v = line.split() - index = int(v[0]) - 1 - coord[index][0] = float(v[1]) - coord[index][1] = float(v[2]) - return calc_distance(coord, name) - - -def get_operator(ins, penalty=1e5): - """Generate Hamiltonian for TSP of a graph. - - Args: - ins (TspData) : TSP data including coordinates and distances. - penalty (float) : Penalty coefficient for the constraints - - Returns: - tuple(WeightedPauliOperator, float): operator for the Hamiltonian and a - constant shift for the obj function. - - """ - num_nodes = ins.dim - num_qubits = num_nodes ** 2 - zero = np.zeros(num_qubits, dtype=np.bool) - pauli_list = [] - shift = 0 - for i in range(num_nodes): - for j in range(num_nodes): - if i == j: - continue - for p__ in range(num_nodes): - q = (p__ + 1) % num_nodes - shift += ins.w[i, j] / 4 - - z_p = np.zeros(num_qubits, dtype=np.bool) - z_p[i * num_nodes + p__] = True - pauli_list.append([-ins.w[i, j] / 4, Pauli(z_p, zero)]) - - z_p = np.zeros(num_qubits, dtype=np.bool) - z_p[j * num_nodes + q] = True - pauli_list.append([-ins.w[i, j] / 4, Pauli(z_p, zero)]) - - z_p = np.zeros(num_qubits, dtype=np.bool) - z_p[i * num_nodes + p__] = True - z_p[j * num_nodes + q] = True - pauli_list.append([ins.w[i, j] / 4, Pauli(z_p, zero)]) - - for i in range(num_nodes): - for p__ in range(num_nodes): - z_p = np.zeros(num_qubits, dtype=np.bool) - z_p[i * num_nodes + p__] = True - pauli_list.append([penalty, Pauli(z_p, zero)]) - shift += -penalty - - for p__ in range(num_nodes): - for i in range(num_nodes): - for j in range(i): - shift += penalty / 2 - - z_p = np.zeros(num_qubits, dtype=np.bool) - z_p[i * num_nodes + p__] = True - pauli_list.append([-penalty / 2, Pauli(z_p, zero)]) - - z_p = np.zeros(num_qubits, dtype=np.bool) - z_p[j * num_nodes + p__] = True - pauli_list.append([-penalty / 2, Pauli(z_p, zero)]) - - z_p = np.zeros(num_qubits, dtype=np.bool) - z_p[i * num_nodes + p__] = True - z_p[j * num_nodes + p__] = True - pauli_list.append([penalty / 2, Pauli(z_p, zero)]) - - for i in range(num_nodes): - for p__ in range(num_nodes): - for q in range(p__): - shift += penalty / 2 - - z_p = np.zeros(num_qubits, dtype=np.bool) - z_p[i * num_nodes + p__] = True - pauli_list.append([-penalty / 2, Pauli(z_p, zero)]) - - z_p = np.zeros(num_qubits, dtype=np.bool) - z_p[i * num_nodes + q] = True - pauli_list.append([-penalty / 2, Pauli(z_p, zero)]) - - z_p = np.zeros(num_qubits, dtype=np.bool) - z_p[i * num_nodes + p__] = True - z_p[i * num_nodes + q] = True - pauli_list.append([penalty / 2, Pauli(z_p, zero)]) - shift += 2 * penalty * num_nodes - return WeightedPauliOperator(paulis=pauli_list), shift - - -def tsp_value(z, w): - """Compute the TSP value of a solution. - - Args: - z (list[int]): list of cities. - w (numpy.ndarray): adjacency matrix. - - Returns: - float: value of the cut. - """ - ret = 0.0 - for i in range(len(z) - 1): - ret += w[z[i], z[i + 1]] - ret += w[z[-1], z[0]] - return ret - - -def tsp_feasible(x): - """Check whether a solution is feasible or not. - - Args: - x (numpy.ndarray) : binary string as numpy array. - - Returns: - bool: feasible or not. - """ - n = int(np.sqrt(len(x))) - y = np.zeros((n, n)) - for i in range(n): - for p__ in range(n): - y[i, p__] = x[i * n + p__] - for i in range(n): - if sum(y[i, p] for p in range(n)) != 1: - return False - for p__ in range(n): - if sum(y[i, p__] for i in range(n)) != 1: - return False - return True - - -def get_tsp_solution(x): - """Get graph solution from binary string. - - Args: - x (numpy.ndarray) : binary string as numpy array. - - Returns: - list[int]: sequence of cities to traverse. - """ - n = int(np.sqrt(len(x))) - z = [] - for p__ in range(n): - for i in range(n): - if x[i * n + p__] >= 0.999: - assert len(z) == p__ - z.append(i) - return z diff --git a/qiskit/optimization/ising/vehicle_routing.py b/qiskit/optimization/ising/vehicle_routing.py deleted file mode 100644 index 74dbb40aae..0000000000 --- a/qiskit/optimization/ising/vehicle_routing.py +++ /dev/null @@ -1,192 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020 -# -# 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. - -""" -Converts vehicle routing instances into a list of Paulis, -and provides some related routines (extracting a solution, -checking its objective function value). -""" - -import numpy as np -from qiskit.quantum_info import Pauli - -from qiskit.aqua.operators import WeightedPauliOperator - - -def get_vehiclerouting_matrices(instance, n, K): # pylint: disable=invalid-name - """Constructs auxiliary matrices from a vehicle routing instance, - which represent the encoding into a binary quadratic program. - This is used in the construction of the qubit ops and computation - of the solution cost. - - Args: - instance (numpy.ndarray) : a customers-to-customers distance matrix. - n (integer) : the number of customers. - K (integer) : the number of vehicles available. - - Returns: - tuple(numpy.ndarray, numpy.ndarray, float): - a matrix defining the interactions between variables. - a matrix defining the contribution from the individual variables. - the constant offset. - """ - # pylint: disable=invalid-name - # N = (n - 1) * n - A = np.max(instance) * 100 # A parameter of cost function - - # Determine the weights w - instance_vec = instance.reshape(n**2) - w_list = [instance_vec[x] for x in range(n**2) if instance_vec[x] > 0] - w = np.zeros(n * (n - 1)) - for i_i, _ in enumerate(w_list): - w[i_i] = w_list[i_i] - - # Some additional variables - id_n = np.eye(n) - im_n_1 = np.ones([n - 1, n - 1]) - iv_n_1 = np.ones(n) - iv_n_1[0] = 0 - iv_n = np.ones(n - 1) - neg_iv_n_1 = np.ones(n) - iv_n_1 - - v = np.zeros([n, n * (n - 1)]) - for i_i in range(n): - count = i_i - 1 - for j_j in range(n * (n - 1)): - - if j_j // (n - 1) == i_i: - count = i_i - - if j_j // (n - 1) != i_i and j_j % (n - 1) == count: - v[i_i][j_j] = 1. - - v_n = np.sum(v[1:], axis=0) - - # Q defines the interactions between variables - Q = A * (np.kron(id_n, im_n_1) + np.dot(v.T, v)) - - # g defines the contribution from the individual variables - g = w - 2 * A * (np.kron(iv_n_1, iv_n) + v_n.T) - \ - 2 * A * K * (np.kron(neg_iv_n_1, iv_n) + v[0].T) - - # c is the constant offset - c = 2 * A * (n - 1) + 2 * A * (K**2) - - return (Q, g, c) - - -def get_vehiclerouting_cost(instance, n, K, x_sol): # pylint: disable=invalid-name - """Computes the cost of a solution to an instance of a vehicle routing problem. - - Args: - instance (numpy.ndarray) : a customers-to-customers distance matrix. - n (integer) : the number of customers. - K (integer) : the number of vehicles available. - x_sol (numpy.ndarray): a solution, i.e., a path, in its binary representation. - - Returns: - float: objective function value. - """ - # pylint: disable=invalid-name - (Q, g, c) = get_vehiclerouting_matrices(instance, n, K) - - def fun(x): - return np.dot(np.around(x), np.dot(Q, np.around(x))) + np.dot(g, np.around(x)) + c - cost = fun(x_sol) - return cost - - -def get_operator(instance, n, K): # pylint: disable=invalid-name - """Converts an instance of a vehicle routing problem into a list of Paulis. - - Args: - instance (numpy.ndarray) : a customers-to-customers distance matrix. - n (integer) : the number of customers. - K (integer) : the number of vehicles available. - - Returns: - WeightedPauliOperator: operator for the Hamiltonian. - """ - # pylint: disable=invalid-name - N = (n - 1) * n - (Q, g__, c) = get_vehiclerouting_matrices(instance, n, K) - - # Defining the new matrices in the Z-basis - i_v = np.ones(N) - q_z = (Q / 4) - g_z = (-g__ / 2 - np.dot(i_v, Q / 4) - np.dot(Q / 4, i_v)) - c_z = (c + np.dot(g__ / 2, i_v) + np.dot(i_v, np.dot(Q / 4, i_v))) - - c_z = c_z + np.trace(q_z) - q_z = q_z - np.diag(np.diag(q_z)) - - # Getting the Hamiltonian in the form of a list of Pauli terms - - pauli_list = [] - for i in range(N): - if g_z[i] != 0: - w_p = np.zeros(N) - v_p = np.zeros(N) - v_p[i] = 1 - pauli_list.append((g_z[i], Pauli(v_p, w_p))) - for i in range(N): - for j in range(i): - if q_z[i, j] != 0: - w_p = np.zeros(N) - v_p = np.zeros(N) - v_p[i] = 1 - v_p[j] = 1 - pauli_list.append((2 * q_z[i, j], Pauli(v_p, w_p))) - - pauli_list.append((c_z, Pauli(np.zeros(N), np.zeros(N)))) - return WeightedPauliOperator(paulis=pauli_list) - - -def get_vehiclerouting_solution(instance, n, K, result): # pylint: disable=invalid-name - """Tries to obtain a feasible solution (in vector form) of an instance - of vehicle routing from the results dictionary. - - Args: - instance (numpy.ndarray) : a customers-to-customers distance matrix. - n (integer) : the number of customers. - K (integer) : the number of vehicles available. - result (dictionary) : a dictionary obtained by QAOA.run or VQE.run containing key 'eigvecs'. - - Returns: - numpy.ndarray: a solution, i.e., a path, in its binary representation. - - #TODO: support statevector simulation, results should be a statevector or counts format, not - a result from algorithm run - """ - # pylint: disable=invalid-name - del instance, K # unused - v = result['eigvecs'][0] - N = (n - 1) * n - - index_value = [x for x in range(len(v)) if v[x] == max(v)][0] - string_value = "{0:b}".format(index_value) - - while len(string_value) < N: - string_value = '0' + string_value - - x_sol = list() - for elements in string_value: - if elements == '0': - x_sol.append(0) - else: - x_sol.append(1) - - x_sol = np.flip(x_sol, axis=0) - - return x_sol diff --git a/qiskit/optimization/ising/vertex_cover.py b/qiskit/optimization/ising/vertex_cover.py deleted file mode 100644 index 436366173a..0000000000 --- a/qiskit/optimization/ising/vertex_cover.py +++ /dev/null @@ -1,115 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -""" -Convert vertex cover instances into Pauli list -Deal with Gset format. See https://web.stanford.edu/~yyye/yyye/Gset/ -""" - -import logging - -import numpy as np -from qiskit.quantum_info import Pauli - -from qiskit.aqua.operators import WeightedPauliOperator - -logger = logging.getLogger(__name__) - - -def get_operator(weight_matrix): - r"""Generate Hamiltonian for the vertex cover - Args: - weight_matrix (numpy.ndarray) : adjacency matrix. - - Returns: - tuple(WeightedPauliOperator, float): operator for the Hamiltonian and a - constant shift for the obj function. - - Goals: - 1 color some vertices as red such that every edge is connected to some red vertex - 2 minimize the vertices to be colored as red - - Hamiltonian: - H = A * H_A + H_B - H_A = sum\_{(i,j)\in E}{(1-Xi)(1-Xj)} - H_B = sum_{i}{Zi} - - H_A is to achieve goal 1 while H_b is to achieve goal 2. - H_A is hard constraint so we place a huge penality on it. A=5. - Note Xi = (Zi+1)/2 - - """ - n = len(weight_matrix) - pauli_list = [] - shift = 0 - a__ = 5 - - for i in range(n): - for j in range(i): - if weight_matrix[i, j] != 0: - w_p = np.zeros(n) - v_p = np.zeros(n) - v_p[i] = 1 - v_p[j] = 1 - pauli_list.append([a__*0.25, Pauli(v_p, w_p)]) - - v_p2 = np.zeros(n) - v_p2[i] = 1 - pauli_list.append([-a__*0.25, Pauli(v_p2, w_p)]) - - v_p3 = np.zeros(n) - v_p3[j] = 1 - pauli_list.append([-a__*0.25, Pauli(v_p3, w_p)]) - - shift += a__*0.25 - - for i in range(n): - w_p = np.zeros(n) - v_p = np.zeros(n) - v_p[i] = 1 - pauli_list.append([0.5, Pauli(v_p, w_p)]) - shift += 0.5 - return WeightedPauliOperator(paulis=pauli_list), shift - - -def check_full_edge_coverage(x, w): - """ - Args: - x (numpy.ndarray): binary string as numpy array. - w (numpy.ndarray): adjacency matrix. - - Returns: - float: value of the cut. - """ - first = w.shape[0] - second = w.shape[1] - for i in range(first): - for j in range(second): - if w[i, j] != 0: - if x[i] != 1 and x[j] != 1: - return False - - return True - - -def get_graph_solution(x): - """Get graph solution from binary string. - - Args: - x (numpy.ndarray) : binary string as numpy array. - - Returns: - numpy.ndarray: graph solution as binary numpy array. - """ - return 1 - x From f6bf628c1560c88a1c44e3dd13eaea78bc2cacbe Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Wed, 4 Mar 2020 18:21:59 +0900 Subject: [PATCH 006/323] (wip) quadratic constraints and cleanup --- .../problems/quadratic_constraint.py | 28 ++++++++--- qiskit/optimization/utils/helpers.py | 48 +++++++++++++++---- 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index a327ec7c11..14a52e806a 100755 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -12,9 +12,10 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +from typing import List, Tuple, Dict from qiskit.optimization.utils.base import BaseInterface from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError -from qiskit.optimization.utils.helpers import unpack_pair, unpack_triple +from qiskit.optimization.utils.helpers import unpack_pair, unpack_triple, NameIndexConverter from cplex import SparsePair, SparseTriple @@ -29,8 +30,14 @@ def __init__(self): is not meant to be used externally. """ super(QuadraticConstraintInterface, self).__init__() - - def get_num(self): + self._rhs = [] + self._senses = [] + self._names = [] + self._lin_expr = [] + self._quad_expr = [] + self._index = NameIndexConverter() + + def get_num(self) -> int: """Returns the number of quadratic constraints. Example usage: @@ -45,7 +52,7 @@ def get_num(self): >>> op.quadratic_constraints.get_num() 10 """ - None + return len(self._names) def _add(self, lin_expr, quad_expr, sense, rhs, name): """non-public""" @@ -104,6 +111,13 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): if quad_expr is None: quad_expr = SparseTriple([0], [0], [0.0]) # We only ever create one quadratic constraint at a time. + + if sense not in ['L', 'G', 'E']: + raise QiskitOptimizationError('Invalid sense: %s'.format(sense)) + else: + self._senses.append(sense) + self._rhs.append(rhs) + self._names.append(name) return self._add_single(self.get_num, self._add, lin_expr, quad_expr, sense, rhs, name) @@ -207,7 +221,7 @@ def get_rhs(self, *args): >>> op.quadratic_constraints.get_rhs() [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] """ - return [] + return self._rhs def get_senses(self, *args): """Returns the senses of a set of quadratic constraints. @@ -251,7 +265,7 @@ def get_senses(self, *args): >>> op.quadratic_constraints.get_senses() ['G', 'G', 'L', 'L'] """ - return [] + return self._senses def get_linear_num_nonzeros(self, *args): """Returns the number of nonzeros in the linear part of a set of quadratic constraints. @@ -488,4 +502,4 @@ def get_names(self, *args): >>> op.quadratic_constraints.get_names() ['q1', 'q2', 'q3', 'q4', 'q5', 'q6', 'q7', 'q8', 'q9', 'q10'] """ - None + self._names diff --git a/qiskit/optimization/utils/helpers.py b/qiskit/optimization/utils/helpers.py index c0419ec53a..3ecf810536 100755 --- a/qiskit/optimization/utils/helpers.py +++ b/qiskit/optimization/utils/helpers.py @@ -13,10 +13,12 @@ # that they have been altered from the originals. -from qiskit.optimization.utils import QiskitOptimizationError - from collections.abc import Sequence +from typing import Union, List, Tuple, Dict +from cplex import SparsePair, SparseTriple + +from qiskit.optimization.utils import QiskitOptimizationError _defaultgetindex = {} @@ -87,6 +89,33 @@ def convert(name, getindexfunc=_defaultgetindexfunc, cache=None): return name +class NameIndexConverter: + def __init__(self): + self._cache = {} + + def to_dict(self) -> Dict[str, int]: + return self._cache + + def build(self, names: List[str]): + self._cache = {i: e for i, e in enumerate(names)} + + def convert(self, names: Union[str, List[str]]) -> Union[int, List[int]]: + if isinstance(names, str): + return self._convert_str(names) + elif isinstance(names, Sequence): + return self._convert_seq(names) + else: + raise QiskitOptimizationError('Invalid argument: %s'.format(names)) + + def _convert_str(self, name: str) -> int: + if name not in self._cache: + self._cache[name] = len(self._cache) + return self._cache[name] + + def _convert_seq(self, names: List[str]) -> List[int]: + return [self._convert_str(e) if isinstance(e, str) else e for e in names] + + def init_list_args(*args): """Initialize default arguments with empty lists if necessary.""" return tuple([] if a is None else a for a in args) @@ -121,7 +150,7 @@ def validate_arg_lengths(arg_list, allow_empty=True): pass -def unpack_pair(item): +def unpack_pair(item: Union[SparsePair, List, Tuple]) -> Tuple[List[int], List[float]]: """Extracts the indices and values from an object. The argument item can either be an instance of SparsePair or a @@ -134,16 +163,19 @@ def unpack_pair(item): >>> lin_expr = [[], []] >>> ind, val = unpack_pair(lin_expr) """ - try: + if isinstance(item, SparsePair): assert item.isvalid() ind, val = item.unpack() - except AttributeError: + elif isinstance(item, (tuple, list)): ind, val = item[0:2] + else: + raise QiskitOptimizationError('Invalid object for unpack_pair {}'.format(item)) validate_arg_lengths([ind, val]) return ind, val -def unpack_triple(item): +def unpack_triple(item: Union[SparseTriple, List, Tuple]) \ + -> Tuple[List[int], List[int], List[float]]: """Extracts the indices and values from an object. The argument item can either be an instance of SparseTriple or a @@ -156,10 +188,10 @@ def unpack_triple(item): >>> quad_expr = [[], [], []] >>> ind1, ind2, val = unpack_triple(quad_expr) """ - try: + if isinstance(item, SparseTriple): assert item.isvalid() ind1, ind2, val = item.unpack() - except AttributeError: + elif isinstance(item, (list, tuple)): ind1, ind2, val = item[0:3] validate_arg_lengths([ind1, ind2, val]) return ind1, ind2, val From a376dd89739af2a9b3d08c1d2c2eeb7bdd0a3905 Mon Sep 17 00:00:00 2001 From: Atsushi Matsuo Date: Wed, 4 Mar 2020 19:32:13 +0900 Subject: [PATCH 007/323] added modes for InequalityToEqualityConverter --- .../inequality_to_equality_converter.py | 186 +++++++++++------- 1 file changed, 119 insertions(+), 67 deletions(-) diff --git a/qiskit/optimization/converters/inequality_to_equality_converter.py b/qiskit/optimization/converters/inequality_to_equality_converter.py index 00051345ea..ce1ed5fd03 100644 --- a/qiskit/optimization/converters/inequality_to_equality_converter.py +++ b/qiskit/optimization/converters/inequality_to_equality_converter.py @@ -14,13 +14,16 @@ import copy +import math from typing import List, Tuple, Dict import numpy as np +from cplex import SparsePair -from qiskit.optimization.problems.optimization_problem import OptimizationProblem -from qiskit.optimization.results.optimization_result import OptimizationResult +from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.results import OptimizationResult from qiskit.optimization.utils import QiskitOptimizationError +from qiskit.aqua import AquaError class InequalityToEqualityConverter: @@ -41,15 +44,19 @@ def __init__(self): self._src = None self._dst = None self._conv: Dict[str, List[Tuple[str, int]]] = {} - # e.g., self._conv = {'c1': [('s@1', 1), ('s@2', 2)]} + # e.g., self._conv = {'c1': [c1@slack_var]} - def encode(self, op: OptimizationProblem, name: str = None) -> OptimizationProblem: + def encode(self, op: OptimizationProblem, name: str = None, mode: str = 'auto') -> OptimizationProblem: """ Convert a problem with inequality constraints into new one with only equality constraints. Args: op: The problem to be solved, that may contain inequality constraints. name: The name of the converted problem. + mode: To chose the type of slack variables. There are 3 options for mode. + - 'integer': All slack variables will be integer variables. + - 'continuous': All slack variables will be continuous variables + - 'auto': Try to use integer variables but if it's not possible, use continuous variables Returns: The converted problem, that contain only equality constraints. @@ -108,78 +115,32 @@ def encode(self, op: OptimizationProblem, name: str = None) -> OptimizationProbl # When the type of a constraint is L, make an equality constraint # with slack variables which represent [lb, ub] = [0, constant - the lower bound of lhs] elif senses[i] == 'L': - lhs_lb = 0 - for ind, val in zip(rows[i].ind, rows[i].val): - if self._dst.variables.get_types(ind) == 'B': - ub = 1 - else: - ub = self._dst.variables.get_upper_bounds(ind) - lb = self._dst.variables.get_lower_bounds(ind) - - lhs_lb += min(lb * val, ub * val) - - slack_vars = self._encode_var(name=name + '_slack', lb=0, ub=rhs[i] - lhs_lb) - self._dst.variables.add(names=[name for name, _ in slack_vars], - types='B' * len(slack_vars)) - self._conv[names[i]] = slack_vars - - new_ind = rows[i].ind - new_val = rows[i].val - - for name, coef in slack_vars: - new_ind.append(self._dst.variables._varsgetindex[name]) - new_val.append(coef) - self._dst.linear_constraints.add(lin_expr=[rows[i]], senses=['E'], rhs=[rhs[i]], - names=[names[i]]) + if mode == 'integer': + self._add_int_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], sense=senses[i]) + elif mode == 'continuous': + self._add_continuous_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], sense=senses[i]) + elif mode == 'auto': + self._add_auto_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], sense=senses[i]) + else: + raise AquaError('Unsupported mode is selected' + mode) # When the type of a constraint is G, make an equality constraint # with slack variables which represent [lb, ub] = [0, the upper bound of lhs] elif senses[i] == 'G': - lhs_ub = 0 - for ind, val in zip(rows[i].ind, rows[i].val): - if self._dst.variables.get_types(ind) == 'B': - ub = 1 - else: - ub = self._dst.variables.get_upper_bounds(ind) - lb = self._dst.variables.get_lower_bounds(ind) - - lhs_ub += max(lb * val, ub * val) - slack_vars = self._encode_var(name=name + '_slack', lb=0, ub=lhs_ub - rhs[i]) - self._dst.variables.add(names=[name for name, _ in slack_vars], - types='B' * len(slack_vars)) - self._conv[names[i]] = slack_vars - - new_ind = rows[i].ind - new_val = rows[i].val - - for name, coef in slack_vars: - new_ind.append(self._dst.variables._varsgetindex[name]) - new_val.append(-1 * coef) - self._dst.linear_constraints.add(lin_expr=[rows[i]], senses=['E'], rhs=[rhs[i]], - names=[names[i]]) + if mode == 'integer': + self._add_int_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], sense=senses[i]) + elif mode == 'continuous': + self._add_continuous_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], sense=senses[i]) + elif mode == 'auto': + self._add_auto_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], sense=senses[i]) + else: + raise AquaError('Unsupported mode is selected' + mode) else: - raise QiskitOptimizationError('Sense type not supported: ' + senses[i]) + raise QiskitOptimizationError('The type of Sense in ' + name + 'is not supported') return self._dst - def _encode_var(self, name: str, lb: int, ub: int) -> List[Tuple[str, int]]: - # bounded-coefficient encoding proposed in arxiv:1706.01945 (Eq. (5)) - var_range = ub - lb - power = int(np.log2(var_range)) - bounded_coef = var_range - (2 ** power - 1) - - lst = [] - for i in range(power): - coef = 2 ** i - new_name = name + self._delimiter + str(i) - lst.append((new_name, coef)) - - new_name = name + self._delimiter + str(power) - lst.append((new_name, bounded_coef)) - - return lst - def decode(self, result: OptimizationResult) -> OptimizationResult: """ Convert a result of a converted problem into that of the original problem. @@ -214,3 +175,94 @@ def _decode_var(self, names, vals) -> List[int]: else: new_vals.append(sol[name]) return new_vals + + def _add_int_slack_var_constraint(self, name, row, rhs, sense): + # If a coefficient that is not integer exist, raise error + if any(isinstance(coef, float) and not coef.is_integer() for coef in row.val): + raise AquaError('Can not use a slack variable for ' + name) + + slack_name = name + self._delimiter + 'int_slack' + lhs_lb, lhs_ub = self._calc_bounds(row) + + # If rhs is float number, round up/down to the nearest integer. + if sense == 'L': + new_rhs = math.floor(rhs) + if sense == 'G': + new_rhs = math.ceil(rhs) + + # Add a new integer variable. + if sense == 'L': + sign = 1 + self._dst.variables.add(names=[slack_name], + lb=[0], ub=[new_rhs - lhs_lb], types=['I']) + elif sense == 'G': + sign = -1 + self._dst.variables.add(names=[slack_name], + lb=[0], ub=[lhs_ub - new_rhs], types=['I']) + else: + raise QiskitOptimizationError('The type of Sense in ' + name + 'is not supported') + + self._conv[name] = slack_name + + new_ind = copy.deepcopy(row.ind) + new_val = copy.deepcopy(row.val) + + new_ind.append(self._dst.variables._varsgetindex[slack_name]) + new_val.append(sign) + + # Add a new equality constraint. + self._dst.linear_constraints.add(lin_expr=[SparsePair(ind=new_ind, val=new_val)], senses=['E'], + rhs=[new_rhs], names=[name]) + + def _add_continuous_slack_var_constraint(self, name, row, rhs, sense): + slack_name = name + self._delimiter + 'continuous_slack' + lhs_lb, lhs_ub = self._calc_bounds(row) + + if sense == 'L': + sign = 1 + self._dst.variables.add(names=[slack_name], + lb=[0], ub=[rhs - lhs_lb], types=['C']) + elif sense == 'G': + sign = -1 + print(lhs_ub - rhs) + self._dst.variables.add(names=[slack_name], + lb=[0], ub=[lhs_ub - rhs], types=['C']) + else: + raise QiskitOptimizationError('The type of Sense in ' + name + 'is not supported') + + self._conv[name] = slack_name + + new_ind = copy.deepcopy(row.ind) + new_val = copy.deepcopy(row.val) + + new_ind.append(self._dst.variables._varsgetindex[slack_name]) + new_val.append(sign) + + # Add a new equality constraint. + self._dst.linear_constraints.add(lin_expr=[SparsePair(ind=new_ind, val=new_val)], senses=['E'], + rhs=[rhs], names=[name]) + + def _add_auto_slack_var_constraint(self, name, row, rhs, sense): + # If a coefficient that is not integer exist, use a continuous slack variable + if any(isinstance(coef, float) and not coef.is_integer() for coef in row.val): + self._add_continuous_slack_var_constraint(name=name, row=row, rhs=rhs, + sense=sense) + # Else use an integer slack variable + else: + self._add_int_slack_var_constraint(name=name, row=row, rhs=rhs, + sense=sense) + + def _calc_bounds(self, row): + lhs_lb = 0 + lhs_ub = 0 + for ind, val in zip(row.ind, row.val): + if self._dst.variables.get_types(ind) == 'B': + ub = 1 + else: + ub = self._dst.variables.get_upper_bounds(ind) + lb = self._dst.variables.get_lower_bounds(ind) + + lhs_lb += min(lb * val, ub * val) + lhs_ub += max(lb * val, ub * val) + + return lhs_lb, lhs_ub From df76443434b54e5e5b60109d748405b6b4a9ebdb Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Wed, 4 Mar 2020 15:14:26 +0000 Subject: [PATCH 008/323] start ADMM full iteration --- .../optimization/algorithms/admm_optimizer.py | 258 ++++++++++++++++-- test/optimization/test_admm_miskp.py | 4 - 2 files changed, 236 insertions(+), 26 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 870fc8241e..431358e948 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -1,7 +1,10 @@ +import time from typing import List import numpy as np from cplex import SparsePair + +from qiskit.optimization.algorithms import CplexOptimizer from qiskit.optimization.algorithms.optimization_algorithm import OptimizationAlgorithm from qiskit.optimization.problems.optimization_problem import OptimizationProblem from qiskit.optimization.problems.variables import CPX_BINARY, CPX_CONTINUOUS @@ -10,26 +13,62 @@ class ADMMParameters: - def __init__(self, rho=10000, factor_c=100000, beta=1000) -> None: + def __init__(self, rho=10000, factor_c=100000, beta=1000, max_iter=10, tol=1.e-4, max_time=1800, + three_block=True, vary_rho=0, tau_incr=2, tau_decr=2, mu_res=10, + mu=1000) -> None: """ - Defines parameter for ADMM. + Defines parameters for ADMM. :param rho: Rho parameter of ADMM. :param factor_c: Penalizing factor for equality constraints, when mapping to QUBO. :param beta: Penalization for y decision variables. + :param max_iter: Maximum number of iterations for ADMM. + :param tol: Tolerance for the residual convergence. + :param max_time: Maximum running time (in seconds) for ADMM. + :param three_block: Boolean flag to select the 3-block ADMM implementation. + :param vary_rho: Flag to select the rule to update rho. + If set to 0, then rho increases by 10% at each iteartion. + If set to 1, then rho is modified according to primal and dual residuals. + :param tau_incr: Parameter used in the rho update. + :param tau_decr: Parameter used in the rho update. + :param mu_res: Parameter used in the rho update. + :param mu: Penalization for constraint residual. Used to compute the merit values. """ super().__init__() + self.mu = mu + self.mu_res = mu_res + self.tau_decr = tau_decr + self.tau_incr = tau_incr + self.vary_rho = vary_rho + self.three_block = three_block + self.max_time = max_time + self.tol = tol + self.max_iter = max_iter self.factor_c = factor_c + # TODO: rho and beta should be moved into the state self.rho = rho self.beta = beta - class ADMMState: def __init__(self, binary_size: int) -> None: + """ + These are the parameters that are updated in the ADMM iterations. + :param binary_size: Number of binary decision variables of the original problem + """ super().__init__() - self.y = np.zeros(binary_size) + self.x0 = np.zeros(binary_size) self.z = np.zeros(binary_size) + self.y = np.zeros(binary_size) self.lambda_mult = np.zeros(binary_size) - self.x0 = np.zeros(binary_size) + + self.cost_iterates = [] + self.residuals = [] + self.dual_residuals = [] + self.cons_r = [] + self.merits = [] + self.lambdas = [] + self.x0_saved = {} + self.z_saved = {} + self.y_saved = {} class ADMMOptimizer(OptimizationAlgorithm): @@ -42,9 +81,20 @@ def __init__(self, params: ADMMParameters = None) -> None: # create default params params = ADMMParameters() # todo: keep parameters as ADMMParameters or copy to the class level? + self._three_block = params.three_block + self._max_time = params.max_time + self._tol = params.tol + self._max_iter = params.max_iter self._factor_c = params.factor_c self._rho = params.rho self._beta = params.beta + self._mu_res = params.mu_res + self._tau_decr = params.tau_decr + self._tau_incr = params.tau_incr + self._vary_rho = params.vary_rho + self._three_block = params.three_block + self._mu = params.mu + # internal state where we'll keep intermediate solution self._state = None @@ -90,26 +140,74 @@ def solve(self, problem: OptimizationProblem): # debug # self.__dump_matrices_and_vectors() - op1 = self._create_step1_problem() - # debug - op1.write("op1.lp") + start_time = time.time() + + it = 0 + r = 1.e+2 + + # TODO: Handle objective sense. This has to be feed to the solvers of the subproblems. + + end_time = time.time() - start_time + + while (it < self._max_iter and r > self._tol) and (end_time < self._max_time): + + op1 = self._create_step1_problem() + # debug + op1.write("op1.lp") + + # TODO: qubo_solver and continuous_solver will be `solve` arguments later + qubo_solver = CplexOptimizer() + self._state.x0 = self.update_x0(qubo_solver, op1) + # debug + print("x0={}".format(self._state.x0)) + + op2 = self._create_step2_problem() + op2.write("op2.lp") - op2 = self._create_step2_problem() - op2.write("op2.lp") + continuous_solver = CplexOptimizer() + self._state.z = self.update_x1(continuous_solver, op2) + # debug + print("z={}".format(self._state.z)) - op3 = self._create_step3_problem() - op3.write("op3.lp") + if self._three_block: + op3 = self._create_step3_problem() + op3.write("op3.lp") + self._state.y = self.update_y(continuous_solver, op3) + # debug + print("y={}".format(self._state.y)) - # solve the problem - # ... - # prepare the solution + lambda_mult = self.update_lambda_mult() + + cost_iterate = self.get_cost_val() + + cr = self.get_cons_res() + + r, s = self.get_sol_res(it) + + merit = self.get_merit() + + # costs and merits are saved with their original sign + # TODO: obtain the sense + self._state.cost_iterates.append(sense * cost_iterate) + self._state.residuals.append(r) + self._state.dual_residuals.append(s) + self._state.cons_r.append(cr) + self._state.merits.append(sense * merit) + self._state.lambdas.append(np.linalg.norm(lambda_mult)) + + self._state.x0_saved[it] = self._state.x0 + self._state.z_saved[it] = self._state.z + self._state.z_saved[it] = self._state.y + + self.update_rho(r, s) + + it += 1 + end_time = time.time() - start_time + + sol, sol_val = self.get_min_mer_sol() - # actual results - x = 0 - # function value - fval = 0 # third parameter is our internal state of computations - result = OptimizationResult(x, fval, self._state) + result = OptimizationResult(sol, sol_val, self._state) return result def _get_variable_indices(self, var_type: str) -> List[int]: @@ -121,9 +219,11 @@ def _get_variable_indices(self, var_type: str) -> List[int]: return indices def get_q0(self): + # TODO: Flip the sign, according to the optimization sense return self._get_q(self._binary_indices) def get_q1(self): + # TODO: Flip the sign, according to the optimization sense return self._get_q(self._continuous_indices) def _get_q(self, variable_indices: List[int]) -> np.ndarray: @@ -141,9 +241,11 @@ def _get_c(self, variable_indices: List[int]) -> np.ndarray: return c def get_c0(self): + # TODO: Flip the sign, according to the optimization sense return self._get_c(self._binary_indices) def get_c1(self): + # TODO: Flip the sign, according to the optimization sense return self._get_c(self._continuous_indices) def _assign_row_values(self, matrix: List[List[float]], vector: List[float], constraint_index, variable_indices): @@ -275,8 +377,8 @@ def _create_step2_problem(self): continuous_size = len(self._continuous_indices) binary_size = len(self._binary_indices) - lb = self._op.variables.get_lower_bounds(self._binary_indices) - ub = self._op.variables.get_upper_bounds(self._binary_indices) + lb = self._op.variables.get_lower_bounds(self._continuous_indices) + ub = self._op.variables.get_upper_bounds(self._continuous_indices) if continuous_size: # add u variables op2.variables.add(names=["u0_" + str(i + 1) for i in range(continuous_size)], @@ -437,3 +539,115 @@ def __dump_matrices_and_vectors(self): print(b2) print("b2 shape") print(b2.shape) + + def update_x0(self, qubo_solver: OptimizationAlgorithm, op1: OptimizationProblem) -> np.ndarray: + return np.asarray(qubo_solver.solve(op1).x) + + def update_x1(self, continuous_solver: OptimizationAlgorithm, op2: OptimizationProblem) -> np.ndarray: + vars_op2 = continuous_solver.solve(op2).x + # TODO: Is there a more elegant way to access the number of cont vars below? + return np.asarray(vars_op2[len(self._continuous_indices):]) # Here, only z variables have to be obtained + + def update_y(self, continuous_solver, op3): + return np.asarray(continuous_solver.solve(op3).x) + + def get_min_mer_sol(self): + """ + The ADMM solution is that for which the merit value is the least + :return sol: Iterate with the least merit value + :return sol_val: Value of sol, according to the original objective + """ + it_min_merits = self._state.merits.index(min(self._state.merits)) + sol = self._state.x0_saved[it_min_merits] + sol_val = self._state.cost_iterates[it_min_merits] + return sol, sol_val + + def update_lambda_mult(self): + return self._state.lambda_mult + self._rho * (self._state.x0 - self._state.z + self._state.y) + + def update_rho(self, r, s): + """ + Updating the rho parameter in ADMM + :param r: primal residual + :param s: dual residual + :return: + """ + + if self._vary_rho == 0: + # Increase rho, to aid convergence. + if self._rho < 1.e+10: + self._rho *= 1.1 + elif self._vary_rho == 1: + if r > self._mu_res * s: + self._rho = self._tau_incr * self._rho + elif s > self._mu_res * r: + self._rho = self._tau_decr * self._rho + + def get_cons_res(self): + """Compute violation of the constraints of the original problem, as: + - norm 1 of the body-rhs of the constraints A0 x0 - b0 + - -1 * min(body - rhs, 0) for \geq constraints + - max(body - rhs, 0) for \leq constraints + """ + + # TODO: think whether a0, b0 should be saved somewhere.. + a0, b0 = self.get_a0_b0() + cr0 = sum(np.abs(np.dot(a0, self._state.x0) - b0)) + + a1, b1 = self.get_a1_b1() + eq1 = np.dot(a1, self._state.x0) - b1 + cr1 = sum(max(val, 0) for val in eq1) + + a2, a3, b2 = self.get_a2_a3_b2() + eq2 = np.dot(a2, self._state.x0) + np.dot(a3, self._state.u) - b2 + cr2 = sum(max(val, 0) for val in eq2) + + return cr0+cr1+cr2 + + def get_merit(self): + """ + Compute merit value associated with the current iterate + """ + return self._state.cost_iterate + self._mu * self._state.cr + + def get_cost_val(self): + """ + Computes the value of the objective function. + :param x0: + :param u: + :return: + """ + quadr_form = lambda A, x, c: np.dot(x.T, np.dot(A, x)) + np.dot(c.T, x) + + q0 = self.get_q0() + q1 = self.get_q1() + c0 = self.get_c0() + c1 = self.get_c1() + + obj_val = quadr_form(q0, self._state.x0, c0) + obj_val += quadr_form(q1, self._state.u, c1) + + return obj_val + + def get_sol_res(self, it): + """ + Compute primal and dual residual. + + :param x0: + :param z: + :param y: + :param z_old: + :return: + """ + elements = self._state.x0 - self._state.z - self._state.y + # debug + # elements = np.asarray([x0[i] - z[i] + y[i] for i in self.range_x0_vars]) + r = pow(sum(e ** 2 for e in elements), 0.5) + elements_dual = self._state.z - self._state.z_saved[it-1] + # debug + # elements_dual = np.asarray([z[i] - z_old[i] for i in self.range_x0_vars]) + s = self._rho * pow(sum(e ** 2 for e in elements_dual), 0.5) + + return r, s + + diff --git a/test/optimization/test_admm_miskp.py b/test/optimization/test_admm_miskp.py index e12dd7878d..6c7e024bfc 100644 --- a/test/optimization/test_admm_miskp.py +++ b/test/optimization/test_admm_miskp.py @@ -305,11 +305,7 @@ def run_op(self): self.create_params() self.create_vars() self.create_obj() - start_time = time.time() self.create_cons() - constraints_time = time.time() - start_time - if self.verbose: - print ("Time to populate constraints:", constraints_time) # Save the model create_folder(self.lp_folder) From d3c98ca0adf22ecb2ee4da4295a156c42b6303d5 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Thu, 5 Mar 2020 00:04:27 +0100 Subject: [PATCH 009/323] add recursive optimization --- qiskit/optimization/algorithms/__init__.py | 4 +- .../algorithms/recursive_ising_optimizer.py | 49 ----- .../recursive_min_eigen_optimizer.py | 172 ++++++++++++++++++ .../test_recursive_optimization.py | 83 +++++++++ 4 files changed, 258 insertions(+), 50 deletions(-) delete mode 100644 qiskit/optimization/algorithms/recursive_ising_optimizer.py create mode 100644 qiskit/optimization/algorithms/recursive_min_eigen_optimizer.py create mode 100755 test/optimization/test_recursive_optimization.py diff --git a/qiskit/optimization/algorithms/__init__.py b/qiskit/optimization/algorithms/__init__.py index 5cbe3f2ab8..b7c10e79dc 100644 --- a/qiskit/optimization/algorithms/__init__.py +++ b/qiskit/optimization/algorithms/__init__.py @@ -28,5 +28,7 @@ from qiskit.optimization.algorithms.cplex_optimizer import CplexOptimizer from qiskit.optimization.algorithms.cobyla_optimizer import CobylaOptimizer from qiskit.optimization.algorithms.min_eigen_optimizer import MinEigenOptimizer +from qiskit.optimization.algorithms.recursive_min_eigen_optimizer import RecursiveMinEigenOptimizer -__all__ = ["OptimizationAlgorithm", "CplexOptimizer", "CobylaOptimizer", "MinEigenOptimizer"] +__all__ = ["OptimizationAlgorithm", "CplexOptimizer", "CobylaOptimizer", "MinEigenOptimizer", + "RecursiveMinEigenOptimizer"] diff --git a/qiskit/optimization/algorithms/recursive_ising_optimizer.py b/qiskit/optimization/algorithms/recursive_ising_optimizer.py deleted file mode 100644 index 360f74209b..0000000000 --- a/qiskit/optimization/algorithms/recursive_ising_optimizer.py +++ /dev/null @@ -1,49 +0,0 @@ - -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2019. -# -# 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. - -"""A recursive minimal eigen optimizer in Qiskit Optimization. - - Examples: - >>> problem = OptimizationProblem() - >>> # specify problem here - >>> # specify minimum eigen solver to be used, e.g., QAOA - >>> qaoa = QAOA(...) - >>> optimizer = RecursiveMinEigenOptimizer(qaoa) - >>> result = optimizer.solve(problem) -""" - -from qiskit.optimization.algorithms import OptimizationAlgorithm - - -class RecursiveIsingOptimizer(OptimizationAlgorithm): - """ - TODO - """ - - def __init__(self, ising_solver, mode='correlation', min_num_vars=0): - """ - TODO - """ - # TODO: should also allow function that maps problem to -correlators? - # --> would support efficient classical implementation for QAOA with depth p=1 - self._eigen_solver = eigen_solver # TODO: base on eigen_solver or ising_optimizer? - self._mode = mode - self._min_num_vars = min_num_vars - - def solve(self, problem): - # handle variable replacements via variable names - # --> allows to easily adjust problems and roll-out final results - # TODO - pass diff --git a/qiskit/optimization/algorithms/recursive_min_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_min_eigen_optimizer.py new file mode 100644 index 0000000000..da0b9d59a6 --- /dev/null +++ b/qiskit/optimization/algorithms/recursive_min_eigen_optimizer.py @@ -0,0 +1,172 @@ + +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +"""A recursive minimal eigen optimizer in Qiskit Optimization. + + Examples: + >>> problem = OptimizationProblem() + >>> # specify problem here + >>> # specify minimum eigen solver to be used, e.g., QAOA + >>> qaoa = QAOA(...) + >>> optimizer = RecursiveMinEigenOptimizer(qaoa) + >>> result = optimizer.solve(problem) +""" + +from typing import Optional +import numpy as np +from cplex import SparseTriple +from copy import deepcopy + +from qiskit.optimization import QiskitOptimizationError +from qiskit.optimization.algorithms import OptimizationAlgorithm, MinEigenOptimizer +from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.results import OptimizationResult +from qiskit.optimization.converters import (OptimizationProblemToOperator, + PenalizeLinearEqualityConstraints, + IntegerToBinaryConverter) +from qiskit.aqua.algorithms import ExactEigensolver + + +class RecursiveMinEigenOptimizer(OptimizationAlgorithm): + """ + TODO + """ + + def __init__(self, min_eigen_optimizer: MinEigenOptimizer, min_num_vars: int = 1, + min_num_vars_optimizer: Optional[OptimizationAlgorithm] = None, + penalty: Optional[float] = None) -> None: + """ + TODO: add flag to store full history... + """ + # TODO: should also allow function that maps problem to -correlators? + # --> would support efficient classical implementation for QAOA with depth p=1 + self._min_eigen_optimizer = min_eigen_optimizer + if min_num_vars < 1: + raise QiskitOptimizationError('Minimal problem size needs to be >= 1!') + self._min_num_vars = min_num_vars + if min_num_vars_optimizer: + self._min_num_vars_optimizer = min_num_vars_optimizer + else: + self._min_num_vars_optimizer = MinEigenOptimizer(ExactEigensolver()) + self._penalty = penalty + + def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + """ + TODO + """ + return None + + def solve(self, problem: OptimizationProblem) -> OptimizationResult: + """ + # handle variable replacements via variable names + # --> allows to easily adjust problems and roll-out final results + # TODO + """ + + # analyze compatibility of problem + msg = self.is_compatible(problem) + if msg is not None: + raise QiskitOptimizationError('Incompatible problem: %s' % msg) + + # map integer variables to binary variables + int_to_bin_converter = IntegerToBinaryConverter() + problem_ = int_to_bin_converter.encode(problem) + + # penalize linear equality constraints with only binary variables + if self._penalty is None: + # TODO: should be derived from problem + penalty = 1e5 + else: + penalty = self._penalty + lin_eq_converter = PenalizeLinearEqualityConstraints() + problem_ = lin_eq_converter.encode(problem_, penalty_factor=penalty) + problem_ref = deepcopy(problem_) + + # run recursive optimization until the resulting problem is small enough + replacements = {} + while problem_.variables.get_num() > self._min_num_vars: + + # solve current problem with optimizer + result = self._min_eigen_optimizer.solve(problem_) + details = result.results[0] + + # analyze results to get strongest correlation + states = [v[0] for v in details] + probs = [v[2] for v in details] + correlations = self._construct_correlations(states, probs) + i, j = self._find_strongest_correlation(correlations) + + xi = problem_.variables.get_names(i) + xj = problem_.variables.get_names(j) + if correlations[i, j] > 0: + problem_ = problem_.substitute_variables(variables=SparseTriple([i], [j], [1])) + replacements[xi] = (xj, 1) + else: + problem_ = problem_.substitute_variables(variables=SparseTriple([i], [j], [-1])) + replacements[xi] = (xj, -1) + + # solve remaining problem + result = self._min_num_vars_optimizer.solve(problem_) + + # unroll replacements + var_values = {} + for i, name in enumerate(problem_.variables.get_names()): + var_values[name] = result.x[i] + + def find_value(x, replacements, var_values): + if x in var_values: + # if value for variable is known, return it + return var_values[x] + elif x in replacements: + # get replacement for variable + (y, sgn) = replacements[x] + # find details for replacing variable + value = find_value(y, replacements, var_values) + # construct, set, and return new value + var_values[x] = value if sgn == 1 else 1 - value + return var_values[x] + else: + raise QiskitOptimizationError('Invalid values!') + + # loop over all variables to set their values + for xi in problem_ref.variables.get_names(): + if xi not in var_values: + find_value(xi, replacements, var_values) + + # construct result + x = [var_values[name] for name in problem_ref.variables.get_names()] + fval = 0 # TODO: problem.objective.evaluate(x) + results = OptimizationResult(x, fval, (replacements, int_to_bin_converter)) + results = int_to_bin_converter.decode(results) + return results + + def _construct_correlations(self, states, probs): + n = len(states[0]) + correlations = np.zeros((n, n)) + for k, p in enumerate(probs): + b = states[k][::-1] + for i in range(n): + for j in range(i): + if b[i] == b[j]: + correlations[i, j] += p + else: + correlations[i, j] -= p + return correlations + + def _find_strongest_correlation(self, M): + m_max = np.argmax(np.abs(M.flatten())) + i = m_max // len(M) + j = m_max - i*len(M) + return (i, j) diff --git a/test/optimization/test_recursive_optimization.py b/test/optimization/test_recursive_optimization.py new file mode 100755 index 0000000000..7a5db21e00 --- /dev/null +++ b/test/optimization/test_recursive_optimization.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# 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. + +""" Test Recursive Min Eigen Optimizer """ + +from test.optimization.common import QiskitOptimizationTestCase +import numpy as np +from ddt import ddt, data + +from qiskit import BasicAer + +from qiskit.aqua import QuantumInstance +from qiskit.aqua.algorithms import ExactEigensolver +from qiskit.aqua.algorithms import QAOA +from qiskit.aqua.components.optimizers import COBYLA + +from qiskit.optimization.algorithms import (MinEigenOptimizer, CplexOptimizer, + RecursiveMinEigenOptimizer) +from qiskit.optimization.problems import OptimizationProblem + + +@ddt +class TestRecursiveMinEigenOptimizer(QiskitOptimizationTestCase): + """Rrecursive Min Eigen Optimizer Tests.""" + + def setUp(self): + super().setUp() + + self.resource_path = './test/optimization/resources/' + + # setup minimum eigen solvers + self.min_eigen_solvers = {} + + # exact eigen solver + self.min_eigen_solvers['exact'] = ExactEigensolver() + + # QAOA + optimizer = COBYLA() + self.min_eigen_solvers['qaoa'] = QAOA(optimizer=optimizer) + + @data( + ('exact', None, 'op_ip1.lp'), + # ('qaoa', 'statevector_simulator', 'op_ip1.lp'), + # ('qaoa', 'qasm_simulator', 'op_ip1.lp') + ) + def test_recursive_min_eigen_optimizer(self, config): + """ Min Eigen Optimizer Test """ + + # unpack configuration + min_eigen_solver_name, backend, filename = config + + # get minimum eigen solver + min_eigen_solver = self.min_eigen_solvers[min_eigen_solver_name] + if backend: + min_eigen_solver._quantum_instance = BasicAer.get_backend(backend) + min_eigen_optimizer = MinEigenOptimizer(min_eigen_solver) + # construct minimum eigen optimizer + recursive_min_eigen_optimizer = RecursiveMinEigenOptimizer(min_eigen_optimizer) + + # load optimization problem + problem = OptimizationProblem() + problem.read(self.resource_path + filename) + + # solve problem with cplex + cplex = CplexOptimizer() + cplex_result = cplex.solve(problem) + + # solve problem + result = recursive_min_eigen_optimizer.solve(problem) + + # analyze results + self.assertAlmostEqual(cplex_result.fval, result.fval) From d17f1f332f578c813f9b8e79330bd0caaeba5505 Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Thu, 5 Mar 2020 13:24:37 +0000 Subject: [PATCH 010/323] added z_init, u_saved. admm tested on bpp, miskp with cplex_optimizer. --- .../optimization/algorithms/admm_optimizer.py | 85 ++++++++++++------- test/optimization/test_admm_bpp.py | 6 +- 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 431358e948..5c70f2cf84 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -44,31 +44,34 @@ def __init__(self, rho=10000, factor_c=100000, beta=1000, max_iter=10, tol=1.e-4 self.tol = tol self.max_iter = max_iter self.factor_c = factor_c - # TODO: rho and beta should be moved into the state - self.rho = rho self.beta = beta + # TODO: rho should be moved into the state + self.rho = rho class ADMMState: def __init__(self, binary_size: int) -> None: """ - These are the parameters that are updated in the ADMM iterations. :param binary_size: Number of binary decision variables of the original problem """ super().__init__() - self.x0 = np.zeros(binary_size) - self.z = np.zeros(binary_size) - self.y = np.zeros(binary_size) - self.lambda_mult = np.zeros(binary_size) - + # These are the parameters that are updated in the ADMM iterations. + self.x0: np.ndarray = np.zeros(binary_size) + self.z: np.ndarray = np.zeros(binary_size) + self.z_init: np.ndarray = self.z + self.y: np.ndarray = np.zeros(binary_size) + self.lambda_mult: np.ndarray = np.zeros(binary_size) + + # The following structures store quantities obtained in each ADMM iteration. self.cost_iterates = [] self.residuals = [] self.dual_residuals = [] self.cons_r = [] self.merits = [] self.lambdas = [] - self.x0_saved = {} - self.z_saved = {} - self.y_saved = {} + self.x0_saved = [] + self.u_saved = [] + self.z_saved = [] + self.y_saved = [] class ADMMOptimizer(OptimizationAlgorithm): @@ -95,7 +98,6 @@ def __init__(self, params: ADMMParameters = None) -> None: self._three_block = params.three_block self._mu = params.mu - # internal state where we'll keep intermediate solution self._state = None @@ -128,6 +130,14 @@ def is_compatible(self, problem: OptimizationProblem): return True def solve(self, problem: OptimizationProblem): + """ + + :param problem: The original optimization problem. + :return: result: It is an instance of OptimizationResult. + Note that result.x it is a list [x0, u], with x0 + being the value of the binary variables in the ADMM solution, + and u is the value of the continuous variables in the ADMM solution. + """ self._op = problem # parse problem and convert to an ADMM specific representation @@ -155,7 +165,7 @@ def solve(self, problem: OptimizationProblem): # debug op1.write("op1.lp") - # TODO: qubo_solver and continuous_solver will be `solve` arguments later + # TODO: qubo_solver and continuous_solver will be `solve` arguments later, or what else? qubo_solver = CplexOptimizer() self._state.x0 = self.update_x0(qubo_solver, op1) # debug @@ -165,8 +175,9 @@ def solve(self, problem: OptimizationProblem): op2.write("op2.lp") continuous_solver = CplexOptimizer() - self._state.z = self.update_x1(continuous_solver, op2) + self._state.u, self._state.z = self.update_x1(continuous_solver, op2) # debug + print("u={}".format(self._state.u)) print("z={}".format(self._state.z)) if self._three_block: @@ -184,20 +195,24 @@ def solve(self, problem: OptimizationProblem): r, s = self.get_sol_res(it) - merit = self.get_merit() + merit = self.get_merit(cost_iterate, cr) + # debug + print("cost_iterate, cr, merit", cost_iterate, cr, merit) + # costs and merits are saved with their original sign - # TODO: obtain the sense - self._state.cost_iterates.append(sense * cost_iterate) + # TODO: obtain the sense, and update cost iterates and merits + self._state.cost_iterates.append(cost_iterate) self._state.residuals.append(r) self._state.dual_residuals.append(s) self._state.cons_r.append(cr) - self._state.merits.append(sense * merit) + self._state.merits.append(merit) self._state.lambdas.append(np.linalg.norm(lambda_mult)) - self._state.x0_saved[it] = self._state.x0 - self._state.z_saved[it] = self._state.z - self._state.z_saved[it] = self._state.y + self._state.x0_saved.append(self._state.x0) + self._state.u_saved.append(self._state.u) + self._state.z_saved.append(self._state.z) + self._state.z_saved.append(self._state.y) self.update_rho(r, s) @@ -208,6 +223,9 @@ def solve(self, problem: OptimizationProblem): # third parameter is our internal state of computations result = OptimizationResult(sol, sol_val, self._state) + # debug + print("sol={0}, sol_val={1}".format(sol, sol_val)) + print("it {0}, state {1}".format(it, self._state)) return result def _get_variable_indices(self, var_type: str) -> List[int]: @@ -541,14 +559,18 @@ def __dump_matrices_and_vectors(self): print(b2.shape) def update_x0(self, qubo_solver: OptimizationAlgorithm, op1: OptimizationProblem) -> np.ndarray: + # TODO: Check output type of qubo_solver.solve(op1).x return np.asarray(qubo_solver.solve(op1).x) - def update_x1(self, continuous_solver: OptimizationAlgorithm, op2: OptimizationProblem) -> np.ndarray: + def update_x1(self, continuous_solver: OptimizationAlgorithm, op2: OptimizationProblem) -> (np.ndarray, np.ndarray): vars_op2 = continuous_solver.solve(op2).x - # TODO: Is there a more elegant way to access the number of cont vars below? - return np.asarray(vars_op2[len(self._continuous_indices):]) # Here, only z variables have to be obtained + # TODO: Check output type + u = np.asarray(vars_op2[:len(self._continuous_indices)]) + z = np.asarray(vars_op2[len(self._continuous_indices):]) + return u, z def update_y(self, continuous_solver, op3): + # TODO: Check output type return np.asarray(continuous_solver.solve(op3).x) def get_min_mer_sol(self): @@ -558,7 +580,9 @@ def get_min_mer_sol(self): :return sol_val: Value of sol, according to the original objective """ it_min_merits = self._state.merits.index(min(self._state.merits)) - sol = self._state.x0_saved[it_min_merits] + x0 = self._state.x0_saved[it_min_merits] + u = self._state.u_saved[it_min_merits] + sol = [x0, u] sol_val = self._state.cost_iterates[it_min_merits] return sol, sol_val @@ -590,7 +614,7 @@ def get_cons_res(self): - max(body - rhs, 0) for \leq constraints """ - # TODO: think whether a0, b0 should be saved somewhere.. + # TODO: think whether a0, b0 should be saved somewhere.. Might move to state? a0, b0 = self.get_a0_b0() cr0 = sum(np.abs(np.dot(a0, self._state.x0) - b0)) @@ -604,11 +628,11 @@ def get_cons_res(self): return cr0+cr1+cr2 - def get_merit(self): + def get_merit(self, cost_iterate, cr): """ Compute merit value associated with the current iterate """ - return self._state.cost_iterate + self._mu * self._state.cr + return cost_iterate + self._mu * cr def get_cost_val(self): """ @@ -643,7 +667,10 @@ def get_sol_res(self, it): # debug # elements = np.asarray([x0[i] - z[i] + y[i] for i in self.range_x0_vars]) r = pow(sum(e ** 2 for e in elements), 0.5) - elements_dual = self._state.z - self._state.z_saved[it-1] + if it>0: + elements_dual = self._state.z - self._state.z_saved[it-1] + else: + elements_dual = self._state.z - self._state.z_init # debug # elements_dual = np.asarray([z[i] - z_old[i] for i in self.range_x0_vars]) s = self._rho * pow(sum(e ** 2 for e in elements_dual), 0.5) diff --git a/test/optimization/test_admm_bpp.py b/test/optimization/test_admm_bpp.py index 8d8e5bde50..1428948efc 100644 --- a/test/optimization/test_admm_bpp.py +++ b/test/optimization/test_admm_bpp.py @@ -7,7 +7,7 @@ import sys import numpy as np -from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer +from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters from qiskit.optimization.problems import OptimizationProblem @@ -342,8 +342,8 @@ def run_op(self, save_vars=False): self.op.write(self.lp_folder + "bpp.lp") - - solver = ADMMOptimizer() + params = ADMMParameters(max_iter=1) + solver = ADMMOptimizer(params) solution = solver.solve(self.op) return solution From a2a6904f412f2987b61e28e60ce3e706d1a51ea4 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Thu, 5 Mar 2020 15:05:04 +0000 Subject: [PATCH 011/323] introduced rho as a state variable --- .../optimization/algorithms/admm_optimizer.py | 204 +++++++++--------- 1 file changed, 102 insertions(+), 102 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 5c70f2cf84..ac11429294 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -13,12 +13,11 @@ class ADMMParameters: - def __init__(self, rho=10000, factor_c=100000, beta=1000, max_iter=10, tol=1.e-4, max_time=1800, + def __init__(self, rho_initial=10000, factor_c=100000, beta=1000, max_iter=10, tol=1.e-4, max_time=1800, three_block=True, vary_rho=0, tau_incr=2, tau_decr=2, mu_res=10, mu=1000) -> None: - """ - Defines parameters for ADMM. - :param rho: Rho parameter of ADMM. + """Defines parameters for ADMM optimizer and their default values. + :param rho_initial: Initial value of rho parameter of ADMM. :param factor_c: Penalizing factor for equality constraints, when mapping to QUBO. :param beta: Penalization for y decision variables. :param max_iter: Maximum number of iterations for ADMM. @@ -45,13 +44,16 @@ def __init__(self, rho=10000, factor_c=100000, beta=1000, max_iter=10, tol=1.e-4 self.max_iter = max_iter self.factor_c = factor_c self.beta = beta - # TODO: rho should be moved into the state - self.rho = rho + self.rho_initial = rho_initial + class ADMMState: - def __init__(self, binary_size: int) -> None: - """ + def __init__(self, binary_size: int, rho_initial: float) -> None: + """Internal computation state of the ADMM implementation. Here, various variables are stored that are + being updated during problem solving. The values are relevant to the problem being solved. + The state is recreated for each optimization problem. :param binary_size: Number of binary decision variables of the original problem + :param rho_initial: Initial value of the rho parameter. """ super().__init__() # These are the parameters that are updated in the ADMM iterations. @@ -72,6 +74,7 @@ def __init__(self, binary_size: int) -> None: self.u_saved = [] self.z_saved = [] self.y_saved = [] + self.rho = rho_initial class ADMMOptimizer(OptimizationAlgorithm): @@ -83,13 +86,11 @@ def __init__(self, params: ADMMParameters = None) -> None: if params is None: # create default params params = ADMMParameters() - # todo: keep parameters as ADMMParameters or copy to the class level? self._three_block = params.three_block self._max_time = params.max_time self._tol = params.tol self._max_iter = params.max_iter self._factor_c = params.factor_c - self._rho = params.rho self._beta = params.beta self._mu_res = params.mu_res self._tau_decr = params.tau_decr @@ -97,8 +98,10 @@ def __init__(self, params: ADMMParameters = None) -> None: self._vary_rho = params.vary_rho self._three_block = params.three_block self._mu = params.mu - + self._rho_initial = params.rho_initial + # internal state where we'll keep intermediate solution + # here, we just declare the class variable self._state = None def is_compatible(self, problem: OptimizationProblem): @@ -131,7 +134,6 @@ def is_compatible(self, problem: OptimizationProblem): def solve(self, problem: OptimizationProblem): """ - :param problem: The original optimization problem. :return: result: It is an instance of OptimizationResult. Note that result.x it is a list [x0, u], with x0 @@ -145,21 +147,21 @@ def solve(self, problem: OptimizationProblem): self._continuous_indices = self._get_variable_indices(CPX_CONTINUOUS) # create our computation state - self._state = ADMMState(len(self._binary_indices)) + self._state = ADMMState(len(self._binary_indices), self._rho_initial) # debug # self.__dump_matrices_and_vectors() start_time = time.time() + # we have not stated our computations yet, so elapsed time initialized as zero + elapsed_time = 0 it = 0 r = 1.e+2 # TODO: Handle objective sense. This has to be feed to the solvers of the subproblems. - end_time = time.time() - start_time - - while (it < self._max_iter and r > self._tol) and (end_time < self._max_time): + while (it < self._max_iter and r > self._tol) and (elapsed_time < self._max_time): op1 = self._create_step1_problem() # debug @@ -199,7 +201,6 @@ def solve(self, problem: OptimizationProblem): # debug print("cost_iterate, cr, merit", cost_iterate, cr, merit) - # costs and merits are saved with their original sign # TODO: obtain the sense, and update cost iterates and merits self._state.cost_iterates.append(cost_iterate) @@ -217,7 +218,7 @@ def solve(self, problem: OptimizationProblem): self.update_rho(r, s) it += 1 - end_time = time.time() - start_time + elapsed_time = time.time() - start_time sol, sol_val = self.get_min_mer_sol() @@ -376,7 +377,7 @@ def _create_step1_problem(self): a0, b0 = self.get_a0_b0() quadratic_objective = 2 * ( self.get_q0() + self._factor_c / 2 * np.dot(a0.transpose(), a0) + - self._rho / 2 * np.eye(binary_size) + self._state.rho / 2 * np.eye(binary_size) ) for i in range(binary_size): for j in range(i, binary_size): @@ -385,7 +386,7 @@ def _create_step1_problem(self): # prepare and set linear objective c0 = self.get_c0() - linear_objective = c0 - self._factor_c * np.dot(b0, a0) + self._rho * (self._state.y - self._state.z) + linear_objective = c0 - self._factor_c * np.dot(b0, a0) + self._state.rho * (self._state.y - self._state.z) for i in range(binary_size): op1.objective.set_linear(i, linear_objective[i]) return op1 @@ -413,13 +414,15 @@ def _create_step2_problem(self): q_u = 2 * (self.get_q1()) #NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. for i in range(continuous_size): for j in range(i, continuous_size): + # todo: verify that we don't need both calls op2.objective.set_quadratic_coefficients(i, j, q_u[i, j]) op2.objective.set_quadratic_coefficients(j, i, q_u[i, j]) # set quadratic objective coefficients for z variables. - q_z = 2 * (self._rho / 2 * np.eye(binary_size)) # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. + q_z = 2 * (self._state.rho / 2 * np.eye(binary_size)) # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. for i in range(binary_size): for j in range(i, binary_size): + # todo: verify that we don't need both calls op2.objective.set_quadratic_coefficients(i + continuous_size, j + continuous_size, q_z[i, j]) op2.objective.set_quadratic_coefficients(j + continuous_size, i + continuous_size, q_z[i, j]) @@ -430,7 +433,7 @@ def _create_step2_problem(self): op2.objective.set_linear(i, linear_u[i]) # set linear objective for z variables - linear_z = -1 * self._state.lambda_mult - self._rho * (self._state.x0 + self._state.y) + linear_z = -1 * self._state.lambda_mult - self._state.rho * (self._state.x0 + self._state.y) for i in range(binary_size): op2.objective.set_linear(i + continuous_size, linear_z[i]) @@ -457,8 +460,6 @@ def _create_step2_problem(self): lin_expr = [SparsePair(ind=list(range(continuous_size)), val=self._to_list(a4[i, :])) for i in range(constraint_count)] op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, rhs=self._to_list(b3)) - # todo: do we keep u bounds, z bounds as bounds or as constraints. I would keep bounds as bounds. - return op2 def _create_step3_problem(self): @@ -469,13 +470,13 @@ def _create_step3_problem(self): types=["C"] * binary_size) # set quadratic objective. NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. - q_y = 2 * (self._beta / 2 * np.eye(binary_size) + self._rho / 2 * np.eye(binary_size)) + q_y = 2 * (self._beta / 2 * np.eye(binary_size) + self._state.rho / 2 * np.eye(binary_size)) for i in range(binary_size): for j in range(i, binary_size): op3.objective.set_quadratic_coefficients(i, j, q_y[i, j]) op3.objective.set_quadratic_coefficients(j, i, q_y[i, j]) - linear_y = self._state.lambda_mult + self._rho * (self._state.x0 - self._state.z) + linear_y = self._state.lambda_mult + self._state.rho * (self._state.x0 - self._state.z) for i in range(binary_size): op3.objective.set_linear(i, linear_y[i]) @@ -489,75 +490,6 @@ def _to_list(self, values): out_list.append(float(el)) return out_list - # only for debugging! - def __dump_matrices_and_vectors(self): - print("In admm_optimizer.py") - q0 = self.get_q0() - print("Q0") - print(q0) - print("Q0 shape") - print(q0.shape) - q1 = self.get_q1() - print("Q1") - print(q1) - print("Q1") - print(q1.shape) - - c0 = self.get_c0() - print("c0") - print(c0) - print("c0 shape") - print(c0.shape) - c1 = self.get_c1() - print("c1") - print(c1) - print("c1 shape") - print(c1.shape) - - a0, b0 = self.get_a0_b0() - print("A0") - print(a0) - print("A0") - print(a0.shape) - print("b0") - print(b0) - print("b0 shape") - print(b0.shape) - - a1, b1 = self.get_a1_b1() - print("A1") - print(a1) - print("A1 shape") - print(a1.shape) - print("b1") - print(b1) - print("b1 shape") - print(b1.shape) - - a4, b3 = self.get_a4_b3() - print("A4") - print(a4) - print("A4 shape") - print(a4.shape) - print("b3") - print(b3) - print("b3 shape") - print(b3.shape) - - a2, a3, b2 = self.get_a2_a3_b2() - print("A2") - print(a2) - print("A2 shape") - print(a2.shape) - print("A3") - print(a3) - print("A3") - print(a3.shape) - print("b2") - print(b2) - print("b2 shape") - print(b2.shape) - def update_x0(self, qubo_solver: OptimizationAlgorithm, op1: OptimizationProblem) -> np.ndarray: # TODO: Check output type of qubo_solver.solve(op1).x return np.asarray(qubo_solver.solve(op1).x) @@ -587,25 +519,25 @@ def get_min_mer_sol(self): return sol, sol_val def update_lambda_mult(self): - return self._state.lambda_mult + self._rho * (self._state.x0 - self._state.z + self._state.y) + return self._state.lambda_mult + self._state.rho * (self._state.x0 - self._state.z + self._state.y) def update_rho(self, r, s): """ Updating the rho parameter in ADMM :param r: primal residual :param s: dual residual - :return: + :return: None """ if self._vary_rho == 0: # Increase rho, to aid convergence. - if self._rho < 1.e+10: - self._rho *= 1.1 + if self._state.rho < 1.e+10: + self._state.rho *= 1.1 elif self._vary_rho == 1: if r > self._mu_res * s: - self._rho = self._tau_incr * self._rho + self._state.rho = self._tau_incr * self._state.rho elif s > self._mu_res * r: - self._rho = self._tau_decr * self._rho + self._state.rho = self._tau_decr * self._state.rho def get_cons_res(self): """Compute violation of the constraints of the original problem, as: @@ -673,8 +605,76 @@ def get_sol_res(self, it): elements_dual = self._state.z - self._state.z_init # debug # elements_dual = np.asarray([z[i] - z_old[i] for i in self.range_x0_vars]) - s = self._rho * pow(sum(e ** 2 for e in elements_dual), 0.5) + s = self._state.rho * pow(sum(e ** 2 for e in elements_dual), 0.5) return r, s + # only for debugging! + def __dump_matrices_and_vectors(self): + print("In admm_optimizer.py") + q0 = self.get_q0() + print("Q0") + print(q0) + print("Q0 shape") + print(q0.shape) + q1 = self.get_q1() + print("Q1") + print(q1) + print("Q1") + print(q1.shape) + + c0 = self.get_c0() + print("c0") + print(c0) + print("c0 shape") + print(c0.shape) + c1 = self.get_c1() + print("c1") + print(c1) + print("c1 shape") + print(c1.shape) + + a0, b0 = self.get_a0_b0() + print("A0") + print(a0) + print("A0") + print(a0.shape) + print("b0") + print(b0) + print("b0 shape") + print(b0.shape) + + a1, b1 = self.get_a1_b1() + print("A1") + print(a1) + print("A1 shape") + print(a1.shape) + print("b1") + print(b1) + print("b1 shape") + print(b1.shape) + + a4, b3 = self.get_a4_b3() + print("A4") + print(a4) + print("A4 shape") + print(a4.shape) + print("b3") + print(b3) + print("b3 shape") + print(b3.shape) + + a2, a3, b2 = self.get_a2_a3_b2() + print("A2") + print(a2) + print("A2 shape") + print(a2.shape) + print("A3") + print(a3) + print("A3") + print(a3.shape) + print("b2") + print(b2) + print("b2 shape") + print(b2.shape) From 4d20727a9f66ff2c207b3b15c9f6305d7942eaf3 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Thu, 5 Mar 2020 17:20:30 +0000 Subject: [PATCH 012/323] reformatted comments, added class parameters for specifying solvers --- .../optimization/algorithms/admm_optimizer.py | 125 ++++++++++-------- 1 file changed, 72 insertions(+), 53 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index ac11429294..ca4ba509aa 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -15,22 +15,27 @@ class ADMMParameters: def __init__(self, rho_initial=10000, factor_c=100000, beta=1000, max_iter=10, tol=1.e-4, max_time=1800, three_block=True, vary_rho=0, tau_incr=2, tau_decr=2, mu_res=10, - mu=1000) -> None: + mu=1000, qubo_solver_class: OptimizationAlgorithm = CplexOptimizer, + continuous_solver_class: OptimizationAlgorithm = CplexOptimizer) -> None: """Defines parameters for ADMM optimizer and their default values. - :param rho_initial: Initial value of rho parameter of ADMM. - :param factor_c: Penalizing factor for equality constraints, when mapping to QUBO. - :param beta: Penalization for y decision variables. - :param max_iter: Maximum number of iterations for ADMM. - :param tol: Tolerance for the residual convergence. - :param max_time: Maximum running time (in seconds) for ADMM. - :param three_block: Boolean flag to select the 3-block ADMM implementation. - :param vary_rho: Flag to select the rule to update rho. - If set to 0, then rho increases by 10% at each iteartion. - If set to 1, then rho is modified according to primal and dual residuals. - :param tau_incr: Parameter used in the rho update. - :param tau_decr: Parameter used in the rho update. - :param mu_res: Parameter used in the rho update. - :param mu: Penalization for constraint residual. Used to compute the merit values. + + Args: + rho_initial: Initial value of rho parameter of ADMM. + factor_c: Penalizing factor for equality constraints, when mapping to QUBO. + beta: Penalization for y decision variables. + max_iter: Maximum number of iterations for ADMM. + tol: Tolerance for the residual convergence. + max_time: Maximum running time (in seconds) for ADMM. + three_block: Boolean flag to select the 3-block ADMM implementation. + vary_rho: Flag to select the rule to update rho. + If set to 0, then rho increases by 10% at each iteartion. + If set to 1, then rho is modified according to primal and dual residuals. + tau_incr: Parameter used in the rho update. + tau_decr: Parameter used in the rho update. + mu_res: Parameter used in the rho update. + mu: Penalization for constraint residual. Used to compute the merit values. + qubo_solver_class: A subclass of OptimizationAlgorithm that can effectively solve QUBO problems + continuous_solver_class: A subclass of OptimizationAlgorithm that can solve continuous problems """ super().__init__() self.mu = mu @@ -45,15 +50,20 @@ def __init__(self, rho_initial=10000, factor_c=100000, beta=1000, max_iter=10, t self.factor_c = factor_c self.beta = beta self.rho_initial = rho_initial + self.qubo_solver_class = qubo_solver_class + self.continuous_solver_class = continuous_solver_class class ADMMState: def __init__(self, binary_size: int, rho_initial: float) -> None: - """Internal computation state of the ADMM implementation. Here, various variables are stored that are + """ + Internal computation state of the ADMM implementation. Here, various variables are stored that are being updated during problem solving. The values are relevant to the problem being solved. The state is recreated for each optimization problem. - :param binary_size: Number of binary decision variables of the original problem - :param rho_initial: Initial value of the rho parameter. + + Args: + binary_size: Number of binary decision variables of the original problem + rho_initial: Initial value of the rho parameter. """ super().__init__() # These are the parameters that are updated in the ADMM iterations. @@ -86,6 +96,7 @@ def __init__(self, params: ADMMParameters = None) -> None: if params is None: # create default params params = ADMMParameters() + # todo: consider keeping params as an object instead of copying self._three_block = params.three_block self._max_time = params.max_time self._tol = params.tol @@ -100,6 +111,10 @@ def __init__(self, params: ADMMParameters = None) -> None: self._mu = params.mu self._rho_initial = params.rho_initial + # note, we create instances of the solvers here instead of keeping classes + self._qubo_solver = params.qubo_solver_class() + self._continuous_solver = params.continuous_solver_class() + # internal state where we'll keep intermediate solution # here, we just declare the class variable self._state = None @@ -133,12 +148,18 @@ def is_compatible(self, problem: OptimizationProblem): return True def solve(self, problem: OptimizationProblem): - """ - :param problem: The original optimization problem. - :return: result: It is an instance of OptimizationResult. - Note that result.x it is a list [x0, u], with x0 - being the value of the binary variables in the ADMM solution, - and u is the value of the continuous variables in the ADMM solution. + """Tries to solves the given problem using ADMM algorithm. + + Args: + problem: The problem to be solved. + + Returns: + The result of the optimizer applied to the problem. Note that result.x it is a list [x0, u], with x0 + being the value of the binary variables in the ADMM solution, and u is the value of the continuous + variables in the ADMM solution. + + Raises: + QiskitOptimizationError: If the problem is incompatible with the optimizer. """ self._op = problem @@ -167,17 +188,14 @@ def solve(self, problem: OptimizationProblem): # debug op1.write("op1.lp") - # TODO: qubo_solver and continuous_solver will be `solve` arguments later, or what else? - qubo_solver = CplexOptimizer() - self._state.x0 = self.update_x0(qubo_solver, op1) + self._state.x0 = self.update_x0(op1) # debug print("x0={}".format(self._state.x0)) op2 = self._create_step2_problem() op2.write("op2.lp") - continuous_solver = CplexOptimizer() - self._state.u, self._state.z = self.update_x1(continuous_solver, op2) + self._state.u, self._state.z = self.update_x1(op2) # debug print("u={}".format(self._state.u)) print("z={}".format(self._state.z)) @@ -185,7 +203,7 @@ def solve(self, problem: OptimizationProblem): if self._three_block: op3 = self._create_step3_problem() op3.write("op3.lp") - self._state.y = self.update_y(continuous_solver, op3) + self._state.y = self.update_y(op3) # debug print("y={}".format(self._state.y)) @@ -490,26 +508,31 @@ def _to_list(self, values): out_list.append(float(el)) return out_list - def update_x0(self, qubo_solver: OptimizationAlgorithm, op1: OptimizationProblem) -> np.ndarray: + def update_x0(self, op1: OptimizationProblem) -> np.ndarray: # TODO: Check output type of qubo_solver.solve(op1).x - return np.asarray(qubo_solver.solve(op1).x) + return np.asarray(self._qubo_solver.solve(op1).x) - def update_x1(self, continuous_solver: OptimizationAlgorithm, op2: OptimizationProblem) -> (np.ndarray, np.ndarray): - vars_op2 = continuous_solver.solve(op2).x + def update_x1(self, op2: OptimizationProblem) -> (np.ndarray, np.ndarray): + vars_op2 = self._continuous_solver.solve(op2).x # TODO: Check output type u = np.asarray(vars_op2[:len(self._continuous_indices)]) z = np.asarray(vars_op2[len(self._continuous_indices):]) return u, z - def update_y(self, continuous_solver, op3): + def update_y(self, op3): # TODO: Check output type - return np.asarray(continuous_solver.solve(op3).x) + return np.asarray(self._continuous_solver.solve(op3).x) def get_min_mer_sol(self): """ The ADMM solution is that for which the merit value is the least - :return sol: Iterate with the least merit value - :return sol_val: Value of sol, according to the original objective + * sol: Iterate with the least merit value + * sol_val: Value of sol, according to the original objective + + Returns: + A tuple of (sol, sol_val), where + * sol: Iterate with the least merit value + * sol_val: Value of sol, according to the original objective """ it_min_merits = self._state.merits.index(min(self._state.merits)) x0 = self._state.x0_saved[it_min_merits] @@ -524,9 +547,10 @@ def update_lambda_mult(self): def update_rho(self, r, s): """ Updating the rho parameter in ADMM - :param r: primal residual - :param s: dual residual - :return: None + + Args: + r: primal residual + s: dual residual """ if self._vary_rho == 0: @@ -540,10 +564,11 @@ def update_rho(self, r, s): self._state.rho = self._tau_decr * self._state.rho def get_cons_res(self): - """Compute violation of the constraints of the original problem, as: - - norm 1 of the body-rhs of the constraints A0 x0 - b0 - - -1 * min(body - rhs, 0) for \geq constraints - - max(body - rhs, 0) for \leq constraints + """ + Compute violation of the constraints of the original problem, as: + * norm 1 of the body-rhs of the constraints A0 x0 - b0 + * -1 * min(body - rhs, 0) for \geq constraints + * max(body - rhs, 0) for \leq constraints """ # TODO: think whether a0, b0 should be saved somewhere.. Might move to state? @@ -569,9 +594,6 @@ def get_merit(self, cost_iterate, cr): def get_cost_val(self): """ Computes the value of the objective function. - :param x0: - :param u: - :return: """ quadr_form = lambda A, x, c: np.dot(x.T, np.dot(A, x)) + np.dot(c.T, x) @@ -589,11 +611,8 @@ def get_sol_res(self, it): """ Compute primal and dual residual. - :param x0: - :param z: - :param y: - :param z_old: - :return: + Args: + it: """ elements = self._state.x0 - self._state.z - self._state.y # debug From 6325b465da235691ac6e58c61d9d4d2025640227 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Thu, 5 Mar 2020 17:49:43 +0000 Subject: [PATCH 013/323] formatting --- .../optimization/algorithms/admm_optimizer.py | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index ca4ba509aa..c378497b5f 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -391,7 +391,8 @@ def _create_step1_problem(self): lb=[0.] * binary_size, ub=[1.] * binary_size) - # prepare and set quadratic objective. NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. + # prepare and set quadratic objective. + # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. a0, b0 = self.get_a0_b0() quadratic_objective = 2 * ( self.get_q0() + self._factor_c / 2 * np.dot(a0.transpose(), a0) + @@ -429,15 +430,17 @@ def _create_step2_problem(self): # set quadratic objective coefficients for u variables if continuous_size: - q_u = 2 * (self.get_q1()) #NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. + # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. + q_u = 2 * (self.get_q1()) for i in range(continuous_size): for j in range(i, continuous_size): # todo: verify that we don't need both calls op2.objective.set_quadratic_coefficients(i, j, q_u[i, j]) op2.objective.set_quadratic_coefficients(j, i, q_u[i, j]) - # set quadratic objective coefficients for z variables. - q_z = 2 * (self._state.rho / 2 * np.eye(binary_size)) # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. + # set quadratic objective coefficients for z variables. + # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. + q_z = 2 * (self._state.rho / 2 * np.eye(binary_size)) for i in range(binary_size): for j in range(i, binary_size): # todo: verify that we don't need both calls @@ -459,23 +462,28 @@ def _create_step2_problem(self): # A1 z <= b1 a1, b1 = self.get_a1_b1() constraint_count = a1.shape[0] - # in SparsePair val="something from numpy" causes an exception when saving a model via cplex method. rhs="something from numpy" is ok + # in SparsePair val="something from numpy" causes an exception when saving a model via cplex method. + # rhs="something from numpy" is ok # so, we convert every single value to python float, todo: consider removing this conversion - lin_expr = [SparsePair(ind=list(range(continuous_size, continuous_size + binary_size)), val=self._to_list(a1[i, :])) for i in range(constraint_count)] + lin_expr = [SparsePair(ind=list(range(continuous_size, continuous_size + binary_size)), + val=self._to_list(a1[i, :])) for i in range(constraint_count)] op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, rhs=list(b1)) if continuous_size: # A2 z + A3 u <= b2 a2, a3, b2 = self.get_a2_a3_b2() constraint_count = a2.shape[0] - lin_expr = [SparsePair(ind=list(range(continuous_size + binary_size)), val=self._to_list(a3[i, :]) + self._to_list(a2[i, :])) for i in range(constraint_count)] + lin_expr = [SparsePair(ind=list(range(continuous_size + binary_size)), + val=self._to_list(a3[i, :]) + self._to_list(a2[i, :])) + for i in range(constraint_count)] op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, rhs=self._to_list(b2)) if continuous_size: # A4 u <= b3 a4, b3 = self.get_a4_b3() constraint_count = a4.shape[0] - lin_expr = [SparsePair(ind=list(range(continuous_size)), val=self._to_list(a4[i, :])) for i in range(constraint_count)] + lin_expr = [SparsePair(ind=list(range(continuous_size)), + val=self._to_list(a4[i, :])) for i in range(constraint_count)] op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, rhs=self._to_list(b3)) return op2 @@ -487,7 +495,8 @@ def _create_step3_problem(self): op3.variables.add(names=["y_" + str(i + 1) for i in range(binary_size)], types=["C"] * binary_size) - # set quadratic objective. NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. + # set quadratic objective. + # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. q_y = 2 * (self._beta / 2 * np.eye(binary_size) + self._state.rho / 2 * np.eye(binary_size)) for i in range(binary_size): for j in range(i, binary_size): @@ -567,8 +576,8 @@ def get_cons_res(self): """ Compute violation of the constraints of the original problem, as: * norm 1 of the body-rhs of the constraints A0 x0 - b0 - * -1 * min(body - rhs, 0) for \geq constraints - * max(body - rhs, 0) for \leq constraints + * -1 * min(body - rhs, 0) for geq constraints + * max(body - rhs, 0) for leq constraints """ # TODO: think whether a0, b0 should be saved somewhere.. Might move to state? @@ -595,15 +604,16 @@ def get_cost_val(self): """ Computes the value of the objective function. """ - quadr_form = lambda A, x, c: np.dot(x.T, np.dot(A, x)) + np.dot(c.T, x) + # quadr_form = lambda A, x, c: np.dot(x.T, np.dot(A, x)) + np.dot(c.T, x) + def quadratic_form(matrix, x, c): return np.dot(x.T, np.dot(matrix, x)) + np.dot(c.T, x) q0 = self.get_q0() q1 = self.get_q1() c0 = self.get_c0() c1 = self.get_c1() - obj_val = quadr_form(q0, self._state.x0, c0) - obj_val += quadr_form(q1, self._state.u, c1) + obj_val = quadratic_form(q0, self._state.x0, c0) + obj_val += quadratic_form(q1, self._state.u, c1) return obj_val @@ -618,7 +628,7 @@ def get_sol_res(self, it): # debug # elements = np.asarray([x0[i] - z[i] + y[i] for i in self.range_x0_vars]) r = pow(sum(e ** 2 for e in elements), 0.5) - if it>0: + if it > 0: elements_dual = self._state.z - self._state.z_saved[it-1] else: elements_dual = self._state.z - self._state.z_init @@ -696,4 +706,3 @@ def __dump_matrices_and_vectors(self): print(b2) print("b2 shape") print(b2.shape) - From 82c3e3162cc43312c262b00a936cd55e442b491c Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Fri, 6 Mar 2020 18:52:00 +0900 Subject: [PATCH 014/323] implement all methods quadratic constraints --- .../problems/quadratic_constraint.py | 214 +++++++++++++++--- qiskit/optimization/utils/helpers.py | 35 ++- 2 files changed, 207 insertions(+), 42 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index 14a52e806a..68a86a76da 100755 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -12,17 +12,23 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -from typing import List, Tuple, Dict +from collections.abc import Sequence +from logging import getLogger +from typing import List, Dict, Tuple + +from cplex import SparsePair, SparseTriple + from qiskit.optimization.utils.base import BaseInterface +from qiskit.optimization.utils.helpers import convert, NameIndex from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError -from qiskit.optimization.utils.helpers import unpack_pair, unpack_triple, NameIndexConverter -from cplex import SparsePair, SparseTriple + +logger = getLogger(__name__) class QuadraticConstraintInterface(BaseInterface): """Methods for adding, modifying, and querying quadratic constraints.""" - def __init__(self): + def __init__(self, varsgetindexfunc=None): """Creates a new QuadraticConstraintInterface. The quadratic constraints interface is exposed by the top-level @@ -33,9 +39,10 @@ def __init__(self): self._rhs = [] self._senses = [] self._names = [] - self._lin_expr = [] - self._quad_expr = [] - self._index = NameIndexConverter() + self._lin_expr: List[Dict[int, float]] = [] + self._quad_expr: List[Dict[Tuple[int, int], float]] = [] + self._name_index = NameIndex() + self._varsgetindexfunc = varsgetindexfunc def get_num(self) -> int: """Returns the number of quadratic constraints. @@ -54,16 +61,6 @@ def get_num(self) -> int: """ return len(self._names) - def _add(self, lin_expr, quad_expr, sense, rhs, name): - """non-public""" - ind, val = unpack_pair(lin_expr) - if len(val) == 1 and val[0] == 0.0: - ind = [] - val = [] - ind1, ind2, qval = unpack_triple(quad_expr) - varcache = {} - None - def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): """Adds a quadratic constraint to the problem. @@ -106,20 +103,62 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): ... sense = "G") 0 """ + # We only ever create one quadratic constraint at a time. + + # check constraint name + if name == '': + name = 'q{}'.format(len(self._names)) + if name in self._name_index: + raise QiskitOptimizationError('Duplicate quadratic constraint name: %s'.format(name)) + self._names.append(name) + + # linear terms + lin_expr_dict = {} if lin_expr is None: - lin_expr = SparsePair([0], [0.0]) + ind, val = [], [] + elif isinstance(lin_expr, SparsePair): + ind, val = lin_expr.ind, lin_expr.val + elif isinstance(lin_expr, Sequence): + if len(lin_expr) != 2 or len(lin_expr[0]) != len(lin_expr[1]): + raise QiskitOptimizationError('Invalid lin_expr: %s'.format(lin_expr)) + ind, val = lin_expr + else: + raise QiskitOptimizationError('Invalid lin_expr: %s'.format(lin_expr)) + for i, val in zip(ind, val): + i2 = convert(i, self._varsgetindexfunc) + if i2 in lin_expr_dict: + logger.warning('lin_expr contains duplicate index: %s'.format(i)) + lin_expr_dict[i2] = val + self._lin_expr.append(lin_expr_dict) + + # quadratic terms + quad_expr_dict = {} if quad_expr is None: - quad_expr = SparseTriple([0], [0], [0.0]) - # We only ever create one quadratic constraint at a time. + ind1, ind2, val = [], [], [] + elif isinstance(quad_expr, SparseTriple): + ind1, ind2, val = quad_expr.ind1, quad_expr.ind2, quad_expr.val + elif isinstance(quad_expr, Sequence): + if len(quad_expr) != 3 or len(quad_expr[0]) != len(quad_expr[1]) or \ + len(quad_expr[1]) != len(quad_expr[2]): + raise QiskitOptimizationError('Invalid quad_expr: %s'.format(quad_expr)) + ind1, ind2, val = quad_expr + else: + raise QiskitOptimizationError('Invalid quad_expr: %s'.format(quad_expr)) + for i, j, val in zip(ind1, ind2, val): + i2 = convert(i, self._varsgetindexfunc) + j2 = convert(j, self._varsgetindexfunc) + if (i2, j2) in quad_expr_dict or (j2, i2) in quad_expr_dict: + logger.warning('quad_expr contains duplicate index: %s %s'.format(i, j)) + quad_expr_dict[i2, j2] = quad_expr_dict[j2, i2] = val + self._quad_expr.append(quad_expr_dict) if sense not in ['L', 'G', 'E']: raise QiskitOptimizationError('Invalid sense: %s'.format(sense)) else: self._senses.append(sense) self._rhs.append(rhs) - self._names.append(name) - return self._add_single(self.get_num, self._add, - lin_expr, quad_expr, sense, rhs, name) + + return self._name_index.convert(name) def delete(self, *args): """Deletes quadratic constraints from the problem. @@ -176,7 +215,33 @@ def delete(self, *args): >>> op.quadratic_constraints.get_names() [] """ - None + if len(args) == 0: + # delete all + self._rhs = [] + self._senses = [] + self._names = [] + self._lin_expr = [] + self._quad_expr = [] + self._name_index = NameIndex() + return + elif len(args) == 1: + # one item or sequence + keys = self._name_index.convert(args[0]) + if isinstance(keys, int): + keys = [keys] + elif len(args) == 2: + # begin and end of a range + keys = self._name_index.convert(range(*args)) + else: + raise QiskitOptimizationError('Invalid arguments: %s'.format(args)) + + for i in sorted(keys, reverse=True): + del self._rhs[i] + del self._senses[i] + del self._names[i] + del self._lin_expr[i] + del self._quad_expr[i] + self._name_index.build(self._names) def get_rhs(self, *args): """Returns the righthand side of a set of quadratic constraints. @@ -221,7 +286,17 @@ def get_rhs(self, *args): >>> op.quadratic_constraints.get_rhs() [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] """ - return self._rhs + if len(args) == 0: + return self._rhs + elif len(args) == 1: + keys = self._name_index.convert(args[0]) + elif len(args) == 2: + keys = self._name_index.convert(range(*args)) + else: + raise QiskitOptimizationError('Invalid arguments: %s'.format(args)) + if isinstance(keys, int): + return self._rhs[keys] + return [self._rhs[k] for k in keys] def get_senses(self, *args): """Returns the senses of a set of quadratic constraints. @@ -265,7 +340,17 @@ def get_senses(self, *args): >>> op.quadratic_constraints.get_senses() ['G', 'G', 'L', 'L'] """ - return self._senses + if len(args) == 0: + return self._senses + elif len(args) == 1: + keys = self._name_index.convert(args[0]) + elif len(args) == 2: + keys = self._name_index.convert(range(*args)) + else: + raise QiskitOptimizationError('Invalid arguments: %s'.format(args)) + if isinstance(keys, int): + return self._senses[keys] + return [self._senses[k] for k in keys] def get_linear_num_nonzeros(self, *args): """Returns the number of nonzeros in the linear part of a set of quadratic constraints. @@ -312,7 +397,21 @@ def get_linear_num_nonzeros(self, *args): >>> op.quadratic_constraints.get_linear_num_nonzeros() [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] """ - [] + + def _nonzero(tab: Dict[int, float]) -> int: + return len([0 for v in tab.values() if v != 0.0]) + + if len(args) == 0: + keys = range(self.get_num()) + elif len(args) == 1: + keys = self._name_index.convert(args[0]) + elif len(args) == 2: + keys = self._name_index.convert(range(*args)) + else: + raise QiskitOptimizationError('Invalid arguments: %s'.format(args)) + if isinstance(keys, int): + return _nonzero(self._lin_expr[keys]) + return [_nonzero(self._lin_expr[k]) for k in keys] def get_linear_components(self, *args): """Returns the linear part of a set of quadratic constraints. @@ -362,7 +461,21 @@ def get_linear_components(self, *args): >>> op.quadratic_constraints.get_linear_components() [SparsePair(ind = [], val = []), SparsePair(ind = [0], val = [1.0]), SparsePair(ind = [0, 1], val = [1.0, 2.0]), SparsePair(ind = [0, 1, 2], val = [1.0, 2.0, 3.0])] """ - return [] + + def _linear_component(tab: Dict[int, float]) -> SparsePair: + return SparsePair(ind=tab.keys(), val=tab.values()) + + if len(args) == 0: + return len(self._lin_expr) + elif len(args) == 1: + keys = self._name_index.convert(args[0]) + elif len(args) == 2: + keys = self._name_index.convert(range(*args)) + else: + raise QiskitOptimizationError('Invalid arguments: %s'.format(args)) + if isinstance(keys, int): + return _linear_component(self._lin_expr[keys]) + return [_linear_component(self._lin_expr[k]) for k in keys] def get_quad_num_nonzeros(self, *args): """Returns the number of nonzeros in the quadratic part of a set of quadratic constraints. @@ -409,7 +522,21 @@ def get_quad_num_nonzeros(self, *args): >>> op.quadratic_constraints.get_quad_num_nonzeros() [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] """ - return [] + + def _nonzero(tab: Dict[int, int, float]) -> int: + return len([0 for v in tab.values() if v != 0.0]) + + if len(args) == 0: + keys = range(self.get_num()) + elif len(args) == 1: + keys = self._name_index.convert(args[0]) + elif len(args) == 2: + keys = self._name_index.convert(range(*args)) + else: + raise QiskitOptimizationError('Invalid arguments: %s'.format(args)) + if isinstance(keys, int): + return _nonzero(self._quad_expr[keys]) + return [_nonzero(self._quad_expr[k]) for k in keys] def get_quadratic_components(self, *args): """Returns the quadratic part of a set of quadratic constraints. @@ -457,7 +584,22 @@ def get_quadratic_components(self, *args): >>> op.quadratic_constraints.get_quadratic_components() [SparseTriple(ind1 = [0], ind2 = [0], val = [1.0]), SparseTriple(ind1 = [0, 1], ind2 = [0, 1], val = [1.0, 2.0]), SparseTriple(ind1 = [0, 1, 2], ind2 = [0, 1, 2], val = [1.0, 2.0, 3.0]), SparseTriple(ind1 = [0, 1, 2, 3], ind2 = [0, 1, 2, 3], val = [1.0, 2.0, 3.0, 4.0])] """ - return [] + + def _quadtratic_component(tab: Dict[Tuple[int, int], float]) -> SparseTriple: + ind1, ind2 = zip(*tab.keys()) + return SparseTriple(ind1=ind1, ind2=ind2, val=tab.values()) + + if len(args) == 0: + return len(self._lin_expr) + elif len(args) == 1: + keys = self._name_index.convert(args[0]) + elif len(args) == 2: + keys = self._name_index.convert(range(*args)) + else: + raise QiskitOptimizationError('Invalid arguments: %s'.format(args)) + if isinstance(keys, int): + return _quadtratic_component(self._quad_expr[keys]) + return [_quadtratic_component(self._quad_expr[k]) for k in keys] def get_names(self, *args): """Returns the names of a set of quadratic constraints. @@ -502,4 +644,14 @@ def get_names(self, *args): >>> op.quadratic_constraints.get_names() ['q1', 'q2', 'q3', 'q4', 'q5', 'q6', 'q7', 'q8', 'q9', 'q10'] """ - self._names + if len(args) == 0: + return self._names + elif len(args) == 1: + keys = self._name_index.convert(args[0]) + elif len(args) == 2: + keys = self._name_index.convert(range(*args)) + else: + raise QiskitOptimizationError('Invalid arguments: %s'.format(args)) + if isinstance(keys, int): + return self._names[keys] + return [self._names[k] for k in keys] diff --git a/qiskit/optimization/utils/helpers.py b/qiskit/optimization/utils/helpers.py index 3ecf810536..7bdb283478 100755 --- a/qiskit/optimization/utils/helpers.py +++ b/qiskit/optimization/utils/helpers.py @@ -13,8 +13,7 @@ # that they have been altered from the originals. -from collections.abc import Sequence -from typing import Union, List, Tuple, Dict +from typing import Union, List, Tuple, Dict, Sequence from cplex import SparsePair, SparseTriple @@ -89,32 +88,46 @@ def convert(name, getindexfunc=_defaultgetindexfunc, cache=None): return name -class NameIndexConverter: +class NameIndex: def __init__(self): - self._cache = {} + self._dict = {} def to_dict(self) -> Dict[str, int]: - return self._cache + return self._dict def build(self, names: List[str]): - self._cache = {i: e for i, e in enumerate(names)} + self._dict = {i: e for i, e in enumerate(names)} - def convert(self, names: Union[str, List[str]]) -> Union[int, List[int]]: + def convert(self, names: Union[str, int, Sequence[Union[str, int]]]) -> Union[int, List[int]]: if isinstance(names, str): return self._convert_str(names) + elif isinstance(names, int): + if names not in self._dict: + raise QiskitOptimizationError('Invalid index: %d', names) + return names elif isinstance(names, Sequence): return self._convert_seq(names) else: raise QiskitOptimizationError('Invalid argument: %s'.format(names)) def _convert_str(self, name: str) -> int: - if name not in self._cache: - self._cache[name] = len(self._cache) - return self._cache[name] + if name not in self._dict: + self._dict[name] = len(self._dict) + return self._dict[name] - def _convert_seq(self, names: List[str]) -> List[int]: + def _convert_seq(self, names: Sequence[str]) -> List[int]: return [self._convert_str(e) if isinstance(e, str) else e for e in names] + def delete(self, names: Union[str, List[str]]): + if isinstance(names, str): + del self._dict[names] + elif isinstance(names, Sequence): + for name in names: + del self._dict[name] + + def __contains__(self, item: str) -> bool: + return item in self._dict + def init_list_args(*args): """Initialize default arguments with empty lists if necessary.""" From 1cceb23786b8760689af0d7117fc32e864bf1ba1 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Fri, 6 Mar 2020 22:25:36 +0900 Subject: [PATCH 015/323] (wip) add tests --- .../problems/optimization_problem.py | 3 +- .../problems/quadratic_constraint.py | 54 ++-- qiskit/optimization/utils/helpers.py | 4 +- .../test_quadratic_constraints.py | 278 ++++++++++++++++++ 4 files changed, 310 insertions(+), 29 deletions(-) create mode 100644 test/optimization/test_quadratic_constraints.py diff --git a/qiskit/optimization/problems/optimization_problem.py b/qiskit/optimization/problems/optimization_problem.py index 5460371aed..956dd4cfef 100755 --- a/qiskit/optimization/problems/optimization_problem.py +++ b/qiskit/optimization/problems/optimization_problem.py @@ -68,7 +68,8 @@ def __init__(self, *args): varsgetindexfunc=self.variables._varsgetindexfunc) """See `qiskit.optimization.LinearConstraintInterface()` """ - self.quadratic_constraints = QuadraticConstraintInterface() + self.quadratic_constraints = QuadraticConstraintInterface( + varsgetindexfunc = self.variables._varsgetindexfunc) """See `qiskit.optimization.QuadraticConstraintInterface()` """ # pylint: disable=unexpected-keyword-arg diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index 68a86a76da..54b72771a2 100755 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -109,7 +109,7 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): if name == '': name = 'q{}'.format(len(self._names)) if name in self._name_index: - raise QiskitOptimizationError('Duplicate quadratic constraint name: %s'.format(name)) + raise QiskitOptimizationError('Duplicate quadratic constraint name: {}'.format(name)) self._names.append(name) # linear terms @@ -120,14 +120,14 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): ind, val = lin_expr.ind, lin_expr.val elif isinstance(lin_expr, Sequence): if len(lin_expr) != 2 or len(lin_expr[0]) != len(lin_expr[1]): - raise QiskitOptimizationError('Invalid lin_expr: %s'.format(lin_expr)) + raise QiskitOptimizationError('Invalid lin_expr: {}'.format(lin_expr)) ind, val = lin_expr else: - raise QiskitOptimizationError('Invalid lin_expr: %s'.format(lin_expr)) + raise QiskitOptimizationError('Invalid lin_expr: {}'.format(lin_expr)) for i, val in zip(ind, val): i2 = convert(i, self._varsgetindexfunc) if i2 in lin_expr_dict: - logger.warning('lin_expr contains duplicate index: %s'.format(i)) + logger.warning('lin_expr contains duplicate index: {}'.format(i)) lin_expr_dict[i2] = val self._lin_expr.append(lin_expr_dict) @@ -140,20 +140,22 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): elif isinstance(quad_expr, Sequence): if len(quad_expr) != 3 or len(quad_expr[0]) != len(quad_expr[1]) or \ len(quad_expr[1]) != len(quad_expr[2]): - raise QiskitOptimizationError('Invalid quad_expr: %s'.format(quad_expr)) + raise QiskitOptimizationError('Invalid quad_expr: {}'.format(quad_expr)) ind1, ind2, val = quad_expr else: - raise QiskitOptimizationError('Invalid quad_expr: %s'.format(quad_expr)) + raise QiskitOptimizationError('Invalid quad_expr: {}'.format(quad_expr)) for i, j, val in zip(ind1, ind2, val): i2 = convert(i, self._varsgetindexfunc) j2 = convert(j, self._varsgetindexfunc) - if (i2, j2) in quad_expr_dict or (j2, i2) in quad_expr_dict: - logger.warning('quad_expr contains duplicate index: %s %s'.format(i, j)) - quad_expr_dict[i2, j2] = quad_expr_dict[j2, i2] = val + if i2 < j2: + i2, j2 = j2, i2 + if (i2, j2) in quad_expr_dict: + logger.warning('quad_expr contains duplicate index: {} {}'.format(i, j)) + quad_expr_dict[i2, j2] = val self._quad_expr.append(quad_expr_dict) if sense not in ['L', 'G', 'E']: - raise QiskitOptimizationError('Invalid sense: %s'.format(sense)) + raise QiskitOptimizationError('Invalid sense: {}'.format(sense)) else: self._senses.append(sense) self._rhs.append(rhs) @@ -233,7 +235,7 @@ def delete(self, *args): # begin and end of a range keys = self._name_index.convert(range(*args)) else: - raise QiskitOptimizationError('Invalid arguments: %s'.format(args)) + raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) for i in sorted(keys, reverse=True): del self._rhs[i] @@ -293,7 +295,7 @@ def get_rhs(self, *args): elif len(args) == 2: keys = self._name_index.convert(range(*args)) else: - raise QiskitOptimizationError('Invalid arguments: %s'.format(args)) + raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) if isinstance(keys, int): return self._rhs[keys] return [self._rhs[k] for k in keys] @@ -347,7 +349,7 @@ def get_senses(self, *args): elif len(args) == 2: keys = self._name_index.convert(range(*args)) else: - raise QiskitOptimizationError('Invalid arguments: %s'.format(args)) + raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) if isinstance(keys, int): return self._senses[keys] return [self._senses[k] for k in keys] @@ -408,7 +410,7 @@ def _nonzero(tab: Dict[int, float]) -> int: elif len(args) == 2: keys = self._name_index.convert(range(*args)) else: - raise QiskitOptimizationError('Invalid arguments: %s'.format(args)) + raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) if isinstance(keys, int): return _nonzero(self._lin_expr[keys]) return [_nonzero(self._lin_expr[k]) for k in keys] @@ -463,16 +465,16 @@ def get_linear_components(self, *args): """ def _linear_component(tab: Dict[int, float]) -> SparsePair: - return SparsePair(ind=tab.keys(), val=tab.values()) + return SparsePair(ind=tuple(tab.keys()), val=tuple(tab.values())) if len(args) == 0: - return len(self._lin_expr) + keys = range(self.get_num()) elif len(args) == 1: keys = self._name_index.convert(args[0]) elif len(args) == 2: keys = self._name_index.convert(range(*args)) else: - raise QiskitOptimizationError('Invalid arguments: %s'.format(args)) + raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) if isinstance(keys, int): return _linear_component(self._lin_expr[keys]) return [_linear_component(self._lin_expr[k]) for k in keys] @@ -523,7 +525,7 @@ def get_quad_num_nonzeros(self, *args): [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] """ - def _nonzero(tab: Dict[int, int, float]) -> int: + def _nonzero(tab: Dict[Tuple[int, int], float]) -> int: return len([0 for v in tab.values() if v != 0.0]) if len(args) == 0: @@ -533,7 +535,7 @@ def _nonzero(tab: Dict[int, int, float]) -> int: elif len(args) == 2: keys = self._name_index.convert(range(*args)) else: - raise QiskitOptimizationError('Invalid arguments: %s'.format(args)) + raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) if isinstance(keys, int): return _nonzero(self._quad_expr[keys]) return [_nonzero(self._quad_expr[k]) for k in keys] @@ -585,21 +587,21 @@ def get_quadratic_components(self, *args): [SparseTriple(ind1 = [0], ind2 = [0], val = [1.0]), SparseTriple(ind1 = [0, 1], ind2 = [0, 1], val = [1.0, 2.0]), SparseTriple(ind1 = [0, 1, 2], ind2 = [0, 1, 2], val = [1.0, 2.0, 3.0]), SparseTriple(ind1 = [0, 1, 2, 3], ind2 = [0, 1, 2, 3], val = [1.0, 2.0, 3.0, 4.0])] """ - def _quadtratic_component(tab: Dict[Tuple[int, int], float]) -> SparseTriple: + def _quadratic_component(tab: Dict[Tuple[int, int], float]) -> SparseTriple: ind1, ind2 = zip(*tab.keys()) - return SparseTriple(ind1=ind1, ind2=ind2, val=tab.values()) + return SparseTriple(ind1=ind1, ind2=ind2, val=tuple(tab.values())) if len(args) == 0: - return len(self._lin_expr) + keys = range(self.get_num()) elif len(args) == 1: keys = self._name_index.convert(args[0]) elif len(args) == 2: keys = self._name_index.convert(range(*args)) else: - raise QiskitOptimizationError('Invalid arguments: %s'.format(args)) + raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) if isinstance(keys, int): - return _quadtratic_component(self._quad_expr[keys]) - return [_quadtratic_component(self._quad_expr[k]) for k in keys] + return _quadratic_component(self._quad_expr[keys]) + return [_quadratic_component(self._quad_expr[k]) for k in keys] def get_names(self, *args): """Returns the names of a set of quadratic constraints. @@ -651,7 +653,7 @@ def get_names(self, *args): elif len(args) == 2: keys = self._name_index.convert(range(*args)) else: - raise QiskitOptimizationError('Invalid arguments: %s'.format(args)) + raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) if isinstance(keys, int): return self._names[keys] return [self._names[k] for k in keys] diff --git a/qiskit/optimization/utils/helpers.py b/qiskit/optimization/utils/helpers.py index 7bdb283478..185fc5a18e 100755 --- a/qiskit/optimization/utils/helpers.py +++ b/qiskit/optimization/utils/helpers.py @@ -103,12 +103,12 @@ def convert(self, names: Union[str, int, Sequence[Union[str, int]]]) -> Union[in return self._convert_str(names) elif isinstance(names, int): if names not in self._dict: - raise QiskitOptimizationError('Invalid index: %d', names) + raise QiskitOptimizationError('Invalid index: {}'.format(names)) return names elif isinstance(names, Sequence): return self._convert_seq(names) else: - raise QiskitOptimizationError('Invalid argument: %s'.format(names)) + raise QiskitOptimizationError('Invalid argument: {}'.format(names)) def _convert_str(self, name: str) -> int: if name not in self._dict: diff --git a/test/optimization/test_quadratic_constraints.py b/test/optimization/test_quadratic_constraints.py new file mode 100644 index 0000000000..64a112983c --- /dev/null +++ b/test/optimization/test_quadratic_constraints.py @@ -0,0 +1,278 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" Test QuadraticConstraintInterface """ + +from cplex import SparsePair, SparseTriple + +from qiskit.optimization import QiskitOptimizationError +from qiskit.optimization.problems import OptimizationProblem +from test.optimization.common import QiskitOptimizationTestCase + + +class TestQuadraticConstraints(QiskitOptimizationTestCase): + """Test LinearConstraintInterface.""" + + def setUp(self): + super().setUp() + + def test_initial1(self): + op = OptimizationProblem() + c1 = op.quadratic_constraints.add(name='c1') + c2 = op.quadratic_constraints.add(name='c2') + c3 = op.quadratic_constraints.add(name='c3') + self.assertEqual(op.quadratic_constraints.get_num(), 3) + self.assertListEqual(op.quadratic_constraints.get_names(), ['c1', 'c2', 'c3']) + self.assertListEqual([c1, c2, c3], [0, 1, 2]) + self.assertRaises(QiskitOptimizationError, lambda: op.quadratic_constraints.add(name='c1')) + + def test_initial2(self): + op = OptimizationProblem() + op.variables.add(names=['x1', 'x2', 'x3'], types='B' * 3) + c = op.quadratic_constraints.add( + lin_expr=SparsePair(ind=['x1', 'x3'], val=[1.0, -1.0]), + quad_expr=SparseTriple(ind1=['x1', 'x2'], ind2=['x2', 'x3'], val=[1.0, -1.0]), + sense='E', + rhs=1.0 + ) + quad = op.quadratic_constraints + self.assertEqual(quad.get_num(), 1) + self.assertListEqual(quad.get_names(), ['q0']) + self.assertListEqual(quad.get_rhs(), [1.0]) + self.assertListEqual(quad.get_senses(), ['E']) + self.assertListEqual(quad.get_linear_num_nonzeros(), [2]) + self.assertListEqual(quad.get_quad_num_nonzeros(), [2]) + l = quad.get_linear_components() + self.assertEqual(len(l), 1) + self.assertTupleEqual(l[0].ind, (0, 2)) + self.assertTupleEqual(l[0].val, (1.0, -1.0)) + q = quad.get_quadratic_components() + self.assertEqual(len(q), 1) + self.assertTupleEqual(q[0].ind1, (1, 2)) + self.assertTupleEqual(q[0].ind2, (0, 1)) + self.assertTupleEqual(q[0].val, (1.0, -1.0)) + + def test_initial3(self): + op = OptimizationProblem() + op.linear_constraints.add(names=[str(i) for i in range(10)]) + self.assertEqual(op.linear_constraints.get_num(), 10) + op.linear_constraints.delete(8) + self.assertEqual(len(op.linear_constraints.get_names()), 9) + self.assertEqual(op.linear_constraints.get_names()[0], '0') + # ['0', '1', '2', '3', '4', '5', '6', '7', '9'] + op.linear_constraints.delete("1", 3) + self.assertEqual(len(op.linear_constraints.get_names()), 6) + # ['0', '4', '5', '6', '7', '9'] + op.linear_constraints.delete([2, "0", 5]) + self.assertEqual(len(op.linear_constraints.get_names()), 3) + self.assertEqual(op.linear_constraints.get_names()[0], '4') + # ['4', '6', '7'] + op.linear_constraints.delete() + self.assertEqual(len(op.linear_constraints.get_names()), 0) + # [] + + def test_rhs1(self): + op = OptimizationProblem() + op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) + self.assertEqual(len(op.linear_constraints.get_rhs()), 4) + self.assertAlmostEqual(op.linear_constraints.get_rhs()[0], 0.0) + # [0.0, 0.0, 0.0, 0.0] + op.linear_constraints.set_rhs("c1", 1.0) + self.assertEqual(len(op.linear_constraints.get_rhs()), 4) + self.assertAlmostEqual(op.linear_constraints.get_rhs()[1], 1.0) + # [0.0, 1.0, 0.0, 0.0] + op.linear_constraints.set_rhs([("c3", 2.0), (2, -1.0)]) + self.assertEqual(len(op.linear_constraints.get_rhs()), 4) + self.assertAlmostEqual(op.linear_constraints.get_rhs()[2], -1.0) + self.assertAlmostEqual(op.linear_constraints.get_rhs()[3], 2.0) + # [0.0, 1.0, -1.0, 2.0] + + def test_names(self): + op = OptimizationProblem() + op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) + op.linear_constraints.set_names("c1", "second") + self.assertEqual(op.linear_constraints.get_names(1), 'second') + op.linear_constraints.set_names([("c3", "last"), (2, "middle")]) + op.linear_constraints.get_names() + self.assertEqual(len(op.linear_constraints.get_names()), 4) + self.assertEqual(op.linear_constraints.get_names()[0], 'c0') + self.assertEqual(op.linear_constraints.get_names()[1], 'second') + self.assertEqual(op.linear_constraints.get_names()[2], 'middle') + self.assertEqual(op.linear_constraints.get_names()[3], 'last') + # ['c0', 'second', 'middle', 'last'] + + def test_senses1(self): + op = OptimizationProblem() + op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) + op.linear_constraints.get_senses() + self.assertEqual(len(op.linear_constraints.get_senses()), 4) + self.assertEqual(op.linear_constraints.get_senses()[0], 'E') + # ['E', 'E', 'E', 'E'] + op.linear_constraints.set_senses("c1", "G") + self.assertEqual(op.linear_constraints.get_senses(1), 'G') + op.linear_constraints.set_senses([("c3", "L"), (2, "R")]) + # ['E', 'G', 'R', 'L'] + self.assertEqual(op.linear_constraints.get_senses()[0], 'E') + self.assertEqual(op.linear_constraints.get_senses()[1], 'G') + self.assertEqual(op.linear_constraints.get_senses()[2], 'R') + self.assertEqual(op.linear_constraints.get_senses()[3], 'L') + + def test_linear_components(self): + op = OptimizationProblem() + op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) + op.variables.add(names=["x0", "x1"]) + op.linear_constraints.set_linear_components("c0", [["x0"], [1.0]]) + self.assertEqual(op.linear_constraints.get_rows("c0").ind[0], 0) + self.assertAlmostEqual(op.linear_constraints.get_rows("c0").val[0], 1.0) + # SparsePair(ind = [0], val = [1.0]) + op.linear_constraints.set_linear_components([("c3", SparsePair(ind=["x1"], val=[-1.0])), + (2, [[0, 1], [-2.0, 3.0]])]) + op.linear_constraints.get_rows() + # [SparsePair(ind = [0], val = [1.0]), + # SparsePair(ind = [], val = []), + # SparsePair(ind = [0, 1], val = [-2.0, 3.0]), + # SparsePair(ind = [1], val = [-1.0])] + self.assertEqual(op.linear_constraints.get_rows()[0].ind[0], 0) + self.assertAlmostEqual(op.linear_constraints.get_rows()[0].val[0], 1.0) + self.assertEqual(op.linear_constraints.get_rows()[2].ind[0], 0) + self.assertAlmostEqual(op.linear_constraints.get_rows()[2].val[0], -2.0) + + def test_linear_components_ranges(self): + op = OptimizationProblem() + op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) + op.linear_constraints.set_range_values("c1", 1.0) + self.assertEqual(len(op.linear_constraints.get_range_values()), 4) + self.assertAlmostEqual(op.linear_constraints.get_range_values()[0], 0.0) + # [0.0, 1.0, 0.0, 0.0] + op.linear_constraints.set_range_values([("c3", 2.0), (2, -1.0)]) + # [0.0, 1.0, -1.0, 2.0] + self.assertEqual(len(op.linear_constraints.get_range_values()), 4) + self.assertAlmostEqual(op.linear_constraints.get_range_values()[2], -1.0) + self.assertAlmostEqual(op.linear_constraints.get_range_values()[3], 2.0) + + def test_rows(self): + op = OptimizationProblem() + op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) + op.variables.add(names=["x0", "x1"]) + op.linear_constraints.set_coefficients("c0", "x1", 1.0) + self.assertEqual(op.linear_constraints.get_rows(0).ind[0], 1) + self.assertAlmostEqual(op.linear_constraints.get_rows(0).val[0], 1.0) + # SparsePair(ind = [1], val = [1.0]) + op.linear_constraints.set_coefficients([("c2", "x0", 2.0), + ("c2", "x1", -1.0)]) + # SparsePair(ind = [0, 1], val = [2.0, -1.0]) + self.assertEqual(op.linear_constraints.get_rows("c2").ind[0], 0) + self.assertAlmostEqual(op.linear_constraints.get_rows("c2").val[0], 2.0) + self.assertEqual(op.linear_constraints.get_rows("c2").ind[1], 1) + self.assertAlmostEqual(op.linear_constraints.get_rows("c2").val[1], -1.0) + + def test_rhs2(self): + op = OptimizationProblem() + op.linear_constraints.add(rhs=[1.5 * i for i in range(10)], + names=[str(i) for i in range(10)]) + self.assertEqual(op.linear_constraints.get_num(), 10) + self.assertAlmostEqual(op.linear_constraints.get_rhs(8), 12.0) + self.assertAlmostEqual(op.linear_constraints.get_rhs([2, "0", 5])[0], 3.0) + self.assertAlmostEqual(op.linear_constraints.get_rhs([2, "0", 5])[1], 0.0) + self.assertAlmostEqual(op.linear_constraints.get_rhs([2, "0", 5])[2], 7.5) + # [3.0, 0.0, 7.5] + self.assertEqual(len(op.linear_constraints.get_rhs()), 10) + self.assertEqual(sum(op.linear_constraints.get_rhs()), 67.5) + # [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] + + def test_senses2(self): + op = OptimizationProblem() + op.linear_constraints.add( + senses=["E", "G", "L", "R"], + names=[str(i) for i in range(4)]) + self.assertEqual(op.linear_constraints.get_num(), 4) + self.assertEqual(op.linear_constraints.get_senses(1), 'G') + self.assertEqual(op.linear_constraints.get_senses([2, "0", 1])[0], 'L') + self.assertEqual(op.linear_constraints.get_senses([2, "0", 1])[1], 'E') + self.assertEqual(op.linear_constraints.get_senses([2, "0", 1])[2], 'G') + # ['L', 'E', 'G'] + self.assertEqual(op.linear_constraints.get_senses()[0], 'E') + self.assertEqual(op.linear_constraints.get_senses()[1], 'G') + # ['E', 'G', 'L', 'R'] + + def test_range_values(self): + op = OptimizationProblem() + op.linear_constraints.add( + range_values=[1.5 * i for i in range(10)], + senses=["R"] * 10, + names=[str(i) for i in range(10)]) + self.assertEqual(op.linear_constraints.get_num(), 10) + self.assertAlmostEqual(op.linear_constraints.get_range_values(8), 12.0) + self.assertAlmostEqual(sum(op.linear_constraints.get_range_values([2, "0", 5])), 10.5) + # [3.0, 0.0, 7.5] + self.assertAlmostEqual(sum(op.linear_constraints.get_range_values()), 67.5) + # [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] + + def test_coefficients(self): + op = OptimizationProblem() + op.variables.add(names=["x0", "x1"]) + op.linear_constraints.add( + names=["c0", "c1"], + lin_expr=[[[1], [1.0]], [[0, 1], [2.0, -1.0]]]) + self.assertAlmostEqual(op.linear_constraints.get_coefficients("c0", "x1"), 1.0) + self.assertAlmostEqual(op.linear_constraints.get_coefficients( + [("c1", "x0"), ("c1", "x1")])[0], 2.0) + self.assertAlmostEqual(op.linear_constraints.get_coefficients( + [("c1", "x0"), ("c1", "x1")])[1], -1.0) + + def test_rows2(self): + op = OptimizationProblem() + op.variables.add(names=["x1", "x2", "x3"]) + op.linear_constraints.add( + names=["c0", "c1", "c2", "c3"], + lin_expr=[SparsePair(ind=["x1", "x3"], val=[1.0, -1.0]), + SparsePair(ind=["x1", "x2"], val=[1.0, 1.0]), + SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), + SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])]) + self.assertEqual(op.linear_constraints.get_rows(0).ind[0], 0) + self.assertAlmostEqual(op.linear_constraints.get_rows(0).val[0], 1.0) + self.assertEqual(op.linear_constraints.get_rows(0).ind[1], 2) + self.assertAlmostEqual(op.linear_constraints.get_rows(0).val[1], -1.0) + # SparsePair(ind = [0, 2], val = [1.0, -1.0]) + self.assertEqual(op.linear_constraints.get_rows("c2").ind[0], 0) + self.assertAlmostEqual(op.linear_constraints.get_rows("c2").val[0], -1.0) + self.assertEqual(op.linear_constraints.get_rows("c2").ind[1], 1) + self.assertAlmostEqual(op.linear_constraints.get_rows("c2").val[1], -1.0) + # [SparsePair(ind = [0, 1, 2], val = [-1.0, -1.0, -1.0]), + # SparsePair(ind = [0, 2], val = [1.0, -1.0])] + self.assertEqual(len(op.linear_constraints.get_rows()), 4) + # [SparsePair(ind = [0, 2], val = [1.0, -1.0]), + # SparsePair(ind = [0, 1], val = [1.0, 1.0]), + # SparsePair(ind = [0, 1, 2], val = [-1.0, -1.0, -1.0]), + # SparsePair(ind = [1, 2], val = [10.0, -2.0])] + + def test_nnz(self): + op = OptimizationProblem() + op.variables.add(names=["x1", "x2", "x3"]) + op.linear_constraints.add(names=["c0", "c1", "c2", "c3"], + lin_expr=[SparsePair(ind=["x1", "x3"], val=[1.0, -1.0]), + SparsePair(ind=["x1", "x2"], val=[1.0, 1.0]), + SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), + SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])]) + self.assertEqual(op.linear_constraints.get_num_nonzeros(), 9) + + def test_names2(self): + op = OptimizationProblem() + op.linear_constraints.add(names=["c" + str(i) for i in range(10)]) + self.assertEqual(op.linear_constraints.get_num(), 10) + self.assertEqual(op.linear_constraints.get_names(8), 'c8') + self.assertEqual(op.linear_constraints.get_names([2, 0, 5])[0], 'c2') + # ['c2', 'c0', 'c5'] + self.assertEqual(len(op.linear_constraints.get_names()), 10) + # ['c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9'] From c0abd52513eb99e196c0247612bd235c20f4af3d Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Mon, 9 Mar 2020 16:31:27 +0900 Subject: [PATCH 016/323] add test_delete --- .../problems/quadratic_constraint.py | 4 ++- qiskit/optimization/utils/helpers.py | 11 +------ .../test_quadratic_constraints.py | 30 ++++++++----------- 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index 54b72771a2..d1cebe7a4c 100755 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -233,7 +233,9 @@ def delete(self, *args): keys = [keys] elif len(args) == 2: # begin and end of a range - keys = self._name_index.convert(range(*args)) + begin = self._name_index.convert(args[0]) + end = self._name_index.convert(args[1]) + 1 + keys = range(begin, end) else: raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) diff --git a/qiskit/optimization/utils/helpers.py b/qiskit/optimization/utils/helpers.py index 185fc5a18e..a44d6189f1 100755 --- a/qiskit/optimization/utils/helpers.py +++ b/qiskit/optimization/utils/helpers.py @@ -96,14 +96,12 @@ def to_dict(self) -> Dict[str, int]: return self._dict def build(self, names: List[str]): - self._dict = {i: e for i, e in enumerate(names)} + self._dict = {e: i for i, e in enumerate(names)} def convert(self, names: Union[str, int, Sequence[Union[str, int]]]) -> Union[int, List[int]]: if isinstance(names, str): return self._convert_str(names) elif isinstance(names, int): - if names not in self._dict: - raise QiskitOptimizationError('Invalid index: {}'.format(names)) return names elif isinstance(names, Sequence): return self._convert_seq(names) @@ -118,13 +116,6 @@ def _convert_str(self, name: str) -> int: def _convert_seq(self, names: Sequence[str]) -> List[int]: return [self._convert_str(e) if isinstance(e, str) else e for e in names] - def delete(self, names: Union[str, List[str]]): - if isinstance(names, str): - del self._dict[names] - elif isinstance(names, Sequence): - for name in names: - del self._dict[name] - def __contains__(self, item: str) -> bool: return item in self._dict diff --git a/test/optimization/test_quadratic_constraints.py b/test/optimization/test_quadratic_constraints.py index 64a112983c..15c767b456 100644 --- a/test/optimization/test_quadratic_constraints.py +++ b/test/optimization/test_quadratic_constraints.py @@ -63,24 +63,20 @@ def test_initial2(self): self.assertTupleEqual(q[0].ind2, (0, 1)) self.assertTupleEqual(q[0].val, (1.0, -1.0)) - def test_initial3(self): + def test_delete(self): op = OptimizationProblem() - op.linear_constraints.add(names=[str(i) for i in range(10)]) - self.assertEqual(op.linear_constraints.get_num(), 10) - op.linear_constraints.delete(8) - self.assertEqual(len(op.linear_constraints.get_names()), 9) - self.assertEqual(op.linear_constraints.get_names()[0], '0') - # ['0', '1', '2', '3', '4', '5', '6', '7', '9'] - op.linear_constraints.delete("1", 3) - self.assertEqual(len(op.linear_constraints.get_names()), 6) - # ['0', '4', '5', '6', '7', '9'] - op.linear_constraints.delete([2, "0", 5]) - self.assertEqual(len(op.linear_constraints.get_names()), 3) - self.assertEqual(op.linear_constraints.get_names()[0], '4') - # ['4', '6', '7'] - op.linear_constraints.delete() - self.assertEqual(len(op.linear_constraints.get_names()), 0) - # [] + q0 = [op.quadratic_constraints.add(name=str(i)) for i in range(10)] + self.assertListEqual(q0, list(range(10))) + q = op.quadratic_constraints + self.assertListEqual(q.get_names(), ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']) + q.delete(8) + self.assertListEqual(q.get_names(), ['0', '1', '2', '3', '4', '5', '6', '7', '9']) + q.delete("1", 3) + self.assertListEqual(q.get_names(), ['0', '4', '5', '6', '7', '9']) + q.delete([2, "0", 5]) + self.assertListEqual(q.get_names(), ['4', '6', '7']) + q.delete() + self.assertListEqual(q.get_names(), []) def test_rhs1(self): op = OptimizationProblem() From fb48e4ea44f5a64c709c95e71af762f30817dfa3 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Mon, 9 Mar 2020 16:38:38 +0900 Subject: [PATCH 017/323] add test_rhs --- .../problems/quadratic_constraint.py | 35 +++++++++++++++---- .../test_quadratic_constraints.py | 24 ++++++------- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index d1cebe7a4c..ec12e050dd 100755 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -295,7 +295,10 @@ def get_rhs(self, *args): elif len(args) == 1: keys = self._name_index.convert(args[0]) elif len(args) == 2: - keys = self._name_index.convert(range(*args)) + # begin and end of a range + begin = self._name_index.convert(args[0]) + end = self._name_index.convert(args[1]) + 1 + keys = range(begin, end) else: raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) if isinstance(keys, int): @@ -349,7 +352,10 @@ def get_senses(self, *args): elif len(args) == 1: keys = self._name_index.convert(args[0]) elif len(args) == 2: - keys = self._name_index.convert(range(*args)) + # begin and end of a range + begin = self._name_index.convert(args[0]) + end = self._name_index.convert(args[1]) + 1 + keys = range(begin, end) else: raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) if isinstance(keys, int): @@ -410,7 +416,10 @@ def _nonzero(tab: Dict[int, float]) -> int: elif len(args) == 1: keys = self._name_index.convert(args[0]) elif len(args) == 2: - keys = self._name_index.convert(range(*args)) + # begin and end of a range + begin = self._name_index.convert(args[0]) + end = self._name_index.convert(args[1]) + 1 + keys = range(begin, end) else: raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) if isinstance(keys, int): @@ -474,7 +483,10 @@ def _linear_component(tab: Dict[int, float]) -> SparsePair: elif len(args) == 1: keys = self._name_index.convert(args[0]) elif len(args) == 2: - keys = self._name_index.convert(range(*args)) + # begin and end of a range + begin = self._name_index.convert(args[0]) + end = self._name_index.convert(args[1]) + 1 + keys = range(begin, end) else: raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) if isinstance(keys, int): @@ -535,7 +547,10 @@ def _nonzero(tab: Dict[Tuple[int, int], float]) -> int: elif len(args) == 1: keys = self._name_index.convert(args[0]) elif len(args) == 2: - keys = self._name_index.convert(range(*args)) + # begin and end of a range + begin = self._name_index.convert(args[0]) + end = self._name_index.convert(args[1]) + 1 + keys = range(begin, end) else: raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) if isinstance(keys, int): @@ -598,7 +613,10 @@ def _quadratic_component(tab: Dict[Tuple[int, int], float]) -> SparseTriple: elif len(args) == 1: keys = self._name_index.convert(args[0]) elif len(args) == 2: - keys = self._name_index.convert(range(*args)) + # begin and end of a range + begin = self._name_index.convert(args[0]) + end = self._name_index.convert(args[1]) + 1 + keys = range(begin, end) else: raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) if isinstance(keys, int): @@ -653,7 +671,10 @@ def get_names(self, *args): elif len(args) == 1: keys = self._name_index.convert(args[0]) elif len(args) == 2: - keys = self._name_index.convert(range(*args)) + # begin and end of a range + begin = self._name_index.convert(args[0]) + end = self._name_index.convert(args[1]) + 1 + keys = range(begin, end) else: raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) if isinstance(keys, int): diff --git a/test/optimization/test_quadratic_constraints.py b/test/optimization/test_quadratic_constraints.py index 15c767b456..27adb5ef38 100644 --- a/test/optimization/test_quadratic_constraints.py +++ b/test/optimization/test_quadratic_constraints.py @@ -78,21 +78,17 @@ def test_delete(self): q.delete() self.assertListEqual(q.get_names(), []) - def test_rhs1(self): + def test_rhs(self): op = OptimizationProblem() - op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) - self.assertEqual(len(op.linear_constraints.get_rhs()), 4) - self.assertAlmostEqual(op.linear_constraints.get_rhs()[0], 0.0) - # [0.0, 0.0, 0.0, 0.0] - op.linear_constraints.set_rhs("c1", 1.0) - self.assertEqual(len(op.linear_constraints.get_rhs()), 4) - self.assertAlmostEqual(op.linear_constraints.get_rhs()[1], 1.0) - # [0.0, 1.0, 0.0, 0.0] - op.linear_constraints.set_rhs([("c3", 2.0), (2, -1.0)]) - self.assertEqual(len(op.linear_constraints.get_rhs()), 4) - self.assertAlmostEqual(op.linear_constraints.get_rhs()[2], -1.0) - self.assertAlmostEqual(op.linear_constraints.get_rhs()[3], 2.0) - # [0.0, 1.0, -1.0, 2.0] + indices = op.variables.add(names=[str(i) for i in range(10)]) + q0 = [op.quadratic_constraints.add(rhs=1.5 * i, name=str(i)) for i in range(10)] + self.assertListEqual(q0, list(range(10))) + q = op.quadratic_constraints + self.assertEqual(q.get_num(), 10) + self.assertEqual(q.get_rhs(8), 12.0) + self.assertListEqual(q.get_rhs('1', 3), [1.5, 3.0, 4.5]) + self.assertListEqual(q.get_rhs([2, '0', 5]), [3.0, 0.0, 7.5]) + self.assertListEqual(q.get_rhs(), [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5]) def test_names(self): op = OptimizationProblem() From 30f01a0c0a595e6d9bc6f676c6fd275669f55011 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Mon, 9 Mar 2020 17:12:35 +0900 Subject: [PATCH 018/323] simplify --- .../problems/quadratic_constraint.py | 93 ++----------------- qiskit/optimization/utils/helpers.py | 47 ++++++---- 2 files changed, 38 insertions(+), 102 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index ec12e050dd..5b2aa1ca3e 100755 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -226,19 +226,10 @@ def delete(self, *args): self._quad_expr = [] self._name_index = NameIndex() return - elif len(args) == 1: - # one item or sequence - keys = self._name_index.convert(args[0]) - if isinstance(keys, int): - keys = [keys] - elif len(args) == 2: - # begin and end of a range - begin = self._name_index.convert(args[0]) - end = self._name_index.convert(args[1]) + 1 - keys = range(begin, end) - else: - raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) + keys = self._name_index.convert(*args) + if isinstance(keys, int): + keys = [keys] for i in sorted(keys, reverse=True): del self._rhs[i] del self._senses[i] @@ -292,15 +283,7 @@ def get_rhs(self, *args): """ if len(args) == 0: return self._rhs - elif len(args) == 1: - keys = self._name_index.convert(args[0]) - elif len(args) == 2: - # begin and end of a range - begin = self._name_index.convert(args[0]) - end = self._name_index.convert(args[1]) + 1 - keys = range(begin, end) - else: - raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) + keys = self._name_index.convert(*args) if isinstance(keys, int): return self._rhs[keys] return [self._rhs[k] for k in keys] @@ -349,15 +332,7 @@ def get_senses(self, *args): """ if len(args) == 0: return self._senses - elif len(args) == 1: - keys = self._name_index.convert(args[0]) - elif len(args) == 2: - # begin and end of a range - begin = self._name_index.convert(args[0]) - end = self._name_index.convert(args[1]) + 1 - keys = range(begin, end) - else: - raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) + keys = self._name_index.convert(*args) if isinstance(keys, int): return self._senses[keys] return [self._senses[k] for k in keys] @@ -411,17 +386,7 @@ def get_linear_num_nonzeros(self, *args): def _nonzero(tab: Dict[int, float]) -> int: return len([0 for v in tab.values() if v != 0.0]) - if len(args) == 0: - keys = range(self.get_num()) - elif len(args) == 1: - keys = self._name_index.convert(args[0]) - elif len(args) == 2: - # begin and end of a range - begin = self._name_index.convert(args[0]) - end = self._name_index.convert(args[1]) + 1 - keys = range(begin, end) - else: - raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) + keys = self._name_index.convert(*args) if isinstance(keys, int): return _nonzero(self._lin_expr[keys]) return [_nonzero(self._lin_expr[k]) for k in keys] @@ -478,17 +443,7 @@ def get_linear_components(self, *args): def _linear_component(tab: Dict[int, float]) -> SparsePair: return SparsePair(ind=tuple(tab.keys()), val=tuple(tab.values())) - if len(args) == 0: - keys = range(self.get_num()) - elif len(args) == 1: - keys = self._name_index.convert(args[0]) - elif len(args) == 2: - # begin and end of a range - begin = self._name_index.convert(args[0]) - end = self._name_index.convert(args[1]) + 1 - keys = range(begin, end) - else: - raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) + keys = self._name_index.convert(*args) if isinstance(keys, int): return _linear_component(self._lin_expr[keys]) return [_linear_component(self._lin_expr[k]) for k in keys] @@ -542,17 +497,7 @@ def get_quad_num_nonzeros(self, *args): def _nonzero(tab: Dict[Tuple[int, int], float]) -> int: return len([0 for v in tab.values() if v != 0.0]) - if len(args) == 0: - keys = range(self.get_num()) - elif len(args) == 1: - keys = self._name_index.convert(args[0]) - elif len(args) == 2: - # begin and end of a range - begin = self._name_index.convert(args[0]) - end = self._name_index.convert(args[1]) + 1 - keys = range(begin, end) - else: - raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) + keys = self._name_index.convert(*args) if isinstance(keys, int): return _nonzero(self._quad_expr[keys]) return [_nonzero(self._quad_expr[k]) for k in keys] @@ -608,17 +553,7 @@ def _quadratic_component(tab: Dict[Tuple[int, int], float]) -> SparseTriple: ind1, ind2 = zip(*tab.keys()) return SparseTriple(ind1=ind1, ind2=ind2, val=tuple(tab.values())) - if len(args) == 0: - keys = range(self.get_num()) - elif len(args) == 1: - keys = self._name_index.convert(args[0]) - elif len(args) == 2: - # begin and end of a range - begin = self._name_index.convert(args[0]) - end = self._name_index.convert(args[1]) + 1 - keys = range(begin, end) - else: - raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) + keys = self._name_index.convert(*args) if isinstance(keys, int): return _quadratic_component(self._quad_expr[keys]) return [_quadratic_component(self._quad_expr[k]) for k in keys] @@ -668,15 +603,7 @@ def get_names(self, *args): """ if len(args) == 0: return self._names - elif len(args) == 1: - keys = self._name_index.convert(args[0]) - elif len(args) == 2: - # begin and end of a range - begin = self._name_index.convert(args[0]) - end = self._name_index.convert(args[1]) + 1 - keys = range(begin, end) - else: - raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) + keys = self._name_index.convert(*args) if isinstance(keys, int): return self._names[keys] return [self._names[k] for k in keys] diff --git a/qiskit/optimization/utils/helpers.py b/qiskit/optimization/utils/helpers.py index a44d6189f1..fdcf3ce994 100755 --- a/qiskit/optimization/utils/helpers.py +++ b/qiskit/optimization/utils/helpers.py @@ -95,29 +95,38 @@ def __init__(self): def to_dict(self) -> Dict[str, int]: return self._dict + def __contains__(self, item: str) -> bool: + return item in self._dict + def build(self, names: List[str]): self._dict = {e: i for i, e in enumerate(names)} - def convert(self, names: Union[str, int, Sequence[Union[str, int]]]) -> Union[int, List[int]]: - if isinstance(names, str): - return self._convert_str(names) - elif isinstance(names, int): - return names - elif isinstance(names, Sequence): - return self._convert_seq(names) + def _convert_one(self, arg: Union[str, int]) -> int: + if isinstance(arg, int): + return arg + if not isinstance(arg, str): + raise QiskitOptimizationError('Invalid argument" {}'.format(arg)) + if arg not in self._dict: + self._dict[arg] = len(self._dict) + return self._dict[arg] + + def convert(self, *args) -> Union[int, List[int]]: + if len(args) == 0: + return list(self._dict.values()) + elif len(args) == 1: + a0 = args[0] + if isinstance(a0, (int, str)): + return self._convert_one(a0) + elif isinstance(a0, Sequence): + return [self._convert_one(e) for e in a0] + else: + raise QiskitOptimizationError('Invalid argument: {}'.format(args)) + elif len(args) == 2: + begin = self._convert_one(args[0]) + end = self._convert_one(args[1]) + 1 + return list(range(begin, end)) else: - raise QiskitOptimizationError('Invalid argument: {}'.format(names)) - - def _convert_str(self, name: str) -> int: - if name not in self._dict: - self._dict[name] = len(self._dict) - return self._dict[name] - - def _convert_seq(self, names: Sequence[str]) -> List[int]: - return [self._convert_str(e) if isinstance(e, str) else e for e in names] - - def __contains__(self, item: str) -> bool: - return item in self._dict + raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) def init_list_args(*args): From c061c52da1a25ec63f1cc69871277325b1081efa Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Mon, 9 Mar 2020 18:02:51 +0900 Subject: [PATCH 019/323] completed tests of quadratic constraints --- .../problems/quadratic_constraint.py | 4 +- .../test_quadratic_constraints.py | 316 ++++++++---------- 2 files changed, 144 insertions(+), 176 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index 5b2aa1ca3e..71b60ac121 100755 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -441,7 +441,7 @@ def get_linear_components(self, *args): """ def _linear_component(tab: Dict[int, float]) -> SparsePair: - return SparsePair(ind=tuple(tab.keys()), val=tuple(tab.values())) + return SparsePair(ind=list(tab.keys()), val=list(tab.values())) keys = self._name_index.convert(*args) if isinstance(keys, int): @@ -551,7 +551,7 @@ def get_quadratic_components(self, *args): def _quadratic_component(tab: Dict[Tuple[int, int], float]) -> SparseTriple: ind1, ind2 = zip(*tab.keys()) - return SparseTriple(ind1=ind1, ind2=ind2, val=tuple(tab.values())) + return SparseTriple(ind1=list(ind1), ind2=list(ind2), val=list(tab.values())) keys = self._name_index.convert(*args) if isinstance(keys, int): diff --git a/test/optimization/test_quadratic_constraints.py b/test/optimization/test_quadratic_constraints.py index 27adb5ef38..8cb7d972da 100644 --- a/test/optimization/test_quadratic_constraints.py +++ b/test/optimization/test_quadratic_constraints.py @@ -55,13 +55,13 @@ def test_initial2(self): self.assertListEqual(quad.get_quad_num_nonzeros(), [2]) l = quad.get_linear_components() self.assertEqual(len(l), 1) - self.assertTupleEqual(l[0].ind, (0, 2)) - self.assertTupleEqual(l[0].val, (1.0, -1.0)) + self.assertListEqual(l[0].ind, [0, 2]) + self.assertListEqual(l[0].val, [1.0, -1.0]) q = quad.get_quadratic_components() self.assertEqual(len(q), 1) - self.assertTupleEqual(q[0].ind1, (1, 2)) - self.assertTupleEqual(q[0].ind2, (0, 1)) - self.assertTupleEqual(q[0].val, (1.0, -1.0)) + self.assertListEqual(q[0].ind1, [1, 2]) + self.assertListEqual(q[0].ind2, [0, 1]) + self.assertListEqual(q[0].val, [1.0, -1.0]) def test_delete(self): op = OptimizationProblem() @@ -80,7 +80,7 @@ def test_delete(self): def test_rhs(self): op = OptimizationProblem() - indices = op.variables.add(names=[str(i) for i in range(10)]) + op.variables.add(names=[str(i) for i in range(10)]) q0 = [op.quadratic_constraints.add(rhs=1.5 * i, name=str(i)) for i in range(10)] self.assertListEqual(q0, list(range(10))) q = op.quadratic_constraints @@ -92,179 +92,147 @@ def test_rhs(self): def test_names(self): op = OptimizationProblem() - op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) - op.linear_constraints.set_names("c1", "second") - self.assertEqual(op.linear_constraints.get_names(1), 'second') - op.linear_constraints.set_names([("c3", "last"), (2, "middle")]) - op.linear_constraints.get_names() - self.assertEqual(len(op.linear_constraints.get_names()), 4) - self.assertEqual(op.linear_constraints.get_names()[0], 'c0') - self.assertEqual(op.linear_constraints.get_names()[1], 'second') - self.assertEqual(op.linear_constraints.get_names()[2], 'middle') - self.assertEqual(op.linear_constraints.get_names()[3], 'last') - # ['c0', 'second', 'middle', 'last'] - - def test_senses1(self): - op = OptimizationProblem() - op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) - op.linear_constraints.get_senses() - self.assertEqual(len(op.linear_constraints.get_senses()), 4) - self.assertEqual(op.linear_constraints.get_senses()[0], 'E') - # ['E', 'E', 'E', 'E'] - op.linear_constraints.set_senses("c1", "G") - self.assertEqual(op.linear_constraints.get_senses(1), 'G') - op.linear_constraints.set_senses([("c3", "L"), (2, "R")]) - # ['E', 'G', 'R', 'L'] - self.assertEqual(op.linear_constraints.get_senses()[0], 'E') - self.assertEqual(op.linear_constraints.get_senses()[1], 'G') - self.assertEqual(op.linear_constraints.get_senses()[2], 'R') - self.assertEqual(op.linear_constraints.get_senses()[3], 'L') - - def test_linear_components(self): - op = OptimizationProblem() - op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) - op.variables.add(names=["x0", "x1"]) - op.linear_constraints.set_linear_components("c0", [["x0"], [1.0]]) - self.assertEqual(op.linear_constraints.get_rows("c0").ind[0], 0) - self.assertAlmostEqual(op.linear_constraints.get_rows("c0").val[0], 1.0) - # SparsePair(ind = [0], val = [1.0]) - op.linear_constraints.set_linear_components([("c3", SparsePair(ind=["x1"], val=[-1.0])), - (2, [[0, 1], [-2.0, 3.0]])]) - op.linear_constraints.get_rows() - # [SparsePair(ind = [0], val = [1.0]), - # SparsePair(ind = [], val = []), - # SparsePair(ind = [0, 1], val = [-2.0, 3.0]), - # SparsePair(ind = [1], val = [-1.0])] - self.assertEqual(op.linear_constraints.get_rows()[0].ind[0], 0) - self.assertAlmostEqual(op.linear_constraints.get_rows()[0].val[0], 1.0) - self.assertEqual(op.linear_constraints.get_rows()[2].ind[0], 0) - self.assertAlmostEqual(op.linear_constraints.get_rows()[2].val[0], -2.0) - - def test_linear_components_ranges(self): - op = OptimizationProblem() - op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) - op.linear_constraints.set_range_values("c1", 1.0) - self.assertEqual(len(op.linear_constraints.get_range_values()), 4) - self.assertAlmostEqual(op.linear_constraints.get_range_values()[0], 0.0) - # [0.0, 1.0, 0.0, 0.0] - op.linear_constraints.set_range_values([("c3", 2.0), (2, -1.0)]) - # [0.0, 1.0, -1.0, 2.0] - self.assertEqual(len(op.linear_constraints.get_range_values()), 4) - self.assertAlmostEqual(op.linear_constraints.get_range_values()[2], -1.0) - self.assertAlmostEqual(op.linear_constraints.get_range_values()[3], 2.0) - - def test_rows(self): - op = OptimizationProblem() - op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) - op.variables.add(names=["x0", "x1"]) - op.linear_constraints.set_coefficients("c0", "x1", 1.0) - self.assertEqual(op.linear_constraints.get_rows(0).ind[0], 1) - self.assertAlmostEqual(op.linear_constraints.get_rows(0).val[0], 1.0) - # SparsePair(ind = [1], val = [1.0]) - op.linear_constraints.set_coefficients([("c2", "x0", 2.0), - ("c2", "x1", -1.0)]) - # SparsePair(ind = [0, 1], val = [2.0, -1.0]) - self.assertEqual(op.linear_constraints.get_rows("c2").ind[0], 0) - self.assertAlmostEqual(op.linear_constraints.get_rows("c2").val[0], 2.0) - self.assertEqual(op.linear_constraints.get_rows("c2").ind[1], 1) - self.assertAlmostEqual(op.linear_constraints.get_rows("c2").val[1], -1.0) - - def test_rhs2(self): - op = OptimizationProblem() - op.linear_constraints.add(rhs=[1.5 * i for i in range(10)], - names=[str(i) for i in range(10)]) - self.assertEqual(op.linear_constraints.get_num(), 10) - self.assertAlmostEqual(op.linear_constraints.get_rhs(8), 12.0) - self.assertAlmostEqual(op.linear_constraints.get_rhs([2, "0", 5])[0], 3.0) - self.assertAlmostEqual(op.linear_constraints.get_rhs([2, "0", 5])[1], 0.0) - self.assertAlmostEqual(op.linear_constraints.get_rhs([2, "0", 5])[2], 7.5) - # [3.0, 0.0, 7.5] - self.assertEqual(len(op.linear_constraints.get_rhs()), 10) - self.assertEqual(sum(op.linear_constraints.get_rhs()), 67.5) - # [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] - - def test_senses2(self): - op = OptimizationProblem() - op.linear_constraints.add( - senses=["E", "G", "L", "R"], - names=[str(i) for i in range(4)]) - self.assertEqual(op.linear_constraints.get_num(), 4) - self.assertEqual(op.linear_constraints.get_senses(1), 'G') - self.assertEqual(op.linear_constraints.get_senses([2, "0", 1])[0], 'L') - self.assertEqual(op.linear_constraints.get_senses([2, "0", 1])[1], 'E') - self.assertEqual(op.linear_constraints.get_senses([2, "0", 1])[2], 'G') - # ['L', 'E', 'G'] - self.assertEqual(op.linear_constraints.get_senses()[0], 'E') - self.assertEqual(op.linear_constraints.get_senses()[1], 'G') - # ['E', 'G', 'L', 'R'] - - def test_range_values(self): - op = OptimizationProblem() - op.linear_constraints.add( - range_values=[1.5 * i for i in range(10)], - senses=["R"] * 10, - names=[str(i) for i in range(10)]) - self.assertEqual(op.linear_constraints.get_num(), 10) - self.assertAlmostEqual(op.linear_constraints.get_range_values(8), 12.0) - self.assertAlmostEqual(sum(op.linear_constraints.get_range_values([2, "0", 5])), 10.5) - # [3.0, 0.0, 7.5] - self.assertAlmostEqual(sum(op.linear_constraints.get_range_values()), 67.5) - # [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] + op.variables.add(names=[str(i) for i in range(11)]) + q = op.quadratic_constraints + [q.add(name="q" + str(i), + quad_expr=[range(i), range(i), [1.0 * (j + 1.0) for j in range(i)]]) + for i in range(1, 11)] + self.assertEqual(q.get_num(), 10) + self.assertEqual(q.get_names(8), 'q9') + self.assertListEqual(q.get_names(1, 3), ['q2', 'q3', 'q4']) + self.assertListEqual(q.get_names([2, 0, 5]), ['q3', 'q1', 'q6']) + self.assertListEqual(q.get_names(), + ['q1', 'q2', 'q3', 'q4', 'q5', 'q6', 'q7', 'q8', 'q9', 'q10']) - def test_coefficients(self): + def test_senses(self): op = OptimizationProblem() - op.variables.add(names=["x0", "x1"]) - op.linear_constraints.add( - names=["c0", "c1"], - lin_expr=[[[1], [1.0]], [[0, 1], [2.0, -1.0]]]) - self.assertAlmostEqual(op.linear_constraints.get_coefficients("c0", "x1"), 1.0) - self.assertAlmostEqual(op.linear_constraints.get_coefficients( - [("c1", "x0"), ("c1", "x1")])[0], 2.0) - self.assertAlmostEqual(op.linear_constraints.get_coefficients( - [("c1", "x0"), ("c1", "x1")])[1], -1.0) + op.variables.add(names=["x0"]) + q = op.quadratic_constraints + q0 = [q.add(name=str(i), sense=j) for i, j in enumerate('GGLL')] + self.assertListEqual(q0, [0, 1, 2, 3]) + self.assertEqual(q.get_senses(1), 'G') + self.assertListEqual(q.get_senses('1', 3), ['G', 'L', 'L']) + self.assertListEqual(q.get_senses([2, '0', 1]), ['L', 'G', 'G']) + self.assertListEqual(q.get_senses(), ['G', 'G', 'L', 'L']) - def test_rows2(self): + def test_linear_num_nonzeros(self): op = OptimizationProblem() - op.variables.add(names=["x1", "x2", "x3"]) - op.linear_constraints.add( - names=["c0", "c1", "c2", "c3"], - lin_expr=[SparsePair(ind=["x1", "x3"], val=[1.0, -1.0]), - SparsePair(ind=["x1", "x2"], val=[1.0, 1.0]), - SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), - SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])]) - self.assertEqual(op.linear_constraints.get_rows(0).ind[0], 0) - self.assertAlmostEqual(op.linear_constraints.get_rows(0).val[0], 1.0) - self.assertEqual(op.linear_constraints.get_rows(0).ind[1], 2) - self.assertAlmostEqual(op.linear_constraints.get_rows(0).val[1], -1.0) - # SparsePair(ind = [0, 2], val = [1.0, -1.0]) - self.assertEqual(op.linear_constraints.get_rows("c2").ind[0], 0) - self.assertAlmostEqual(op.linear_constraints.get_rows("c2").val[0], -1.0) - self.assertEqual(op.linear_constraints.get_rows("c2").ind[1], 1) - self.assertAlmostEqual(op.linear_constraints.get_rows("c2").val[1], -1.0) - # [SparsePair(ind = [0, 1, 2], val = [-1.0, -1.0, -1.0]), - # SparsePair(ind = [0, 2], val = [1.0, -1.0])] - self.assertEqual(len(op.linear_constraints.get_rows()), 4) - # [SparsePair(ind = [0, 2], val = [1.0, -1.0]), - # SparsePair(ind = [0, 1], val = [1.0, 1.0]), - # SparsePair(ind = [0, 1, 2], val = [-1.0, -1.0, -1.0]), - # SparsePair(ind = [1, 2], val = [10.0, -2.0])] + op.variables.add(names=[str(i) for i in range(11)], types="B" * 11) + q = op.quadratic_constraints + [q.add(name=str(i), + lin_expr=[range(i), [1.0 * (j + 1.0) for j in range(i)]]) + for i in range(10)] + self.assertEqual(q.get_linear_num_nonzeros(8), 8) + self.assertListEqual(q.get_linear_num_nonzeros('1', 3), [1, 2, 3]) + self.assertListEqual(q.get_linear_num_nonzeros('1', 3), [1, 2, 3]) + self.assertListEqual(q.get_linear_num_nonzeros([2, '0', 5]), [2, 0, 5]) + self.assertListEqual(q.get_linear_num_nonzeros(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - def test_nnz(self): + def test_linear_components(self): op = OptimizationProblem() - op.variables.add(names=["x1", "x2", "x3"]) - op.linear_constraints.add(names=["c0", "c1", "c2", "c3"], - lin_expr=[SparsePair(ind=["x1", "x3"], val=[1.0, -1.0]), - SparsePair(ind=["x1", "x2"], val=[1.0, 1.0]), - SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), - SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])]) - self.assertEqual(op.linear_constraints.get_num_nonzeros(), 9) + op.variables.add(names=[str(i) for i in range(11)], types="B" * 11) + q = op.quadratic_constraints + [q.add(name=str(i), + lin_expr=[range(i), [1.0 * (j + 1.0) for j in range(i)]]) + for i in range(10)] + s = q.get_linear_components(8) + self.assertListEqual(s.ind, [0, 1, 2, 3, 4, 5, 6, 7]) + self.assertListEqual(s.val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]) + + s = q.get_linear_components('1', 3) + self.assertEqual(len(s), 3) + self.assertListEqual(s[0].ind, [0]) + self.assertListEqual(s[0].val, [1.0]) + self.assertListEqual(s[1].ind, [0, 1]) + self.assertListEqual(s[1].val, [1.0, 2.0]) + self.assertListEqual(s[2].ind, [0, 1, 2]) + self.assertListEqual(s[2].val, [1.0, 2.0, 3.0]) + + s = q.get_linear_components([2, '0', 5]) + self.assertEqual(len(s), 3) + self.assertListEqual(s[0].ind, [0, 1]) + self.assertListEqual(s[0].val, [1.0, 2.0]) + self.assertListEqual(s[1].ind, []) + self.assertListEqual(s[1].val, []) + self.assertListEqual(s[2].ind, [0, 1, 2, 3, 4]) + self.assertListEqual(s[2].val, [1.0, 2.0, 3.0, 4.0, 5.0]) + + q.delete(4, 9) + s = q.get_linear_components() + self.assertEqual(len(s), 4) + self.assertListEqual(s[0].ind, []) + self.assertListEqual(s[0].val, []) + self.assertListEqual(s[1].ind, [0]) + self.assertListEqual(s[1].val, [1.0]) + self.assertListEqual(s[2].ind, [0, 1]) + self.assertListEqual(s[2].val, [1.0, 2.0]) + self.assertListEqual(s[3].ind, [0, 1, 2]) + self.assertListEqual(s[3].val, [1.0, 2.0, 3.0]) + + def test_quad_num_nonzeros(self): + op = OptimizationProblem() + op.variables.add(names=[str(i) for i in range(11)]) + q = op.quadratic_constraints + [q.add(name=str(i), + quad_expr=[range(i), range(i), [1.0 * (j + 1.0) for j in range(i)]]) + for i in range(1, 11)] + self.assertEqual(q.get_num(), 10) + self.assertEqual(q.get_quad_num_nonzeros(8), 9) + self.assertListEqual(q.get_quad_num_nonzeros('1', 2), [1, 2, 3]) + self.assertListEqual(q.get_quad_num_nonzeros([2, '1', 5]), [3, 1, 6]) + self.assertListEqual(q.get_quad_num_nonzeros(), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) - def test_names2(self): + def test_quadratic_components(self): op = OptimizationProblem() - op.linear_constraints.add(names=["c" + str(i) for i in range(10)]) - self.assertEqual(op.linear_constraints.get_num(), 10) - self.assertEqual(op.linear_constraints.get_names(8), 'c8') - self.assertEqual(op.linear_constraints.get_names([2, 0, 5])[0], 'c2') - # ['c2', 'c0', 'c5'] - self.assertEqual(len(op.linear_constraints.get_names()), 10) - # ['c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9'] + op.variables.add(names=[str(i) for i in range(11)]) + q = op.quadratic_constraints + [q.add(name=str(i), + quad_expr=[range(i), range(i), [1.0 * (j + 1.0) for j in range(i)]]) + for i in range(1, 11)] + s = q.get_quadratic_components(8) + self.assertListEqual(s.ind1, [0, 1, 2, 3, 4, 5, 6, 7, 8]) + self.assertListEqual(s.ind2, [0, 1, 2, 3, 4, 5, 6, 7, 8]) + self.assertListEqual(s.val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]) + + s = q.get_quadratic_components('1', 3) + self.assertEqual(len(s), 4) + self.assertListEqual(s[0].ind1, [0]) + self.assertListEqual(s[0].ind2, [0]) + self.assertListEqual(s[0].val, [1.0]) + self.assertListEqual(s[1].ind1, [0, 1]) + self.assertListEqual(s[1].ind2, [0, 1]) + self.assertListEqual(s[1].val, [1.0, 2.0]) + self.assertListEqual(s[2].ind1, [0, 1, 2]) + self.assertListEqual(s[2].ind2, [0, 1, 2]) + self.assertListEqual(s[2].val, [1.0, 2.0, 3.0]) + self.assertListEqual(s[3].ind1, [0, 1, 2, 3]) + self.assertListEqual(s[3].ind2, [0, 1, 2, 3]) + self.assertListEqual(s[3].val, [1.0, 2.0, 3.0, 4.0]) + + s = q.get_quadratic_components([2, '1', 5]) + self.assertEqual(len(s), 3) + self.assertListEqual(s[0].ind1, [0, 1, 2]) + self.assertListEqual(s[0].ind2, [0, 1, 2]) + self.assertListEqual(s[0].val, [1.0, 2.0, 3.0]) + self.assertListEqual(s[1].ind1, [0]) + self.assertListEqual(s[1].ind2, [0]) + self.assertListEqual(s[1].val, [1.0]) + self.assertListEqual(s[2].ind1, [0, 1, 2, 3, 4, 5]) + self.assertListEqual(s[2].ind2, [0, 1, 2, 3, 4, 5]) + self.assertListEqual(s[2].val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) + + q.delete(4, 9) + s = q.get_quadratic_components() + self.assertEqual(len(s), 4) + self.assertListEqual(s[0].ind1, [0]) + self.assertListEqual(s[0].ind2, [0]) + self.assertListEqual(s[0].val, [1.0]) + self.assertListEqual(s[1].ind1, [0, 1]) + self.assertListEqual(s[1].ind2, [0, 1]) + self.assertListEqual(s[1].val, [1.0, 2.0]) + self.assertListEqual(s[2].ind1, [0, 1, 2]) + self.assertListEqual(s[2].ind2, [0, 1, 2]) + self.assertListEqual(s[2].val, [1.0, 2.0, 3.0]) + self.assertListEqual(s[3].ind1, [0, 1, 2, 3]) + self.assertListEqual(s[3].ind2, [0, 1, 2, 3]) + self.assertListEqual(s[3].val, [1.0, 2.0, 3.0, 4.0]) From 63c4f633ac4c8e0ac2786a2e5ec5c0ee79c90fdc Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Mon, 9 Mar 2020 18:09:56 +0900 Subject: [PATCH 020/323] fix a comment --- test/optimization/test_quadratic_constraints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/optimization/test_quadratic_constraints.py b/test/optimization/test_quadratic_constraints.py index 8cb7d972da..2799415c97 100644 --- a/test/optimization/test_quadratic_constraints.py +++ b/test/optimization/test_quadratic_constraints.py @@ -22,7 +22,7 @@ class TestQuadraticConstraints(QiskitOptimizationTestCase): - """Test LinearConstraintInterface.""" + """Test QuadraticConstraintInterface.""" def setUp(self): super().setUp() From ef652c283fea4437129485ac2d2271365eda4561 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Mon, 9 Mar 2020 11:48:39 +0100 Subject: [PATCH 021/323] bug fixes for unit tests --- qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py | 11 +++++------ qiskit/aqua/algorithms/vq_algorithm.py | 11 +++-------- test/optimization/test_optimization_problem.py | 3 +-- test/optimization/test_readme_sample.py | 4 ++-- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py index cd0777a269..04d249dcd7 100755 --- a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py @@ -147,12 +147,6 @@ def __init__(self, initial_point = var_form.preferred_init_points self._max_evals_grouped = max_evals_grouped - - super().__init__(var_form=var_form, - optimizer=optimizer, - cost_fn=self._energy_evaluation, - initial_point=initial_point) - self._quantum_instance = quantum_instance self._in_operator = None self._operator = None @@ -166,6 +160,11 @@ def __init__(self, self._eval_time = None self._eval_count = 0 + super().__init__(var_form=var_form, + optimizer=optimizer, + cost_fn=self._energy_evaluation, + initial_point=initial_point) + logger.info(self.print_settings()) self._var_form_params = None if self.var_form is not None: diff --git a/qiskit/aqua/algorithms/vq_algorithm.py b/qiskit/aqua/algorithms/vq_algorithm.py index dc7c09815e..a57645ef85 100644 --- a/qiskit/aqua/algorithms/vq_algorithm.py +++ b/qiskit/aqua/algorithms/vq_algorithm.py @@ -62,13 +62,13 @@ def __init__(self, """ super().__init__() + self._initial_point = initial_point self.var_form = var_form if optimizer is None: raise ValueError('Missing optimizer.') self._optimizer = optimizer self._cost_fn = cost_fn - self._initial_point = initial_point self._parameterized_circuits = None @@ -81,6 +81,8 @@ def var_form(self) -> Optional[VariationalForm]: def var_form(self, var_form: VariationalForm): """ Sets variational form """ self._var_form = var_form + if var_form: + self._var_form_params = ParameterVector('θ', var_form.num_parameters) @property def optimizer(self) -> Optional[Optimizer]: @@ -251,13 +253,6 @@ def optimizer_evals(self) -> int: """ Returns number of optimizer evaluations """ return self.get('optimizer_evals') - @var_form.setter - def var_form(self, new_value): - """ sets var forms """ - self._var_form = new_value - if new_value: - self._var_form_params = ParameterVector('θ', new_value.num_parameters) - @optimizer_evals.setter def optimizer_evals(self, value: int) -> None: """ Sets number of optimizer evaluations """ diff --git a/test/optimization/test_optimization_problem.py b/test/optimization/test_optimization_problem.py index 0f297139d0..2b87165986 100644 --- a/test/optimization/test_optimization_problem.py +++ b/test/optimization/test_optimization_problem.py @@ -14,11 +14,10 @@ """ Test OptimizationProblem """ -import numpy as np import os.path import tempfile -import qiskit.optimization.problems.optimization_problem from test.optimization.common import QiskitOptimizationTestCase +import qiskit.optimization.problems.optimization_problem from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError diff --git a/test/optimization/test_readme_sample.py b/test/optimization/test_readme_sample.py index 9d7d1521c9..72452131af 100644 --- a/test/optimization/test_readme_sample.py +++ b/test/optimization/test_readme_sample.py @@ -39,8 +39,8 @@ def test_readme_sample(self): from qiskit.aqua import aqua_globals, QuantumInstance from qiskit.aqua.algorithms import QAOA from qiskit.aqua.components.optimizers import SPSA - from qiskit.optimization.ising import docplex, max_cut - from qiskit.optimization.ising.common import sample_most_likely + from qiskit.optimization.applications.ising import docplex, max_cut + from qiskit.optimization.applications.ising.common import sample_most_likely # Generate a graph of 4 nodes n = 4 From 0d79b20f2cbb34697b0108b914a2ae5f892f1577 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Mon, 9 Mar 2020 23:03:57 +0900 Subject: [PATCH 022/323] revise docstrings --- .../problems/quadratic_constraint.py | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index 71b60ac121..38e8638c13 100755 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -66,31 +66,36 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): Takes up to five keyword arguments: - lin_expr : either a SparsePair or a list of two lists specifying - the linear component of the constraint. + Args: + lin_expr : either a SparsePair or a list of two lists specifying + the linear component of the constraint. - Note - lin_expr must not contain duplicate indices. If lin_expr - references a variable more than once, either by index, name, - or a combination of index and name, an exception will be - raised. + Note + lin_expr must not contain duplicate indices. If lin_expr + references a variable more than once, either by index, name, + or a combination of index and name, an exception will be + raised. - quad_expr : either a SparseTriple or a list of three lists - specifying the quadratic component of the constraint. + quad_expr : either a SparseTriple or a list of three lists + specifying the quadratic component of the constraint. - Note - quad_expr must not contain duplicate indices. If quad_expr - references a matrix entry more than once, either by indices, - names, or a combination of indices and names, an exception - will be raised. + Note + quad_expr must not contain duplicate indices. If quad_expr + references a matrix entry more than once, either by indices, + names, or a combination of indices and names, an exception + will be raised. - sense : either "L", "G", or "E" + sense : either "L", "G", or "E" - rhs : a float specifying the righthand side of the constraint. + rhs : a float specifying the righthand side of the constraint. - name : the name of the constraint. + name : the name of the constraint. - Returns the index of the added quadratic constraint. + Returns: + The index of the added quadratic constraint. + + Raises: + QiskitOptimizationError: if invalid argument is given. >>> op = qiskit.optimization.OptimizationProblem() >>> indices = op.variables.add(names = ['x','y']) From de427e88e918b51cd0644ed4e170e2c4735dd21c Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Mon, 9 Mar 2020 19:26:25 +0100 Subject: [PATCH 023/323] update unittests for minimum eigen optimizer --- .../eigen_solvers/classical_eigen_solver.py | 3 +- .../algorithms/minimum_eigen_solvers/vqe.py | 13 +++++--- qiskit/optimization/algorithms/__init__.py | 9 +++--- ...ptimizer.py => minimum_eigen_optimizer.py} | 13 ++++---- ...y => recursive_minimum_eigen_optimizer.py} | 15 ++++----- test/aqua/test_compute_min_eigenvalue.py | 32 ++++++++----------- test/optimization/test_min_eigen_optimizer.py | 10 +++--- .../test_recursive_optimization.py | 14 ++++---- 8 files changed, 54 insertions(+), 55 deletions(-) rename qiskit/optimization/algorithms/{min_eigen_optimizer.py => minimum_eigen_optimizer.py} (94%) rename qiskit/optimization/algorithms/{recursive_min_eigen_optimizer.py => recursive_minimum_eigen_optimizer.py} (93%) diff --git a/qiskit/aqua/algorithms/eigen_solvers/classical_eigen_solver.py b/qiskit/aqua/algorithms/eigen_solvers/classical_eigen_solver.py index ed5bcbcfa2..a99a95410a 100755 --- a/qiskit/aqua/algorithms/eigen_solvers/classical_eigen_solver.py +++ b/qiskit/aqua/algorithms/eigen_solvers/classical_eigen_solver.py @@ -21,6 +21,7 @@ import numpy as np from scipy import sparse as scisparse +from qiskit.aqua import AquaError from qiskit.aqua.algorithms import ClassicalAlgorithm from qiskit.aqua.operators import MatrixOperator, op_converter # pylint: disable=unused-import from qiskit.aqua.operators import BaseOperator @@ -189,7 +190,7 @@ def _run(self): ValueError: if no operator has been provided """ if self._operator is None: - raise ValueError("Operator was never provided") + raise AquaError("Operator was never provided") self._ret = {} self._solve() diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py index 04d249dcd7..5b92b729c4 100755 --- a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py @@ -147,7 +147,6 @@ def __init__(self, initial_point = var_form.preferred_init_points self._max_evals_grouped = max_evals_grouped - self._quantum_instance = quantum_instance self._in_operator = None self._operator = None self._in_aux_operators = None @@ -165,6 +164,8 @@ def __init__(self, cost_fn=self._energy_evaluation, initial_point=initial_point) + self._quantum_instance = quantum_instance + logger.info(self.print_settings()) self._var_form_params = None if self.var_form is not None: @@ -204,11 +205,13 @@ def aux_operators(self, aux_operators: List[BaseOperator]) -> None: @VQAlgorithm.var_form.setter def var_form(self, var_form: VariationalForm): """ Sets variational form """ + VQAlgorithm.var_form.fset(self, var_form) - self._var_form_params = ParameterVector('θ', var_form.num_parameters) - if self.initial_point is None: - self.initial_point = var_form.preferred_init_points - self._check_operator_varform() + if var_form: + self._var_form_params = ParameterVector('θ', var_form.num_parameters) + if self.initial_point is None: + self.initial_point = var_form.preferred_init_points + self._check_operator_varform() def _check_operator_varform(self): if self.operator is not None and self.var_form is not None: diff --git a/qiskit/optimization/algorithms/__init__.py b/qiskit/optimization/algorithms/__init__.py index b7c10e79dc..f4e76b06c1 100644 --- a/qiskit/optimization/algorithms/__init__.py +++ b/qiskit/optimization/algorithms/__init__.py @@ -27,8 +27,9 @@ from qiskit.optimization.algorithms.optimization_algorithm import OptimizationAlgorithm from qiskit.optimization.algorithms.cplex_optimizer import CplexOptimizer from qiskit.optimization.algorithms.cobyla_optimizer import CobylaOptimizer -from qiskit.optimization.algorithms.min_eigen_optimizer import MinEigenOptimizer -from qiskit.optimization.algorithms.recursive_min_eigen_optimizer import RecursiveMinEigenOptimizer +from qiskit.optimization.algorithms.minimum_eigen_optimizer import MinimumEigenOptimizer +from qiskit.optimization.algorithms.recursive_minimum_eigen_optimizer import\ + RecursiveMinimumEigenOptimizer -__all__ = ["OptimizationAlgorithm", "CplexOptimizer", "CobylaOptimizer", "MinEigenOptimizer", - "RecursiveMinEigenOptimizer"] +__all__ = ["OptimizationAlgorithm", "CplexOptimizer", "CobylaOptimizer", "MinimumEigenOptimizer", + "RecursiveMinimumEigenOptimizer"] diff --git a/qiskit/optimization/algorithms/min_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py similarity index 94% rename from qiskit/optimization/algorithms/min_eigen_optimizer.py rename to qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 4827ae02b4..b50c4fc27b 100644 --- a/qiskit/optimization/algorithms/min_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -26,7 +26,7 @@ from typing import Optional -from qiskit.aqua.algorithms import MinEigenSolver +from qiskit.aqua.algorithms import MinimumEigensolver from qiskit.optimization.problems import OptimizationProblem from qiskit.optimization.algorithms import OptimizationAlgorithm from qiskit.optimization.utils import QiskitOptimizationError @@ -36,10 +36,8 @@ from qiskit.optimization.utils import eigenvector_to_solutions from qiskit.optimization.results import OptimizationResult -from qiskit import Aer - -class MinEigenOptimizer(OptimizationAlgorithm): +class MinimumEigenOptimizer(OptimizationAlgorithm): """A wrapper for minimum eigen solvers from Qiskit Aqua to be used within Qiskit Optimization. This class provides a wrapper for minimum eigen solvers from Qiskit Aqua. @@ -54,7 +52,8 @@ class MinEigenOptimizer(OptimizationAlgorithm): """ - def __init__(self, min_eigen_solver: MinEigenSolver, penalty: Optional[float] = None) -> None: + def __init__(self, min_eigen_solver: MinimumEigensolver, penalty: Optional[float] = None + ) -> None: """Initializes the minimum eigen optimizer. This initializer takes the minimum eigen solver to be used to approximate the groundstate @@ -148,11 +147,11 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: operator, offset = operator_converter.encode(problem_) # approximate ground state of operator using min eigen solver - eigenvector, _ = self._min_eigen_solver.compute_min_eigenvalue(operator) + eigen_results = self._min_eigen_solver.compute_minimum_eigenvalue(operator) # analyze results # TODO: handle min_probability depending on backend or some other property - results = eigenvector_to_solutions(eigenvector, operator, min_probability=0) + results = eigenvector_to_solutions(eigen_results.eigenstate, operator, min_probability=0) results = [(res[0], problem_.objective.get_sense() * (res[1] + offset), res[2]) for res in results] results.sort(key=lambda x: problem_.objective.get_sense() * x[1]) diff --git a/qiskit/optimization/algorithms/recursive_min_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py similarity index 93% rename from qiskit/optimization/algorithms/recursive_min_eigen_optimizer.py rename to qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index da0b9d59a6..1face47a63 100644 --- a/qiskit/optimization/algorithms/recursive_min_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -24,27 +24,26 @@ >>> result = optimizer.solve(problem) """ +from copy import deepcopy from typing import Optional import numpy as np from cplex import SparseTriple -from copy import deepcopy from qiskit.optimization import QiskitOptimizationError -from qiskit.optimization.algorithms import OptimizationAlgorithm, MinEigenOptimizer +from qiskit.optimization.algorithms import OptimizationAlgorithm, MinimumEigenOptimizer from qiskit.optimization.problems import OptimizationProblem from qiskit.optimization.results import OptimizationResult -from qiskit.optimization.converters import (OptimizationProblemToOperator, - PenalizeLinearEqualityConstraints, +from qiskit.optimization.converters import (PenalizeLinearEqualityConstraints, IntegerToBinaryConverter) -from qiskit.aqua.algorithms import ExactEigensolver +from qiskit.aqua.algorithms import ClassicalMinimumEigensolver -class RecursiveMinEigenOptimizer(OptimizationAlgorithm): +class RecursiveMinimumEigenOptimizer(OptimizationAlgorithm): """ TODO """ - def __init__(self, min_eigen_optimizer: MinEigenOptimizer, min_num_vars: int = 1, + def __init__(self, min_eigen_optimizer: MinimumEigenOptimizer, min_num_vars: int = 1, min_num_vars_optimizer: Optional[OptimizationAlgorithm] = None, penalty: Optional[float] = None) -> None: """ @@ -59,7 +58,7 @@ def __init__(self, min_eigen_optimizer: MinEigenOptimizer, min_num_vars: int = 1 if min_num_vars_optimizer: self._min_num_vars_optimizer = min_num_vars_optimizer else: - self._min_num_vars_optimizer = MinEigenOptimizer(ExactEigensolver()) + self._min_num_vars_optimizer = MinimumEigenOptimizer(ClassicalMinimumEigensolver()) self._penalty = penalty def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: diff --git a/test/aqua/test_compute_min_eigenvalue.py b/test/aqua/test_compute_min_eigenvalue.py index 9a1995a29f..0b0a64583b 100755 --- a/test/aqua/test_compute_min_eigenvalue.py +++ b/test/aqua/test_compute_min_eigenvalue.py @@ -23,7 +23,7 @@ from qiskit.aqua.components.variational_forms import RY, RYRZ from qiskit.aqua.components.optimizers import L_BFGS_B, SPSA, SLSQP from qiskit.aqua.components.initial_states import Zero -from qiskit.aqua.algorithms import VQE, ExactEigensolver +from qiskit.aqua.algorithms import VQE, ClassicalMinimumEigensolver class TestComputeMinEigenvalue(QiskitAquaTestCase): @@ -51,40 +51,36 @@ def test_vqe(self): seed_simulator=aqua_globals.random_seed, seed_transpiler=aqua_globals.random_seed) - dummy_operator = MatrixOperator([]) - vqe = VQE(dummy_operator, RYRZ(self.qubit_op.num_qubits), L_BFGS_B(), + vqe = VQE(var_form=RYRZ(self.qubit_op.num_qubits), + optimizer=L_BFGS_B(), quantum_instance=quantum_instance) - - output = vqe.compute_min_eigenvalue(self.qubit_op) - - vector, energy = output - self.assertAlmostEqual(energy, -1.85727503) + output = vqe.compute_minimum_eigenvalue(self.qubit_op) + self.assertAlmostEqual(output.eigenvalue, -1.85727503) def test_vqe_qasm(self): """ VQE QASM test """ backend = BasicAer.get_backend('qasm_simulator') num_qubits = self.qubit_op.num_qubits - var_form = RY(num_qubits, 3) + var_form = RY(num_qubits, num_qubits) optimizer = SPSA(max_trials=300, last_avg=5) quantum_instance = QuantumInstance(backend, shots=10000, seed_simulator=self.seed, seed_transpiler=self.seed) - dummy_operator = MatrixOperator([]) - vqe = VQE(dummy_operator, var_form, optimizer, max_evals_grouped=1, + vqe = VQE(var_form=var_form, + optimizer=optimizer, + max_evals_grouped=1, quantum_instance=quantum_instance) - output = vqe.compute_min_eigenvalue(self.qubit_op) - - vector, energy = output - self.assertAlmostEqual(energy, -1.85727503, places=2) + output = vqe.compute_minimum_eigenvalue(self.qubit_op) + self.assertAlmostEqual(output.eigenvalue, -1.85727503, places=1) def test_ee(self): """ EE test """ dummy_operator = MatrixOperator([[1]]) - ee = ExactEigensolver(self.qubit_op, k=1, aux_operators=[]) - eigenvector, eigenvalue = ee.compute_min_eigenvalue(self.qubit_op) + ee = ClassicalMinimumEigensolver() + output = ee.compute_minimum_eigenvalue(self.qubit_op) - self.assertAlmostEqual(eigenvalue, -1.85727503) + self.assertAlmostEqual(output.eigenvalue, -1.85727503) if __name__ == '__main__': diff --git a/test/optimization/test_min_eigen_optimizer.py b/test/optimization/test_min_eigen_optimizer.py index adb4d1a2c5..7b2fc8b980 100644 --- a/test/optimization/test_min_eigen_optimizer.py +++ b/test/optimization/test_min_eigen_optimizer.py @@ -21,11 +21,11 @@ from qiskit import BasicAer from qiskit.aqua import QuantumInstance -from qiskit.aqua.algorithms import ExactEigensolver +from qiskit.aqua.algorithms import ClassicalMinimumEigensolver from qiskit.aqua.algorithms import QAOA from qiskit.aqua.components.optimizers import COBYLA -from qiskit.optimization.algorithms import MinEigenOptimizer, CplexOptimizer +from qiskit.optimization.algorithms import MinimumEigenOptimizer, CplexOptimizer from qiskit.optimization.problems import OptimizationProblem @@ -42,7 +42,7 @@ def setUp(self): self.min_eigen_solvers = {} # exact eigen solver - self.min_eigen_solvers['exact'] = ExactEigensolver() + self.min_eigen_solvers['exact'] = ClassicalMinimumEigensolver() # QAOA optimizer = COBYLA() @@ -62,10 +62,10 @@ def test_min_eigen_optimizer(self, config): # get minimum eigen solver min_eigen_solver = self.min_eigen_solvers[min_eigen_solver_name] if backend: - min_eigen_solver._quantum_instance = BasicAer.get_backend(backend) + min_eigen_solver.quantum_instance = BasicAer.get_backend(backend) # construct minimum eigen optimizer - min_eigen_optimizer = MinEigenOptimizer(min_eigen_solver) + min_eigen_optimizer = MinimumEigenOptimizer(min_eigen_solver) # load optimization problem problem = OptimizationProblem() diff --git a/test/optimization/test_recursive_optimization.py b/test/optimization/test_recursive_optimization.py index 7a5db21e00..116ad03270 100755 --- a/test/optimization/test_recursive_optimization.py +++ b/test/optimization/test_recursive_optimization.py @@ -21,18 +21,18 @@ from qiskit import BasicAer from qiskit.aqua import QuantumInstance -from qiskit.aqua.algorithms import ExactEigensolver +from qiskit.aqua.algorithms import ClassicalMinimumEigensolver from qiskit.aqua.algorithms import QAOA from qiskit.aqua.components.optimizers import COBYLA -from qiskit.optimization.algorithms import (MinEigenOptimizer, CplexOptimizer, - RecursiveMinEigenOptimizer) +from qiskit.optimization.algorithms import (MinimumEigenOptimizer, CplexOptimizer, + RecursiveMinimumEigenOptimizer) from qiskit.optimization.problems import OptimizationProblem @ddt class TestRecursiveMinEigenOptimizer(QiskitOptimizationTestCase): - """Rrecursive Min Eigen Optimizer Tests.""" + """Recursive Min Eigen Optimizer Tests.""" def setUp(self): super().setUp() @@ -43,7 +43,7 @@ def setUp(self): self.min_eigen_solvers = {} # exact eigen solver - self.min_eigen_solvers['exact'] = ExactEigensolver() + self.min_eigen_solvers['exact'] = ClassicalMinimumEigensolver() # QAOA optimizer = COBYLA() @@ -64,9 +64,9 @@ def test_recursive_min_eigen_optimizer(self, config): min_eigen_solver = self.min_eigen_solvers[min_eigen_solver_name] if backend: min_eigen_solver._quantum_instance = BasicAer.get_backend(backend) - min_eigen_optimizer = MinEigenOptimizer(min_eigen_solver) + min_eigen_optimizer = MinimumEigenOptimizer(min_eigen_solver) # construct minimum eigen optimizer - recursive_min_eigen_optimizer = RecursiveMinEigenOptimizer(min_eigen_optimizer) + recursive_min_eigen_optimizer = RecursiveMinimumEigenOptimizer(min_eigen_optimizer) # load optimization problem problem = OptimizationProblem() From a53ac537d7ee3ae499698c82e7fb8ba60f97359b Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Tue, 10 Mar 2020 21:18:50 +0900 Subject: [PATCH 024/323] (wip) refactor variable --- qiskit/optimization/problems/variables.py | 296 +++++++++------------- test/optimization/test_variables.py | 118 ++++----- 2 files changed, 173 insertions(+), 241 deletions(-) mode change 100755 => 100644 qiskit/optimization/problems/variables.py diff --git a/qiskit/optimization/problems/variables.py b/qiskit/optimization/problems/variables.py old mode 100755 new mode 100644 index 5184620fe2..005025ca64 --- a/qiskit/optimization/problems/variables.py +++ b/qiskit/optimization/problems/variables.py @@ -12,16 +12,13 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -from collections.abc import Sequence import copy +from collections.abc import Sequence -from qiskit.optimization.utils.helpers import ( - init_list_args, validate_arg_lengths, max_arg_length, listify, convert) +from qiskit.optimization import infinity from qiskit.optimization.utils.base import BaseInterface +from qiskit.optimization.utils.helpers import init_list_args, listify, NameIndex from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError -from qiskit.optimization import infinity - -from cplex.exceptions import CplexSolverError CPX_CONTINUOUS = 'C' CPX_BINARY = 'B' @@ -48,7 +45,8 @@ def __getitem__(self, item): Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> op.variables.type.binary 'B' >>> op.variables.type['B'] @@ -71,7 +69,8 @@ class VariablesInterface(BaseInterface): Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = ["x0", "x1", "x2"]) >>> # default values for lower_bounds are 0.0 >>> op.variables.get_lower_bounds() @@ -107,24 +106,16 @@ def __init__(self): self._types = [] # self._obj = [] # self._columns = [] - self._varsgetindex = {} - - def _varsgetindexfunc(self, item): - if item not in self._varsgetindex: - self._varsgetindex[item] = len(self._varsgetindex) - return self._varsgetindex[item] - - def _varsrebuildindex(self): - self._varsgetindex = {} - for (cnt, item) in enumerate(self._names): - self._varsgetindex[item] = cnt + self._index = NameIndex() + self._varsgetindexfunc = {} # TODO: to be deleted def get_num(self): """Returns the number of variables in the problem. Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) >>> op.variables.get_num() @@ -137,106 +128,115 @@ def get_num_continuous(self): Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) >>> op.variables.get_num_continuous() 1 """ - return self._types.count(VarTypes().continuous) + return self._types.count(VarTypes.continuous) def get_num_integer(self): """Returns the number of integer variables in the problem. Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) >>> op.variables.get_num_integer() 1 """ - return self._types.count(VarTypes().integer) + return self._types.count(VarTypes.integer) def get_num_binary(self): """Returns the number of binary variables in the problem. Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.semi_continuous, t.binary, t.integer]) >>> op.variables.get_num_binary() 1 """ - return self._types.count(VarTypes().binary) + return self._types.count(VarTypes.binary) def get_num_semicontinuous(self): """Returns the number of semi-continuous variables in the problem. Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.semi_continuous, t.semi_integer, t.semi_integer]) >>> op.variables.get_num_semicontinuous() 1 """ - return self._types.count(VarTypes().semi_continuous) + return self._types.count(VarTypes.semi_continuous) def get_num_semiinteger(self): """Returns the number of semi-integer variables in the problem. Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.semi_continuous, t.semi_integer, t.semi_integer]) >>> op.variables.get_num_semiinteger() 2 """ - return self._types.count(VarTypes().semi_integer) + return self._types.count(VarTypes.semi_integer) def add(self, obj=None, lb=None, ub=None, types="", names=None, columns=None): """Adds variables and related data to the problem. - variables.add accepts the keyword arguments obj, lb, ub, - types, names, and columns. + variables.add accepts the keyword arguments obj, lb, ub, types, names, and columns. - If more than one argument is specified, all arguments must - have the same length. + If more than one argument is specified, all arguments must have the same length. - obj is a list of floats specifying the linear objective - coefficients of the variables. + Note + `obj` and `columns` have not been supported yet. + Use `objective` and `linear_constraint` instead. - lb is a list of floats specifying the lower bounds on the - variables. + Args: + obj: a list of floats specifying the linear objective coefficients of the variables. - ub is a list of floats specifying the upper bounds on the - variables. + lb: a list of floats specifying the lower bounds on the variables. - types must be either a list of single-character strings or a - string containing the types of the variables. + ub: a list of floats specifying the upper bounds on the variables. - Note - If types is specified, the problem type will be a MIP, even if - all variables are specified to be continuous. + types: must be either a list of single-character strings or a string containing + the types of the variables. - names is a list of strings. + Note + If types is specified, the problem type will be a MIP, even if all variables are + specified to be continuous. - columns may be either a list of sparse vectors or a matrix in - list-of-lists format. + names: a list of strings. - Note - The entries of columns must not contain duplicate indices. - If an entry of columns references a row more than once, - either by index, name, or a combination of index and name, - an exception will be raised. + columns: may be either a list of sparse vectors or a matrix in list-of-lists format. - Returns an iterator containing the indices of the added variables. + Note + The entries of columns must not contain duplicate indices. + If an entry of columns references a row more than once, + either by index, name, or a combination of index and name, + an exception will be raised. - >>> op = qiskit.optimization.OptimizationProblem() + Returns: + an iterator containing the indices of the added variables. + + Example usage: + + >>> from qiskit.optimization.problems import OptimizationProblem + >>> from cplex import SparsePair, infinity + >>> op = OptimizationProblem() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2"]) >>> indices = op.variables.add(obj = [1.0, 2.0, 3.0],\ types = [op.variables.type.integer] * 3) @@ -286,8 +286,7 @@ def add(self, obj=None, lb=None, ub=None, types="", names=None, for cnt in range(len(self._names), len(self._names) + max_length)] self._names.extend(names) - for name in names: - self._varsgetindexfunc(name) + self._index.build(names) return range(len(self._names) - max_length, len(self._names)) @@ -320,23 +319,25 @@ def delete(self, *args): Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names=[str(i) for i in range(10)]) >>> op.variables.get_num() 10 >>> op.variables.delete(8) >>> op.variables.get_names() ['0', '1', '2', '3', '4', '5', '6', '7', '9'] + >>> op.variables.delete("1", 3) + >>> op.variables.get_names() + ['0', '4', '5', '6', '7', '9'] >>> op.variables.delete([2, "0", 5]) >>> op.variables.get_names() - ['1', '3', '5', '6', '7', '9'] + ['4', '6', '7'] >>> op.variables.delete() >>> op.variables.get_names() [] """ - # TODO: does not update varsgetindexfunc - def _delete(i): del self._names[i] del self._ub[i] @@ -344,28 +345,20 @@ def _delete(i): del self._types[i] if len(args) == 0: - # Delete All: + # Delete all self._names = [] self._ub = [] self._lb = [] self._types = [] # self._columns = [] - self._varsgetindex = {} - elif len(args) == 1: - # Delete all items from a possibly unordered list of mixed types: - args = listify(args[0]) - for i in args: - i = convert(i, self._varsgetindexfunc) - _delete(i) - self._varsrebuildindex() - elif len(args) == 2: - # Delete range from arg[0] to arg[1]: - for i in reversed(range(convert(args[0], self._varsgetindexfunc), - convert(args[1], self._varsgetindexfunc))): - _delete(i) - self._varsrebuildindex() - else: - raise QiskitOptimizationError("Wrong number of arguments.") + self._index = NameIndex() + + keys = self._index.convert(*args) + if isinstance(keys, int): + keys = [keys] + for i in sorted(keys, reverse=True): + _delete(i) + self._index.build(self._names) def set_lower_bounds(self, *args): """Sets the lower bound for a variable or set of variables. @@ -385,7 +378,8 @@ def set_lower_bounds(self, *args): the corresponding values. Equivalent to [variables.set_lower_bounds(pair[0], pair[1]) for pair in seq_of_pairs]. - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = ["x0", "x1", "x2"]) >>> op.variables.set_lower_bounds(0, 1.0) >>> op.variables.get_lower_bounds() @@ -396,20 +390,16 @@ def set_lower_bounds(self, *args): """ def _set(i, v): - try: - v = v + 0.0 - self._lb[convert(i, self._varsgetindexfunc)] = v - except CplexSolverError: - raise QiskitOptimizationError("Lower bound must support addition of a float.") + self._lb[self._index.convert(i)] = v # check for all elements in args whether they are types - if len(args) == 1 and all(hasattr(el, '__len__') and len(el) == 2 for el in args[0]): + if len(args) == 1 and all(isinstance(el, Sequence) and len(el) == 2 for el in args[0]): for el in args[0]: _set(*el) elif len(args) == 2: _set(*args) else: - raise QiskitOptimizationError("Invalid arguments to set_lower_bounds!") + raise QiskitOptimizationError("Invalid arguments to set_lower_bounds: %s".format(args)) def set_upper_bounds(self, *args): """Sets the upper bound for a variable or set of variables. @@ -438,20 +428,16 @@ def set_upper_bounds(self, *args): """ def _set(i, v): - try: - v = v + 0.0 - self._ub[convert(i, self._varsgetindexfunc)] = v - except CplexSolverError: - raise QiskitOptimizationError("Upper bound must support addition of a float.") + self._ub[self._index.convert(i)] = v # check for all elements in args whether they are types - if len(args) == 1 and all(hasattr(el, '__len__') and len(el) == 2 for el in args[0]): + if len(args) == 1 and all(isinstance(el, Sequence) and len(el) == 2 for el in args[0]): for el in args[0]: _set(*el) elif len(args) == 2: _set(*args) else: - raise QiskitOptimizationError("Invalid arguments to set_upper_bounds!") + raise QiskitOptimizationError("Invalid arguments to set_upper_bounds: %s".format(args)) def set_names(self, *args): """Sets the name of a variable or set of variables. @@ -470,7 +456,8 @@ def set_names(self, *args): corresponding strings. Equivalent to [variables.set_names(pair[0], pair[1]) for pair in seq_of_pairs]. - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) >>> op.variables.set_names(0, "first") @@ -480,16 +467,8 @@ def set_names(self, *args): """ def _set(i, v): - try: - v = v + "" - i = convert(i, self._varsgetindexfunc) - old_name = self._names[i] - self._names[i] = v - self._varsgetindex[v] = i - self._varsgetindex.pop(old_name) - except CplexSolverError: - raise QiskitOptimizationError( - "First argument must refer to currently defined variable, second to string.") + i = self._index.convert(i) + self._names[i] = v if len(args) == 2: _set(args[0], args[1]) @@ -499,6 +478,7 @@ def _set(i, v): _set(iv[0], iv[1]) else: raise QiskitOptimizationError("Wrong number of arguments.") + self._index.build(self._names) def set_types(self, *args): """Sets the type of a variable or set of variables. @@ -521,7 +501,8 @@ def set_types(self, *args): If the types are set, the problem will be treated as a MIP, even if all variable types are continuous. - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = [str(i) for i in range(5)]) >>> op.variables.set_types(0, op.variables.type.continuous) >>> op.variables.set_types([("1", op.variables.type.integer),\ @@ -535,24 +516,20 @@ def set_types(self, *args): """ def _set(i, v): - try: - if v not in [CPX_CONTINUOUS, CPX_BINARY, CPX_INTEGER, CPX_SEMICONT, CPX_SEMIINT]: - raise QiskitOptimizationError( - "Second argument must be a string, as per VarTypes constants.") - i = convert(i, self._varsgetindexfunc) - self._types[i] = v - except CplexSolverError: + if v not in [CPX_CONTINUOUS, CPX_BINARY, CPX_INTEGER, CPX_SEMICONT, CPX_SEMIINT]: raise QiskitOptimizationError( - "First argument must be index of a currently defined variable.") + "Second argument must be a string, as per VarTypes constants.") + i = self._index.convert(i) + self._types[i] = v if len(args) == 2: - _set(args[0], args[1]) + _set(*args) elif len(args) == 1: args = listify(args[0]) for (i, v) in args: _set(i, v) else: - raise QiskitOptimizationError("Wrong number of arguments.") + raise QiskitOptimizationError("Wrong number of arguments: {}".format(args)) def get_lower_bounds(self, *args): """Returns the lower bounds on variables from the problem. @@ -572,7 +549,8 @@ def get_lower_bounds(self, *args): of s. Equivalent to [variables.get_lower_bounds(i) for i in s] - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(lb = [1.5 * i for i in range(10)],\ names = [str(i) for i in range(10)]) >>> op.variables.get_num() @@ -584,24 +562,12 @@ def get_lower_bounds(self, *args): >>> op.variables.get_lower_bounds() [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] """ - - def _get(i): - i = convert(i, self._varsgetindexfunc) - return self._lb[i] - - out = [] if len(args) == 0: return copy.deepcopy(self._lb) - elif len(args) == 1: - if isinstance(args[0], Sequence): - for i in args[0]: - out.append(_get(i)) - else: - return _get(args[0]) - else: - for i in args: - out.append(_get(i)) - return out + keys = self._index.convert(*args) + if isinstance(keys, int): + return self._lb[keys] + return [self._lb[k] for k in keys] def get_upper_bounds(self, *args): """Returns the upper bounds on variables from the problem. @@ -627,7 +593,8 @@ def get_upper_bounds(self, *args): begin and end, inclusive of end. Equivalent to variables.get_upper_bounds(range(begin, end + 1)). - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(ub = [(1.5 * i) + 1.0 for i in range(10)],\ names = [str(i) for i in range(10)]) >>> op.variables.get_num() @@ -639,24 +606,12 @@ def get_upper_bounds(self, *args): >>> op.variables.get_upper_bounds() [1.0, 2.5, 4.0, 5.5, 7.0, 8.5, 10.0, 11.5, 13.0, 14.5] """ - - def _get(i): - i = convert(i, self._varsgetindexfunc) - return self._ub[i] - - out = [] if len(args) == 0: return copy.deepcopy(self._ub) - elif len(args) == 1: - if isinstance(args[0], Sequence): - for i in args[0]: - out.append(_get(i)) - else: - return _get(args[0]) - else: - for i in args: - out.append(_get(i)) - return out + keys = self._index.convert(*args) + if isinstance(keys, int): + return self._ub[keys] + return [self._ub[k] for k in keys] def get_names(self, *args): """Returns the names of variables from the problem. @@ -685,24 +640,12 @@ def get_names(self, *args): >>> op.variables.get_names() ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9'] """ - - def _get(i): - i = convert(i, self._varsgetindexfunc) - return self._names[i] - - out = [] if len(args) == 0: return copy.deepcopy(self._names) - elif len(args) == 1: - if isinstance(args[0], Sequence): - for i in args[0]: - out.append(_get(i)) - else: - return _get(args[0]) - else: - for i in args: - out.append(_get(i)) - return out + keys = self._index.convert(*args) + if isinstance(keys, int): + return self._names[keys] + return [self._names[k] for k in keys] def get_types(self, *args): """Returns the types of variables from the problem. @@ -721,7 +664,8 @@ def get_types(self, *args): the types of the variables with indices the members of s. Equivalent to [variables.get_types(i) for i in s] - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> t = op.variables.type >>> indices = op.variables.add(names = [str(i) for i in range(5)],\ types = [t.continuous, t.integer,\ @@ -735,24 +679,12 @@ def get_types(self, *args): >>> op.variables.get_types() ['C', 'I', 'B', 'S', 'N'] """ - - def _get(i): - i = convert(i, self._varsgetindexfunc) - return self._types[i] - - out = [] if len(args) == 0: return copy.deepcopy(self._types) - elif len(args) == 1: - if isinstance(args[0], Sequence): - for i in args[0]: - out.append(_get(i)) - else: - return _get(args[0]) - else: - for i in args: - out.append(_get(i)) - return out + keys = self._index.convert(*args) + if isinstance(keys, int): + return self._types[keys] + return [self._types[k] for k in keys] def get_cols(self, *args): raise QiskitOptimizationError("Please use LinearConstraintInterface instead.") diff --git a/test/optimization/test_variables.py b/test/optimization/test_variables.py index 1d77a9eb7d..a5bf3935d8 100644 --- a/test/optimization/test_variables.py +++ b/test/optimization/test_variables.py @@ -14,15 +14,10 @@ """ Test VariablesInterface """ -import numpy as np -import os.path -import tempfile -from cplex import SparsePair -import qiskit.optimization.problems.optimization_problem -from cplex import infinity -from test.optimization.common import QiskitOptimizationTestCase +from qiskit.optimization.problems import OptimizationProblem from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError - +from test.optimization.common import QiskitOptimizationTestCase +from cplex import SparsePair, infinity class TestVariables(QiskitOptimizationTestCase): """Test VariablesInterface.""" @@ -31,12 +26,12 @@ def setUp(self): super().setUp() def test_type(self): - op = qiskit.optimization.OptimizationProblem() - op.variables.type.binary - op.variables.type['B'] + op = OptimizationProblem() + self.assertEqual(op.variables.type.binary, 'B') + self.assertEqual(op.variables.type['B'], 'binary') def test_initial(self): - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() op.variables.add(names=["x0", "x1", "x2"]) # default values for lower_bounds are 0.0 self.assertAlmostEqual(sum(op.variables.get_lower_bounds()), 0.0) @@ -55,70 +50,82 @@ def test_initial(self): self.assertEqual(op.variables.get_num_binary(), 1) def test_get_num(self): - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() t = op.variables.type op.variables.add(types=[t.continuous, t.binary, t.integer]) self.assertEqual(op.variables.get_num(), 3) def test_get_num_continuous(self): - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() t = op.variables.type op.variables.add(types=[t.continuous, t.binary, t.integer]) self.assertEqual(op.variables.get_num_continuous(), 1) def test_get_num_integer(self): - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() t = op.variables.type op.variables.add(types=[t.continuous, t.binary, t.integer]) self.assertEqual(op.variables.get_num_integer(), 1) def test_get_num_binary(self): - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() t = op.variables.type op.variables.add(types=[t.semi_continuous, t.binary, t.integer]) self.assertEqual(op.variables.get_num_binary(), 1) def test_get_num_semicontinuous(self): - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() t = op.variables.type op.variables.add(types=[t.semi_continuous, t.semi_integer, t.semi_integer]) self.assertEqual(op.variables.get_num_semicontinuous(), 1) def test_get_num_semiinteger(self): - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() t = op.variables.type op.variables.add(types=[t.semi_continuous, t.semi_integer, t.semi_integer]) self.assertEqual(op.variables.get_num_semiinteger(), 2) + def test_add(self): + op = OptimizationProblem() + op.linear_constraints.add(names=["c0", "c1", "c2"]) + op.variables.add(types=[op.variables.type.integer] * 3) + op.variables.add( + lb=[-1.0, 1.0, 0.0], + ub=[100.0, infinity, infinity], + types=[op.variables.type.integer] * 3, + names=["0", "1", "2"] + ) + self.assertListEqual(op.variables.get_lower_bounds(), + [0.0, 0.0, 0.0, -1.0, 1.0, 0.0]) + self.assertListEqual(op.variables.get_upper_bounds(), + [infinity, infinity, infinity, 100.0, infinity, infinity]) + def test_delete1(self): - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(10)]) self.assertEqual(op.variables.get_num(), 10) + self.assertListEqual(op.variables.get_names(), + ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']) op.variables.delete(8) - self.assertEqual(len(op.variables.get_names()), 9) - self.assertEqual(op.variables.get_names(), ['0', '1', '2', '3', '4', '5', '6', '7', '9']) - op.variables.delete([2, "0", 5]) - self.assertEqual(len(op.variables.get_names()), 6) - self.assertEqual(op.variables.get_names(), ['1', '3', '4', '5', '6', '9']) + self.assertListEqual(op.variables.get_names(), + ['0', '1', '2', '3', '4', '5', '6', '7', '9']) + op.variables.delete("1", 3) + self.assertListEqual(op.variables.get_names(), ['0', '4', '5', '6', '7', '9']) + op.variables.delete([2, '0', 5]) + self.assertListEqual(op.variables.get_names(), ['4', '6', '7']) op.variables.delete() - self.assertEqual(len(op.variables.get_names()), 0) + self.assertListEqual(op.variables.get_names(), []) def test_lower_bounds(self): - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() op.variables.add(names=["x0", "x1", "x2"]) op.variables.set_lower_bounds(0, 1.0) - op.variables.get_lower_bounds() - # [1.0, 0.0, 0.0] - self.assertEqual(len(op.variables.get_lower_bounds()), 3) - self.assertAlmostEqual(sum(op.variables.get_lower_bounds()), 1.0) + self.assertListEqual(op.variables.get_lower_bounds(), [1.0, 0.0, 0.0]) op.variables.set_lower_bounds([(2, 3.0), ("x1", -1.0)]) - op.variables.get_lower_bounds() - # [1.0, -1.0, 3.0] - self.assertEqual(len(op.variables.get_lower_bounds()), 3) - self.assertAlmostEqual(sum(op.variables.get_lower_bounds()), 3.0) + self.assertListEqual(op.variables.get_lower_bounds(), [1.0, -1.0, 3.0]) def test_upper_bounds(self): - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() op.variables.add(names=["x0", "x1", "x2"]) op.variables.set_upper_bounds(0, 1.0) op.variables.set_upper_bounds([("x1", 10.0), (2, 3.0)]) @@ -127,7 +134,7 @@ def test_upper_bounds(self): self.assertAlmostEqual(sum(op.variables.get_upper_bounds()), 14.0) def test_names(self): - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() t = op.variables.type op.variables.add(types=[t.continuous, t.binary, t.integer]) op.variables.set_names(0, "first") @@ -136,45 +143,38 @@ def test_names(self): # ['first', 'second', 'third'] def test_lower_bounds1(self): - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(5)]) op.variables.set_types(0, op.variables.type.continuous) op.variables.set_types([("1", op.variables.type.integer), ("2", op.variables.type.binary), ("3", op.variables.type.semi_continuous), ("4", op.variables.type.semi_integer)]) - self.assertEqual(len(op.variables.get_types()), 5) - # ['C', 'I', 'B', 'S', 'N'] - self.assertEqual(op.variables.get_types(0), 'C') + self.assertListEqual(op.variables.get_types(), ['C', 'I', 'B', 'S', 'N']) def test_lower_bounds2(self): - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() op.variables.add(lb=[1.5 * i for i in range(10)], names=[str(i) for i in range(10)]) self.assertEqual(op.variables.get_num(), 10) self.assertAlmostEqual(op.variables.get_lower_bounds(8), 12.0) - self.assertEqual(len(op.variables.get_lower_bounds([2, "0", 5])), 3) - self.assertAlmostEqual(op.variables.get_lower_bounds([2, "0", 5])[0], 3.0) - self.assertAlmostEqual(op.variables.get_lower_bounds([2, "0", 5])[1], 0.0) - self.assertAlmostEqual(op.variables.get_lower_bounds([2, "0", 5])[2], 7.5) - self.assertEqual(len(op.variables.get_lower_bounds()), 10) - # [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] - self.assertAlmostEqual(op.variables.get_lower_bounds()[0], 0.0) + self.assertListEqual(op.variables.get_lower_bounds([2, "0", 5]), [3.0, 0.0, 7.5]) + self.assertEqual(op.variables.get_lower_bounds(), + [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5]) def test_upper_bounds2(self): - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() op.variables.add(ub=[(1.5 * i) + 1.0 for i in range(10)], names=[str(i) for i in range(10)]) self.assertEqual(op.variables.get_num(), 10) self.assertAlmostEqual(op.variables.get_upper_bounds(8), 13.0) - self.assertEqual(len(op.variables.get_upper_bounds([2, "0", 5])), 3) - self.assertAlmostEqual(op.variables.get_upper_bounds([2, "0", 5])[0], 4.0) - self.assertEqual(len(op.variables.get_upper_bounds()), 10) - # [1.0, 2.5, 4.0, 5.5, 7.0, 8.5, 10.0, 11.5, 13.0, 14.5] + self.assertListEqual(op.variables.get_upper_bounds([2, "0", 5]), [4.0, 1.0, 8.5]) + self.assertListEqual(op.variables.get_upper_bounds(), + [1.0, 2.5, 4.0, 5.5, 7.0, 8.5, 10.0, 11.5, 13.0, 14.5]) self.assertAlmostEqual(op.variables.get_upper_bounds()[0], 1.0) def test_names2(self): - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() op.variables.add(names=['x' + str(i) for i in range(10)]) self.assertAlmostEqual(op.variables.get_num(), 10) self.assertEqual(op.variables.get_names(8), 'x8') @@ -185,14 +185,14 @@ def test_names2(self): # ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9'] def test_types(self): - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() t = op.variables.type op.variables.add(names=[str(i) for i in range(5)], types=[t.continuous, t.integer, t.binary, t.semi_continuous, t.semi_integer]) self.assertEqual(op.variables.get_num(), 5) self.assertEqual(op.variables.get_types(3), 'S') - types = op.variables.get_types([2,0,4]) + types = op.variables.get_types([2, 0, 4]) # ['B', 'C', 'N'] self.assertEqual(len(types), 3) self.assertEqual(types[0], 'B') @@ -200,7 +200,7 @@ def test_types(self): self.assertEqual(types[2], 'N') types = op.variables.get_types() - #['C', 'I', 'B', 'S', 'N'] + # ['C', 'I', 'B', 'S', 'N'] self.assertEqual(len(types), 5) self.assertEqual(types[0], 'C') self.assertEqual(types[1], 'I') @@ -209,11 +209,11 @@ def test_types(self): self.assertEqual(types[4], 'N') def test_cols(self): - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() with self.assertRaises(QiskitOptimizationError): op.variables.get_cols() def test_obj(self): - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() with self.assertRaises(QiskitOptimizationError): op.variables.get_obj() From f2bf8b79c3fdbb7873b0bd4feb9b768a3b9177d3 Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Tue, 10 Mar 2020 13:26:00 +0000 Subject: [PATCH 025/323] testing EignOptimizers --- test/optimization/test_admm_miskp.py | 47 ++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/test/optimization/test_admm_miskp.py b/test/optimization/test_admm_miskp.py index 6c7e024bfc..f9dd21f86e 100644 --- a/test/optimization/test_admm_miskp.py +++ b/test/optimization/test_admm_miskp.py @@ -12,7 +12,11 @@ import time import numpy as np -from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer +from qiskit.aqua.algorithms import QAOA +from qiskit.aqua.components.optimizers import COBYLA + +from qiskit.optimization.algorithms import CplexOptimizer, MinimumEigenOptimizer +from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters from qiskit.optimization.problems import OptimizationProblem @@ -174,6 +178,7 @@ def get_instance_params(): return K, T, P, S, D, C + class Miskp: def __init__(self, K, T, P, S, D: np.ndarray, C: np.ndarray, verbose=False, relativeGap=0.0, @@ -316,6 +321,37 @@ def run_op(self): solution = solver.solve(self.op) return solution + + def run_op_eigens(self): + """Main method, which populates and solve the OptimizationProblem model via OptimizationAlgorithm + + """ + + self.op = OptimizationProblem() + + self.create_params() + self.create_vars() + self.create_obj() + self.create_cons() + + # Save the model + create_folder(self.lp_folder) + + self.op.write(self.lp_folder + "miskp.lp") + + # QAOA + optimizer = COBYLA() + min_eigen_solver = QAOA(optimizer=optimizer) + qubo_solver_class = MinimumEigenOptimizer(min_eigen_solver) + + continuous_solver_class = CplexOptimizer() + + admm_params = ADMMParameters(qubo_solver_class=qubo_solver_class, continuous_solver_class=continuous_solver_class) + + solver = ADMMOptimizer(params=admm_params) + solution = solver.solve(self.op) + + return solution def toy_cplex_api(): @@ -329,11 +365,18 @@ def toy_op(): K, T, P, S, D, C = get_instance_params() pb = Miskp(K, T, P, S, D, C) out = pb.run_op() + +def toy_op_eigens(): + + K, T, P, S, D, C = get_instance_params() + pb = Miskp(K, T, P, S, D, C) + out = pb.run_op_eigens() if __name__ == '__main__': # toy_cplex_api() - toy_op() + # toy_op() + toy_op_eigens() From 4a1377384390aa4dd8362d7dba05c440c4028452 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Tue, 10 Mar 2020 22:38:10 +0900 Subject: [PATCH 026/323] have refactoring of variable done --- qiskit/optimization/problems/variables.py | 6 ++-- test/optimization/test_variables.py | 36 ++++++++--------------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/qiskit/optimization/problems/variables.py b/qiskit/optimization/problems/variables.py index 005025ca64..c0a6eab0f7 100644 --- a/qiskit/optimization/problems/variables.py +++ b/qiskit/optimization/problems/variables.py @@ -419,7 +419,8 @@ def set_upper_bounds(self, *args): the corresponding values. Equivalent to [variables.set_upper_bounds(pair[0], pair[1]) for pair in seq_of_pairs]. - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = ["x0", "x1", "x2"]) >>> op.variables.set_upper_bounds(0, 1.0) >>> op.variables.set_upper_bounds([("x1", 10.0), (2, 3.0)]) @@ -629,7 +630,8 @@ def get_names(self, *args): names of the variables with indices the members of s. Equivalent to [variables.get_names(i) for i in s] - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = ['x' + str(i) for i in range(10)]) >>> op.variables.get_num() 10 diff --git a/test/optimization/test_variables.py b/test/optimization/test_variables.py index a5bf3935d8..5ce0044e89 100644 --- a/test/optimization/test_variables.py +++ b/test/optimization/test_variables.py @@ -14,10 +14,12 @@ """ Test VariablesInterface """ +from cplex import infinity + from qiskit.optimization.problems import OptimizationProblem from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError from test.optimization.common import QiskitOptimizationTestCase -from cplex import SparsePair, infinity + class TestVariables(QiskitOptimizationTestCase): """Test VariablesInterface.""" @@ -129,9 +131,7 @@ def test_upper_bounds(self): op.variables.add(names=["x0", "x1", "x2"]) op.variables.set_upper_bounds(0, 1.0) op.variables.set_upper_bounds([("x1", 10.0), (2, 3.0)]) - # [1.0, 10.0, 3.0] - self.assertEqual(len(op.variables.get_upper_bounds()), 3) - self.assertAlmostEqual(sum(op.variables.get_upper_bounds()), 14.0) + self.assertListEqual(op.variables.get_upper_bounds(), [1.0, 10.0, 3.0]) def test_names(self): op = OptimizationProblem() @@ -139,8 +139,7 @@ def test_names(self): op.variables.add(types=[t.continuous, t.binary, t.integer]) op.variables.set_names(0, "first") op.variables.set_names([(2, "third"), (1, "second")]) - self.assertEqual(len(op.variables.get_names()), 3) - # ['first', 'second', 'third'] + self.assertListEqual(op.variables.get_names(), ['first', 'second', 'third']) def test_lower_bounds1(self): op = OptimizationProblem() @@ -176,13 +175,11 @@ def test_upper_bounds2(self): def test_names2(self): op = OptimizationProblem() op.variables.add(names=['x' + str(i) for i in range(10)]) - self.assertAlmostEqual(op.variables.get_num(), 10) + self.assertEqual(op.variables.get_num(), 10) self.assertEqual(op.variables.get_names(8), 'x8') - self.assertEqual(len(op.variables.get_names([2, 0, 5])), 3) - self.assertEqual(op.variables.get_names([2, 0, 5])[0], 'x2') - # ['x2', 'x0', 'x5'] - self.assertEqual(len(op.variables.get_names()), 10) - # ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9'] + self.assertListEqual(op.variables.get_names([2, 0, 5]), ['x2', 'x0', 'x5']) + self.assertListEqual(op.variables.get_names(), + ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9']) def test_types(self): op = OptimizationProblem() @@ -192,21 +189,12 @@ def test_types(self): t.binary, t.semi_continuous, t.semi_integer]) self.assertEqual(op.variables.get_num(), 5) self.assertEqual(op.variables.get_types(3), 'S') + types = op.variables.get_types([2, 0, 4]) - # ['B', 'C', 'N'] - self.assertEqual(len(types), 3) - self.assertEqual(types[0], 'B') - self.assertEqual(types[1], 'C') - self.assertEqual(types[2], 'N') + self.assertListEqual(types, ['B', 'C', 'N']) types = op.variables.get_types() - # ['C', 'I', 'B', 'S', 'N'] - self.assertEqual(len(types), 5) - self.assertEqual(types[0], 'C') - self.assertEqual(types[1], 'I') - self.assertEqual(types[2], 'B') - self.assertEqual(types[3], 'S') - self.assertEqual(types[4], 'N') + self.assertEqual(types, ['C', 'I', 'B', 'S', 'N']) def test_cols(self): op = OptimizationProblem() From 50ebca61e7117ebf4efa2209c597b23e865b107a Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Tue, 10 Mar 2020 13:39:36 +0000 Subject: [PATCH 027/323] qubo_solver, continuous_solver --- .../optimization/algorithms/admm_optimizer.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index c378497b5f..a0bed81bb3 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -15,8 +15,8 @@ class ADMMParameters: def __init__(self, rho_initial=10000, factor_c=100000, beta=1000, max_iter=10, tol=1.e-4, max_time=1800, three_block=True, vary_rho=0, tau_incr=2, tau_decr=2, mu_res=10, - mu=1000, qubo_solver_class: OptimizationAlgorithm = CplexOptimizer, - continuous_solver_class: OptimizationAlgorithm = CplexOptimizer) -> None: + mu=1000, qubo_solver: OptimizationAlgorithm = CplexOptimizer, + continuous_solver: OptimizationAlgorithm = CplexOptimizer) -> None: """Defines parameters for ADMM optimizer and their default values. Args: @@ -34,8 +34,8 @@ def __init__(self, rho_initial=10000, factor_c=100000, beta=1000, max_iter=10, t tau_decr: Parameter used in the rho update. mu_res: Parameter used in the rho update. mu: Penalization for constraint residual. Used to compute the merit values. - qubo_solver_class: A subclass of OptimizationAlgorithm that can effectively solve QUBO problems - continuous_solver_class: A subclass of OptimizationAlgorithm that can solve continuous problems + qubo_solver: An instance of OptimizationAlgorithm that can effectively solve QUBO problems + continuous_solver: An instance of OptimizationAlgorithm that can solve continuous problems """ super().__init__() self.mu = mu @@ -50,8 +50,8 @@ def __init__(self, rho_initial=10000, factor_c=100000, beta=1000, max_iter=10, t self.factor_c = factor_c self.beta = beta self.rho_initial = rho_initial - self.qubo_solver_class = qubo_solver_class - self.continuous_solver_class = continuous_solver_class + self.qubo_solver = qubo_solver + self.continuous_solver = continuous_solver class ADMMState: @@ -111,9 +111,8 @@ def __init__(self, params: ADMMParameters = None) -> None: self._mu = params.mu self._rho_initial = params.rho_initial - # note, we create instances of the solvers here instead of keeping classes - self._qubo_solver = params.qubo_solver_class() - self._continuous_solver = params.continuous_solver_class() + self._qubo_solver = params.qubo_solver + self._continuous_solver = params.continuous_solver # internal state where we'll keep intermediate solution # here, we just declare the class variable From c6f43d2144b1858e7d89e4d30c596335e2936f85 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Tue, 10 Mar 2020 13:48:26 +0000 Subject: [PATCH 028/323] added support for problem's sense, docstrings, refactorings --- .../optimization/algorithms/admm_optimizer.py | 173 +++++++++++------- 1 file changed, 105 insertions(+), 68 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index a0bed81bb3..1fa0b37fcd 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -1,3 +1,19 @@ +# -*-coding: utf-8 -*- +# This code is part of Qiskit. +# +# (C) Copyright IBM 2000. +# +# 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. + +"""An implementation of the ADMM algorithm. +""" + import time from typing import List @@ -11,12 +27,14 @@ from qiskit.optimization.results.optimization_result import OptimizationResult +from qiskit.optimization.problems.objective import ObjSense + class ADMMParameters: def __init__(self, rho_initial=10000, factor_c=100000, beta=1000, max_iter=10, tol=1.e-4, max_time=1800, three_block=True, vary_rho=0, tau_incr=2, tau_decr=2, mu_res=10, - mu=1000, qubo_solver: OptimizationAlgorithm = CplexOptimizer, - continuous_solver: OptimizationAlgorithm = CplexOptimizer) -> None: + mu=1000, qubo_solver: OptimizationAlgorithm = None, + continuous_solver: OptimizationAlgorithm = None) -> None: """Defines parameters for ADMM optimizer and their default values. Args: @@ -50,23 +68,38 @@ def __init__(self, rho_initial=10000, factor_c=100000, beta=1000, max_iter=10, t self.factor_c = factor_c self.beta = beta self.rho_initial = rho_initial - self.qubo_solver = qubo_solver - self.continuous_solver = continuous_solver + self.qubo_solver = qubo_solver if qubo_solver is not None else CplexOptimizer() + self.continuous_solver = continuous_solver if continuous_solver is not None else CplexOptimizer() class ADMMState: - def __init__(self, binary_size: int, rho_initial: float) -> None: + def __init__(self, + op: OptimizationProblem, + binary_indices: List[int], + continuous_indices: List[int], + rho_initial: float) -> None: """ Internal computation state of the ADMM implementation. Here, various variables are stored that are being updated during problem solving. The values are relevant to the problem being solved. The state is recreated for each optimization problem. Args: - binary_size: Number of binary decision variables of the original problem + op: The optimization problem being solved + binary_indices: Indices of the binary decision variables of the original problem + continuous_indices: Indices of the continuous decision variables of the original problem rho_initial: Initial value of the rho parameter. """ super().__init__() + + # Optimization problem itself + self.op = op + # Indices of the variables + self.binary_indices = binary_indices + self.continuous_indices = continuous_indices + self.sense = op.objective.get_sense() + # These are the parameters that are updated in the ADMM iterations. + binary_size = len(binary_indices) self.x0: np.ndarray = np.zeros(binary_size) self.z: np.ndarray = np.zeros(binary_size) self.z_init: np.ndarray = self.z @@ -90,9 +123,6 @@ def __init__(self, binary_size: int, rho_initial: float) -> None: class ADMMOptimizer(OptimizationAlgorithm): def __init__(self, params: ADMMParameters = None) -> None: super().__init__() - self._op = None - self._binary_indices = [] - self._continuous_indices = [] if params is None: # create default params params = ADMMParameters() @@ -119,32 +149,40 @@ def __init__(self, params: ADMMParameters = None) -> None: self._state = None def is_compatible(self, problem: OptimizationProblem): + """Checks whether a given problem can be solved with the optimizer implementing this method. + + Args: + problem: The optimization problem to check compatibility. + + Returns: + Returns ``None`` if the problem is compatible and else a string with the error message. + """ + # 1. only binary and continuous variables are supported for var_index, var_type in enumerate(problem.variables.get_types()): if var_type != CPX_BINARY and var_type != CPX_CONTINUOUS: # var var_index is not binary and not continuous - return False + return "Only binary and continuous variables are supported" - self._op = problem - self._binary_indices = self._get_variable_indices(CPX_BINARY) - self._continuous_indices = self._get_variable_indices(CPX_CONTINUOUS) + binary_indices = self._get_variable_indices(problem, CPX_BINARY) + continuous_indices = self._get_variable_indices(problem, CPX_CONTINUOUS) # 2. binary and continuous variables are separable in objective - for binary_index in self._binary_indices: - for continuous_index in self._continuous_indices: + for binary_index in binary_indices: + for continuous_index in continuous_indices: coeff = problem.objective.get_quadratic_coefficients(binary_index, continuous_index) if coeff != 0: # binary and continuous vars are mixed - return False + return "Binary and continuous variables are not separable in the objective" # 3. no quadratic constraints are supported quad_constraints = problem.quadratic_constraints.get_num() if quad_constraints is not None and quad_constraints > 0: # quadratic constraints are not supported - return False + return "Quadratic constraints are not supported" # todo: verify other properties of the problem - return True + return None def solve(self, problem: OptimizationProblem): """Tries to solves the given problem using ADMM algorithm. @@ -160,14 +198,12 @@ def solve(self, problem: OptimizationProblem): Raises: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ - self._op = problem - # parse problem and convert to an ADMM specific representation - self._binary_indices = self._get_variable_indices(CPX_BINARY) - self._continuous_indices = self._get_variable_indices(CPX_CONTINUOUS) + binary_indices = self._get_variable_indices(problem, CPX_BINARY) + continuous_indices = self._get_variable_indices(problem, CPX_CONTINUOUS) # create our computation state - self._state = ADMMState(len(self._binary_indices), self._rho_initial) + self._state = ADMMState(problem, binary_indices, continuous_indices, self._rho_initial) # debug # self.__dump_matrices_and_vectors() @@ -246,56 +282,57 @@ def solve(self, problem: OptimizationProblem): print("it {0}, state {1}".format(it, self._state)) return result - def _get_variable_indices(self, var_type: str) -> List[int]: + @staticmethod + def _get_variable_indices(op: OptimizationProblem, var_type: str) -> List[int]: indices = [] - for i, variable_type in enumerate(self._op.variables.get_types()): + for i, variable_type in enumerate(op.variables.get_types()): if variable_type == var_type: indices.append(i) return indices def get_q0(self): - # TODO: Flip the sign, according to the optimization sense - return self._get_q(self._binary_indices) + return self._get_q(self._state.binary_indices) def get_q1(self): - # TODO: Flip the sign, according to the optimization sense - return self._get_q(self._continuous_indices) + return self._get_q(self._state.continuous_indices) def _get_q(self, variable_indices: List[int]) -> np.ndarray: size = len(variable_indices) q = np.zeros(shape=(size, size)) # fill in the matrix # in fact we use re-indexed variables - [q.itemset((i, j), self._op.objective.get_quadratic_coefficients(var_index_i, var_index_j)) + [q.itemset((i, j), self._state.op.objective.get_quadratic_coefficients(var_index_i, var_index_j)) for i, var_index_i in enumerate(variable_indices) for j, var_index_j in enumerate(variable_indices)] - return q + + # flip the sign, according to the optimization sense, e.g. sense == 1 if minimize, sense == -1 if maximize + return q * self._state.sense def _get_c(self, variable_indices: List[int]) -> np.ndarray: - c = np.array(self._op.objective.get_linear(variable_indices)) + c = np.array(self._state.op.objective.get_linear(variable_indices)) + # flip the sign, according to the optimization sense, e.g. sense == 1 if minimize, sense == -1 if maximize + c = c * self._state.sense return c def get_c0(self): - # TODO: Flip the sign, according to the optimization sense - return self._get_c(self._binary_indices) + return self._get_c(self._state.binary_indices) def get_c1(self): - # TODO: Flip the sign, according to the optimization sense - return self._get_c(self._continuous_indices) + return self._get_c(self._state.continuous_indices) def _assign_row_values(self, matrix: List[List[float]], vector: List[float], constraint_index, variable_indices): # assign matrix row row = [] - [row.append(self._op.linear_constraints.get_coefficients(constraint_index, var_index)) + [row.append(self._state.op.linear_constraints.get_coefficients(constraint_index, var_index)) for var_index in variable_indices] matrix.append(row) # assign vector row - vector.append(self._op.linear_constraints.get_rhs(constraint_index)) + vector.append(self._state.op.linear_constraints.get_rhs(constraint_index)) # flip the sign if constraint is G, we want L constraints - if self._op.linear_constraints.get_senses(constraint_index) == "G": + if self._state.op.linear_constraints.get_senses(constraint_index) == "G": # invert the sign to make constraint "L" matrix[-1] = [-1 * el for el in matrix[-1]] vector[-1] = -1 * vector[-1] @@ -311,27 +348,27 @@ def get_a0_b0(self) -> (np.ndarray, np.ndarray): matrix = [] vector = [] - senses = self._op.linear_constraints.get_senses() - index_set = set(self._binary_indices) + senses = self._state.op.linear_constraints.get_senses() + index_set = set(self._state.binary_indices) for constraint_index, sense in enumerate(senses): # we check only equality constraints here if sense != "E": continue - row = self._op.linear_constraints.get_rows(constraint_index) + row = self._state.op.linear_constraints.get_rows(constraint_index) if set(row.ind).issubset(index_set): - self._assign_row_values(matrix, vector, constraint_index, self._binary_indices) + self._assign_row_values(matrix, vector, constraint_index, self._state.binary_indices) else: raise ValueError( "Linear constraint with the 'E' sense must contain only binary variables, " - "row indices: {}, binary variable indices: {}".format(row, self._binary_indices)) + "row indices: {}, binary variable indices: {}".format(row, self._state.binary_indices)) - return self._create_ndarrays(matrix, vector, len(self._binary_indices)) + return self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) -> (List[List[float]], List[float]): # extracting matrix and vector from constraints like Ax <= b matrix = [] vector = [] - senses = self._op.linear_constraints.get_senses() + senses = self._state.op.linear_constraints.get_senses() index_set = set(variable_indices) for constraint_index, sense in enumerate(senses): @@ -339,35 +376,35 @@ def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) -> (Lis # TODO: Ranged constraints should be supported continue # sense either G or L - row = self._op.linear_constraints.get_rows(constraint_index) + row = self._state.op.linear_constraints.get_rows(constraint_index) if set(row.ind).issubset(index_set): self._assign_row_values(matrix, vector, constraint_index, variable_indices) return matrix, vector def get_a1_b1(self) -> (np.ndarray, np.ndarray): - matrix, vector = self._get_inequality_matrix_and_vector(self._binary_indices) - return self._create_ndarrays(matrix, vector, len(self._binary_indices)) + matrix, vector = self._get_inequality_matrix_and_vector(self._state.binary_indices) + return self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) def get_a4_b3(self) -> (np.ndarray, np.ndarray): - matrix, vector = self._get_inequality_matrix_and_vector(self._continuous_indices) + matrix, vector = self._get_inequality_matrix_and_vector(self._state.continuous_indices) - return self._create_ndarrays(matrix, vector, len(self._continuous_indices)) + return self._create_ndarrays(matrix, vector, len(self._state.continuous_indices)) def get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): matrix = [] vector = [] - senses = self._op.linear_constraints.get_senses() + senses = self._state.op.linear_constraints.get_senses() - binary_index_set = set(self._binary_indices) - continuous_index_set = set(self._continuous_indices) - all_variables = self._binary_indices + self._continuous_indices + binary_index_set = set(self._state.binary_indices) + continuous_index_set = set(self._state.continuous_indices) + all_variables = self._state.binary_indices + self._state.continuous_indices for constraint_index, sense in enumerate(senses): if sense == "E" or sense == "R": # TODO: Ranged constraints should be supported as well continue # sense either G or L - row = self._op.linear_constraints.get_rows(constraint_index) + row = self._state.op.linear_constraints.get_rows(constraint_index) row_indices = set(row.ind) # we must have a least one binary and one continuous variable, otherwise it is another type of constraints if len(row_indices & binary_index_set) != 0 and len(row_indices & continuous_index_set) != 0: @@ -375,14 +412,14 @@ def get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): matrix, b2 = self._create_ndarrays(matrix, vector, len(all_variables)) # a2 - a2 = matrix[:, 0:len(self._binary_indices)] - a3 = matrix[:, len(self._binary_indices):] + a2 = matrix[:, 0:len(self._state.binary_indices)] + a3 = matrix[:, len(self._state.binary_indices):] return a2, a3, b2 def _create_step1_problem(self): op1 = OptimizationProblem() - binary_size = len(self._binary_indices) + binary_size = len(self._state.binary_indices) # create the same binary variables # op1.variables.add(names=["x0_" + str(i + 1) for i in range(binary_size)], types=["B"] * binary_size) op1.variables.add(names=["x0_" + str(i + 1) for i in range(binary_size)], @@ -412,10 +449,10 @@ def _create_step1_problem(self): def _create_step2_problem(self): op2 = OptimizationProblem() - continuous_size = len(self._continuous_indices) - binary_size = len(self._binary_indices) - lb = self._op.variables.get_lower_bounds(self._continuous_indices) - ub = self._op.variables.get_upper_bounds(self._continuous_indices) + continuous_size = len(self._state.continuous_indices) + binary_size = len(self._state.binary_indices) + lb = self._state.op.variables.get_lower_bounds(self._state.continuous_indices) + ub = self._state.op.variables.get_upper_bounds(self._state.continuous_indices) if continuous_size: # add u variables op2.variables.add(names=["u0_" + str(i + 1) for i in range(continuous_size)], @@ -490,7 +527,7 @@ def _create_step2_problem(self): def _create_step3_problem(self): op3 = OptimizationProblem() # add y variables - binary_size = len(self._binary_indices) + binary_size = len(self._state.binary_indices) op3.variables.add(names=["y_" + str(i + 1) for i in range(binary_size)], types=["C"] * binary_size) @@ -523,8 +560,8 @@ def update_x0(self, op1: OptimizationProblem) -> np.ndarray: def update_x1(self, op2: OptimizationProblem) -> (np.ndarray, np.ndarray): vars_op2 = self._continuous_solver.solve(op2).x # TODO: Check output type - u = np.asarray(vars_op2[:len(self._continuous_indices)]) - z = np.asarray(vars_op2[len(self._continuous_indices):]) + u = np.asarray(vars_op2[:len(self._state.continuous_indices)]) + z = np.asarray(vars_op2[len(self._state.continuous_indices):]) return u, z def update_y(self, op3): @@ -548,10 +585,10 @@ def get_min_mer_sol(self): sol = [x0, u] sol_val = self._state.cost_iterates[it_min_merits] return sol, sol_val - + def update_lambda_mult(self): return self._state.lambda_mult + self._state.rho * (self._state.x0 - self._state.z + self._state.y) - + def update_rho(self, r, s): """ Updating the rho parameter in ADMM From 5341fd887dbe1bdd6efed672864a99fe932b4643 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Tue, 10 Mar 2020 22:56:11 +0900 Subject: [PATCH 029/323] simplify --- qiskit/optimization/problems/variables.py | 30 ++++++++++------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/qiskit/optimization/problems/variables.py b/qiskit/optimization/problems/variables.py index c0a6eab0f7..6f22195dab 100644 --- a/qiskit/optimization/problems/variables.py +++ b/qiskit/optimization/problems/variables.py @@ -468,15 +468,13 @@ def set_names(self, *args): """ def _set(i, v): - i = self._index.convert(i) - self._names[i] = v - - if len(args) == 2: - _set(args[0], args[1]) - elif len(args) == 1: - args = listify(args[0]) - for iv in args: - _set(iv[0], iv[1]) + self._names[self._index.convert(i)] = v + + if len(args) == 1 and all(isinstance(el, Sequence) and len(el) == 2 for el in args[0]): + for el in args[0]: + _set(*el) + elif len(args) == 2: + _set(*args) else: raise QiskitOptimizationError("Wrong number of arguments.") self._index.build(self._names) @@ -520,17 +518,15 @@ def _set(i, v): if v not in [CPX_CONTINUOUS, CPX_BINARY, CPX_INTEGER, CPX_SEMICONT, CPX_SEMIINT]: raise QiskitOptimizationError( "Second argument must be a string, as per VarTypes constants.") - i = self._index.convert(i) - self._types[i] = v + self._types[self._index.convert(i)] = v - if len(args) == 2: + if len(args) == 1 and all(isinstance(el, Sequence) and len(el) == 2 for el in args[0]): + for el in args[0]: + _set(*el) + elif len(args) == 2: _set(*args) - elif len(args) == 1: - args = listify(args[0]) - for (i, v) in args: - _set(i, v) else: - raise QiskitOptimizationError("Wrong number of arguments: {}".format(args)) + raise QiskitOptimizationError("Wrong number of arguments.") def get_lower_bounds(self, *args): """Returns the lower bounds on variables from the problem. From 564a9111717cd17ec4c758b897a3bbca10030ba9 Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Tue, 10 Mar 2020 14:09:52 +0000 Subject: [PATCH 030/323] providing backend --- test/optimization/test_admm_miskp.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/optimization/test_admm_miskp.py b/test/optimization/test_admm_miskp.py index f9dd21f86e..c521129493 100644 --- a/test/optimization/test_admm_miskp.py +++ b/test/optimization/test_admm_miskp.py @@ -11,6 +11,7 @@ import sys import time import numpy as np +from qiskit import BasicAer from qiskit.aqua.algorithms import QAOA from qiskit.aqua.components.optimizers import COBYLA @@ -342,11 +343,16 @@ def run_op_eigens(self): # QAOA optimizer = COBYLA() min_eigen_solver = QAOA(optimizer=optimizer) - qubo_solver_class = MinimumEigenOptimizer(min_eigen_solver) + qubo_solver = MinimumEigenOptimizer(min_eigen_solver) - continuous_solver_class = CplexOptimizer() + # Note: a backend needs to be given, otherwise an error is raised in the _run method of VQE. + backend = 'statevector_simulator' + # backend = 'qasm_simulator' + min_eigen_solver.quantum_instance = BasicAer.get_backend(backend) - admm_params = ADMMParameters(qubo_solver_class=qubo_solver_class, continuous_solver_class=continuous_solver_class) + continuous_solver = CplexOptimizer() + + admm_params = ADMMParameters(qubo_solver=qubo_solver, continuous_solver=continuous_solver) solver = ADMMOptimizer(params=admm_params) solution = solver.solve(self.op) From a4e87a29e66270d6eef6b9624be1b6eff23dffe6 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Tue, 10 Mar 2020 23:15:11 +0900 Subject: [PATCH 031/323] remove repeated codes --- qiskit/optimization/problems/variables.py | 83 ++++++++--------------- 1 file changed, 28 insertions(+), 55 deletions(-) diff --git a/qiskit/optimization/problems/variables.py b/qiskit/optimization/problems/variables.py index 6f22195dab..74b1c8157f 100644 --- a/qiskit/optimization/problems/variables.py +++ b/qiskit/optimization/problems/variables.py @@ -14,10 +14,11 @@ import copy from collections.abc import Sequence +from typing import Callable, Union, List, Any from qiskit.optimization import infinity from qiskit.optimization.utils.base import BaseInterface -from qiskit.optimization.utils.helpers import init_list_args, listify, NameIndex +from qiskit.optimization.utils.helpers import init_list_args, NameIndex from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError CPX_CONTINUOUS = 'C' @@ -360,6 +361,24 @@ def _delete(i): _delete(i) self._index.build(self._names) + def _setter(self, setfunc: Callable[[Union[int, str], Any], None], *args): + # check for all elements in args whether they are types + if len(args) == 1 and all(isinstance(el, Sequence) and len(el) == 2 for el in args[0]): + for el in args[0]: + setfunc(*el) + elif len(args) == 2: + setfunc(*args) + else: + raise QiskitOptimizationError("Invalid arguments to set_lower_bounds: {}".format(args)) + + def _getter(self, lst: List[Any], *args): + if len(args) == 0: + return copy.deepcopy(lst) + keys = self._index.convert(*args) + if isinstance(keys, int): + return lst[keys] + return [lst[k] for k in keys] + def set_lower_bounds(self, *args): """Sets the lower bound for a variable or set of variables. @@ -392,14 +411,7 @@ def set_lower_bounds(self, *args): def _set(i, v): self._lb[self._index.convert(i)] = v - # check for all elements in args whether they are types - if len(args) == 1 and all(isinstance(el, Sequence) and len(el) == 2 for el in args[0]): - for el in args[0]: - _set(*el) - elif len(args) == 2: - _set(*args) - else: - raise QiskitOptimizationError("Invalid arguments to set_lower_bounds: %s".format(args)) + self._setter(_set, *args) def set_upper_bounds(self, *args): """Sets the upper bound for a variable or set of variables. @@ -431,14 +443,7 @@ def set_upper_bounds(self, *args): def _set(i, v): self._ub[self._index.convert(i)] = v - # check for all elements in args whether they are types - if len(args) == 1 and all(isinstance(el, Sequence) and len(el) == 2 for el in args[0]): - for el in args[0]: - _set(*el) - elif len(args) == 2: - _set(*args) - else: - raise QiskitOptimizationError("Invalid arguments to set_upper_bounds: %s".format(args)) + self._setter(_set, *args) def set_names(self, *args): """Sets the name of a variable or set of variables. @@ -470,13 +475,7 @@ def set_names(self, *args): def _set(i, v): self._names[self._index.convert(i)] = v - if len(args) == 1 and all(isinstance(el, Sequence) and len(el) == 2 for el in args[0]): - for el in args[0]: - _set(*el) - elif len(args) == 2: - _set(*args) - else: - raise QiskitOptimizationError("Wrong number of arguments.") + self._setter(_set, *args) self._index.build(self._names) def set_types(self, *args): @@ -520,13 +519,7 @@ def _set(i, v): "Second argument must be a string, as per VarTypes constants.") self._types[self._index.convert(i)] = v - if len(args) == 1 and all(isinstance(el, Sequence) and len(el) == 2 for el in args[0]): - for el in args[0]: - _set(*el) - elif len(args) == 2: - _set(*args) - else: - raise QiskitOptimizationError("Wrong number of arguments.") + self._setter(_set, *args) def get_lower_bounds(self, *args): """Returns the lower bounds on variables from the problem. @@ -559,12 +552,7 @@ def get_lower_bounds(self, *args): >>> op.variables.get_lower_bounds() [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] """ - if len(args) == 0: - return copy.deepcopy(self._lb) - keys = self._index.convert(*args) - if isinstance(keys, int): - return self._lb[keys] - return [self._lb[k] for k in keys] + return self._getter(self._lb, *args) def get_upper_bounds(self, *args): """Returns the upper bounds on variables from the problem. @@ -603,12 +591,7 @@ def get_upper_bounds(self, *args): >>> op.variables.get_upper_bounds() [1.0, 2.5, 4.0, 5.5, 7.0, 8.5, 10.0, 11.5, 13.0, 14.5] """ - if len(args) == 0: - return copy.deepcopy(self._ub) - keys = self._index.convert(*args) - if isinstance(keys, int): - return self._ub[keys] - return [self._ub[k] for k in keys] + return self._getter(self._ub, *args) def get_names(self, *args): """Returns the names of variables from the problem. @@ -638,12 +621,7 @@ def get_names(self, *args): >>> op.variables.get_names() ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9'] """ - if len(args) == 0: - return copy.deepcopy(self._names) - keys = self._index.convert(*args) - if isinstance(keys, int): - return self._names[keys] - return [self._names[k] for k in keys] + return self._getter(self._names, *args) def get_types(self, *args): """Returns the types of variables from the problem. @@ -677,12 +655,7 @@ def get_types(self, *args): >>> op.variables.get_types() ['C', 'I', 'B', 'S', 'N'] """ - if len(args) == 0: - return copy.deepcopy(self._types) - keys = self._index.convert(*args) - if isinstance(keys, int): - return self._types[keys] - return [self._types[k] for k in keys] + return self._getter(self._types, *args) def get_cols(self, *args): raise QiskitOptimizationError("Please use LinearConstraintInterface instead.") From e49f905ecefe7e0929bdd5df1dfddb1a632e3b90 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Tue, 10 Mar 2020 15:40:12 +0100 Subject: [PATCH 032/323] update recursive optimization --- .../algorithms/minimum_eigen_optimizer.py | 3 +-- .../recursive_minimum_eigen_optimizer.py | 23 +++++++++++++++++-- qiskit/optimization/problems/objective.py | 3 ++- .../utils/eigenvector_to_solutions.py | 17 +++++++------- .../test_recursive_optimization.py | 6 ++--- 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index b50c4fc27b..8d2bdd8400 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -150,8 +150,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: eigen_results = self._min_eigen_solver.compute_minimum_eigenvalue(operator) # analyze results - # TODO: handle min_probability depending on backend or some other property - results = eigenvector_to_solutions(eigen_results.eigenstate, operator, min_probability=0) + results = eigenvector_to_solutions(eigen_results.eigenstate, operator) results = [(res[0], problem_.objective.get_sense() * (res[1] + offset), res[2]) for res in results] results.sort(key=lambda x: problem_.objective.get_sense() * x[1]) diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 1face47a63..b17755c434 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -110,9 +110,28 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: xi = problem_.variables.get_names(i) xj = problem_.variables.get_names(j) if correlations[i, j] > 0: + # set xi = xj problem_ = problem_.substitute_variables(variables=SparseTriple([i], [j], [1])) replacements[xi] = (xj, 1) else: + # set xi = 1 - xj, this is done in two steps: + # 1. set xi = 1 + xi + # 2. set xi = -xj + + # 1a. get additional offset + offset = problem_.objective.get_offset() + offset += problem_.objective.get_quadratic_coefficients(i, i) / 2 + offset += problem_.objective.get_linear(i) + problem_.objective.set_offset(offset) + + # 1b. get additional linear part + for k in range(problem_.variables.get_num()): + coeff = problem_.objective.get_quadratic_coefficients(i, k) + if np.abs(coeff) > 1e-10: + coeff += problem_.objective.get_linear(k) + problem_.objective.set_linear(k, coeff) + + # 2. replace xi by -xj problem_ = problem_.substitute_variables(variables=SparseTriple([i], [j], [-1])) replacements[xi] = (xj, -1) @@ -146,7 +165,7 @@ def find_value(x, replacements, var_values): # construct result x = [var_values[name] for name in problem_ref.variables.get_names()] - fval = 0 # TODO: problem.objective.evaluate(x) + fval = result.fval results = OptimizationResult(x, fval, (replacements, int_to_bin_converter)) results = int_to_bin_converter.decode(results) return results @@ -155,7 +174,7 @@ def _construct_correlations(self, states, probs): n = len(states[0]) correlations = np.zeros((n, n)) for k, p in enumerate(probs): - b = states[k][::-1] + b = states[k] for i in range(n): for j in range(i): if b[i] == b[j]: diff --git a/qiskit/optimization/problems/objective.py b/qiskit/optimization/problems/objective.py index 863ea15f5d..7cd1fe5017 100755 --- a/qiskit/optimization/problems/objective.py +++ b/qiskit/optimization/problems/objective.py @@ -19,6 +19,7 @@ from qiskit.optimization.utils import BaseInterface, QiskitOptimizationError from qiskit.optimization.utils.helpers import listify, convert from cplex import SparsePair +from cplex.exceptions import CplexSolverError CPX_MAX = -1 CPX_MIN = 1 @@ -107,7 +108,7 @@ def _set(i, v): self._linear.pop(i) else: self._linear[convert(i, self._varsgetindexfunc)] = v - except: + except CplexSolverError: raise QiskitOptimizationError( "Value of a coefficient needs to allow for addition of a float!") diff --git a/qiskit/optimization/utils/eigenvector_to_solutions.py b/qiskit/optimization/utils/eigenvector_to_solutions.py index d4424fb0af..65163366ba 100644 --- a/qiskit/optimization/utils/eigenvector_to_solutions.py +++ b/qiskit/optimization/utils/eigenvector_to_solutions.py @@ -50,11 +50,11 @@ def eigenvector_to_solutions(eigenvector: Union[dict, numpy.ndarray], # iterate over all samples for bitstr, count in eigenvector.items(): sampling_probability = count / all_counts - # add the bitstring, if the sampling probability exceeds the threshold - if sampling_probability >= min_probability: - value = eval_operator_at_bitstring(operator, bitstr) - solutions += [(bitstr, value, sampling_probability)] + if sampling_probability > 0: + if sampling_probability >= min_probability: + value = eval_operator_at_bitstring(operator, bitstr) + solutions += [(bitstr, value, sampling_probability)] elif isinstance(eigenvector, numpy.ndarray): num_qubits = int(numpy.log2(eigenvector.size)) @@ -64,10 +64,11 @@ def eigenvector_to_solutions(eigenvector: Union[dict, numpy.ndarray], for i, sampling_probability in enumerate(probabilities): # add the i-th state if the sampling probability exceeds the threshold - if sampling_probability >= min_probability: - bitstr = '{:b}'.format(i).rjust(num_qubits, '0') - value = eval_operator_at_bitstring(operator, bitstr) - solutions += [(bitstr, value, sampling_probability)] + if sampling_probability > 0: + if sampling_probability >= min_probability: + bitstr = '{:b}'.format(i).rjust(num_qubits, '0')[::-1] + value = eval_operator_at_bitstring(operator, bitstr) + solutions += [(bitstr, value, sampling_probability)] else: raise TypeError('Unsupported format of eigenvector. Provide a dict or numpy.ndarray.') diff --git a/test/optimization/test_recursive_optimization.py b/test/optimization/test_recursive_optimization.py index 116ad03270..e401e50521 100755 --- a/test/optimization/test_recursive_optimization.py +++ b/test/optimization/test_recursive_optimization.py @@ -51,8 +51,8 @@ def setUp(self): @data( ('exact', None, 'op_ip1.lp'), - # ('qaoa', 'statevector_simulator', 'op_ip1.lp'), - # ('qaoa', 'qasm_simulator', 'op_ip1.lp') + ('qaoa', 'statevector_simulator', 'op_ip1.lp'), + ('qaoa', 'qasm_simulator', 'op_ip1.lp') ) def test_recursive_min_eigen_optimizer(self, config): """ Min Eigen Optimizer Test """ @@ -63,7 +63,7 @@ def test_recursive_min_eigen_optimizer(self, config): # get minimum eigen solver min_eigen_solver = self.min_eigen_solvers[min_eigen_solver_name] if backend: - min_eigen_solver._quantum_instance = BasicAer.get_backend(backend) + min_eigen_solver.quantum_instance = BasicAer.get_backend(backend) min_eigen_optimizer = MinimumEigenOptimizer(min_eigen_solver) # construct minimum eigen optimizer recursive_min_eigen_optimizer = RecursiveMinimumEigenOptimizer(min_eigen_optimizer) From 5193bcb9e53ff2af43727bf623f61ddded6468ce Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Tue, 10 Mar 2020 16:54:50 +0100 Subject: [PATCH 033/323] Update test_recursive_optimization.py --- test/optimization/test_recursive_optimization.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/optimization/test_recursive_optimization.py b/test/optimization/test_recursive_optimization.py index e401e50521..6cff5f16b0 100755 --- a/test/optimization/test_recursive_optimization.py +++ b/test/optimization/test_recursive_optimization.py @@ -64,9 +64,13 @@ def test_recursive_min_eigen_optimizer(self, config): min_eigen_solver = self.min_eigen_solvers[min_eigen_solver_name] if backend: min_eigen_solver.quantum_instance = BasicAer.get_backend(backend) + if backend == 'qasm_simulator': + min_eigen_solver.quantum_instance.run_config.shots = 10000 + min_eigen_optimizer = MinimumEigenOptimizer(min_eigen_solver) # construct minimum eigen optimizer - recursive_min_eigen_optimizer = RecursiveMinimumEigenOptimizer(min_eigen_optimizer) + recursive_min_eigen_optimizer = RecursiveMinimumEigenOptimizer(min_eigen_optimizer, + min_num_vars=3) # load optimization problem problem = OptimizationProblem() From 629336c992c6af021f74be81dd796fea1a8feb4c Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Tue, 10 Mar 2020 19:15:39 +0100 Subject: [PATCH 034/323] update optimization unit tests --- test/optimization/test_qaoa.py | 11 ++++------- test/optimization/test_recursive_optimization.py | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/test/optimization/test_qaoa.py b/test/optimization/test_qaoa.py index 0cefb7f383..19882816c5 100755 --- a/test/optimization/test_qaoa.py +++ b/test/optimization/test_qaoa.py @@ -76,15 +76,12 @@ def test_qaoa(self, w, prob, m, solutions): quantum_instance = QuantumInstance(backend, seed_simulator=seed, seed_transpiler=seed) result = qaoa.run(quantum_instance) - x = sample_most_likely(result['eigvecs'][0]) + x = sample_most_likely(result.eigenstate) graph_solution = max_cut.get_graph_solution(x) - self.log.debug('energy: %s', result['energy']) - self.log.debug('time: %s', result['eval_time']) - self.log.debug('maxcut objective: %s', result['energy'] + offset) + self.log.debug('energy: %s', result.eigenvalue.real) + self.log.debug('time: %s', result.optimizer_time) + self.log.debug('maxcut objective: %s', result.eigenvalue.real + offset) self.log.debug('solution: %s', graph_solution) self.log.debug('solution objective: %s', max_cut.max_cut_value(x, w)) self.assertIn(''.join([str(int(i)) for i in graph_solution]), solutions) - -if __name__ == '__main__': - unittest.main() diff --git a/test/optimization/test_recursive_optimization.py b/test/optimization/test_recursive_optimization.py index 6cff5f16b0..430f7c2c3d 100755 --- a/test/optimization/test_recursive_optimization.py +++ b/test/optimization/test_recursive_optimization.py @@ -70,7 +70,7 @@ def test_recursive_min_eigen_optimizer(self, config): min_eigen_optimizer = MinimumEigenOptimizer(min_eigen_solver) # construct minimum eigen optimizer recursive_min_eigen_optimizer = RecursiveMinimumEigenOptimizer(min_eigen_optimizer, - min_num_vars=3) + min_num_vars=4) # load optimization problem problem = OptimizationProblem() From 5dddf22c3e1b8ac3f6cdf0b6c04cb9001bf25311 Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Mon, 9 Mar 2020 11:16:09 -0400 Subject: [PATCH 035/323] Grover Optimization Code --- qiskit/optimization/__init__.py | 1 + .../grover_optimization/__init__.py | 50 ++++ .../grover_minimum_finder.py | 215 ++++++++++++++++++ .../grover_optimization_results.py | 53 +++++ ...zation_problem_to_negative_value_oracle.py | 182 +++++++++++++++ .../grover_optimization/portfolio_util.py | 72 ++++++ .../test_grover_minimum_finder.py | 128 +++++++++++ ...zation_problem_to_negative_value_oracle.py | 147 ++++++++++++ 8 files changed, 848 insertions(+) create mode 100644 qiskit/optimization/grover_optimization/__init__.py create mode 100644 qiskit/optimization/grover_optimization/grover_minimum_finder.py create mode 100644 qiskit/optimization/grover_optimization/grover_optimization_results.py create mode 100644 qiskit/optimization/grover_optimization/optimization_problem_to_negative_value_oracle.py create mode 100644 qiskit/optimization/grover_optimization/portfolio_util.py create mode 100644 test/optimization/test_grover_minimum_finder.py create mode 100644 test/optimization/test_optimization_problem_to_negative_value_oracle.py diff --git a/qiskit/optimization/__init__.py b/qiskit/optimization/__init__.py index f94b1c5d36..f5c6ce5c59 100644 --- a/qiskit/optimization/__init__.py +++ b/qiskit/optimization/__init__.py @@ -26,6 +26,7 @@ :toctree: ising + grover_optimization """ diff --git a/qiskit/optimization/grover_optimization/__init__.py b/qiskit/optimization/grover_optimization/__init__.py new file mode 100644 index 0000000000..ca740de62d --- /dev/null +++ b/qiskit/optimization/grover_optimization/__init__.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" +Grover Optimization (:mod:`qiskit.optimization.grover_optimization`) +==================================================================== +Grover models for optimization problems + +.. currentmodule:: qiskit.optimization.grover_optimization + +Grover Models +============= + +.. autosummary:: + :toctree: ../stubs/ + :nosignatures: + + grover_minimum_finder + +Generators for QUBO Operators and Oracles +========================================= + +.. autosummary:: + :toctree: ../stubs/ + :nosignatures: + + optimization_problem_to_negative_value_oracle + +Utilities/Results Objects +========================= + +.. autosummary:: + :toctree: ../stubs/ + :nosignatures: + + grover_optimization_results + portfolio_util + +""" diff --git a/qiskit/optimization/grover_optimization/grover_minimum_finder.py b/qiskit/optimization/grover_optimization/grover_minimum_finder.py new file mode 100644 index 0000000000..db2edd6f2f --- /dev/null +++ b/qiskit/optimization/grover_optimization/grover_minimum_finder.py @@ -0,0 +1,215 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +from qiskit.optimization.grover_optimization.optimization_problem_to_negative_value_oracle import OptimizationProblemToNegativeValueOracle +from qiskit.optimization.grover_optimization.grover_optimization_results import GroverOptimizationResults +from qiskit.optimization.grover_optimization.portfolio_util import get_qubo_solutions + +from qiskit.aqua.algorithms.amplitude_amplifiers.grover import Grover +from qiskit.visualization import plot_histogram +from qiskit import Aer +import numpy as np +import random +import math + + +class GroverMinimumFinder: + + def __init__(self, num_iterations=3, backend=Aer.get_backend('statevector_simulator'), verbose=False): + """ + Initializes a GroverMinimumFinder optimizer. + :param num_iterations: The number of iterations the algorithm will search with no improvement. + :param backend: A valid backend object. Default statevector_simulator. + :param verbose: Verbose flag - prints and plots state at each iteration of GAS. + """ + self._n_iterations = num_iterations + self._verbose = verbose + self._backend = backend + + def solve(self, quadratic, linear, constant, num_output_qubits): + # Variables for tracking the optimum. + optimum_found = False + optimum_key = math.inf + optimum_value = math.inf + threshold = 0 + n_key = len(linear) + n_value = num_output_qubits + + # Variables for tracking the solutions encountered. + num_solutions = 2**n_key + keys_measured = [] + + # Variables for result object. + f = {} + operation_count = {} + iteration = 0 + + # Variables for stopping if we've hit the rotation max. + rotations = 0 + max_rotations = np.ceil(2 ** (n_key / 2)) + + # Initialize oracle helper object. + opt_prob_converter = OptimizationProblemToNegativeValueOracle(n_value, verbose=self._verbose, + backend=self._backend) + + while not optimum_found: + m = 1 + improvement_found = False + + # Iterate until we measure a negative. + loops_with_no_improvement = 0 + while not improvement_found: + # Determine the number of rotations. + loops_with_no_improvement += 1 + rotation_count = int(np.ceil(random.uniform(0, m-1))) + rotations += rotation_count + + # Get state preparation operator A and oracle O for the current threshold. + a_operator, oracle, f = opt_prob_converter.encode(linear, quadratic, constant - threshold) + + # Apply Grover's Algorithm to find values below the threshold. + if rotation_count > 0: + grover = Grover(oracle, init_state=a_operator, num_iterations=rotation_count) + circuit = grover.construct_circuit(measurement=self._backend.name() != "statevector_simulator") + else: + circuit = a_operator._circuit + + # Get the next outcome. + outcome = self.__measure(circuit, n_key, n_value, self._backend, verbose=self._verbose) + k = int(outcome[0:n_key], 2) + v = outcome[n_key:n_key + n_value] + + # Convert the binary string to integer. + int_v = self.__bin_to_int(v, n_value) + threshold + v = self.__twos_complement(int_v, n_value) + + if self._verbose: + print("Iterations:", rotation_count) + print("Outcome:", outcome) + print("Value:", v, "=", int_v) + + # If the value is an improvement, we update the iteration parameters (e.g. oracle). + if int_v < optimum_value: + optimum_key = k + optimum_value = int_v + if self._verbose: + print("Current Optimum Key:", optimum_key) + print("Current Optimum:", optimum_value) + if v.startswith("1"): + improvement_found = True + threshold = optimum_value + else: + # If we haven't found a better number after an iterative threshold, we've found the optimal value. + if loops_with_no_improvement >= self._n_iterations: + improvement_found = True + optimum_found = True + + # Using Durr-Hoyer's method, increase m. + m = int(np.ceil(min(m * 8/7, 2**(n_key / 2)))) + if self._verbose: + print("No Improvement.") + print("M:", m) + + # Check if we've already seen this value. + if k not in keys_measured: + keys_measured.append(k) + + # Stop if we've seen all the keys or hit the rotation max. + if len(keys_measured) == num_solutions or rotations >= max_rotations: + improvement_found = True + optimum_found = True + + # Track the operation count. + oc = circuit.count_ops() + operation_count[iteration] = oc + iteration += 1 + + if self._verbose: + print("Operation Count:", oc) + print("\n") + + # Get original key and value pairs. + f[-1] = constant + solutions = get_qubo_solutions(f, n_key) + + # If the constant is 0 and we didn't find a negative, the answer is likely 0. + if optimum_value >= 0 and constant == 0: + optimum_key = 0 + + return GroverOptimizationResults(optimum_key, solutions[optimum_key], operation_count, + rotations, n_key, n_value, f) + + def __measure(self, circuit, n_key, n_value, backend, shots=1024, verbose=False): + # Get probabilities from the given backend. + probs = self.__get_probs(n_key, n_value, circuit, backend, shots) + freq = sorted(probs.items(), key=lambda x: x[1], reverse=True) + + # Pick a random outcome. + freq[len(freq)-1] = (freq[len(freq)-1][0], 1 - sum([x[1] for x in freq[0:len(freq)-1]])) + idx = np.random.choice(len(freq), 1, p=[x[1] for x in freq])[0] + if verbose: + print("Frequencies:", freq) + outcomes = {} + for label in probs: + key = str(int(label[:n_key], 2)) + " -> " + str(self.__bin_to_int(label[n_key:n_key + n_value], n_value)) + outcomes[key] = probs[label] + plot_histogram(outcomes).show() + + return freq[idx][0] + + @staticmethod + def __get_probs(n_key, n_value, qc, backend, shots): + from qiskit import execute + + # Execute job and filter results. + job = execute(qc, backend=backend, shots=shots) + result = job.result() + if backend.name() == 'statevector_simulator': + state = np.round(result.get_statevector(qc), 5) + keys = [bin(i)[2::].rjust(int(np.log2(len(state))), '0')[::-1] for i in range(0, len(state))] + probs = [np.round(abs(a)*abs(a), 5) for a in state] + f_hist = dict(zip(keys, probs)) + hist = {} + for key in f_hist: + hist[key[:n_key] + key[n_key:n_key+n_value][::-1] + key[n_key+n_value:]] = f_hist[key] + else: + state = result.get_counts(qc) + hist = {} + for key in state: + hist[key[:n_key] + key[n_key:n_key+n_value][::-1] + key[n_key+n_value:]] = state[key] / shots + hist = dict(filter(lambda p: p[1] > 0, hist.items())) + + return hist + + @staticmethod + def __twos_complement(v, n_bits): + assert -2**n_bits <= v < 2**n_bits + + if v < 0: + v += 2**n_bits + bin_v = bin(v)[2:] + else: + format_string = '{0:0'+str(n_bits)+'b}' + bin_v = format_string.format(v) + + return bin_v + + @staticmethod + def __bin_to_int(v, num_value_bits): + if v.startswith("1"): + int_v = int(v, 2) - 2 ** num_value_bits + else: + int_v = int(v, 2) + + return int_v diff --git a/qiskit/optimization/grover_optimization/grover_optimization_results.py b/qiskit/optimization/grover_optimization/grover_optimization_results.py new file mode 100644 index 0000000000..ab1eff54e8 --- /dev/null +++ b/qiskit/optimization/grover_optimization/grover_optimization_results.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + + +class GroverOptimizationResults: + + def __init__(self, optimum_input, optimum_output, operation_counts, rotations, n_input_qubits, n_output_qubits, f): + self._optimum_input = optimum_input + self._optimum_output = optimum_output + self._operation_counts = operation_counts + self._rotations = rotations + self._n_input_qubits = n_input_qubits + self._n_output_qubits = n_output_qubits + self._f = f + + @property + def optimum_input(self): + return self._optimum_input + + @property + def optimum_output(self): + return self._optimum_output + + @property + def operation_counts(self): + return self._operation_counts + + @property + def rotation_count(self): + return self._rotations + + @property + def n_input_qubits(self): + return self._n_input_qubits + + @property + def n_output_qubits(self): + return self._n_output_qubits + + @property + def function(self): + return self._f diff --git a/qiskit/optimization/grover_optimization/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/grover_optimization/optimization_problem_to_negative_value_oracle.py new file mode 100644 index 0000000000..4ec901c6da --- /dev/null +++ b/qiskit/optimization/grover_optimization/optimization_problem_to_negative_value_oracle.py @@ -0,0 +1,182 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister +from qiskit.aqua.components.oracles import CustomCircuitOracle +from qiskit.aqua.components.initial_states import Custom +from qiskit.aqua.components.iqfts import Standard as IQFT +import numpy as np + + +class OptimizationProblemToNegativeValueOracle: + + def __init__(self, num_output_qubits, verbose=False, backend='statevector_simulator'): + """ A helper function that converts a quadratic function into a state preparation operator A (InitialState) and + oracle O (CustomCircuitOracle) that recognizes negative numbers. + :param num_output_qubits: The number of qubits required to represent the output. + :param verbose: Verbose flag. + :param backend: A string corresponding to a valid Qiskit backend. + """ + self._num_value = num_output_qubits + self._verbose = verbose + self._backend = backend + self._largest = 1 # If self.approximate, this value is used to approximate to and from non-integers. + + def encode(self, linear_coeff, quadratic_coeff, constant): + """ Takes the coefficients and constants of a quadratic function and returns a state preparation operator A + (InitialState.Custom) that encodes the function, and an oracle O (Oracle.CustomCircuitOracle) that + recognizes negative values. + :param linear_coeff: (n x 1 matrix) The linear coefficients of the function. + :param quadratic_coeff: (n x n matrix) The quadratic coefficients of the function.. + :param constant: (int) The constant of the function. + """ + # Get circuit requirements from input. + num_key = len(linear_coeff) + num_ancilla = max(num_key, self._num_value) - 1 + + # Get the function dictionary. + f = self.__get_function(num_key, linear_coeff, quadratic_coeff, constant) + if self._verbose: + print("f:", f, "\n") + + # Define state preparation operator A from function. + qd = QQUBODictionary(num_key, self._num_value, num_ancilla, f, backend=self._backend) + a_operator_circuit = qd.circuit + a_operator = Custom(a_operator_circuit.width(), circuit=a_operator_circuit) + + # Get registers from the A operator circuit. + reg_map = {} + for reg in a_operator_circuit.qregs: + reg_map[reg.name] = reg + key_val = reg_map["key_value"] + anc = reg_map["ancilla"] + + # Build negative value oracle O. + oracle_bit = QuantumRegister(1, "oracle") + oracle_circuit = QuantumCircuit(key_val, anc, oracle_bit) + self.__cxzxz(oracle_circuit, key_val[num_key], oracle_bit[0]) + oracle = CustomCircuitOracle(variable_register=key_val, + output_register=oracle_bit, + ancillary_register=anc, + circuit=oracle_circuit) + + return a_operator, oracle, f + + @staticmethod + def __get_function(num_assets, linear, quadratic, constant): + """Convert the problem to a dictionary format.""" + f = {-1: constant} + for i in range(num_assets): + i_ = i + f[i_] = -linear[i_] + for j in range(i): + j_ = j + f[(i_, j_)] = int(quadratic[i_, j_]) + + return f + + @staticmethod + def __cxzxz(circuit, ctrl, tgt): + circuit.cx(ctrl, tgt) + circuit.cz(ctrl, tgt) + circuit.cx(ctrl, tgt) + circuit.cz(ctrl, tgt) + + +class QDictionary: + """ + A parent class that defines a Quantum Dictionary, which encodes key-value pairs into the quantum state. See + https://arxiv.org/abs/1912.04088 for a formal definition. + """ + + def __init__(self, key_bits, value_bits, ancilla_bits, f, prepare, backend="statevector_simulator"): + """ + Initializes a Quantum Dictionary. + :param key_bits: The number of key bits. + :param value_bits: The number of value bits. + :param ancilla_bits: The number of precision (result) bits. + :param f: The function to encode, can be a list or a lambda. + :param prepare: A method that encodes f into the quantum state. + """ + self.key_bits = key_bits + self.value_bits = value_bits + self.ancilla_bits = ancilla_bits + self.f = f + self.prepare = prepare + self.backend = backend + + def construct_circuit(self): + """ + Creates a circuit for the initialized Quantum Dictionary. + :return: A QuantumCircuit object describing the Quantum Dictionary. + """ + key_val = QuantumRegister(self.key_bits + self.value_bits, "key_value") + ancilla = QuantumRegister(self.ancilla_bits, "ancilla") + + if self.backend == "statevector_simulator": + circuit = QuantumCircuit(key_val, ancilla) + else: + measure = ClassicalRegister(self.key_bits + self.value_bits) + circuit = QuantumCircuit(key_val, ancilla, measure) + + self.prepare(self.f, circuit, key_val) + + return circuit + + +class QQUBODictionary(QDictionary): + """ + A QDictionary that creates a state preparation operator for a given QUBO problem. + """ + + def __init__(self, key_bits, value_bits, ancilla_bits, f, backend="statevector_simulator"): + QDictionary.__init__(self, key_bits, value_bits, ancilla_bits, f, self.prepare_quadratic, backend=backend) + self._circuit = None + + @property + def circuit(self): + if self._circuit is None: + self._circuit = self.construct_circuit() + return self._circuit + + def prepare_quadratic(self, d, circuit, key_val): + """ Encodes a QUBO in the proper dictionary format into the state of a given register. + :param d: (dict) The QUBO problem. The keys should be the subscripts of the coefficients (e.g. x_1 -> 1), with + the constant (if present) being represented with a key of -1 (i.e. d[-1] = constant). Quadratic coefficients + should use a tuple for the key, with the corresponding subscripts inside (e.g. 2*x_1*x_2 -> d[(1,2)]=2). + :param circuit: The QuantumCircuit to apply the operator to. + :param key_val: The QuantumRegister containing the key and value qubits. They are combined here to follow the + register format expected by algorithms like Qiskit Aqua's Grover. + """ + circuit.h(key_val) + + # Linear Coefficients + for i in range(self.value_bits): + if d.get(-1, 0) != 0: + circuit.u1(1/2 ** self.value_bits * 2 * np.pi * 2 ** i * d[-1], key_val[self.key_bits + i]) + for j in range(self.key_bits): + if d.get(j, 0) != 0: + circuit.cu1(1/2 ** self.value_bits * 2 * np.pi * 2 ** i * d[j], key_val[j], + key_val[self.key_bits + i]) + + # Quadratic Coefficients + for i in range(self.value_bits): + for k, v in d.items(): + if isinstance(k, tuple): + circuit.mcu1(1/2 ** self.value_bits * 2 * np.pi * 2 ** i * v, [key_val[k[0]], key_val[k[1]]], + key_val[self.key_bits + i]) + + iqft = IQFT(self.value_bits) + value = [key_val[v] for v in range(self.key_bits, self.key_bits + self.value_bits)] + iqft.construct_circuit(qubits=value, circuit=circuit) diff --git a/qiskit/optimization/grover_optimization/portfolio_util.py b/qiskit/optimization/grover_optimization/portfolio_util.py new file mode 100644 index 0000000000..cdcf10bdb6 --- /dev/null +++ b/qiskit/optimization/grover_optimization/portfolio_util.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +from qiskit.finance.data_providers import RandomDataProvider +import datetime + + +def get_mu_sigma(num_assets): + # Generate expected return and covariance matrix from (random) time-series + stocks = [("TICKER%s" % i) for i in range(num_assets)] + data = RandomDataProvider(tickers=stocks, start=datetime.datetime(2020, 1, 1), end=datetime.datetime(2020, 1, 30)) + data.run() + mu = data.get_period_return_mean_vector() + sigma = data.get_period_return_covariance_matrix() + + return mu, sigma + + +def get_qubo_solutions(f, n_key, print_solutions=False): + # Determine constant. + constant = 0 + if -1 in f: + constant = f[-1] + format_string = '{0:0'+str(n_key)+'b}' + + # Iterate through every key combination. + if print_solutions: + print("QUBO Solutions:") + print("==========================") + solutions = {} + for i in range(2**n_key): + solution = constant + + # Convert int to a list of binary variables. + bin_key = format_string.format(i) + bin_list = [int(bin_key[j]) for j in range(len(bin_key))] + + # Handle the linear terms. + for k in range(len(bin_key)): + if bin_list[k] == 1 and k in f: + solution += f[k] + + # Handle the quadratic terms. + for p in range(len(bin_key)): + for q in range(len(bin_key)): + if (p, q) in f and p != q and bin_list[p] == 1 and bin_list[q] == 1: + solution += f[(p, q)] + + # Print row. + if print_solutions: + spacer = "" if i >= 10 else " " + value_spacer = " " if solution < 0 else " " + print(spacer + str(i), "=", bin_key, "->" + value_spacer + str(round(solution, 4))) + + # Record solution. + solutions[i] = str(round(solution, 4)) + + if print_solutions: + print() + + return solutions diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py new file mode 100644 index 0000000000..1460b9bdd0 --- /dev/null +++ b/test/optimization/test_grover_minimum_finder.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" Test Grover Minimum Finder """ + +from test.optimization import QiskitOptimizationTestCase +from qiskit.optimization.grover_optimization.grover_minimum_finder import GroverMinimumFinder +from qiskit.optimization.grover_optimization.portfolio_util import get_qubo_solutions +import numpy as np + +# Flag for verbosity in all units under test. +verbose = False + + +class TestGroverMinimumFinder(QiskitOptimizationTestCase): + + def validate_results(self, results): + # Get measured values. + n_key = results.n_input_qubits + op_key = results.optimum_input + op_value = results.optimum_output + rot = results.rotation_count + f = results.function + print("Optimum Key:", op_key, "Optimum Value:", op_value, "Rotations:", rot, "\n") + + # Get expected value. + solutions = get_qubo_solutions(f, n_key, print_solutions=True) + min_key = min(solutions, key=lambda key: int(solutions[key])) + min_value = solutions[min_key] + max_rotations = np.ceil(2 ** (n_key / 2)) + + # Validate results. + self.assertTrue(min_key == op_key or max_rotations == rot) + self.assertTrue(min_value == op_value or max_rotations == rot) + + def test_qubo_gas_int_zero(self): + # Circuit parameters. + num_value = 4 + + # Input. + mu = np.array([0, 0]) + sigma = np.array([[0, 0], + [0, 0]]) + constant = 0 + + # Will not find a negative, should return 0. + gmf = GroverMinimumFinder(num_iterations=1, verbose=verbose) + results = gmf.solve(sigma, mu, constant, num_value) + self.assertEqual(results.optimum_input, 0) + self.assertEqual(int(results.optimum_output), 0) + + def test_qubo_gas_int_simple(self): + # Circuit parameters. + num_value = 4 + + # Input. + mu = np.array([1, -2]) + sigma = np.array([[2, 0], + [0, 2]]) + q = 0.5 + sigma = sigma.dot(q) + + # Get the optimum key and value. + gmf = GroverMinimumFinder(num_iterations=6, verbose=verbose) + results = gmf.solve(sigma, mu, 0, num_value) + self.validate_results(results) + + def test_qubo_gas_int_simple_pos_constant(self): + # Circuit parameters. + num_value = 4 + + # Input. + mu = np.array([1, -2]) + sigma = np.array([[2, 0], + [0, 2]]) + q = 0.5 + sigma = sigma.dot(q) + constant = 2 + + # Get the optimum key and value. + gmf = GroverMinimumFinder(num_iterations=6, verbose=verbose) + results = gmf.solve(sigma, mu, constant, num_value) + self.validate_results(results) + + def test_qubo_gas_int_simple_neg_constant(self): + # Circuit parameters. + num_value = 4 + + # Input. + mu = np.array([1, -2]) + sigma = np.array([[2, 0], + [0, 2]]) + q = 0.5 + sigma = sigma.dot(q) + constant = -2 + + # Get the optimum key and value. + gmf = GroverMinimumFinder(num_iterations=6, verbose=verbose) + results = gmf.solve(sigma, mu, constant, num_value) + self.validate_results(results) + + def test_qubo_gas_int_paper_example(self): + # Circuit parameters. + num_value = 5 + + # Input. + mu = np.array([1, -2, 3]) + sigma = np.array([[2, 0, -4], + [0, 4, -2], + [-4, -2, 10]]) + q = 0.5 + sigma = sigma.dot(q) + + # Get the optimum key and value. + gmf = GroverMinimumFinder(num_iterations=8, verbose=verbose) + results = gmf.solve(sigma, mu, 0, num_value) + self.validate_results(results) diff --git a/test/optimization/test_optimization_problem_to_negative_value_oracle.py b/test/optimization/test_optimization_problem_to_negative_value_oracle.py new file mode 100644 index 0000000000..52dab9d6b5 --- /dev/null +++ b/test/optimization/test_optimization_problem_to_negative_value_oracle.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" Test Optimization Problem to Negative Value Oracle """ + +from test.optimization import QiskitOptimizationTestCase +from qiskit.optimization.grover_optimization.optimization_problem_to_negative_value_oracle import OptimizationProblemToNegativeValueOracle +from qiskit.optimization.grover_optimization.portfolio_util import get_qubo_solutions +from qiskit import QuantumCircuit, Aer, execute +import numpy as np + + +class TestOptimizationProblemToNegativeValueOracle(QiskitOptimizationTestCase): + + def _validate_function(self, f, linear, quadratic, constant): + for key in f: + if isinstance(key, int) and key >= 0: + self.assertEqual(-1*linear[key], f[key]) + elif isinstance(key, tuple): + self.assertEqual(quadratic[key[0]][key[1]], f[key]) + else: + self.assertEqual(constant, f[key]) + + def _validate_operator(self, f, n_key, n_value, operator): + # Get expected results. + solutions = get_qubo_solutions(f, n_key, print_solutions=True) + + # Run the state preparation operator A and observe results. + circuit = operator._circuit + qc = QuantumCircuit() + circuit + hist = self._measure(qc, n_key, n_value) + + # Validate operator A. + for label in hist: + key = int(label[:n_key], 2) + value = self._bin_to_int(label[n_key:n_key + n_value], n_value) + self.assertEqual(int(solutions[key]), value) + + @staticmethod + def _measure(qc, n_key, n_value): + backend = Aer.get_backend('statevector_simulator') + job = execute(qc, backend=backend, shots=1) + result = job.result() + state = np.round(result.get_statevector(qc), 5) + keys = [bin(i)[2::].rjust(int(np.log2(len(state))), '0')[::-1] for i in range(0, len(state))] + probs = [np.round(abs(a)*abs(a), 5) for a in state] + f_hist = dict(zip(keys, probs)) + hist = {} + for key in f_hist: + new_key = key[:n_key] + key[n_key:n_key+n_value][::-1] + key[n_key+n_value:] + hist[new_key] = f_hist[key] + hist = dict(filter(lambda p: p[1] > 0, hist.items())) + return hist + + @staticmethod + def _bin_to_int(v, num_value_bits): + if v.startswith("1"): + int_v = int(v, 2) - 2 ** num_value_bits + else: + int_v = int(v, 2) + + return int_v + + def test_optnvo_2_key(self): + # Circuit parameters. + num_value = 4 + + # Input. + linear = np.array([1, -2]) + quadratic = np.array([[1, 0], + [0, 1]]) + constant = 0 + + # Convert to dictionary format with operator/oracle. + converter = OptimizationProblemToNegativeValueOracle(num_value) + a_operator, _, f = converter.encode(linear, quadratic, constant) + + self._validate_function(f, linear, quadratic, constant) + self._validate_operator(f, len(linear), num_value, a_operator) + + def test_optnvo_2_key_w_constant(self): + # Circuit parameters. + num_value = 4 + + # Input. + linear = np.array([1, -2]) + quadratic = np.array([[1, 0], + [0, 1]]) + constant = 1 + + # Convert to dictionary format with operator/oracle. + converter = OptimizationProblemToNegativeValueOracle(num_value) + a_operator, _, f = converter.encode(linear, quadratic, constant) + + self._validate_function(f, linear, quadratic, constant) + self._validate_operator(f, len(linear), num_value, a_operator) + + def test_optnvo_4_key_all_negative(self): + # Circuit parameters. + num_value = 5 + + # Input. + linear = np.array([1, 1, 1, 1]) + quadratic = np.array([[-1, -1, -1, -1], + [-1, -1, -1, -1], + [-1, -1, -1, -1], + [-1, -1, -1, -1]]) + constant = -1 + + # Convert to dictionary format with operator/oracle. + converter = OptimizationProblemToNegativeValueOracle(num_value) + a_operator, _, f = converter.encode(linear, quadratic, constant) + + self._validate_function(f, linear, quadratic, constant) + self._validate_operator(f, len(linear), num_value, a_operator) + + def test_optnvo_6_key(self): + # Circuit parameters. + num_value = 4 + + # Input. + linear = np.array([1, -2, -1, 0, 1, 2]) + quadratic = np.array([[1, 0, 0, -1, 0, 0], + [0, 1, 0, 0, 0, -2], + [0, 0, 1, 0, 0, 0], + [-1, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 1, 0], + [0, -2, 0, 0, 0, 1]]) + constant = 0 + + # Convert to dictionary format with operator/oracle. + converter = OptimizationProblemToNegativeValueOracle(num_value) + a_operator, _, f = converter.encode(linear, quadratic, constant) + + self._validate_function(f, linear, quadratic, constant) + self._validate_operator(f, len(linear), num_value, a_operator) From a24986e607759b8022817388964fede1a0f2fbe6 Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Mon, 9 Mar 2020 11:56:41 -0400 Subject: [PATCH 036/323] Additional Documentation for Grover Optimization --- .../grover_optimization/grover_minimum_finder.py | 15 ++++++++++++--- .../grover_optimization_results.py | 10 ++++++++++ ...timization_problem_to_negative_value_oracle.py | 3 ++- .../grover_optimization/portfolio_util.py | 12 +++++++++++- test/optimization/test_grover_minimum_finder.py | 7 ++++++- ...timization_problem_to_negative_value_oracle.py | 4 ++++ 6 files changed, 45 insertions(+), 6 deletions(-) diff --git a/qiskit/optimization/grover_optimization/grover_minimum_finder.py b/qiskit/optimization/grover_optimization/grover_minimum_finder.py index db2edd6f2f..e092cb02ea 100644 --- a/qiskit/optimization/grover_optimization/grover_minimum_finder.py +++ b/qiskit/optimization/grover_optimization/grover_minimum_finder.py @@ -27,8 +27,7 @@ class GroverMinimumFinder: def __init__(self, num_iterations=3, backend=Aer.get_backend('statevector_simulator'), verbose=False): - """ - Initializes a GroverMinimumFinder optimizer. + """ Initializes a GroverMinimumFinder optimizer. :param num_iterations: The number of iterations the algorithm will search with no improvement. :param backend: A valid backend object. Default statevector_simulator. :param verbose: Verbose flag - prints and plots state at each iteration of GAS. @@ -38,6 +37,13 @@ def __init__(self, num_iterations=3, backend=Aer.get_backend('statevector_simula self._backend = backend def solve(self, quadratic, linear, constant, num_output_qubits): + """ Given the coefficients and constants of a QUBO function, find the minimum output value. + :param quadratic: (nxn matrix) The quadratic coefficients of the QUBO. + :param linear: (nx1 matrix) The linear coefficients of the QUBO. + :param constant: (int) The constant of the QUBO. + :param num_output_qubits: The number of qubits used to represent the output values. + :return: A GroverOptimizationResults object containing information about the run, including the solution. + """ # Variables for tracking the optimum. optimum_found = False optimum_key = math.inf @@ -151,7 +157,7 @@ def solve(self, quadratic, linear, constant, num_output_qubits): rotations, n_key, n_value, f) def __measure(self, circuit, n_key, n_value, backend, shots=1024, verbose=False): - # Get probabilities from the given backend. + """Get probabilities from the given backend, and picks a random outcome.""" probs = self.__get_probs(n_key, n_value, circuit, backend, shots) freq = sorted(probs.items(), key=lambda x: x[1], reverse=True) @@ -170,6 +176,7 @@ def __measure(self, circuit, n_key, n_value, backend, shots=1024, verbose=False) @staticmethod def __get_probs(n_key, n_value, qc, backend, shots): + """Gets probabilities from a given backend.""" from qiskit import execute # Execute job and filter results. @@ -194,6 +201,7 @@ def __get_probs(n_key, n_value, qc, backend, shots): @staticmethod def __twos_complement(v, n_bits): + """Converts an integer into a binary string of n bits using two's complement.""" assert -2**n_bits <= v < 2**n_bits if v < 0: @@ -207,6 +215,7 @@ def __twos_complement(v, n_bits): @staticmethod def __bin_to_int(v, num_value_bits): + """Converts a binary string of n bits using two's complement to an integer.""" if v.startswith("1"): int_v = int(v, 2) - 2 ** num_value_bits else: diff --git a/qiskit/optimization/grover_optimization/grover_optimization_results.py b/qiskit/optimization/grover_optimization/grover_optimization_results.py index ab1eff54e8..e801687442 100644 --- a/qiskit/optimization/grover_optimization/grover_optimization_results.py +++ b/qiskit/optimization/grover_optimization/grover_optimization_results.py @@ -16,6 +16,16 @@ class GroverOptimizationResults: def __init__(self, optimum_input, optimum_output, operation_counts, rotations, n_input_qubits, n_output_qubits, f): + """ Stores the result of a Grover optimization problem. + :param optimum_input: (int) The input that corresponds to the optimum output. + :param optimum_output: (int) The optimum output value. + :param operation_counts: (dict) The counts of each operation performed per iteration. + :param rotations: The total number of Grover rotations performed. + :param n_input_qubits: The number of qubits used to represent the input. + :param n_output_qubits: The number of qubits used to represent the output. + :param f: A dictionary representation of the function, where the keys correspond to a variable, and the + values are the corresponding coefficients. + """ self._optimum_input = optimum_input self._optimum_output = optimum_output self._operation_counts = operation_counts diff --git a/qiskit/optimization/grover_optimization/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/grover_optimization/optimization_problem_to_negative_value_oracle.py index 4ec901c6da..25a759bfe5 100644 --- a/qiskit/optimization/grover_optimization/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/grover_optimization/optimization_problem_to_negative_value_oracle.py @@ -38,7 +38,7 @@ def encode(self, linear_coeff, quadratic_coeff, constant): (InitialState.Custom) that encodes the function, and an oracle O (Oracle.CustomCircuitOracle) that recognizes negative values. :param linear_coeff: (n x 1 matrix) The linear coefficients of the function. - :param quadratic_coeff: (n x n matrix) The quadratic coefficients of the function.. + :param quadratic_coeff: (n x n matrix) The quadratic coefficients of the function. :param constant: (int) The constant of the function. """ # Get circuit requirements from input. @@ -88,6 +88,7 @@ def __get_function(num_assets, linear, quadratic, constant): @staticmethod def __cxzxz(circuit, ctrl, tgt): + """Multiplies by -1.""" circuit.cx(ctrl, tgt) circuit.cz(ctrl, tgt) circuit.cx(ctrl, tgt) diff --git a/qiskit/optimization/grover_optimization/portfolio_util.py b/qiskit/optimization/grover_optimization/portfolio_util.py index cdcf10bdb6..7698d504cf 100644 --- a/qiskit/optimization/grover_optimization/portfolio_util.py +++ b/qiskit/optimization/grover_optimization/portfolio_util.py @@ -17,7 +17,10 @@ def get_mu_sigma(num_assets): - # Generate expected return and covariance matrix from (random) time-series + """ Generate expected return and covariance matrix from (random) time-series. + :param num_assets: The number of assets to generate values for. + :returns mu (linear coefficients, nx1 matrix) and sigma (quadratic coefficients, nxn matrix) + """ stocks = [("TICKER%s" % i) for i in range(num_assets)] data = RandomDataProvider(tickers=stocks, start=datetime.datetime(2020, 1, 1), end=datetime.datetime(2020, 1, 30)) data.run() @@ -28,6 +31,13 @@ def get_mu_sigma(num_assets): def get_qubo_solutions(f, n_key, print_solutions=False): + """ Calculates all of the outputs of a QUBO function representable by n key qubits. + :param f: A dictionary representation of the function, where the keys correspond to a variable, and the + values are the corresponding coefficients. + :param n_key: The number of key qubits. + :param print_solutions: If true, the solutions will be formatted and printed. + :return: A dictionary of the inputs (keys) and outputs (values) of the QUBO function. + """ # Determine constant. constant = 0 if -1 in f: diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index 1460b9bdd0..12c52b41c1 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -45,6 +45,7 @@ def validate_results(self, results): self.assertTrue(min_value == op_value or max_rotations == rot) def test_qubo_gas_int_zero(self): + """ Test for when the answer is zero. """ # Circuit parameters. num_value = 4 @@ -61,6 +62,7 @@ def test_qubo_gas_int_zero(self): self.assertEqual(int(results.optimum_output), 0) def test_qubo_gas_int_simple(self): + """ Test for simple case, with 2 linear coeffs and no quadratic coeffs or constants. """ # Circuit parameters. num_value = 4 @@ -77,6 +79,7 @@ def test_qubo_gas_int_simple(self): self.validate_results(results) def test_qubo_gas_int_simple_pos_constant(self): + """ Test for a positive constant. """ # Circuit parameters. num_value = 4 @@ -94,6 +97,7 @@ def test_qubo_gas_int_simple_pos_constant(self): self.validate_results(results) def test_qubo_gas_int_simple_neg_constant(self): + """ Test for a negative constant. """ # Circuit parameters. num_value = 4 @@ -111,6 +115,7 @@ def test_qubo_gas_int_simple_neg_constant(self): self.validate_results(results) def test_qubo_gas_int_paper_example(self): + """ Test the example from https://arxiv.org/abs/1912.04088. """ # Circuit parameters. num_value = 5 @@ -123,6 +128,6 @@ def test_qubo_gas_int_paper_example(self): sigma = sigma.dot(q) # Get the optimum key and value. - gmf = GroverMinimumFinder(num_iterations=8, verbose=verbose) + gmf = GroverMinimumFinder(num_iterations=10, verbose=verbose) results = gmf.solve(sigma, mu, 0, num_value) self.validate_results(results) diff --git a/test/optimization/test_optimization_problem_to_negative_value_oracle.py b/test/optimization/test_optimization_problem_to_negative_value_oracle.py index 52dab9d6b5..9a61e4c2be 100644 --- a/test/optimization/test_optimization_problem_to_negative_value_oracle.py +++ b/test/optimization/test_optimization_problem_to_negative_value_oracle.py @@ -73,6 +73,7 @@ def _bin_to_int(v, num_value_bits): return int_v def test_optnvo_2_key(self): + """ Test with 2 linear coefficients, no quadratic or constant. """ # Circuit parameters. num_value = 4 @@ -90,6 +91,7 @@ def test_optnvo_2_key(self): self._validate_operator(f, len(linear), num_value, a_operator) def test_optnvo_2_key_w_constant(self): + """ Test with 2 linear coefficients, no quadratic, simple constant. """ # Circuit parameters. num_value = 4 @@ -107,6 +109,7 @@ def test_optnvo_2_key_w_constant(self): self._validate_operator(f, len(linear), num_value, a_operator) def test_optnvo_4_key_all_negative(self): + """ Test with 4 negative linear coefficients, negative quadratic coeffs, and a negative constant. """ # Circuit parameters. num_value = 5 @@ -126,6 +129,7 @@ def test_optnvo_4_key_all_negative(self): self._validate_operator(f, len(linear), num_value, a_operator) def test_optnvo_6_key(self): + """ Test with 6 linear coefficients, negative quadratics, no constant. """ # Circuit parameters. num_value = 4 From 9b514243844ae69f5b85e23c54b8c1eb91d5280a Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Mon, 9 Mar 2020 13:55:08 -0400 Subject: [PATCH 037/323] Style Updates, Added Classical Evaluation to Meet Grover Assert --- ...ptimization_problem_to_negative_value_oracle.py | 11 ++++++++++- test/optimization/test_grover_minimum_finder.py | 14 +++++++------- ...ptimization_problem_to_negative_value_oracle.py | 10 +++++----- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/qiskit/optimization/grover_optimization/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/grover_optimization/optimization_problem_to_negative_value_oracle.py index 25a759bfe5..a4f0ad3510 100644 --- a/qiskit/optimization/grover_optimization/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/grover_optimization/optimization_problem_to_negative_value_oracle.py @@ -69,7 +69,8 @@ def encode(self, linear_coeff, quadratic_coeff, constant): oracle = CustomCircuitOracle(variable_register=key_val, output_register=oracle_bit, ancillary_register=anc, - circuit=oracle_circuit) + circuit=oracle_circuit, + evaluate_classically_callback=self.__evaluate_classically) return a_operator, oracle, f @@ -94,6 +95,14 @@ def __cxzxz(circuit, ctrl, tgt): circuit.cx(ctrl, tgt) circuit.cz(ctrl, tgt) + def __evaluate_classically(self, measurement): + """ evaluate classical """ + assignment = [(var + 1) * (int(tf) * 2 - 1) for tf, var in zip(measurement[::-1], + range(len(measurement)))] + assignment_dict = dict() + for v in assignment: + assignment_dict[v] = bool(v < 0) + return assignment_dict, assignment class QDictionary: """ diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index 12c52b41c1..d9a30b47ee 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -35,7 +35,7 @@ def validate_results(self, results): print("Optimum Key:", op_key, "Optimum Value:", op_value, "Rotations:", rot, "\n") # Get expected value. - solutions = get_qubo_solutions(f, n_key, print_solutions=True) + solutions = get_qubo_solutions(f, n_key, print_solutions=False) min_key = min(solutions, key=lambda key: int(solutions[key])) min_value = solutions[min_key] max_rotations = np.ceil(2 ** (n_key / 2)) @@ -45,7 +45,7 @@ def validate_results(self, results): self.assertTrue(min_value == op_value or max_rotations == rot) def test_qubo_gas_int_zero(self): - """ Test for when the answer is zero. """ + """ Test for when the answer is zero """ # Circuit parameters. num_value = 4 @@ -62,7 +62,7 @@ def test_qubo_gas_int_zero(self): self.assertEqual(int(results.optimum_output), 0) def test_qubo_gas_int_simple(self): - """ Test for simple case, with 2 linear coeffs and no quadratic coeffs or constants. """ + """ Test for simple case, with 2 linear coeffs and no quadratic coeffs or constants """ # Circuit parameters. num_value = 4 @@ -79,7 +79,7 @@ def test_qubo_gas_int_simple(self): self.validate_results(results) def test_qubo_gas_int_simple_pos_constant(self): - """ Test for a positive constant. """ + """ Test for a positive constant """ # Circuit parameters. num_value = 4 @@ -97,7 +97,7 @@ def test_qubo_gas_int_simple_pos_constant(self): self.validate_results(results) def test_qubo_gas_int_simple_neg_constant(self): - """ Test for a negative constant. """ + """ Test for a negative constant """ # Circuit parameters. num_value = 4 @@ -115,7 +115,7 @@ def test_qubo_gas_int_simple_neg_constant(self): self.validate_results(results) def test_qubo_gas_int_paper_example(self): - """ Test the example from https://arxiv.org/abs/1912.04088. """ + """ Test the example from https://arxiv.org/abs/1912.04088 """ # Circuit parameters. num_value = 5 @@ -128,6 +128,6 @@ def test_qubo_gas_int_paper_example(self): sigma = sigma.dot(q) # Get the optimum key and value. - gmf = GroverMinimumFinder(num_iterations=10, verbose=verbose) + gmf = GroverMinimumFinder(num_iterations=20, verbose=verbose) results = gmf.solve(sigma, mu, 0, num_value) self.validate_results(results) diff --git a/test/optimization/test_optimization_problem_to_negative_value_oracle.py b/test/optimization/test_optimization_problem_to_negative_value_oracle.py index 9a61e4c2be..9d30720bf3 100644 --- a/test/optimization/test_optimization_problem_to_negative_value_oracle.py +++ b/test/optimization/test_optimization_problem_to_negative_value_oracle.py @@ -34,7 +34,7 @@ def _validate_function(self, f, linear, quadratic, constant): def _validate_operator(self, f, n_key, n_value, operator): # Get expected results. - solutions = get_qubo_solutions(f, n_key, print_solutions=True) + solutions = get_qubo_solutions(f, n_key, print_solutions=False) # Run the state preparation operator A and observe results. circuit = operator._circuit @@ -73,7 +73,7 @@ def _bin_to_int(v, num_value_bits): return int_v def test_optnvo_2_key(self): - """ Test with 2 linear coefficients, no quadratic or constant. """ + """ Test with 2 linear coefficients, no quadratic or constant """ # Circuit parameters. num_value = 4 @@ -91,7 +91,7 @@ def test_optnvo_2_key(self): self._validate_operator(f, len(linear), num_value, a_operator) def test_optnvo_2_key_w_constant(self): - """ Test with 2 linear coefficients, no quadratic, simple constant. """ + """ Test with 2 linear coefficients, no quadratic, simple constant """ # Circuit parameters. num_value = 4 @@ -109,7 +109,7 @@ def test_optnvo_2_key_w_constant(self): self._validate_operator(f, len(linear), num_value, a_operator) def test_optnvo_4_key_all_negative(self): - """ Test with 4 negative linear coefficients, negative quadratic coeffs, and a negative constant. """ + """ Test with 4 negative linear coefficients, negative quadratic coeffs, and a negative constant """ # Circuit parameters. num_value = 5 @@ -129,7 +129,7 @@ def test_optnvo_4_key_all_negative(self): self._validate_operator(f, len(linear), num_value, a_operator) def test_optnvo_6_key(self): - """ Test with 6 linear coefficients, negative quadratics, no constant. """ + """ Test with 6 linear coefficients, negative quadratics, no constant """ # Circuit parameters. num_value = 4 From 8aa465b497b9afc31394fb733479dd217d9928ab Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Mon, 9 Mar 2020 14:40:32 -0400 Subject: [PATCH 038/323] Changes to Meet Style Constraints, Follow Optimization Stack Structure (#847) --- CHANGELOG.md | 1 + qiskit/optimization/__init__.py | 1 - qiskit/optimization/algorithms/__init__.py | 33 +++++++++++++++ .../grover_minimum_finder.py | 41 ++++++++++++------- .../__init__.py | 19 +++------ ...zation_problem_to_negative_value_oracle.py | 40 ++++++++++-------- .../portfolio_util.py | 7 ++-- qiskit/optimization/results/__init__.py | 33 +++++++++++++++ .../grover_optimization_results.py | 7 ++-- .../test_grover_minimum_finder.py | 4 +- ...zation_problem_to_negative_value_oracle.py | 9 ++-- 11 files changed, 136 insertions(+), 59 deletions(-) create mode 100644 qiskit/optimization/algorithms/__init__.py rename qiskit/optimization/{grover_optimization => algorithms}/grover_minimum_finder.py (86%) rename qiskit/optimization/{grover_optimization => converters}/__init__.py (73%) rename qiskit/optimization/{grover_optimization => converters}/optimization_problem_to_negative_value_oracle.py (85%) rename qiskit/optimization/{grover_optimization => }/portfolio_util.py (93%) create mode 100644 qiskit/optimization/results/__init__.py rename qiskit/optimization/{grover_optimization => results}/grover_optimization_results.py (91%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55233e6f9d..daadfbc5bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ Changed - Refactor Multiclass Extensions to set Estimator internally (#822) - Refactor algorithms (#831) - Moved to Terra: multi-controlled Toffoli, U1 and Pauli rotation gates (including tests) (#833) +- Add Grover Optimization to the Optimization Stack (#847) [0.6.4](https://github.com/Qiskit/qiskit-aqua/compare/0.6.3...0.6.4) - 2020-02-06 ================================================================================= diff --git a/qiskit/optimization/__init__.py b/qiskit/optimization/__init__.py index f5c6ce5c59..f94b1c5d36 100644 --- a/qiskit/optimization/__init__.py +++ b/qiskit/optimization/__init__.py @@ -26,7 +26,6 @@ :toctree: ising - grover_optimization """ diff --git a/qiskit/optimization/algorithms/__init__.py b/qiskit/optimization/algorithms/__init__.py new file mode 100644 index 0000000000..c11508df23 --- /dev/null +++ b/qiskit/optimization/algorithms/__init__.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" +Optimization Algorithms (:mod:`qiskit.optimization.algorithms`) +==================================================================== +Algorithms for optimization problems + +.. currentmodule:: qiskit.optimization.algorithms + +Algorithms +============= + +.. autosummary:: + :toctree: ../stubs/ + :nosignatures: + + grover_minimum_finder + +""" + +from .grover_minimum_finder import GroverMinimumFinder diff --git a/qiskit/optimization/grover_optimization/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py similarity index 86% rename from qiskit/optimization/grover_optimization/grover_minimum_finder.py rename to qiskit/optimization/algorithms/grover_minimum_finder.py index e092cb02ea..19b0621943 100644 --- a/qiskit/optimization/grover_optimization/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -12,9 +12,9 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -from qiskit.optimization.grover_optimization.optimization_problem_to_negative_value_oracle import OptimizationProblemToNegativeValueOracle -from qiskit.optimization.grover_optimization.grover_optimization_results import GroverOptimizationResults -from qiskit.optimization.grover_optimization.portfolio_util import get_qubo_solutions +from qiskit.optimization.converters import OptimizationProblemToNegativeValueOracle +from qiskit.optimization.results import GroverOptimizationResults +from qiskit.optimization.portfolio_util import get_qubo_solutions from qiskit.aqua.algorithms.amplitude_amplifiers.grover import Grover from qiskit.visualization import plot_histogram @@ -26,9 +26,11 @@ class GroverMinimumFinder: - def __init__(self, num_iterations=3, backend=Aer.get_backend('statevector_simulator'), verbose=False): + def __init__(self, num_iterations=3, backend=Aer.get_backend('statevector_simulator'), + verbose=False): """ Initializes a GroverMinimumFinder optimizer. - :param num_iterations: The number of iterations the algorithm will search with no improvement. + :param num_iterations: The number of iterations the algorithm will search with no + improvement. :param backend: A valid backend object. Default statevector_simulator. :param verbose: Verbose flag - prints and plots state at each iteration of GAS. """ @@ -42,7 +44,8 @@ def solve(self, quadratic, linear, constant, num_output_qubits): :param linear: (nx1 matrix) The linear coefficients of the QUBO. :param constant: (int) The constant of the QUBO. :param num_output_qubits: The number of qubits used to represent the output values. - :return: A GroverOptimizationResults object containing information about the run, including the solution. + :return: A GroverOptimizationResults object containing information about the run, + including the solution. """ # Variables for tracking the optimum. optimum_found = False @@ -66,7 +69,8 @@ def solve(self, quadratic, linear, constant, num_output_qubits): max_rotations = np.ceil(2 ** (n_key / 2)) # Initialize oracle helper object. - opt_prob_converter = OptimizationProblemToNegativeValueOracle(n_value, verbose=self._verbose, + opt_prob_converter = OptimizationProblemToNegativeValueOracle(n_value, + verbose=self._verbose, backend=self._backend) while not optimum_found: @@ -82,17 +86,20 @@ def solve(self, quadratic, linear, constant, num_output_qubits): rotations += rotation_count # Get state preparation operator A and oracle O for the current threshold. - a_operator, oracle, f = opt_prob_converter.encode(linear, quadratic, constant - threshold) + a_operator, oracle, f = opt_prob_converter.encode(linear, quadratic, + constant - threshold) # Apply Grover's Algorithm to find values below the threshold. if rotation_count > 0: grover = Grover(oracle, init_state=a_operator, num_iterations=rotation_count) - circuit = grover.construct_circuit(measurement=self._backend.name() != "statevector_simulator") + circuit = grover.construct_circuit( + measurement=self._backend.name() != "statevector_simulator") else: circuit = a_operator._circuit # Get the next outcome. - outcome = self.__measure(circuit, n_key, n_value, self._backend, verbose=self._verbose) + outcome = self.__measure(circuit, n_key, n_value, self._backend, + verbose=self._verbose) k = int(outcome[0:n_key], 2) v = outcome[n_key:n_key + n_value] @@ -116,7 +123,7 @@ def solve(self, quadratic, linear, constant, num_output_qubits): improvement_found = True threshold = optimum_value else: - # If we haven't found a better number after an iterative threshold, we've found the optimal value. + # If we haven't found a better number after n iter, we've found the optimal. if loops_with_no_improvement >= self._n_iterations: improvement_found = True optimum_found = True @@ -168,7 +175,8 @@ def __measure(self, circuit, n_key, n_value, backend, shots=1024, verbose=False) print("Frequencies:", freq) outcomes = {} for label in probs: - key = str(int(label[:n_key], 2)) + " -> " + str(self.__bin_to_int(label[n_key:n_key + n_value], n_value)) + key = str(int(label[:n_key], 2)) + " -> " +\ + str(self.__bin_to_int(label[n_key:n_key + n_value], n_value)) outcomes[key] = probs[label] plot_histogram(outcomes).show() @@ -184,17 +192,20 @@ def __get_probs(n_key, n_value, qc, backend, shots): result = job.result() if backend.name() == 'statevector_simulator': state = np.round(result.get_statevector(qc), 5) - keys = [bin(i)[2::].rjust(int(np.log2(len(state))), '0')[::-1] for i in range(0, len(state))] + keys = [bin(i)[2::].rjust(int(np.log2(len(state))), '0')[::-1] + for i in range(0, len(state))] probs = [np.round(abs(a)*abs(a), 5) for a in state] f_hist = dict(zip(keys, probs)) hist = {} for key in f_hist: - hist[key[:n_key] + key[n_key:n_key+n_value][::-1] + key[n_key+n_value:]] = f_hist[key] + new_key = key[:n_key] + key[n_key:n_key+n_value][::-1] + key[n_key+n_value:] + hist[new_key] = f_hist[key] else: state = result.get_counts(qc) hist = {} for key in state: - hist[key[:n_key] + key[n_key:n_key+n_value][::-1] + key[n_key+n_value:]] = state[key] / shots + hist[key[:n_key] + key[n_key:n_key+n_value][::-1] + key[n_key+n_value:]] = \ + state[key] / shots hist = dict(filter(lambda p: p[1] > 0, hist.items())) return hist diff --git a/qiskit/optimization/grover_optimization/__init__.py b/qiskit/optimization/converters/__init__.py similarity index 73% rename from qiskit/optimization/grover_optimization/__init__.py rename to qiskit/optimization/converters/__init__.py index ca740de62d..ec30ab1607 100644 --- a/qiskit/optimization/grover_optimization/__init__.py +++ b/qiskit/optimization/converters/__init__.py @@ -13,22 +13,13 @@ # that they have been altered from the originals. """ -Grover Optimization (:mod:`qiskit.optimization.grover_optimization`) +Optimization Converters (:mod:`qiskit.optimization.converters`) ==================================================================== -Grover models for optimization problems +Converters for optimization problems -.. currentmodule:: qiskit.optimization.grover_optimization +.. currentmodule:: qiskit.optimization.converters -Grover Models -============= - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - grover_minimum_finder - -Generators for QUBO Operators and Oracles +Converters for Operators and Oracles ========================================= .. autosummary:: @@ -48,3 +39,5 @@ portfolio_util """ + +from .optimization_problem_to_negative_value_oracle import OptimizationProblemToNegativeValueOracle diff --git a/qiskit/optimization/grover_optimization/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py similarity index 85% rename from qiskit/optimization/grover_optimization/optimization_problem_to_negative_value_oracle.py rename to qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index a4f0ad3510..b09e2b4798 100644 --- a/qiskit/optimization/grover_optimization/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -22,8 +22,8 @@ class OptimizationProblemToNegativeValueOracle: def __init__(self, num_output_qubits, verbose=False, backend='statevector_simulator'): - """ A helper function that converts a quadratic function into a state preparation operator A (InitialState) and - oracle O (CustomCircuitOracle) that recognizes negative numbers. + """ A helper function that converts a quadratic function into a state preparation operator A + (InitialState) and oracle O (CustomCircuitOracle) that recognizes negative numbers. :param num_output_qubits: The number of qubits required to represent the output. :param verbose: Verbose flag. :param backend: A string corresponding to a valid Qiskit backend. @@ -31,12 +31,11 @@ def __init__(self, num_output_qubits, verbose=False, backend='statevector_simula self._num_value = num_output_qubits self._verbose = verbose self._backend = backend - self._largest = 1 # If self.approximate, this value is used to approximate to and from non-integers. def encode(self, linear_coeff, quadratic_coeff, constant): - """ Takes the coefficients and constants of a quadratic function and returns a state preparation operator A - (InitialState.Custom) that encodes the function, and an oracle O (Oracle.CustomCircuitOracle) that - recognizes negative values. + """ Takes the coefficients and constants of a quadratic function and returns a state + preparation operator A (InitialState.Custom) that encodes the function, and an oracle O + (Oracle.CustomCircuitOracle) that recognizes negative values. :param linear_coeff: (n x 1 matrix) The linear coefficients of the function. :param quadratic_coeff: (n x n matrix) The quadratic coefficients of the function. :param constant: (int) The constant of the function. @@ -104,13 +103,15 @@ def __evaluate_classically(self, measurement): assignment_dict[v] = bool(v < 0) return assignment_dict, assignment + class QDictionary: """ - A parent class that defines a Quantum Dictionary, which encodes key-value pairs into the quantum state. See - https://arxiv.org/abs/1912.04088 for a formal definition. + A parent class that defines a Quantum Dictionary, which encodes key-value pairs into the + quantum state. See https://arxiv.org/abs/1912.04088 for a formal definition. """ - def __init__(self, key_bits, value_bits, ancilla_bits, f, prepare, backend="statevector_simulator"): + def __init__(self, key_bits, value_bits, ancilla_bits, f, prepare, + backend="statevector_simulator"): """ Initializes a Quantum Dictionary. :param key_bits: The number of key bits. @@ -151,7 +152,8 @@ class QQUBODictionary(QDictionary): """ def __init__(self, key_bits, value_bits, ancilla_bits, f, backend="statevector_simulator"): - QDictionary.__init__(self, key_bits, value_bits, ancilla_bits, f, self.prepare_quadratic, backend=backend) + QDictionary.__init__(self, key_bits, value_bits, ancilla_bits, f, self.prepare_quadratic, + backend=backend) self._circuit = None @property @@ -162,19 +164,21 @@ def circuit(self): def prepare_quadratic(self, d, circuit, key_val): """ Encodes a QUBO in the proper dictionary format into the state of a given register. - :param d: (dict) The QUBO problem. The keys should be the subscripts of the coefficients (e.g. x_1 -> 1), with - the constant (if present) being represented with a key of -1 (i.e. d[-1] = constant). Quadratic coefficients - should use a tuple for the key, with the corresponding subscripts inside (e.g. 2*x_1*x_2 -> d[(1,2)]=2). + :param d: (dict) The QUBO problem. The keys should be the subscripts of the coefficients + (e.g. x_1 -> 1), with the constant (if present) being represented with a key of -1 + (i.e. d[-1] = constant). Quadratic coefficients should use a tuple for the key, with the + corresponding subscripts inside (e.g. 2*x_1*x_2 -> d[(1,2)]=2). :param circuit: The QuantumCircuit to apply the operator to. - :param key_val: The QuantumRegister containing the key and value qubits. They are combined here to follow the - register format expected by algorithms like Qiskit Aqua's Grover. + :param key_val: The QuantumRegister containing the key and value qubits. They are combined + here to follow the register format expected by algorithms like Qiskit Aqua's Grover. """ circuit.h(key_val) # Linear Coefficients for i in range(self.value_bits): if d.get(-1, 0) != 0: - circuit.u1(1/2 ** self.value_bits * 2 * np.pi * 2 ** i * d[-1], key_val[self.key_bits + i]) + circuit.u1(1/2 ** self.value_bits * 2 * np.pi * 2 ** i * d[-1], + key_val[self.key_bits + i]) for j in range(self.key_bits): if d.get(j, 0) != 0: circuit.cu1(1/2 ** self.value_bits * 2 * np.pi * 2 ** i * d[j], key_val[j], @@ -184,8 +188,8 @@ def prepare_quadratic(self, d, circuit, key_val): for i in range(self.value_bits): for k, v in d.items(): if isinstance(k, tuple): - circuit.mcu1(1/2 ** self.value_bits * 2 * np.pi * 2 ** i * v, [key_val[k[0]], key_val[k[1]]], - key_val[self.key_bits + i]) + circuit.mcu1(1/2 ** self.value_bits * 2 * np.pi * 2 ** i * v, + [key_val[k[0]], key_val[k[1]]], key_val[self.key_bits + i]) iqft = IQFT(self.value_bits) value = [key_val[v] for v in range(self.key_bits, self.key_bits + self.value_bits)] diff --git a/qiskit/optimization/grover_optimization/portfolio_util.py b/qiskit/optimization/portfolio_util.py similarity index 93% rename from qiskit/optimization/grover_optimization/portfolio_util.py rename to qiskit/optimization/portfolio_util.py index 7698d504cf..9fec56e8c8 100644 --- a/qiskit/optimization/grover_optimization/portfolio_util.py +++ b/qiskit/optimization/portfolio_util.py @@ -22,7 +22,8 @@ def get_mu_sigma(num_assets): :returns mu (linear coefficients, nx1 matrix) and sigma (quadratic coefficients, nxn matrix) """ stocks = [("TICKER%s" % i) for i in range(num_assets)] - data = RandomDataProvider(tickers=stocks, start=datetime.datetime(2020, 1, 1), end=datetime.datetime(2020, 1, 30)) + data = RandomDataProvider(tickers=stocks, start=datetime.datetime(2020, 1, 1), + end=datetime.datetime(2020, 1, 30)) data.run() mu = data.get_period_return_mean_vector() sigma = data.get_period_return_covariance_matrix() @@ -32,8 +33,8 @@ def get_mu_sigma(num_assets): def get_qubo_solutions(f, n_key, print_solutions=False): """ Calculates all of the outputs of a QUBO function representable by n key qubits. - :param f: A dictionary representation of the function, where the keys correspond to a variable, and the - values are the corresponding coefficients. + :param f: A dictionary representation of the function, where the keys correspond to a + variable, and the values are the corresponding coefficients. :param n_key: The number of key qubits. :param print_solutions: If true, the solutions will be formatted and printed. :return: A dictionary of the inputs (keys) and outputs (values) of the QUBO function. diff --git a/qiskit/optimization/results/__init__.py b/qiskit/optimization/results/__init__.py new file mode 100644 index 0000000000..8441887358 --- /dev/null +++ b/qiskit/optimization/results/__init__.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" +Optimization Results Objects (:mod:`qiskit.optimization.results`) +==================================================================== +Results objects for optimization problems + +.. currentmodule:: qiskit.optimization.results + +Results Objects +========================= + +.. autosummary:: + :toctree: ../stubs/ + :nosignatures: + + grover_optimization_results + +""" + +from .grover_optimization_results import GroverOptimizationResults diff --git a/qiskit/optimization/grover_optimization/grover_optimization_results.py b/qiskit/optimization/results/grover_optimization_results.py similarity index 91% rename from qiskit/optimization/grover_optimization/grover_optimization_results.py rename to qiskit/optimization/results/grover_optimization_results.py index e801687442..c2363eff60 100644 --- a/qiskit/optimization/grover_optimization/grover_optimization_results.py +++ b/qiskit/optimization/results/grover_optimization_results.py @@ -15,7 +15,8 @@ class GroverOptimizationResults: - def __init__(self, optimum_input, optimum_output, operation_counts, rotations, n_input_qubits, n_output_qubits, f): + def __init__(self, optimum_input, optimum_output, operation_counts, rotations, n_input_qubits, + n_output_qubits, f): """ Stores the result of a Grover optimization problem. :param optimum_input: (int) The input that corresponds to the optimum output. :param optimum_output: (int) The optimum output value. @@ -23,8 +24,8 @@ def __init__(self, optimum_input, optimum_output, operation_counts, rotations, n :param rotations: The total number of Grover rotations performed. :param n_input_qubits: The number of qubits used to represent the input. :param n_output_qubits: The number of qubits used to represent the output. - :param f: A dictionary representation of the function, where the keys correspond to a variable, and the - values are the corresponding coefficients. + :param f: A dictionary representation of the function, where the keys correspond to a + variable, and the values are the corresponding coefficients. """ self._optimum_input = optimum_input self._optimum_output = optimum_output diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index d9a30b47ee..5c162b3253 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -15,8 +15,8 @@ """ Test Grover Minimum Finder """ from test.optimization import QiskitOptimizationTestCase -from qiskit.optimization.grover_optimization.grover_minimum_finder import GroverMinimumFinder -from qiskit.optimization.grover_optimization.portfolio_util import get_qubo_solutions +from qiskit.optimization.algorithms import GroverMinimumFinder +from qiskit.optimization.portfolio_util import get_qubo_solutions import numpy as np # Flag for verbosity in all units under test. diff --git a/test/optimization/test_optimization_problem_to_negative_value_oracle.py b/test/optimization/test_optimization_problem_to_negative_value_oracle.py index 9d30720bf3..0dbb8fc366 100644 --- a/test/optimization/test_optimization_problem_to_negative_value_oracle.py +++ b/test/optimization/test_optimization_problem_to_negative_value_oracle.py @@ -15,8 +15,8 @@ """ Test Optimization Problem to Negative Value Oracle """ from test.optimization import QiskitOptimizationTestCase -from qiskit.optimization.grover_optimization.optimization_problem_to_negative_value_oracle import OptimizationProblemToNegativeValueOracle -from qiskit.optimization.grover_optimization.portfolio_util import get_qubo_solutions +from qiskit.optimization.converters import OptimizationProblemToNegativeValueOracle +from qiskit.optimization.portfolio_util import get_qubo_solutions from qiskit import QuantumCircuit, Aer, execute import numpy as np @@ -53,7 +53,8 @@ def _measure(qc, n_key, n_value): job = execute(qc, backend=backend, shots=1) result = job.result() state = np.round(result.get_statevector(qc), 5) - keys = [bin(i)[2::].rjust(int(np.log2(len(state))), '0')[::-1] for i in range(0, len(state))] + keys = [bin(i)[2::].rjust(int(np.log2(len(state))), '0')[::-1] + for i in range(0, len(state))] probs = [np.round(abs(a)*abs(a), 5) for a in state] f_hist = dict(zip(keys, probs)) hist = {} @@ -109,7 +110,7 @@ def test_optnvo_2_key_w_constant(self): self._validate_operator(f, len(linear), num_value, a_operator) def test_optnvo_4_key_all_negative(self): - """ Test with 4 negative linear coefficients, negative quadratic coeffs, and a negative constant """ + """ Test with all negative values """ # Circuit parameters. num_value = 5 From 2ea1edd0e72bcc0181daef557436c5c87df69392 Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Mon, 9 Mar 2020 15:11:40 -0400 Subject: [PATCH 039/323] Rename Util File, Add Email to PR (#847) --- qiskit/optimization/algorithms/grover_minimum_finder.py | 2 +- qiskit/optimization/{portfolio_util.py => util.py} | 0 test/optimization/test_grover_minimum_finder.py | 2 +- .../test_optimization_problem_to_negative_value_oracle.py | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename qiskit/optimization/{portfolio_util.py => util.py} (100%) diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index 19b0621943..c442e6790b 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -14,7 +14,7 @@ from qiskit.optimization.converters import OptimizationProblemToNegativeValueOracle from qiskit.optimization.results import GroverOptimizationResults -from qiskit.optimization.portfolio_util import get_qubo_solutions +from qiskit.optimization.util import get_qubo_solutions from qiskit.aqua.algorithms.amplitude_amplifiers.grover import Grover from qiskit.visualization import plot_histogram diff --git a/qiskit/optimization/portfolio_util.py b/qiskit/optimization/util.py similarity index 100% rename from qiskit/optimization/portfolio_util.py rename to qiskit/optimization/util.py diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index 5c162b3253..679ee1f235 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -16,7 +16,7 @@ from test.optimization import QiskitOptimizationTestCase from qiskit.optimization.algorithms import GroverMinimumFinder -from qiskit.optimization.portfolio_util import get_qubo_solutions +from qiskit.optimization.util import get_qubo_solutions import numpy as np # Flag for verbosity in all units under test. diff --git a/test/optimization/test_optimization_problem_to_negative_value_oracle.py b/test/optimization/test_optimization_problem_to_negative_value_oracle.py index 0dbb8fc366..60dc5a4c4b 100644 --- a/test/optimization/test_optimization_problem_to_negative_value_oracle.py +++ b/test/optimization/test_optimization_problem_to_negative_value_oracle.py @@ -16,7 +16,7 @@ from test.optimization import QiskitOptimizationTestCase from qiskit.optimization.converters import OptimizationProblemToNegativeValueOracle -from qiskit.optimization.portfolio_util import get_qubo_solutions +from qiskit.optimization.util import get_qubo_solutions from qiskit import QuantumCircuit, Aer, execute import numpy as np From 2e3db47d9085b55e7dd29d4a3cf552e76adcd702 Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Tue, 10 Mar 2020 10:33:34 -0400 Subject: [PATCH 040/323] Refactor for pylint Recommendations (#847) --- .../algorithms/grover_minimum_finder.py | 60 ++++---- ...zation_problem_to_negative_value_oracle.py | 144 +++++++++++------- .../results/grover_optimization_results.py | 40 +++-- qiskit/optimization/util.py | 50 +++--- .../test_grover_minimum_finder.py | 70 ++++----- ...zation_problem_to_negative_value_oracle.py | 41 ++--- 6 files changed, 235 insertions(+), 170 deletions(-) diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index c442e6790b..e444d01397 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -12,39 +12,47 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""GroverMinimumFinder module""" + +import random +import math +import numpy as np from qiskit.optimization.converters import OptimizationProblemToNegativeValueOracle from qiskit.optimization.results import GroverOptimizationResults from qiskit.optimization.util import get_qubo_solutions - from qiskit.aqua.algorithms.amplitude_amplifiers.grover import Grover from qiskit.visualization import plot_histogram -from qiskit import Aer -import numpy as np -import random -import math +from qiskit import Aer, execute class GroverMinimumFinder: + """Uses Grover Adaptive Search (GAS) to find the minimum of a QUBO function.""" + def __init__(self, num_iterations=3, backend=Aer.get_backend('statevector_simulator'), verbose=False): - """ Initializes a GroverMinimumFinder optimizer. - :param num_iterations: The number of iterations the algorithm will search with no - improvement. - :param backend: A valid backend object. Default statevector_simulator. - :param verbose: Verbose flag - prints and plots state at each iteration of GAS. + """ + Constructor. + Args: + num_iterations (int, optional): The number of iterations the algorithm will search with + no improvement. + backend (str, optional): Instance of selected backend. + verbose (bool, optional): Verbose flag - prints/plots state at each iteration of GAS. """ self._n_iterations = num_iterations self._verbose = verbose self._backend = backend def solve(self, quadratic, linear, constant, num_output_qubits): - """ Given the coefficients and constants of a QUBO function, find the minimum output value. - :param quadratic: (nxn matrix) The quadratic coefficients of the QUBO. - :param linear: (nx1 matrix) The linear coefficients of the QUBO. - :param constant: (int) The constant of the QUBO. - :param num_output_qubits: The number of qubits used to represent the output values. - :return: A GroverOptimizationResults object containing information about the run, + """ + Given the coefficients and constants of a QUBO function, find the minimum output value. + Args: + quadratic (np.array): The quadratic coefficients of the QUBO. + linear (np.array): The linear coefficients of the QUBO. + constant (int): The constant of the QUBO. + num_output_qubits (int): The number of qubits used to represent the output values. + Returns: + GroverOptimizationResults: A results object containing information about the run, including the solution. """ # Variables for tracking the optimum. @@ -60,7 +68,7 @@ def solve(self, quadratic, linear, constant, num_output_qubits): keys_measured = [] # Variables for result object. - f = {} + func_dict = {} operation_count = {} iteration = 0 @@ -86,8 +94,8 @@ def solve(self, quadratic, linear, constant, num_output_qubits): rotations += rotation_count # Get state preparation operator A and oracle O for the current threshold. - a_operator, oracle, f = opt_prob_converter.encode(linear, quadratic, - constant - threshold) + a_operator, oracle, func_dict = opt_prob_converter.encode(linear, quadratic, + constant - threshold) # Apply Grover's Algorithm to find values below the threshold. if rotation_count > 0: @@ -144,24 +152,24 @@ def solve(self, quadratic, linear, constant, num_output_qubits): optimum_found = True # Track the operation count. - oc = circuit.count_ops() - operation_count[iteration] = oc + operations = circuit.count_ops() + operation_count[iteration] = operations iteration += 1 if self._verbose: - print("Operation Count:", oc) + print("Operation Count:", operations) print("\n") # Get original key and value pairs. - f[-1] = constant - solutions = get_qubo_solutions(f, n_key) + func_dict[-1] = constant + solutions = get_qubo_solutions(func_dict, n_key) # If the constant is 0 and we didn't find a negative, the answer is likely 0. if optimum_value >= 0 and constant == 0: optimum_key = 0 return GroverOptimizationResults(optimum_key, solutions[optimum_key], operation_count, - rotations, n_key, n_value, f) + rotations, n_key, n_value, func_dict) def __measure(self, circuit, n_key, n_value, backend, shots=1024, verbose=False): """Get probabilities from the given backend, and picks a random outcome.""" @@ -185,8 +193,6 @@ def __measure(self, circuit, n_key, n_value, backend, shots=1024, verbose=False) @staticmethod def __get_probs(n_key, n_value, qc, backend, shots): """Gets probabilities from a given backend.""" - from qiskit import execute - # Execute job and filter results. job = execute(qc, backend=backend, shots=shots) result = job.result() diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index b09e2b4798..4171e6504b 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -12,46 +12,61 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""OptimizationProblemToNegativeValueOracle module""" + +import numpy as np from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister from qiskit.aqua.components.oracles import CustomCircuitOracle from qiskit.aqua.components.initial_states import Custom from qiskit.aqua.components.iqfts import Standard as IQFT -import numpy as np class OptimizationProblemToNegativeValueOracle: + """ + Converts an optimization problem (QUBO) to a negative value oracle and state preparation + operators. + """ + def __init__(self, num_output_qubits, verbose=False, backend='statevector_simulator'): - """ A helper function that converts a quadratic function into a state preparation operator A - (InitialState) and oracle O (CustomCircuitOracle) that recognizes negative numbers. - :param num_output_qubits: The number of qubits required to represent the output. - :param verbose: Verbose flag. - :param backend: A string corresponding to a valid Qiskit backend. + """ + Constructor. + Args: + num_output_qubits (int): The number of qubits required to represent the output. + verbose (bool, optional): Verbose flag - prints/plots state at each iteration of GAS. + backend (str, optional): A string corresponding to the name of the selected backend. """ self._num_value = num_output_qubits self._verbose = verbose self._backend = backend def encode(self, linear_coeff, quadratic_coeff, constant): - """ Takes the coefficients and constants of a quadratic function and returns a state - preparation operator A (InitialState.Custom) that encodes the function, and an oracle O - (Oracle.CustomCircuitOracle) that recognizes negative values. - :param linear_coeff: (n x 1 matrix) The linear coefficients of the function. - :param quadratic_coeff: (n x n matrix) The quadratic coefficients of the function. - :param constant: (int) The constant of the function. + """ + A helper function that converts a quadratic function into a state preparation operator A and + oracle O that recognizes negative numbers. + Args: + linear_coeff (np.array): The linear coefficients of the QUBO. + quadratic_coeff (np.array): The quadratic coefficients of the QUBO. + constant (int): The constant of the QUBO. + Returns: + Tuple(InitialState.Custom, CustomCircuitOracle, dict): A state preparation operator A + (InitialState), an oracle O (CustomCircuitOracle) that recognizes negative numbers, and + a dictionary representation of the function coefficients, where the key -1 represents + the constant. """ # Get circuit requirements from input. num_key = len(linear_coeff) num_ancilla = max(num_key, self._num_value) - 1 # Get the function dictionary. - f = self.__get_function(num_key, linear_coeff, quadratic_coeff, constant) + func = self.__get_function(num_key, linear_coeff, quadratic_coeff, constant) if self._verbose: - print("f:", f, "\n") + print("f:", func, "\n") # Define state preparation operator A from function. - qd = QQUBODictionary(num_key, self._num_value, num_ancilla, f, backend=self._backend) - a_operator_circuit = qd.circuit + quantum_dict = QQUBODictionary(num_key, self._num_value, num_ancilla, + func, backend=self._backend) + a_operator_circuit = quantum_dict.circuit a_operator = Custom(a_operator_circuit.width(), circuit=a_operator_circuit) # Get registers from the A operator circuit. @@ -71,20 +86,18 @@ def encode(self, linear_coeff, quadratic_coeff, constant): circuit=oracle_circuit, evaluate_classically_callback=self.__evaluate_classically) - return a_operator, oracle, f + return a_operator, oracle, func @staticmethod def __get_function(num_assets, linear, quadratic, constant): """Convert the problem to a dictionary format.""" - f = {-1: constant} + func = {-1: constant} for i in range(num_assets): - i_ = i - f[i_] = -linear[i_] + func[i] = -linear[i] for j in range(i): - j_ = j - f[(i_, j_)] = int(quadratic[i_, j_]) + func[(i, j)] = int(quadratic[i, j]) - return f + return func @staticmethod def __cxzxz(circuit, ctrl, tgt): @@ -104,33 +117,36 @@ def __evaluate_classically(self, measurement): return assignment_dict, assignment -class QDictionary: +class QuantumDictionary: """ A parent class that defines a Quantum Dictionary, which encodes key-value pairs into the quantum state. See https://arxiv.org/abs/1912.04088 for a formal definition. """ - def __init__(self, key_bits, value_bits, ancilla_bits, f, prepare, + def __init__(self, key_bits, value_bits, ancilla_bits, func_dict, prepare, backend="statevector_simulator"): """ - Initializes a Quantum Dictionary. - :param key_bits: The number of key bits. - :param value_bits: The number of value bits. - :param ancilla_bits: The number of precision (result) bits. - :param f: The function to encode, can be a list or a lambda. - :param prepare: A method that encodes f into the quantum state. + Constructor. + Args: + key_bits (int): The number of key bits. + value_bits (int): The number of value bits. + ancilla_bits (int): The number of precision (result) bits. + func_dict (dict): The dictionary of function coefficients to encode. + prepare (function): A method that encodes f into the quantum state. + backend (str, optional): A string corresponding to the name of the selected backend. """ self.key_bits = key_bits self.value_bits = value_bits self.ancilla_bits = ancilla_bits - self.f = f + self.func_dict = func_dict self.prepare = prepare self.backend = backend def construct_circuit(self): """ - Creates a circuit for the initialized Quantum Dictionary. - :return: A QuantumCircuit object describing the Quantum Dictionary. + Creates a circuit for the initialized Quantum Dictionary. + Returns: + QuantumCircuit: Circuit object describing the Quantum Dictionary. """ key_val = QuantumRegister(self.key_bits + self.value_bits, "key_value") ancilla = QuantumRegister(self.ancilla_bits, "ancilla") @@ -141,55 +157,73 @@ def construct_circuit(self): measure = ClassicalRegister(self.key_bits + self.value_bits) circuit = QuantumCircuit(key_val, ancilla, measure) - self.prepare(self.f, circuit, key_val) + self.prepare(self.func_dict, circuit, key_val) return circuit -class QQUBODictionary(QDictionary): +class QQUBODictionary(QuantumDictionary): """ A QDictionary that creates a state preparation operator for a given QUBO problem. """ - def __init__(self, key_bits, value_bits, ancilla_bits, f, backend="statevector_simulator"): - QDictionary.__init__(self, key_bits, value_bits, ancilla_bits, f, self.prepare_quadratic, - backend=backend) + def __init__(self, key_bits, value_bits, ancilla_bits, func_dict, + backend="statevector_simulator"): + """ + Constructor. + Args: + key_bits (int): The number of key bits. + value_bits (int): The number of value bits. + ancilla_bits (int): The number of precision (result) bits. + func_dict (dict): The dictionary of function coefficients to encode. + backend (str, optional): A string corresponding to the name of the selected backend. + """ + QuantumDictionary.__init__(self, key_bits, value_bits, ancilla_bits, func_dict, + self.prepare_quadratic, backend=backend) self._circuit = None @property def circuit(self): + """ + Provides the circuit of the QuantumDictionary. Will construct one if not yet created. + Returns: + QuantumCircuit: Circuit object describing the Quantum Dictionary. + """ if self._circuit is None: self._circuit = self.construct_circuit() return self._circuit - def prepare_quadratic(self, d, circuit, key_val): - """ Encodes a QUBO in the proper dictionary format into the state of a given register. - :param d: (dict) The QUBO problem. The keys should be the subscripts of the coefficients - (e.g. x_1 -> 1), with the constant (if present) being represented with a key of -1 - (i.e. d[-1] = constant). Quadratic coefficients should use a tuple for the key, with the - corresponding subscripts inside (e.g. 2*x_1*x_2 -> d[(1,2)]=2). - :param circuit: The QuantumCircuit to apply the operator to. - :param key_val: The QuantumRegister containing the key and value qubits. They are combined - here to follow the register format expected by algorithms like Qiskit Aqua's Grover. + def prepare_quadratic(self, func_dict, circuit, key_val): + """ + Encodes a QUBO in the proper dictionary format into the state of a given register. + Args: + func_dict (dict): The QUBO problem. The keys should be subscripts of the coefficients + (e.g. x_1 -> 1), with the constant (if present) being represented with a key of -1 + (i.e. d[-1] = constant). Quadratic coefficients should use a tuple for the key, with + the corresponding subscripts inside (e.g. 2*x_1*x_2 -> d[(1,2)]=2). + circuit (QuantumCircuit): The circuit to apply the operator to. + key_val (QuantumRegister): Register containing the key and value qubits. They are + combined here to follow the register format expected by algorithms like Qiskit + Aqua's Grover. """ circuit.h(key_val) # Linear Coefficients for i in range(self.value_bits): - if d.get(-1, 0) != 0: - circuit.u1(1/2 ** self.value_bits * 2 * np.pi * 2 ** i * d[-1], + if func_dict.get(-1, 0) != 0: + circuit.u1(1 / 2 ** self.value_bits * 2 * np.pi * 2 ** i * func_dict[-1], key_val[self.key_bits + i]) for j in range(self.key_bits): - if d.get(j, 0) != 0: - circuit.cu1(1/2 ** self.value_bits * 2 * np.pi * 2 ** i * d[j], key_val[j], - key_val[self.key_bits + i]) + if func_dict.get(j, 0) != 0: + circuit.cu1(1 / 2 ** self.value_bits * 2 * np.pi * 2 ** i * func_dict[j], + key_val[j], key_val[self.key_bits + i]) # Quadratic Coefficients for i in range(self.value_bits): - for k, v in d.items(): + for k, v in func_dict.items(): if isinstance(k, tuple): circuit.mcu1(1/2 ** self.value_bits * 2 * np.pi * 2 ** i * v, - [key_val[k[0]], key_val[k[1]]], key_val[self.key_bits + i]) + [key_val[k[0]], key_val[k[1]]], key_val[self.key_bits + i]) iqft = IQFT(self.value_bits) value = [key_val[v] for v in range(self.key_bits, self.key_bits + self.value_bits)] diff --git a/qiskit/optimization/results/grover_optimization_results.py b/qiskit/optimization/results/grover_optimization_results.py index c2363eff60..0b9489c5aa 100644 --- a/qiskit/optimization/results/grover_optimization_results.py +++ b/qiskit/optimization/results/grover_optimization_results.py @@ -12,20 +12,27 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""GroverOptimizationResults module""" + class GroverOptimizationResults: + """A results object for Grover Optimization methods.""" + def __init__(self, optimum_input, optimum_output, operation_counts, rotations, n_input_qubits, - n_output_qubits, f): - """ Stores the result of a Grover optimization problem. - :param optimum_input: (int) The input that corresponds to the optimum output. - :param optimum_output: (int) The optimum output value. - :param operation_counts: (dict) The counts of each operation performed per iteration. - :param rotations: The total number of Grover rotations performed. - :param n_input_qubits: The number of qubits used to represent the input. - :param n_output_qubits: The number of qubits used to represent the output. - :param f: A dictionary representation of the function, where the keys correspond to a - variable, and the values are the corresponding coefficients. + n_output_qubits, func_dict): + """ + Constructor. + + Args: + optimum_input (int): The input that corresponds to the optimum output. + optimum_output (int): The optimum output value. + operation_counts (dict): The counts of each operation performed per iteration. + rotations (int): The total number of Grover rotations performed. + n_input_qubits (int): The number of qubits used to represent the input. + n_output_qubits (int): The number of qubits used to represent the output. + func_dict (dict): A dictionary representation of the function, where the keys correspond + to a variable, and the values are the corresponding coefficients. """ self._optimum_input = optimum_input self._optimum_output = optimum_output @@ -33,32 +40,39 @@ def __init__(self, optimum_input, optimum_output, operation_counts, rotations, n self._rotations = rotations self._n_input_qubits = n_input_qubits self._n_output_qubits = n_output_qubits - self._f = f + self._func_dict = func_dict @property def optimum_input(self): + """Getter of optimum_input""" return self._optimum_input @property def optimum_output(self): + """Getter of optimum_output""" return self._optimum_output @property def operation_counts(self): + """Getter of operation_counts""" return self._operation_counts @property def rotation_count(self): + """Getter of rotation_count""" return self._rotations @property def n_input_qubits(self): + """Getter of n_input_qubits""" return self._n_input_qubits @property def n_output_qubits(self): + """Getter of n_output_qubits""" return self._n_output_qubits @property - def function(self): - return self._f + def func_dict(self): + """Getter of func_dict""" + return self._func_dict diff --git a/qiskit/optimization/util.py b/qiskit/optimization/util.py index 9fec56e8c8..cdf54505a0 100644 --- a/qiskit/optimization/util.py +++ b/qiskit/optimization/util.py @@ -12,37 +12,45 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -from qiskit.finance.data_providers import RandomDataProvider +""" Optimization Utilities module """ + import datetime +from qiskit.finance.data_providers import RandomDataProvider def get_mu_sigma(num_assets): - """ Generate expected return and covariance matrix from (random) time-series. - :param num_assets: The number of assets to generate values for. - :returns mu (linear coefficients, nx1 matrix) and sigma (quadratic coefficients, nxn matrix) + """ + Generate expected return and covariance matrix from (random) time-series. + Args: + num_assets (int): The number of assets to generate values for. + Returns: + Tuple(np.array, np.array): mu (linear coefficients) and sigma (quadratic coefficients) """ stocks = [("TICKER%s" % i) for i in range(num_assets)] data = RandomDataProvider(tickers=stocks, start=datetime.datetime(2020, 1, 1), end=datetime.datetime(2020, 1, 30)) data.run() - mu = data.get_period_return_mean_vector() - sigma = data.get_period_return_covariance_matrix() + linear = data.get_period_return_mean_vector() + quadratic = data.get_period_return_covariance_matrix() - return mu, sigma + return linear, quadratic -def get_qubo_solutions(f, n_key, print_solutions=False): - """ Calculates all of the outputs of a QUBO function representable by n key qubits. - :param f: A dictionary representation of the function, where the keys correspond to a - variable, and the values are the corresponding coefficients. - :param n_key: The number of key qubits. - :param print_solutions: If true, the solutions will be formatted and printed. - :return: A dictionary of the inputs (keys) and outputs (values) of the QUBO function. +def get_qubo_solutions(function_dict, n_key, print_solutions=False): + """ + Calculates all of the outputs of a QUBO function representable by n key qubits. + Args: + function_dict (dict): A dictionary representation of the function, where the keys correspond + to a variable, and the values are the corresponding coefficients. + n_key (int): The number of key qubits. + print_solutions (bool, optional): If true, the solutions will be formatted and printed. + Returns: + dict: A dictionary of the inputs (keys) and outputs (values) of the QUBO function. """ # Determine constant. constant = 0 - if -1 in f: - constant = f[-1] + if -1 in function_dict: + constant = function_dict[-1] format_string = '{0:0'+str(n_key)+'b}' # Iterate through every key combination. @@ -59,14 +67,14 @@ def get_qubo_solutions(f, n_key, print_solutions=False): # Handle the linear terms. for k in range(len(bin_key)): - if bin_list[k] == 1 and k in f: - solution += f[k] + if bin_list[k] == 1 and k in function_dict: + solution += function_dict[k] # Handle the quadratic terms. - for p in range(len(bin_key)): + for j in range(len(bin_key)): for q in range(len(bin_key)): - if (p, q) in f and p != q and bin_list[p] == 1 and bin_list[q] == 1: - solution += f[(p, q)] + if (j, q) in function_dict and j != q and bin_list[j] == 1 and bin_list[q] == 1: + solution += function_dict[(j, q)] # Print row. if print_solutions: diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index 679ee1f235..ba60080be0 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -15,27 +15,29 @@ """ Test Grover Minimum Finder """ from test.optimization import QiskitOptimizationTestCase +import numpy as np from qiskit.optimization.algorithms import GroverMinimumFinder from qiskit.optimization.util import get_qubo_solutions -import numpy as np # Flag for verbosity in all units under test. -verbose = False +VERBOSE = False class TestGroverMinimumFinder(QiskitOptimizationTestCase): + """GroverMinimumFinder Tests""" def validate_results(self, results): + """Validate the results object returned by GroverMinimumFinder.""" # Get measured values. n_key = results.n_input_qubits op_key = results.optimum_input op_value = results.optimum_output rot = results.rotation_count - f = results.function + func = results.func_dict print("Optimum Key:", op_key, "Optimum Value:", op_value, "Rotations:", rot, "\n") # Get expected value. - solutions = get_qubo_solutions(f, n_key, print_solutions=False) + solutions = get_qubo_solutions(func, n_key, print_solutions=False) min_key = min(solutions, key=lambda key: int(solutions[key])) min_value = solutions[min_key] max_rotations = np.ceil(2 ** (n_key / 2)) @@ -50,14 +52,14 @@ def test_qubo_gas_int_zero(self): num_value = 4 # Input. - mu = np.array([0, 0]) - sigma = np.array([[0, 0], - [0, 0]]) + linear = np.array([0, 0]) + quadratic = np.array([[0, 0], + [0, 0]]) constant = 0 # Will not find a negative, should return 0. - gmf = GroverMinimumFinder(num_iterations=1, verbose=verbose) - results = gmf.solve(sigma, mu, constant, num_value) + gmf = GroverMinimumFinder(num_iterations=1, verbose=VERBOSE) + results = gmf.solve(quadratic, linear, constant, num_value) self.assertEqual(results.optimum_input, 0) self.assertEqual(int(results.optimum_output), 0) @@ -67,15 +69,15 @@ def test_qubo_gas_int_simple(self): num_value = 4 # Input. - mu = np.array([1, -2]) - sigma = np.array([[2, 0], - [0, 2]]) + linear = np.array([1, -2]) + quadratic = np.array([[2, 0], + [0, 2]]) q = 0.5 - sigma = sigma.dot(q) + quadratic = quadratic.dot(q) # Get the optimum key and value. - gmf = GroverMinimumFinder(num_iterations=6, verbose=verbose) - results = gmf.solve(sigma, mu, 0, num_value) + gmf = GroverMinimumFinder(num_iterations=6, verbose=VERBOSE) + results = gmf.solve(quadratic, linear, 0, num_value) self.validate_results(results) def test_qubo_gas_int_simple_pos_constant(self): @@ -84,16 +86,16 @@ def test_qubo_gas_int_simple_pos_constant(self): num_value = 4 # Input. - mu = np.array([1, -2]) - sigma = np.array([[2, 0], - [0, 2]]) + linear = np.array([1, -2]) + quadratic = np.array([[2, 0], + [0, 2]]) q = 0.5 - sigma = sigma.dot(q) + quadratic = quadratic.dot(q) constant = 2 # Get the optimum key and value. - gmf = GroverMinimumFinder(num_iterations=6, verbose=verbose) - results = gmf.solve(sigma, mu, constant, num_value) + gmf = GroverMinimumFinder(num_iterations=6, verbose=VERBOSE) + results = gmf.solve(quadratic, linear, constant, num_value) self.validate_results(results) def test_qubo_gas_int_simple_neg_constant(self): @@ -102,16 +104,16 @@ def test_qubo_gas_int_simple_neg_constant(self): num_value = 4 # Input. - mu = np.array([1, -2]) - sigma = np.array([[2, 0], - [0, 2]]) + linear = np.array([1, -2]) + quadratic = np.array([[2, 0], + [0, 2]]) q = 0.5 - sigma = sigma.dot(q) + quadratic = quadratic.dot(q) constant = -2 # Get the optimum key and value. - gmf = GroverMinimumFinder(num_iterations=6, verbose=verbose) - results = gmf.solve(sigma, mu, constant, num_value) + gmf = GroverMinimumFinder(num_iterations=6, verbose=VERBOSE) + results = gmf.solve(quadratic, linear, constant, num_value) self.validate_results(results) def test_qubo_gas_int_paper_example(self): @@ -120,14 +122,14 @@ def test_qubo_gas_int_paper_example(self): num_value = 5 # Input. - mu = np.array([1, -2, 3]) - sigma = np.array([[2, 0, -4], - [0, 4, -2], - [-4, -2, 10]]) + linear = np.array([1, -2, 3]) + quadratic = np.array([[2, 0, -4], + [0, 4, -2], + [-4, -2, 10]]) q = 0.5 - sigma = sigma.dot(q) + quadratic = quadratic.dot(q) # Get the optimum key and value. - gmf = GroverMinimumFinder(num_iterations=20, verbose=verbose) - results = gmf.solve(sigma, mu, 0, num_value) + gmf = GroverMinimumFinder(num_iterations=20, verbose=VERBOSE) + results = gmf.solve(quadratic, linear, 0, num_value) self.validate_results(results) diff --git a/test/optimization/test_optimization_problem_to_negative_value_oracle.py b/test/optimization/test_optimization_problem_to_negative_value_oracle.py index 60dc5a4c4b..fa77ed653c 100644 --- a/test/optimization/test_optimization_problem_to_negative_value_oracle.py +++ b/test/optimization/test_optimization_problem_to_negative_value_oracle.py @@ -15,26 +15,27 @@ """ Test Optimization Problem to Negative Value Oracle """ from test.optimization import QiskitOptimizationTestCase +import numpy as np from qiskit.optimization.converters import OptimizationProblemToNegativeValueOracle from qiskit.optimization.util import get_qubo_solutions from qiskit import QuantumCircuit, Aer, execute -import numpy as np class TestOptimizationProblemToNegativeValueOracle(QiskitOptimizationTestCase): + """OPtNVO Tests""" - def _validate_function(self, f, linear, quadratic, constant): - for key in f: + def _validate_function(self, func_dict, linear, quadratic, constant): + for key in func_dict: if isinstance(key, int) and key >= 0: - self.assertEqual(-1*linear[key], f[key]) + self.assertEqual(-1 * linear[key], func_dict[key]) elif isinstance(key, tuple): - self.assertEqual(quadratic[key[0]][key[1]], f[key]) + self.assertEqual(quadratic[key[0]][key[1]], func_dict[key]) else: - self.assertEqual(constant, f[key]) + self.assertEqual(constant, func_dict[key]) - def _validate_operator(self, f, n_key, n_value, operator): + def _validate_operator(self, func_dict, n_key, n_value, operator): # Get expected results. - solutions = get_qubo_solutions(f, n_key, print_solutions=False) + solutions = get_qubo_solutions(func_dict, n_key, print_solutions=False) # Run the state preparation operator A and observe results. circuit = operator._circuit @@ -86,10 +87,10 @@ def test_optnvo_2_key(self): # Convert to dictionary format with operator/oracle. converter = OptimizationProblemToNegativeValueOracle(num_value) - a_operator, _, f = converter.encode(linear, quadratic, constant) + a_operator, _, func_dict = converter.encode(linear, quadratic, constant) - self._validate_function(f, linear, quadratic, constant) - self._validate_operator(f, len(linear), num_value, a_operator) + self._validate_function(func_dict, linear, quadratic, constant) + self._validate_operator(func_dict, len(linear), num_value, a_operator) def test_optnvo_2_key_w_constant(self): """ Test with 2 linear coefficients, no quadratic, simple constant """ @@ -104,10 +105,10 @@ def test_optnvo_2_key_w_constant(self): # Convert to dictionary format with operator/oracle. converter = OptimizationProblemToNegativeValueOracle(num_value) - a_operator, _, f = converter.encode(linear, quadratic, constant) + a_operator, _, func_dict = converter.encode(linear, quadratic, constant) - self._validate_function(f, linear, quadratic, constant) - self._validate_operator(f, len(linear), num_value, a_operator) + self._validate_function(func_dict, linear, quadratic, constant) + self._validate_operator(func_dict, len(linear), num_value, a_operator) def test_optnvo_4_key_all_negative(self): """ Test with all negative values """ @@ -124,10 +125,10 @@ def test_optnvo_4_key_all_negative(self): # Convert to dictionary format with operator/oracle. converter = OptimizationProblemToNegativeValueOracle(num_value) - a_operator, _, f = converter.encode(linear, quadratic, constant) + a_operator, _, func_dict = converter.encode(linear, quadratic, constant) - self._validate_function(f, linear, quadratic, constant) - self._validate_operator(f, len(linear), num_value, a_operator) + self._validate_function(func_dict, linear, quadratic, constant) + self._validate_operator(func_dict, len(linear), num_value, a_operator) def test_optnvo_6_key(self): """ Test with 6 linear coefficients, negative quadratics, no constant """ @@ -146,7 +147,7 @@ def test_optnvo_6_key(self): # Convert to dictionary format with operator/oracle. converter = OptimizationProblemToNegativeValueOracle(num_value) - a_operator, _, f = converter.encode(linear, quadratic, constant) + a_operator, _, func_dict = converter.encode(linear, quadratic, constant) - self._validate_function(f, linear, quadratic, constant) - self._validate_operator(f, len(linear), num_value, a_operator) + self._validate_function(func_dict, linear, quadratic, constant) + self._validate_operator(func_dict, len(linear), num_value, a_operator) From 37adcc1d5c99381f188f102bc7938aa3f47f646b Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Tue, 10 Mar 2020 12:11:39 -0400 Subject: [PATCH 041/323] Fix for Rotation Max and Testing (#847) --- .../algorithms/grover_minimum_finder.py | 2 +- .../test_grover_minimum_finder.py | 30 +++++++++++-------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index e444d01397..5c38617528 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -74,7 +74,7 @@ def solve(self, quadratic, linear, constant, num_output_qubits): # Variables for stopping if we've hit the rotation max. rotations = 0 - max_rotations = np.ceil(2 ** (n_key / 2)) + max_rotations = int(np.ceil(100*np.pi/4)) # Initialize oracle helper object. opt_prob_converter = OptimizationProblemToNegativeValueOracle(n_value, diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index ba60080be0..ebede016dd 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -26,12 +26,13 @@ class TestGroverMinimumFinder(QiskitOptimizationTestCase): """GroverMinimumFinder Tests""" - def validate_results(self, results): + def validate_results(self, results, max_iterations): """Validate the results object returned by GroverMinimumFinder.""" # Get measured values. n_key = results.n_input_qubits op_key = results.optimum_input op_value = results.optimum_output + iterations = len(results.operation_counts) rot = results.rotation_count func = results.func_dict print("Optimum Key:", op_key, "Optimum Value:", op_value, "Rotations:", rot, "\n") @@ -40,11 +41,12 @@ def validate_results(self, results): solutions = get_qubo_solutions(func, n_key, print_solutions=False) min_key = min(solutions, key=lambda key: int(solutions[key])) min_value = solutions[min_key] - max_rotations = np.ceil(2 ** (n_key / 2)) + max_rotations = int(np.ceil(100*np.pi/4)) # Validate results. - self.assertTrue(min_key == op_key or max_rotations == rot) - self.assertTrue(min_value == op_value or max_rotations == rot) + max_hit = max_rotations <= rot or max_iterations <= iterations + self.assertTrue(min_key == op_key or max_hit) + self.assertTrue(min_value == op_value or max_hit) def test_qubo_gas_int_zero(self): """ Test for when the answer is zero """ @@ -76,9 +78,10 @@ def test_qubo_gas_int_simple(self): quadratic = quadratic.dot(q) # Get the optimum key and value. - gmf = GroverMinimumFinder(num_iterations=6, verbose=VERBOSE) + n_iter = 8 + gmf = GroverMinimumFinder(num_iterations=n_iter, verbose=VERBOSE) results = gmf.solve(quadratic, linear, 0, num_value) - self.validate_results(results) + self.validate_results(results, n_iter) def test_qubo_gas_int_simple_pos_constant(self): """ Test for a positive constant """ @@ -94,9 +97,10 @@ def test_qubo_gas_int_simple_pos_constant(self): constant = 2 # Get the optimum key and value. - gmf = GroverMinimumFinder(num_iterations=6, verbose=VERBOSE) + n_iter = 8 + gmf = GroverMinimumFinder(num_iterations=n_iter, verbose=VERBOSE) results = gmf.solve(quadratic, linear, constant, num_value) - self.validate_results(results) + self.validate_results(results, n_iter) def test_qubo_gas_int_simple_neg_constant(self): """ Test for a negative constant """ @@ -112,9 +116,10 @@ def test_qubo_gas_int_simple_neg_constant(self): constant = -2 # Get the optimum key and value. - gmf = GroverMinimumFinder(num_iterations=6, verbose=VERBOSE) + n_iter = 8 + gmf = GroverMinimumFinder(num_iterations=n_iter, verbose=VERBOSE) results = gmf.solve(quadratic, linear, constant, num_value) - self.validate_results(results) + self.validate_results(results, n_iter) def test_qubo_gas_int_paper_example(self): """ Test the example from https://arxiv.org/abs/1912.04088 """ @@ -130,6 +135,7 @@ def test_qubo_gas_int_paper_example(self): quadratic = quadratic.dot(q) # Get the optimum key and value. - gmf = GroverMinimumFinder(num_iterations=20, verbose=VERBOSE) + n_iter = 10 + gmf = GroverMinimumFinder(num_iterations=n_iter, verbose=VERBOSE) results = gmf.solve(quadratic, linear, 0, num_value) - self.validate_results(results) + self.validate_results(results, 10) From bbb1a95868eb6b292d0618ab55c77291b1df9bfb Mon Sep 17 00:00:00 2001 From: Giacomo Nannicini Date: Tue, 10 Mar 2020 15:29:25 -0400 Subject: [PATCH 042/323] Added gaussian smoothed optimizer GSLS --- qiskit/aqua/components/optimizers/__init__.py | 3 + qiskit/aqua/components/optimizers/gsls.py | 309 ++++++++++++++++++ 2 files changed, 312 insertions(+) create mode 100644 qiskit/aqua/components/optimizers/gsls.py diff --git a/qiskit/aqua/components/optimizers/__init__.py b/qiskit/aqua/components/optimizers/__init__.py index 0e23d9839c..6fbb77489e 100644 --- a/qiskit/aqua/components/optimizers/__init__.py +++ b/qiskit/aqua/components/optimizers/__init__.py @@ -50,6 +50,7 @@ CG COBYLA L_BFGS_B + GSLS NELDER_MEAD P_BFGS POWELL @@ -86,6 +87,7 @@ from .cg import CG from .cobyla import COBYLA from .l_bfgs_b import L_BFGS_B +from .gsls import GSLS from .nelder_mead import NELDER_MEAD from .p_bfgs import P_BFGS from .powell import POWELL @@ -104,6 +106,7 @@ 'AQGD', 'CG', 'COBYLA', + 'GSLS', 'L_BFGS_B', 'NELDER_MEAD', 'P_BFGS', diff --git a/qiskit/aqua/components/optimizers/gsls.py b/qiskit/aqua/components/optimizers/gsls.py new file mode 100644 index 0000000000..9d37a3d118 --- /dev/null +++ b/qiskit/aqua/components/optimizers/gsls.py @@ -0,0 +1,309 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2020. +# +# 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. + +"""Line search with Gaussian-smoothed samples on a sphere.""" + +from typing import Optional +import logging +import numpy as np + +from .optimizer import Optimizer + +logger = logging.getLogger(__name__) + + +class GSLS(Optimizer): + """Gaussian-smoothed Line Search. + + + An implementation of the line search algorithm described in + https://arxiv.org/pdf/1905.01332.pdf, using gradient approximation + based on Gaussian-smoothed samples on a sphere. + + """ + + _OPTIONS = ['max_iter', 'disp', 'sampling_radius', 'sample_size_factor', + 'initial_step_size', 'min_step_size', 'step_size_multiplier', + 'armijo_parameter', 'min_gradient_norm', + 'max_failed_rejection_sampling'] + + # pylint: disable=unused-argument + def __init__(self, + max_iter: int = 1000, + disp: bool = False, + sampling_radius: float = 1.0e-3, + sample_size_factor: int = 1, + initial_step_size: float = 1.0e-1, + min_step_size: float = 1.0e-10, + step_size_multiplier: float = 0.5, + armijo_parameter: float = 1.0e-2, + min_gradient_norm: float = 1e-5, + max_failed_rejection_sampling: int = 50) -> None: + """Args: + + sampling_radius : Sampling radius to determine gradient + estimate. + sample_size_factor : The size of the sample set at each + iteration is this number multiplied by + the dimension of the problem, rounded to + the nearest integer. + initial_step_size : Initial step size for the descent + algorithm. + min_step_size : Minimum step size for the descent algorithm. + step_size_multiplier : Step size reduction after unsuccessful + steps, in the interval (0, 1). + armijo_parameter : Armijo parameter for sufficient decrease + criterion, in the interval (0, 1). + min_gradient_norm : If the gradient norm is below this + threshold, the algorithm stops. + max_failed_rejection_sampling : Maximum number of attempts to + sample points within bounds. + + """ + super().__init__() + for k, v in locals().items(): + if k in self._OPTIONS: + self._options[k] = v + + def get_support_level(self): + """ Return support level dictionary """ + return { + 'gradient': Optimizer.SupportLevel.ignored, + 'bounds': Optimizer.SupportLevel.supported, + 'initial_point': Optimizer.SupportLevel.required + } + + def optimize(self, num_vars, objective_function, gradient_function=None, + variable_bounds=None, initial_point=None): + super().optimize(num_vars, objective_function, gradient_function, + variable_bounds, initial_point) + + if initial_point is None: + initial_point = np.random.normal(size=num_vars) + else: + initial_point = np.array(initial_point) + + if variable_bounds is None: + var_lb = np.array([float('-inf')] * num_vars) + var_ub = np.array([float('inf')] * num_vars) + else: + var_lb = np.array([l for (l, u) in variable_bounds]) + var_ub = np.array([u for (l, u) in variable_bounds]) + + x, x_value, n_evals, grad_nom = self.ls_optimize( + num_vars, objective_function, initial_point, var_lb, var_ub) + + return x, x_value, n_evals + + def ls_optimize(self, n, obj_fun, initial_point, var_lb, var_ub): + """Run the line search optimization. + + Args: + + n : Dimension of the problem. + obj_fun : Objective function. + initial_point : Initial point. Must be a Numpy array. + var_lb : Vector of lower bounds on the decision + variables. Vector elements can be float('-inf') if + the corresponding variable is unbounded from + below. Must be a Numpy array. + var_ub : Vector of upper bounds on the decision + variables. Vector elements can be float('inf') if + the corresponding variable is unbounded from + below. Must be a Numpy array. + + Returns: Final iterate as a vector, corresponding objective + function value, number of evaluations, and norm of the + gradient estimate. + + """ + assert(len(initial_point) == n) + assert(len(var_lb) == n) + assert(len(var_ub) == n) + # Initialize counters and data + iter_count = 0 + n_evals = 0 + stop = False + prev_iter_successful = True + consecutive_fail_iter = 0 + alpha = self._options['initial_step_size'] + grad_norm = float('inf') + sample_set_size = int(round(self._options['sample_size_factor'] * n)) + # Initial point + x = initial_point + x_value = obj_fun(x) + n_evals += 1 + while (iter_count < self._options['max_iter'] and not stop): + # Determine set of sample points + u, sample_set_x = self.sample_set(n, x, var_lb, var_ub, + sample_set_size) + sample_set_y = np.array( + [obj_fun(point) for point in sample_set_x]) + n_evals += len(sample_set_x) + # Expand sample set if we could not improve + if (not prev_iter_successful): + u = np.vstack((prev_u, u)) + sample_set_x = np.vstack((prev_sample_set_x, sample_set_x)) + sample_set_y = np.hstack((prev_sample_set_y, sample_set_y)) + # Find gradient approximation and candidate point + grad = self.gradient_approximation(n, x, x_value, u, + sample_set_x, sample_set_y) + grad_norm = np.sqrt(grad.dot(grad)) + d = grad + new_x = np.clip(x - alpha * d, var_lb, var_ub) + new_x_value = obj_fun(new_x) + n_evals += 1 + # Print information + if (self._options['disp']): + print('Iter {:d}'.format(iter_count)) + print('Point ' + str(x) + ' obj ' + str(x_value)) + print('Gradient' + str(grad)) + print('Grad norm ' + str(grad_norm) + ' new_x_value ' + + str(new_x_value) + ' step size ' + str(alpha)) + print('Direction ' + str(d)) + # Test Armijo condition for sufficient decrease + if (new_x_value <= x_value - + self._options['armijo_parameter'] * alpha * grad_norm): + # Accept point + x = new_x + x_value = new_x_value + alpha /= self._options['step_size_multiplier'] + prev_iter_successful = True + consecutive_fail_iter = 0 + # Reset sample set + prev_u = None + prev_sample_set_x = None + prev_sample_set_y = None + else: + # Do not accept point + alpha *= self._options['step_size_multiplier'] + prev_iter_successful = False + consecutive_fail_iter += 1 + # Store sample set to enlarge it + prev_u = u + prev_sample_set_x = sample_set_x + prev_sample_set_y = sample_set_y + iter_count += 1 + if (grad_norm <= self._options['min_gradient_norm'] or + alpha <= self._options['min_step_size']): + stop = True + return x, x_value, n_evals, grad_norm + # -- end function + + def sample_set(self, n, x, var_lb, var_ub, num_points): + """Construct sample set of given size. + + Args: + n : Dimension of the problem. + x : Point around which the sample set is constructed, as a 1D + numpy.ndarray[float] + var_lb : Vector of lower bounds on the decision + variables. Vector elements can be float('-inf') if + the corresponding variable is unbounded from + below. Must be a 1D numpy.ndarray[float]. + var_lb : Vector of upper bounds on the decision + variables. Vector elements can be float('inf') if + the corresponding variable is unbounded from + below. Must be a 1D numpy.ndarray[float]. + num_points : Number of points in the sample set. + + Returns: Matrices of (unit-norm) sample directions and sample + points, one per row. Both matrices are 2D + numpy.ndarray[float]. + + """ + # Generate points uniformly on the sphere + normal_samples = np.random.normal(size=(num_points,n)) + row_norms = np.sqrt(np.sum(normal_samples**2, 1, keepdims=True)) + directions = normal_samples / row_norms + points = x + self._options['sampling_radius'] * directions + # Check bounds + if ((points >= var_lb).all() and (points <= var_ub).all()): + # If all points are within bounds, return them + return directions, (x + self._options['sampling_radius'] * + directions) + else: + # Otherwise we perform rejection sampling until we have + # enough points that satisfy the bounds + indices = np.where((points >= var_lb).all(axis=1) & + (points <= var_ub).all(axis=1))[0] + accepted = directions[indices] + num_trials = 0 + while (len(accepted) < num_points and + num_trials < self._options['max_failed_rejection_sampling']): + # Generate points uniformly on the sphere + normal_samples = np.random.normal(size=(num_points,n)) + row_norms = np.sqrt(np.sum(normal_samples**2, 1, + keepdims=True)) + directions = normal_samples / row_norms + points = x + self._options['sampling_radius'] * directions + indices = np.where((points >= var_lb).all(axis=1) & + (points <= var_ub).all(axis=1))[0] + accepted = np.vstack((accepted, directions[indices])) + num_trials += 1 + # When we are at a corner point, the expected fraction of + # acceptable points may be exponential small in the + # dimension of the problem. Thus, if we keep failing and + # do not have enough points by now, we switch to a + # different method that guarantees finding enough points, + # but they may not be uniformly distributed. + if (len(accepted) < num_points): + normal_samples = np.random.normal(size=(num_points,n)) + row_norms = np.sqrt(np.sum(normal_samples**2, 1, + keepdims=True)) + directions = normal_samples / row_norms + points = x + self._options['sampling_radius'] * directions + to_be_flipped = (points < var_lb) | (points > var_ub) + directions *= np.where(to_be_flipped, -1, 1) + points = x + self._options['sampling_radius'] * directions + indices = np.where((points >= var_lb).all(axis=1) & + (points <= var_ub).all(axis=1))[0] + accepted = np.vstack((accepted, directions[indices])) + # If we still do not have enough sampling points, we have + # failed. + if (len(accepted) < num_points): + raise RuntimeError('Could not generate enough samples ' + + 'within bounds; try smaller radius.') + return (accepted[:num_points], + x + self._options['sampling_radius'] * + accepted[:num_points]) + # -- end function + + def gradient_approximation(self, n, x, x_value, directions, + sample_set_x, sample_set_y): + """Construct gradient approximation from given sample. + + Args: + + n : Dimension of the problem. + x : Point around which the sample set was constructed, as a 1D + numpy.ndarray[float]. + x_value : Objective function value at x. + directions : Directions of the sample points wrt the central + point x, as a 2D numpy.ndarray[float]. + sample_set_x : x-coordinates of the sample set, one point per + row, as a 2D numpy.ndarray[float]. + sample_set_y : Objective function values of the points in + sample_set_x, as a 1D numpy.ndarray[float]. + + Returns: Gradient approximation at x, as a 1D numpy.ndarray[float]. + + """ + ffd = (sample_set_y - x_value) + gradient = ((float(n) / len(sample_set_y)) * np.sum( + ffd.reshape(len(sample_set_y), 1) / + self._options['sampling_radius'] * directions, 0)) + return gradient + # -- end function + From f4119be8b0f7bdd9f7000e1a54ddedb5b0e4002d Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Tue, 10 Mar 2020 16:31:23 -0400 Subject: [PATCH 043/323] Update Comments/Pylint Dict to Match Spelling Recommendations (#847) --- .pylintdict | 3 +++ qiskit/optimization/algorithms/grover_minimum_finder.py | 2 +- .../optimization_problem_to_negative_value_oracle.py | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.pylintdict b/.pylintdict index e586468e76..ce7d46409f 100644 --- a/.pylintdict +++ b/.pylintdict @@ -132,6 +132,7 @@ dj dnf docplex dp +durr ecc ee eigen @@ -206,6 +207,7 @@ hassidim hb hhl hk +hoyer https iadd idx @@ -222,6 +224,7 @@ indices indvar init initializer +initialstate initio innerproduct inreg diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index 5c38617528..16639f121a 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -131,7 +131,7 @@ def solve(self, quadratic, linear, constant, num_output_qubits): improvement_found = True threshold = optimum_value else: - # If we haven't found a better number after n iter, we've found the optimal. + # If we haven't found a better number after the max number of iterations, we assume the optimal. if loops_with_no_improvement >= self._n_iterations: improvement_found = True optimum_found = True diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index 4171e6504b..a4a6a03bb0 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -164,7 +164,7 @@ def construct_circuit(self): class QQUBODictionary(QuantumDictionary): """ - A QDictionary that creates a state preparation operator for a given QUBO problem. + A Quantum Dictionary that creates a state preparation operator for a given QUBO problem. """ def __init__(self, key_bits, value_bits, ancilla_bits, func_dict, @@ -185,7 +185,7 @@ def __init__(self, key_bits, value_bits, ancilla_bits, func_dict, @property def circuit(self): """ - Provides the circuit of the QuantumDictionary. Will construct one if not yet created. + Provides the circuit of the Quantum Dictionary. Will construct one if not yet created. Returns: QuantumCircuit: Circuit object describing the Quantum Dictionary. """ From 746b96a9aa4d0f6501000b844ace033066ab5a06 Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Tue, 10 Mar 2020 17:03:47 -0400 Subject: [PATCH 044/323] Quick fix for Style (#847) --- qiskit/optimization/algorithms/grover_minimum_finder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index 16639f121a..52e0c514be 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -131,12 +131,12 @@ def solve(self, quadratic, linear, constant, num_output_qubits): improvement_found = True threshold = optimum_value else: - # If we haven't found a better number after the max number of iterations, we assume the optimal. + # No better number after the max number of iterations, so we assume the optimal. if loops_with_no_improvement >= self._n_iterations: improvement_found = True optimum_found = True - # Using Durr-Hoyer's method, increase m. + # Using Durr and Hoyer method, increase m. m = int(np.ceil(min(m * 8/7, 2**(n_key / 2)))) if self._verbose: print("No Improvement.") From 83d7faf795f2a4c3d5e73f70007b996fd8f772c5 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Wed, 11 Mar 2020 11:49:11 +0900 Subject: [PATCH 045/323] add a test --- qiskit/optimization/problems/variables.py | 2 +- test/optimization/test_objective.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/qiskit/optimization/problems/variables.py b/qiskit/optimization/problems/variables.py index 74b1c8157f..9553bdac9f 100644 --- a/qiskit/optimization/problems/variables.py +++ b/qiskit/optimization/problems/variables.py @@ -369,7 +369,7 @@ def _setter(self, setfunc: Callable[[Union[int, str], Any], None], *args): elif len(args) == 2: setfunc(*args) else: - raise QiskitOptimizationError("Invalid arguments to set_lower_bounds: {}".format(args)) + raise QiskitOptimizationError("Invalid arguments: {}".format(args)) def _getter(self, lst: List[Any], *args): if len(args) == 0: diff --git a/test/optimization/test_objective.py b/test/optimization/test_objective.py index 05ee460d8a..2d0dca486d 100644 --- a/test/optimization/test_objective.py +++ b/test/optimization/test_objective.py @@ -24,6 +24,13 @@ class TestObjective(QiskitOptimizationTestCase): def setUp(self): super().setUp() + def test_obj_sense(self): + op = OptimizationProblem() + self.assertEqual(op.objective.sense.minimize, 1) + self.assertEqual(op.objective.sense.maximize, -1) + self.assertEqual(op.objective.sense[1], 'minimize') + self.assertEqual(op.objective.sense[-1], 'maximize') + def test_set_empty_quadratic(self): op = OptimizationProblem() op.objective.set_quadratic([]) From c98428fa44f5429c487292476be97cc0de312a27 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Wed, 11 Mar 2020 16:07:32 +0900 Subject: [PATCH 046/323] refactor variables and quadratic constraints --- .../problems/optimization_problem.py | 4 +- .../problems/quadratic_constraint.py | 124 ++++++++++-------- qiskit/optimization/problems/variables.py | 57 ++++---- qiskit/optimization/utils/base.py | 58 ++++---- test/optimization/test_variables.py | 6 + 5 files changed, 138 insertions(+), 111 deletions(-) diff --git a/qiskit/optimization/problems/optimization_problem.py b/qiskit/optimization/problems/optimization_problem.py index 956dd4cfef..441a29d014 100755 --- a/qiskit/optimization/problems/optimization_problem.py +++ b/qiskit/optimization/problems/optimization_problem.py @@ -69,11 +69,11 @@ def __init__(self, *args): """See `qiskit.optimization.LinearConstraintInterface()` """ self.quadratic_constraints = QuadraticConstraintInterface( - varsgetindexfunc = self.variables._varsgetindexfunc) + varindex=self.variables.get_indices) """See `qiskit.optimization.QuadraticConstraintInterface()` """ # pylint: disable=unexpected-keyword-arg - self.objective = ObjectiveInterface(varsgetindexfunc=self.variables._varsgetindexfunc) + self.objective = ObjectiveInterface(varindex=self.variables.get_indices) """See `qiskit.optimization.ObjectiveInterface()` """ self.solution = SolutionInterface() diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index 38e8638c13..71d85b7ea7 100755 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -12,14 +12,15 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +import copy from collections.abc import Sequence from logging import getLogger -from typing import List, Dict, Tuple +from typing import List, Dict, Tuple, Callable from cplex import SparsePair, SparseTriple from qiskit.optimization.utils.base import BaseInterface -from qiskit.optimization.utils.helpers import convert, NameIndex +from qiskit.optimization.utils.helpers import NameIndex from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError logger = getLogger(__name__) @@ -28,7 +29,7 @@ class QuadraticConstraintInterface(BaseInterface): """Methods for adding, modifying, and querying quadratic constraints.""" - def __init__(self, varsgetindexfunc=None): + def __init__(self, varindex: Callable): """Creates a new QuadraticConstraintInterface. The quadratic constraints interface is exposed by the top-level @@ -41,15 +42,16 @@ def __init__(self, varsgetindexfunc=None): self._names = [] self._lin_expr: List[Dict[int, float]] = [] self._quad_expr: List[Dict[Tuple[int, int], float]] = [] - self._name_index = NameIndex() - self._varsgetindexfunc = varsgetindexfunc + self._index = NameIndex() + self._varindex = varindex def get_num(self) -> int: """Returns the number of quadratic constraints. Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = ['x','y']) >>> l = SparsePair(ind = ['x'], val = [1.0]) >>> q = SparseTriple(ind1 = ['x'], ind2 = ['y'], val = [1.0]) @@ -97,7 +99,8 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): Raises: QiskitOptimizationError: if invalid argument is given. - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = ['x','y']) >>> l = SparsePair(ind = ['x'], val = [1.0]) >>> q = SparseTriple(ind1 = ['x'], ind2 = ['y'], val = [1.0]) @@ -113,7 +116,7 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): # check constraint name if name == '': name = 'q{}'.format(len(self._names)) - if name in self._name_index: + if name in self._index: raise QiskitOptimizationError('Duplicate quadratic constraint name: {}'.format(name)) self._names.append(name) @@ -130,7 +133,7 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): else: raise QiskitOptimizationError('Invalid lin_expr: {}'.format(lin_expr)) for i, val in zip(ind, val): - i2 = convert(i, self._varsgetindexfunc) + i2 = self._varindex(i) if i2 in lin_expr_dict: logger.warning('lin_expr contains duplicate index: {}'.format(i)) lin_expr_dict[i2] = val @@ -150,8 +153,8 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): else: raise QiskitOptimizationError('Invalid quad_expr: {}'.format(quad_expr)) for i, j, val in zip(ind1, ind2, val): - i2 = convert(i, self._varsgetindexfunc) - j2 = convert(j, self._varsgetindexfunc) + i2 = self._varindex(i) + j2 = self._varindex(j) if i2 < j2: i2, j2 = j2, i2 if (i2, j2) in quad_expr_dict: @@ -165,7 +168,7 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): self._senses.append(sense) self._rhs.append(rhs) - return self._name_index.convert(name) + return self._index.convert(name) def delete(self, *args): """Deletes quadratic constraints from the problem. @@ -199,7 +202,8 @@ def delete(self, *args): Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names=['x', 'y']) >>> l = SparsePair(ind=['x'], val=[1.0]) >>> q = SparseTriple(ind1=['x'], ind2=['y'], val=[1.0]) @@ -229,10 +233,10 @@ def delete(self, *args): self._names = [] self._lin_expr = [] self._quad_expr = [] - self._name_index = NameIndex() + self._index = NameIndex() return - keys = self._name_index.convert(*args) + keys = self._index.convert(*args) if isinstance(keys, int): keys = [keys] for i in sorted(keys, reverse=True): @@ -241,7 +245,7 @@ def delete(self, *args): del self._names[i] del self._lin_expr[i] del self._quad_expr[i] - self._name_index.build(self._names) + self._index.build(self._names) def get_rhs(self, *args): """Returns the righthand side of a set of quadratic constraints. @@ -270,7 +274,8 @@ def get_rhs(self, *args): end. Equivalent to quadratic_constraints.get_rhs(range(begin, end + 1)). - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = [str(i) for i in range(10)]) >>> [op.quadratic_constraints.add(rhs=1.5 * i, name=str(i)) ... for i in range(10)] @@ -286,12 +291,14 @@ def get_rhs(self, *args): >>> op.quadratic_constraints.get_rhs() [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] """ + + def _get(i): + return self._rhs[i] + if len(args) == 0: - return self._rhs - keys = self._name_index.convert(*args) - if isinstance(keys, int): - return self._rhs[keys] - return [self._rhs[k] for k in keys] + return copy.deepcopy(self._rhs) + keys = self._index.convert(*args) + return self._getter(_get, keys) def get_senses(self, *args): """Returns the senses of a set of quadratic constraints. @@ -319,7 +326,8 @@ def get_senses(self, *args): end. Equivalent to quadratic_constraints.get_senses(range(begin, end + 1)). - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = ["x0"]) >>> [op.quadratic_constraints.add(name=str(i), sense=j) ... for i, j in enumerate("GGLL")] @@ -335,12 +343,14 @@ def get_senses(self, *args): >>> op.quadratic_constraints.get_senses() ['G', 'G', 'L', 'L'] """ + + def _get(i): + return self._senses[i] + if len(args) == 0: - return self._senses - keys = self._name_index.convert(*args) - if isinstance(keys, int): - return self._senses[keys] - return [self._senses[k] for k in keys] + return copy.deepcopy(self._senses) + keys = self._index.convert(*args) + return self._getter(_get, keys) def get_linear_num_nonzeros(self, *args): """Returns the number of nonzeros in the linear part of a set of quadratic constraints. @@ -369,7 +379,8 @@ def get_linear_num_nonzeros(self, *args): inclusive of end. Equivalent to quadratic_constraints.get_linear_num_nonzeros(range(begin, end + 1)). - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = [str(i) for i in range(11)], types = "B" * 11) >>> [op.quadratic_constraints.add( ... name = str(i), @@ -388,13 +399,12 @@ def get_linear_num_nonzeros(self, *args): [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] """ - def _nonzero(tab: Dict[int, float]) -> int: + def _nonzero(i) -> int: + tab = self._lin_expr[i] return len([0 for v in tab.values() if v != 0.0]) - keys = self._name_index.convert(*args) - if isinstance(keys, int): - return _nonzero(self._lin_expr[keys]) - return [_nonzero(self._lin_expr[k]) for k in keys] + keys = self._index.convert(*args) + return self._getter(_nonzero, keys) def get_linear_components(self, *args): """Returns the linear part of a set of quadratic constraints. @@ -425,7 +435,8 @@ def get_linear_components(self, *args): inclusive of end. Equivalent to quadratic_constraints.get_linear_components(range(begin, end + 1)). - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = [str(i) for i in range(11)], types = "B" * 11) >>> [op.quadratic_constraints.add( ... name = str(i), @@ -445,13 +456,12 @@ def get_linear_components(self, *args): [SparsePair(ind = [], val = []), SparsePair(ind = [0], val = [1.0]), SparsePair(ind = [0, 1], val = [1.0, 2.0]), SparsePair(ind = [0, 1, 2], val = [1.0, 2.0, 3.0])] """ - def _linear_component(tab: Dict[int, float]) -> SparsePair: + def _linear_component(i) -> SparsePair: + tab = self._lin_expr[i] return SparsePair(ind=list(tab.keys()), val=list(tab.values())) - keys = self._name_index.convert(*args) - if isinstance(keys, int): - return _linear_component(self._lin_expr[keys]) - return [_linear_component(self._lin_expr[k]) for k in keys] + keys = self._index.convert(*args) + return self._getter(_linear_component, keys) def get_quad_num_nonzeros(self, *args): """Returns the number of nonzeros in the quadratic part of a set of quadratic constraints. @@ -480,7 +490,8 @@ def get_quad_num_nonzeros(self, *args): inclusive of end. Equivalent to quadratic_constraints.get_quad_num_nonzeros(range(begin, end + 1)). - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = [str(i) for i in range(11)]) >>> [op.quadratic_constraints.add( ... name = str(i), @@ -499,13 +510,12 @@ def get_quad_num_nonzeros(self, *args): [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] """ - def _nonzero(tab: Dict[Tuple[int, int], float]) -> int: + def _nonzero(i) -> int: + tab = self._quad_expr[i] return len([0 for v in tab.values() if v != 0.0]) - keys = self._name_index.convert(*args) - if isinstance(keys, int): - return _nonzero(self._quad_expr[keys]) - return [_nonzero(self._quad_expr[k]) for k in keys] + keys = self._index.convert(*args) + return self._getter(_nonzero, keys) def get_quadratic_components(self, *args): """Returns the quadratic part of a set of quadratic constraints. @@ -554,14 +564,13 @@ def get_quadratic_components(self, *args): [SparseTriple(ind1 = [0], ind2 = [0], val = [1.0]), SparseTriple(ind1 = [0, 1], ind2 = [0, 1], val = [1.0, 2.0]), SparseTriple(ind1 = [0, 1, 2], ind2 = [0, 1, 2], val = [1.0, 2.0, 3.0]), SparseTriple(ind1 = [0, 1, 2, 3], ind2 = [0, 1, 2, 3], val = [1.0, 2.0, 3.0, 4.0])] """ - def _quadratic_component(tab: Dict[Tuple[int, int], float]) -> SparseTriple: + def _quadratic_component(i) -> SparseTriple: + tab = self._quad_expr[i] ind1, ind2 = zip(*tab.keys()) return SparseTriple(ind1=list(ind1), ind2=list(ind2), val=list(tab.values())) - keys = self._name_index.convert(*args) - if isinstance(keys, int): - return _quadratic_component(self._quad_expr[keys]) - return [_quadratic_component(self._quad_expr[k]) for k in keys] + keys = self._index.convert(*args) + return self._getter(_quadratic_component, keys) def get_names(self, *args): """Returns the names of a set of quadratic constraints. @@ -588,7 +597,8 @@ def get_names(self, *args): begin and end, inclusive of end. Equivalent to quadratic_constraints.get_names(range(begin, end + 1)). - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization.problems import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = [str(i) for i in range(11)]) >>> [op.quadratic_constraints.add( ... name = "q" + str(i), @@ -606,9 +616,11 @@ def get_names(self, *args): >>> op.quadratic_constraints.get_names() ['q1', 'q2', 'q3', 'q4', 'q5', 'q6', 'q7', 'q8', 'q9', 'q10'] """ + + def _get(i): + return self._names[i] + if len(args) == 0: return self._names - keys = self._name_index.convert(*args) - if isinstance(keys, int): - return self._names[keys] - return [self._names[k] for k in keys] + keys = self._index.convert(*args) + return self._getter(_get, keys) diff --git a/qiskit/optimization/problems/variables.py b/qiskit/optimization/problems/variables.py index 9553bdac9f..97024427aa 100644 --- a/qiskit/optimization/problems/variables.py +++ b/qiskit/optimization/problems/variables.py @@ -13,8 +13,6 @@ # that they have been altered from the originals. import copy -from collections.abc import Sequence -from typing import Callable, Union, List, Any from qiskit.optimization import infinity from qiskit.optimization.utils.base import BaseInterface @@ -286,7 +284,6 @@ def add(self, obj=None, lb=None, ub=None, types="", names=None, names = ["x" + str(cnt) for cnt in range(len(self._names), len(self._names) + max_length)] self._names.extend(names) - self._index.build(names) return range(len(self._names) - max_length, len(self._names)) @@ -361,24 +358,6 @@ def _delete(i): _delete(i) self._index.build(self._names) - def _setter(self, setfunc: Callable[[Union[int, str], Any], None], *args): - # check for all elements in args whether they are types - if len(args) == 1 and all(isinstance(el, Sequence) and len(el) == 2 for el in args[0]): - for el in args[0]: - setfunc(*el) - elif len(args) == 2: - setfunc(*args) - else: - raise QiskitOptimizationError("Invalid arguments: {}".format(args)) - - def _getter(self, lst: List[Any], *args): - if len(args) == 0: - return copy.deepcopy(lst) - keys = self._index.convert(*args) - if isinstance(keys, int): - return lst[keys] - return [lst[k] for k in keys] - def set_lower_bounds(self, *args): """Sets the lower bound for a variable or set of variables. @@ -552,7 +531,14 @@ def get_lower_bounds(self, *args): >>> op.variables.get_lower_bounds() [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] """ - return self._getter(self._lb, *args) + + def _get(i): + return self._lb[i] + + if len(args) == 0: + return copy.deepcopy(self._lb) + keys = self._index.convert(*args) + return self._getter(_get, keys) def get_upper_bounds(self, *args): """Returns the upper bounds on variables from the problem. @@ -591,7 +577,14 @@ def get_upper_bounds(self, *args): >>> op.variables.get_upper_bounds() [1.0, 2.5, 4.0, 5.5, 7.0, 8.5, 10.0, 11.5, 13.0, 14.5] """ - return self._getter(self._ub, *args) + + def _get(i): + return self._ub[i] + + if len(args) == 0: + return copy.deepcopy(self._ub) + keys = self._index.convert(*args) + return self._getter(_get, keys) def get_names(self, *args): """Returns the names of variables from the problem. @@ -621,7 +614,14 @@ def get_names(self, *args): >>> op.variables.get_names() ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9'] """ - return self._getter(self._names, *args) + + def _get(i): + return self._names[i] + + if len(args) == 0: + return copy.deepcopy(self._names) + keys = self._index.convert(*args) + return self._getter(_get, keys) def get_types(self, *args): """Returns the types of variables from the problem. @@ -655,7 +655,14 @@ def get_types(self, *args): >>> op.variables.get_types() ['C', 'I', 'B', 'S', 'N'] """ - return self._getter(self._types, *args) + + def _get(i): + return self._types[i] + + if len(args) == 0: + return copy.deepcopy(self._types) + keys = self._index.convert(*args) + return self._getter(_get, keys) def get_cols(self, *args): raise QiskitOptimizationError("Please use LinearConstraintInterface instead.") diff --git a/qiskit/optimization/utils/base.py b/qiskit/optimization/utils/base.py index bb35c3dc28..12c57145b3 100755 --- a/qiskit/optimization/utils/base.py +++ b/qiskit/optimization/utils/base.py @@ -13,7 +13,10 @@ # that they have been altered from the originals. -from qiskit.optimization.utils.helpers import convert, _defaultgetindexfunc +from typing import Callable, Sequence, Union, Any + +from qiskit.optimization.utils.helpers import NameIndex +from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError class BaseInterface(object): @@ -27,28 +30,9 @@ def __init__(self): """ if type(self) == BaseInterface: raise TypeError("BaseInterface must be sub-classed") + self._index = NameIndex() - def _conv(self, name, cache=None): - """Converts from names to indices as necessary.""" - return convert(name, self._get_index, cache) - - @staticmethod - def _add_iter(getnumfun, addfun, *args, **kwargs): - """non-public""" - old = getnumfun() - addfun(*args, **kwargs) - return range(old, getnumfun()) - - @staticmethod - def _add_single(getnumfun, addfun, *args, **kwargs): - """non-public""" - addfun(*args, **kwargs) - return getnumfun() - 1 # minus one for zero-based indices - - def _get_index(self, name): - return _defaultgetindexfunc(name) - - def get_indices(self, name): + def get_indices(self, *args): """Converts from names to indices. If name is a string, get_indices returns the index of the @@ -64,7 +48,7 @@ def get_indices(self, name): Example usage: - >>> import cplex + >>> from qiskit.optimization.problems import OptimizationProblem >>> c = cplex.Cplex() >>> indices = c.variables.add(names=["a", "b"]) >>> c.variables.get_indices("a") @@ -72,9 +56,27 @@ def get_indices(self, name): >>> c.variables.get_indices(["a", "b"]) [0, 1] """ - if _defaultgetindexfunc is None: - raise NotImplementedError("This is not an indexed interface") - if isinstance(name, str): - return self._get_index(name) + return self._index.convert(*args) + + @staticmethod + def _setter(setfunc: Callable[[int, Any], None], *args): + # check for all elements in args whether they are types + if len(args) == 1 and all(isinstance(el, Sequence) and len(el) == 2 for el in args[0]): + for el in args[0]: + setfunc(*el) + elif len(args) == 2: + setfunc(*args) else: - return [self._get_index(x) for x in name] + raise QiskitOptimizationError("Invalid arguments: {}".format(args)) + + @staticmethod + def _getter(getfunc: Callable[[int], Any], *args): + # `args` should be already converted into `int` by `get_indices`. + if len(args) == 0: + raise QiskitOptimizationError('Empty arguments should be handled in the caller') + if len(args) == 1: + if isinstance(args[0], Sequence): + args = args[0] + else: + return getfunc(args[0]) + return [getfunc(k) for k in args] diff --git a/test/optimization/test_variables.py b/test/optimization/test_variables.py index 5ce0044e89..76aff1fba1 100644 --- a/test/optimization/test_variables.py +++ b/test/optimization/test_variables.py @@ -205,3 +205,9 @@ def test_obj(self): op = OptimizationProblem() with self.assertRaises(QiskitOptimizationError): op.variables.get_obj() + + def test_get_indices(self): + op = OptimizationProblem() + op.variables.add(names=['a', 'b']) + self.assertEqual(op.variables.get_indices('a'), 0) + self.assertListEqual(op.variables.get_indices(), [0, 1]) From 65d64c34f8b839430d37d51308eb5d7cea91dc57 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Wed, 11 Mar 2020 19:25:44 +0900 Subject: [PATCH 047/323] completed tests of objective --- qiskit/optimization/problems/objective.py | 250 ++++++++++------------ test/optimization/test_objective.py | 199 +++++++++++++++++ 2 files changed, 311 insertions(+), 138 deletions(-) mode change 100755 => 100644 qiskit/optimization/problems/objective.py diff --git a/qiskit/optimization/problems/objective.py b/qiskit/optimization/problems/objective.py old mode 100755 new mode 100644 index 7cd1fe5017..d782bd8150 --- a/qiskit/optimization/problems/objective.py +++ b/qiskit/optimization/problems/objective.py @@ -12,18 +12,22 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -from collections.abc import Sequence import copy import numbers +from collections.abc import Sequence +from logging import getLogger +from typing import Callable, Union, List -from qiskit.optimization.utils import BaseInterface, QiskitOptimizationError -from qiskit.optimization.utils.helpers import listify, convert from cplex import SparsePair -from cplex.exceptions import CplexSolverError + +from qiskit.optimization.utils import BaseInterface, QiskitOptimizationError +from qiskit.optimization.utils.helpers import NameIndex CPX_MAX = -1 CPX_MIN = 1 +logger = getLogger(__name__) + class ObjSense(object): """Constants defining the sense of the objective function.""" @@ -35,7 +39,8 @@ def __getitem__(self, item): Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> op.objective.sense.minimize 1 >>> op.objective.sense[1] @@ -52,24 +57,15 @@ class ObjectiveInterface(BaseInterface): sense = ObjSense() # See `ObjSense()` - def __init__(self, varsgetindexfunc=None): + def __init__(self, varindex: Callable[[Union[str, List[str]]], int]): super(ObjectiveInterface, self).__init__() self._linear = {} self._quadratic = {} self._name = None self._sense = ObjSense.minimize self._offset = 0.0 - - def defaultvarsgetindexfunc(i): - if isinstance(i, int): - return i - else: - raise ("Cannot convert variable names without appropriate vargetindexfunc!") - - if varsgetindexfunc: - self._varsgetindexfunc = varsgetindexfunc - else: - self._varsgetindexfunc = defaultvarsgetindexfunc + self._index = NameIndex() + self._varindex = varindex def set_linear(self, *args): """Changes the linear part of the objective function. @@ -86,7 +82,8 @@ def set_linear(self, *args): above. Changes the coefficients for the specified variables to the given values. - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = [str(i) for i in range(4)]) >>> op.objective.get_linear() [0.0, 0.0, 0.0, 0.0] @@ -102,26 +99,15 @@ def set_linear(self, *args): """ def _set(i, v): - try: - v = v + 0.0 - if v == 0.0 and i in self._linear: - self._linear.pop(i) - else: - self._linear[convert(i, self._varsgetindexfunc)] = v - except CplexSolverError: - raise QiskitOptimizationError( - "Value of a coefficient needs to allow for addition of a float!") - - # check for all elements in args whether they are types - if len(args) == 1 and all(hasattr(el, '__len__') and len(el) == 2 for el in args[0]): - for el in args[0]: - _set(*el) - elif len(args) == 2: - _set(*args) - else: - raise QiskitOptimizationError("Invalid arguments to set set_linear!") + i = self._varindex(i) + if v == 0.0 and i in self._linear: + del self._linear[i] + else: + self._linear[i] = v + + self._setter(_set, *args) - def set_quadratic(self, *args): + def set_quadratic(self, args: List): """Sets the quadratic part of the objective function. Call this method with a list with length equal to the number @@ -142,7 +128,8 @@ def set_quadratic(self, *args): quadratic objective function, use the method set_quadratic_coefficients. - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = [str(i) for i in range(3)]) >>> op.objective.set_quadratic([SparsePair(ind = [0, 1, 2], val = [1.0, -2.0, 0.5]),\ SparsePair(ind = [0, 1], val = [-2.0, -1.0]),\ @@ -161,33 +148,33 @@ def set_quadratic(self, *args): self._quadratic = {} def _set(i, j, val): - i = convert(i, self._varsgetindexfunc) - j = convert(j, self._varsgetindexfunc) - i_vals = self._quadratic.setdefault(i, {}) - j_vals = self._quadratic.setdefault(j, {}) - # NOTE: The following check is not necessary, considering we clear the _quadratic first - # if i in j_vals.keys() and j_vals[i] != val or j in i_vals.keys() and i_vals[j] != val: - # raise QiskitOptimizationError('Q is not symmetric in set_quadratic.') - i_vals[j] = val - j_vals[i] = val - - if len(args) != 1: - raise QiskitOptimizationError("set_quadratic expects one argument, which is a list") - if args[0] and isinstance(args[0][0], numbers.Number): - for i, val in enumerate(args[0]): + if val == 0 or val == 0.0: + return + i = self._varindex(i) + j = self._varindex(j) + if i not in self._quadratic: + self._quadratic[i] = {} + if j not in self._quadratic: + self._quadratic[j] = {} + self._quadratic[i][j] = self._quadratic[j][i] = val + + if len(args) == 0: + logger.warning('Empty argument %s', args) + elif isinstance(args[0], numbers.Number): + for i, val in enumerate(args): _set(i, i, val) else: - for i, sp in enumerate(args[0]): + for i, sp in enumerate(args): if isinstance(sp, SparsePair): for j, val in zip(sp.ind, sp.val): _set(i, j, val) elif isinstance(sp, Sequence) and len(sp) == 2: - for i, (j, val) in enumerate(zip(sp[0], sp[1])): + for j, val in zip(sp[0], sp[1]): _set(i, j, val) else: - QiskitOptimizationError( - "set_quadratic expects a list of the length equal to the number of " + - "variables, where each entry has a pair of the indices of the other " + + raise QiskitOptimizationError( + "set_quadratic expects a list of the length equal to the number of " + "variables, where each entry has a pair of the indices of the other " "variables and values, or the corresponding SparsePair") def set_quadratic_coefficients(self, *args): @@ -219,7 +206,8 @@ def set_quadratic_coefficients(self, *args): can be time consuming. Instead, use the method set_quadratic to set the quadratic part of the objective efficiently. - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = [str(i) for i in range(3)]) >>> op.objective.set_quadratic_coefficients(0, 1, 1.0) >>> op.objective.get_quadratic() @@ -236,22 +224,31 @@ def set_quadratic_coefficients(self, *args): """ def _set(i, j, val): - i = convert(i, self._varsgetindexfunc) - j = convert(j, self._varsgetindexfunc) - i_vals = self._quadratic.setdefault(i, {}) - j_vals = self._quadratic.setdefault(j, {}) + # set a value or delete an element if val is zero + i = self._varindex(i) + j = self._varindex(j) if val == 0: - if j in i_vals: - i_vals.pop(j) - if i != j and i in j_vals: - j_vals.pop(i) + if i in self._quadratic and j in self._quadratic[i]: + del self._quadratic[i][j] + if len(self._quadratic[i]) == 0: + del self._quadratic[i] + if j in self._quadratic and i in self._quadratic[j]: + del self._quadratic[j][i] + if len(self._quadratic[j]) == 0: + del self._quadratic[j] else: - i_vals[j] = val - j_vals[i] = val - - if len(args) not in (3, 1): - raise QiskitOptimizationError("Wrong number of arguments") - if isinstance(args[0], (str, int)): + if i not in self._quadratic: + self._quadratic[i] = {} + if j not in self._quadratic: + self._quadratic[j] = {} + self._quadratic[i][j] = self._quadratic[j][i] = val + + if (len(args) == 1 and isinstance(args[0], Sequence)) or len(args) == 3: + # valid arguments + pass + else: + raise QiskitOptimizationError("Wrong number of arguments: {}".format(args)) + if len(args) == 3: arg_list = [args] else: arg_list = args[0] @@ -264,7 +261,8 @@ def set_sense(self, sense): The argument to this method must be either objective.sense.minimize or objective.sense.maximize. - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> op.objective.sense[op.objective.get_sense()] 'minimize' >>> op.objective.set_sense(op.objective.sense.maximize) @@ -286,7 +284,8 @@ def set_name(self, name): Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> op.objective.set_name("cost") >>> op.objective.get_name() 'cost' @@ -313,7 +312,8 @@ def get_linear(self, *args): indices the members of s. Equivalent to [objective.get_linear(i) for i in s] - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(obj = [1.5 * i for i in range(10)],\ names = [str(i) for i in range(10)]) >>> op.variables.get_num() @@ -327,24 +327,12 @@ def get_linear(self, *args): """ def _get(i): - i = convert(i, self._varsgetindexfunc) return self._linear.get(i, 0.0) - out = [] if len(args) == 0: return copy.deepcopy(self._linear) - elif len(args) == 1: - if isinstance(args[0], str): - return _get(args[0]) - elif isinstance(args[0], Sequence): - for i in args[0]: - out.append(_get(i)) - else: - return _get(args[0]) - else: - for i in args: - out.append(_get(i)) - return out + keys = self._varindex(*args) + return self._getter(_get, keys) def get_quadratic(self, *args): """Returns a set of columns of the quadratic component of the objective function. @@ -367,7 +355,8 @@ def get_quadratic(self, *args): with the variables with indices the members of s. Equivalent to [objective.get_quadratic(i) for i in s] - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = [str(i) for i in range(10)]) >>> op.variables.get_num() 10 @@ -386,25 +375,13 @@ def get_quadratic(self, *args): """ def _get(i): - i = convert(i, self._varsgetindexfunc) - return SparsePair(list(self._quadratic[i].keys()), list(self._quadratic[i].values())) + qi = self._quadratic.get(i, {}) + return SparsePair(list(qi.keys()), list(qi.values())) - out = [] if len(args) == 0: return copy.deepcopy(self._quadratic) - elif len(args) == 1: - if isinstance(args[0], str): - return _get(args[0]) - elif isinstance(args[0], Sequence): - for i in args[0]: - out.append(_get(i)) - else: - return _get(args[0]) - else: - # NOTE: This is not documented, but perhaps useful. - for i in args: - out.append(_get(i)) - return out + keys = self._varindex(*args) + return self._getter(_get, keys) def get_quadratic_coefficients(self, *args): """Returns individual coefficients from the quadratic objective function. @@ -422,7 +399,8 @@ def get_quadratic_coefficients(self, *args): where sequence is a list or tuple of pairs (v1, v2) as described above. - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = [str(i) for i in range(3)]) >>> op.objective.set_quadratic_coefficients(0, 1, 1.0) >>> op.objective.get_quadratic_coefficients("1", 0) @@ -432,45 +410,40 @@ def get_quadratic_coefficients(self, *args): [5.0, 2.0, 3.0] """ - def _get(i, j): - i = convert(i, self._varsgetindexfunc) - j = convert(j, self._varsgetindexfunc) + def _get(args): + i, j = args return self._quadratic.get(i, {}).get(j, 0) - out = [] if len(args) == 0: return copy.deepcopy(self._quadratic) - elif len(args) == 2: - return _get(args[0], args[1]) elif len(args) == 1: - if isinstance(args[0], str): - raise QiskitOptimizationError('Incompatible type: %s' % args[0]) - elif isinstance(args[0], Sequence): - for (i, j) in args[0]: - out.append(_get(i, j)) - return out - else: - raise QiskitOptimizationError( - "get_quadratic_coefficients passed a single argument that is not iterable.") + i, j = zip(*args[0]) + i = self._varindex(i) + j = self._varindex(j) + return self._getter(_get, *zip(i, j)) + elif len(args) == 2: + i, j = args + i = self._varindex(i) + j = self._varindex(j) + return _get((i, j)) else: - raise QiskitOptimizationError( - "get_quadratic_coefficients expects either values two indices as two arguments, " + - "or a sequence of tuples.") + raise QiskitOptimizationError('Invalid arguments {}'.format(args)) def get_sense(self): """Returns the sense of the objective function. Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> op.objective.sense[op.objective.get_sense()] - 1 + 'minimize' >>> op.objective.set_sense(op.objective.sense.maximize) >>> op.objective.sense[op.objective.get_sense()] - -1 + 'maximize' >>> op.objective.set_sense(op.objective.sense.minimize) >>> op.objective.sense[op.objective.get_sense()] - 1 + 'minimize' """ return self._sense @@ -491,7 +464,8 @@ def get_num_quadratic_variables(self): Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = [str(i) for i in range(3)]) >>> op.objective.set_quadratic_coefficients(0, 1, 1.0) >>> op.objective.get_num_quadratic_variables() @@ -503,14 +477,15 @@ def get_num_quadratic_variables(self): >>> op.objective.get_num_quadratic_variables() 3 """ - return len(self._quadratic.items()) + return len(self._quadratic) def get_num_quadratic_nonzeros(self): """Returns the number of nonzeros in the quadratic objective function. Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = [str(i) for i in range(3)]) >>> op.objective.set_quadratic_coefficients(0, 1, 1.0) >>> op.objective.get_num_quadratic_nonzeros() @@ -522,17 +497,15 @@ def get_num_quadratic_nonzeros(self): >>> op.objective.get_num_quadratic_nonzeros() 3 """ - nnz = 0 - for (i, v) in self._quadratic.items(): - nnz += len(v) - return nnz + return sum(len(v) for v in self._quadratic.values()) def get_offset(self): """Returns the constant offset of the objective function for a problem. Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> offset = op.objective.get_offset() >>> abs(offset - 0.0) < 1e-6 True @@ -544,7 +517,8 @@ def set_offset(self, offset): Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> op.objective.set_offset(3.14) >>> offset = op.objective.get_offset() >>> abs(offset - 3.14) < 1e-6 diff --git a/test/optimization/test_objective.py b/test/optimization/test_objective.py index 2d0dca486d..2a0f469ff9 100644 --- a/test/optimization/test_objective.py +++ b/test/optimization/test_objective.py @@ -14,6 +14,8 @@ """ Test ObjectiveInterface """ +from cplex import SparsePair + from qiskit.optimization import OptimizationProblem from test.optimization.common import QiskitOptimizationTestCase @@ -31,6 +33,203 @@ def test_obj_sense(self): self.assertEqual(op.objective.sense[1], 'minimize') self.assertEqual(op.objective.sense[-1], 'maximize') + def test_set_linear0(self): + """ + op = OptimizationProblem() + op.variables.add(names=[str(i) for i in range(4)]) + self.assertListEqual(op.objective.get_linear(), [0.0, 0.0, 0.0, 0.0]) + op.objective.set_linear(0, 1.0) + self.assertListEqual(op.objective.get_linear(), [1.0, 0.0, 0.0, 0.0]) + op.objective.set_linear('3', -1.0) + self.assertListEqual(op.objective.get_linear(), [1.0, 0.0, 0.0, -1.0]) + op.objective.set_linear([("2", 2.0), (1, 0.5)]) + self.assertListEqual(op.objective.get_linear(), [1.0, 0.5, 2.0, -1.0]) + """ + pass + + def test_set_linear(self): + op = OptimizationProblem() + n = 4 + op.variables.add(names=[str(i) for i in range(n)]) + self.assertDictEqual(op.objective.get_linear(), {}) + op.objective.set_linear(0, 1.0) + self.assertDictEqual(op.objective.get_linear(), {0: 1.0}) + self.assertListEqual(op.objective.get_linear(range(n)), [1.0, 0.0, 0.0, 0.0]) + op.objective.set_linear('3', -1.0) + self.assertDictEqual(op.objective.get_linear(), {0: 1.0, 3: -1.0}) + op.objective.set_linear([("2", 2.0), (1, 0.5)]) + self.assertDictEqual(op.objective.get_linear(), {0: 1.0, 1: 0.5, 2: 2.0, 3: -1.0}) + self.assertListEqual(op.objective.get_linear(range(n)), [1.0, 0.5, 2.0, -1.0]) + def test_set_empty_quadratic(self): op = OptimizationProblem() op.objective.set_quadratic([]) + self.assertRaises(TypeError, lambda: op.objective.set_quadratic()) + + def test_set_quadratic(self): + op = OptimizationProblem() + n = 3 + op.variables.add(names=[str(i) for i in range(n)]) + obj = op.objective + obj.set_quadratic([SparsePair(ind=[0, 1, 2], val=[1.0, -2.0, 0.5]), + ([0, 1], [-2.0, -1.0]), + SparsePair(ind=[0, 2], val=[0.5, -3.0])]) + lst = obj.get_quadratic(range(n)) + self.assertListEqual(lst[0].ind, [0, 1, 2]) + self.assertListEqual(lst[0].val, [1.0, -2.0, 0.5]) + self.assertListEqual(lst[1].ind, [0, 1]) + self.assertListEqual(lst[1].val, [-2.0, -1.0]) + self.assertListEqual(lst[2].ind, [0, 2]) + self.assertListEqual(lst[2].val, [0.5, -3.0]) + + obj.set_quadratic([1.0, 2.0, 3.0]) + lst = obj.get_quadratic(range(n)) + self.assertListEqual(lst[0].ind, [0]) + self.assertListEqual(lst[0].val, [1.0]) + self.assertListEqual(lst[1].ind, [1]) + self.assertListEqual(lst[1].val, [2.0]) + self.assertListEqual(lst[2].ind, [2]) + self.assertListEqual(lst[2].val, [3.0]) + + def test_set_quadratic_coefficients(self): + op = OptimizationProblem() + n = 3 + op.variables.add(names=[str(i) for i in range(n)]) + obj = op.objective + obj.set_quadratic_coefficients(0, 1, 1.0) + lst = op.objective.get_quadratic(range(n)) + self.assertListEqual(lst[0].ind, [1]) + self.assertListEqual(lst[0].val, [1.0]) + self.assertListEqual(lst[1].ind, [0]) + self.assertListEqual(lst[1].val, [1.0]) + self.assertListEqual(lst[2].ind, []) + self.assertListEqual(lst[2].val, []) + + obj.set_quadratic_coefficients([(1, 1, 2.0), (0, 2, 3.0)]) + lst = op.objective.get_quadratic(range(n)) + self.assertListEqual(lst[0].ind, [1, 2]) + self.assertListEqual(lst[0].val, [1.0, 3.0]) + self.assertListEqual(lst[1].ind, [0, 1]) + self.assertListEqual(lst[1].val, [1.0, 2.0]) + self.assertListEqual(lst[2].ind, [0]) + self.assertListEqual(lst[2].val, [3.0]) + + obj.set_quadratic_coefficients([(0, 1, 4.0), (1, 0, 5.0)]) + lst = op.objective.get_quadratic(range(n)) + self.assertListEqual(lst[0].ind, [1, 2]) + self.assertListEqual(lst[0].val, [5.0, 3.0]) + self.assertListEqual(lst[1].ind, [0, 1]) + self.assertListEqual(lst[1].val, [5.0, 2.0]) + self.assertListEqual(lst[2].ind, [0]) + self.assertListEqual(lst[2].val, [3.0]) + + def test_set_senses(self): + op = OptimizationProblem() + self.assertEqual(op.objective.sense[op.objective.get_sense()], 'minimize') + op.objective.set_sense(op.objective.sense.maximize) + self.assertEqual(op.objective.sense[op.objective.get_sense()], 'maximize') + op.objective.set_sense(op.objective.sense.minimize) + self.assertEqual(op.objective.sense[op.objective.get_sense()], 'minimize') + + def test_set_name(self): + op = OptimizationProblem() + op.objective.set_name('cost') + self.assertEqual(op.objective.get_name(), 'cost') + + def test_get_linear(self): + op = OptimizationProblem() + n = 10 + op.variables.add(names=[str(i) for i in range(n)]) + obj = op.objective + obj.set_linear([(i, 1.5 * i) for i in range(n)]) + self.assertEqual(op.variables.get_num(), 10) + self.assertEqual(obj.get_linear(8), 12) + self.assertListEqual(obj.get_linear('1', 3), [1.5, 3.0, 4.5]) + self.assertListEqual(obj.get_linear([2, '0', 5]), [3.0, 0.0, 7.5]) + self.assertListEqual(obj.get_linear(range(n)), + [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5]) + + def test_get_quadratic(self): + op = OptimizationProblem() + n = 10 + op.variables.add(names=[str(i) for i in range(n)]) + obj = op.objective + obj.set_quadratic([1.5 * i for i in range(n)]) + sp = obj.get_quadratic(8) + self.assertListEqual(sp.ind, [8]) + self.assertListEqual(sp.val, [12.0]) + + sp = obj.get_quadratic('1', 3) + self.assertListEqual(sp[0].ind, [1]) + self.assertListEqual(sp[0].val, [1.5]) + self.assertListEqual(sp[1].ind, [2]) + self.assertListEqual(sp[1].val, [3.0]) + self.assertListEqual(sp[2].ind, [3]) + self.assertListEqual(sp[2].val, [4.5]) + + sp = obj.get_quadratic([3, '1', 5]) + self.assertListEqual(sp[0].ind, [3]) + self.assertListEqual(sp[0].val, [4.5]) + self.assertListEqual(sp[1].ind, [1]) + self.assertListEqual(sp[1].val, [1.5]) + self.assertListEqual(sp[2].ind, [5]) + self.assertListEqual(sp[2].val, [7.5]) + + sp = obj.get_quadratic(range(n)) + for i in range(n): + self.assertListEqual(sp[i].ind, [i]) + self.assertListEqual(sp[i].val, [1.5 * i]) + + def test_get_quadratic(self): + op = OptimizationProblem() + n = 3 + op.variables.add(names=[str(i) for i in range(n)]) + obj = op.objective + obj.set_quadratic_coefficients(0, 1, 1.0) + self.assertEqual(obj.get_quadratic_coefficients('1', 0), 1.0) + obj.set_quadratic_coefficients([(1, 1, 2.0), (0, 2, 3.0), (1, 0, 5.0)]) + self.assertListEqual(obj.get_quadratic_coefficients([(1, 0), (1, "1"), (2, "0")]), + [5.0, 2.0, 3.0]) + + def test_get_sense(self): + op = OptimizationProblem() + self.assertEqual(op.objective.sense[op.objective.get_sense()], 'minimize') + op.objective.set_sense(op.objective.sense.maximize) + self.assertEqual(op.objective.sense[op.objective.get_sense()], 'maximize') + op.objective.set_sense(op.objective.sense.minimize) + self.assertEqual(op.objective.sense[op.objective.get_sense()], 'minimize') + + def test_get_name(self): + op = OptimizationProblem() + op.objective.set_name('cost') + self.assertEqual(op.objective.get_name(), 'cost') + + def test_get_num_quadratic_variables(self): + op = OptimizationProblem() + n = 3 + op.variables.add(names=[str(i) for i in range(n)]) + obj = op.objective + obj.set_quadratic_coefficients(0, 1, 1.0) + self.assertEqual(obj.get_num_quadratic_variables(), 2) + obj.set_quadratic([1.0, 0.0, 0.0]) + self.assertEqual(obj.get_num_quadratic_variables(), 1) + obj.set_quadratic_coefficients([(1, 1, 2.0), (0, 2, 3.0)]) + self.assertEqual(obj.get_num_quadratic_variables(), 3) + + def test_get_num_quadratic_nonzeros(self): + op = OptimizationProblem() + n = 3 + op.variables.add(names=[str(i) for i in range(n)]) + obj = op.objective + obj.set_quadratic_coefficients(0, 1, 1.0) + self.assertEqual(obj.get_num_quadratic_nonzeros(), 2) + obj.set_quadratic_coefficients([(1, 1, 2.0), (0, 2, 3.0)]) + self.assertEqual(obj.get_num_quadratic_nonzeros(), 5) + obj.set_quadratic_coefficients([(0, 1, 4.0), (1, 0, 0.0)]) + self.assertEqual(obj.get_num_quadratic_nonzeros(), 3) + + def test_offset(self): + op = OptimizationProblem() + self.assertEqual(op.objective.get_offset(), 0.0) + op.objective.set_offset(3.14) + self.assertEqual(op.objective.get_offset(), 3.14) From 85af18ef72814c7731c2aefff29ba878f377ed9f Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 11 Mar 2020 14:22:36 +0100 Subject: [PATCH 048/323] move finance ising models to applications folder --- qiskit/finance/{ => applications}/ising/__init__.py | 0 qiskit/finance/{ => applications}/ising/portfolio.py | 0 .../{ => applications}/ising/portfolio_diversification.py | 0 .../algorithms/recursive_minimum_eigen_optimizer.py | 4 ++-- 4 files changed, 2 insertions(+), 2 deletions(-) rename qiskit/finance/{ => applications}/ising/__init__.py (100%) rename qiskit/finance/{ => applications}/ising/portfolio.py (100%) rename qiskit/finance/{ => applications}/ising/portfolio_diversification.py (100%) diff --git a/qiskit/finance/ising/__init__.py b/qiskit/finance/applications/ising/__init__.py similarity index 100% rename from qiskit/finance/ising/__init__.py rename to qiskit/finance/applications/ising/__init__.py diff --git a/qiskit/finance/ising/portfolio.py b/qiskit/finance/applications/ising/portfolio.py similarity index 100% rename from qiskit/finance/ising/portfolio.py rename to qiskit/finance/applications/ising/portfolio.py diff --git a/qiskit/finance/ising/portfolio_diversification.py b/qiskit/finance/applications/ising/portfolio_diversification.py similarity index 100% rename from qiskit/finance/ising/portfolio_diversification.py rename to qiskit/finance/applications/ising/portfolio_diversification.py diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index b17755c434..acb28469d4 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -35,7 +35,7 @@ from qiskit.optimization.results import OptimizationResult from qiskit.optimization.converters import (PenalizeLinearEqualityConstraints, IntegerToBinaryConverter) -from qiskit.aqua.algorithms import ClassicalMinimumEigensolver +from qiskit.aqua.algorithms import NumPyMinimumEigensolver class RecursiveMinimumEigenOptimizer(OptimizationAlgorithm): @@ -58,7 +58,7 @@ def __init__(self, min_eigen_optimizer: MinimumEigenOptimizer, min_num_vars: int if min_num_vars_optimizer: self._min_num_vars_optimizer = min_num_vars_optimizer else: - self._min_num_vars_optimizer = MinimumEigenOptimizer(ClassicalMinimumEigensolver()) + self._min_num_vars_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) self._penalty = penalty def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: From d0862f941d32c09742364bcc7ee7c1d5c959ca8a Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Wed, 11 Mar 2020 22:27:53 +0900 Subject: [PATCH 049/323] (wip) refactor linear constraints --- .../problems/linear_constraint.py | 279 +++++------------- qiskit/optimization/problems/objective.py | 2 +- .../problems/optimization_problem.py | 2 +- qiskit/optimization/utils/base.py | 10 +- 4 files changed, 89 insertions(+), 204 deletions(-) diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index f610b4ab65..0466873c30 100755 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -12,15 +12,14 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +import copy from collections.abc import Sequence -import copy +from cplex import SparsePair from qiskit.optimization.utils.base import BaseInterface +from qiskit.optimization.utils.helpers import init_list_args, convert, NameIndex from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError -from qiskit.optimization.utils.helpers import init_list_args, validate_arg_lengths, listify, convert -from cplex import SparsePair - # TODO: can we delete these? CPX_CON_LOWER_BOUND = 1 @@ -39,7 +38,7 @@ class LinearConstraintInterface(BaseInterface): """Methods for adding, modifying, and querying linear constraints.""" - def __init__(self, varsgetindexfunc=None): + def __init__(self, varindex): """Creates a new LinearConstraintInterface. The linear constraints interface is exposed by the top-level @@ -52,8 +51,8 @@ def __init__(self, varsgetindexfunc=None): self._range_values = [] self._names = [] self._lin_expr = [] - self._linconsgetindex = {} - self._varsgetindexfunc = varsgetindexfunc + self._index = NameIndex() + self._varindex = varindex def get_num(self): """Returns the number of linear constraints. @@ -67,16 +66,6 @@ def get_num(self): """ return len(self._names) - def _linconsgetindexfunc(self, item): - if item not in self._linconsgetindex: - self._linconsgetindex[item] = len(self._linconsgetindex) - return self._linconsgetindex[item] - - def _linconsrebuildindex(self): - self._linconsgetindex = {} - for (cnt, item) in enumerate(self._names): - self._linconsgetindex[item] = cnt - def add(self, lin_expr=None, senses="", rhs=None, range_values=None, names=None): """Adds linear constraints to the problem. @@ -159,34 +148,25 @@ def add(self, lin_expr=None, senses="", rhs=None, range_values=None, names = ["c" + str(cnt) for cnt in range(len(self._names), len(self._names) + max_length)] self._names.extend(names) - for name in names: - self._linconsgetindexfunc(name) + self._index.build(self._names) if not lin_expr: lin_expr = [SparsePair()] * max_length - if all(isinstance(el, SparsePair) for el in lin_expr): - for sp in lin_expr: - lin_expr_dict = {} - for i, val in zip(sp.ind, sp.val): - i = convert(i, self._varsgetindexfunc) - if i in lin_expr_dict: - raise QiskitOptimizationError( - 'Variables should only appear once in linear constraint.') - lin_expr_dict[i] = val - self._lin_expr += [lin_expr_dict] - elif all(isinstance(el, Sequence) for el in lin_expr): - for l in lin_expr: - lin_expr_dict = {} - for i, val in zip(l[0], l[1]): - i = convert(i, self._varsgetindexfunc) - if i in lin_expr_dict: - raise QiskitOptimizationError( - 'Variables should only appear once in linear constraint.') - lin_expr_dict[i] = val - self._lin_expr += [lin_expr_dict] - else: - raise QiskitOptimizationError( - 'Invalid lin_expr format in linear_constraint.add().') + for sp in lin_expr: + lin_expr_dict = {} + if isinstance(sp, SparsePair): + zip_iter = zip(sp.ind, sp.val) + elif isinstance(sp, Sequence) and len(sp) == 2: + zip_iter = zip(sp[0], sp[1]) + else: + raise QiskitOptimizationError('Invalid lin_expr: {}'.format(lin_expr)) + for i, val in zip_iter: + i = self._varindex(i) + if i in lin_expr_dict: + raise QiskitOptimizationError( + 'Variables should only appear once in linear constraint.') + lin_expr_dict[i] = val + self._lin_expr += [lin_expr_dict] return range(len(self._names) - max_length, len(self._names)) @@ -238,16 +218,6 @@ def delete(self, *args): >>> op.linear_constraints.get_names() [] """ - - # TODO: delete does not update the index to find constraints by name etc. - - def _delete(i): - del self._rhs[i] - del self._senses[i] - del self._names[i] - del self._lin_expr[i] - del self._range_values[i] - if len(args) == 0: # Delete All: self._rhs = [] @@ -255,22 +225,18 @@ def _delete(i): self._names = [] self._lin_expr = [] self._range_values = [] - self._linconsgetindex = {} - elif len(args) == 1: - # Delete all items from a possibly unordered list of mixed types: - args = listify(convert(args[0], self._linconsgetindexfunc)) - args = sorted(args) - for i, j in enumerate(args): - _delete(j - i) - self._linconsrebuildindex() - elif len(args) == 2: - # Delete range from arg[0] to arg[1]: - start = convert(args[0], self._linconsgetindexfunc) - end = convert(args[1], self._linconsgetindexfunc) - self.delete(range(start, end + 1)) - self._linconsrebuildindex() - else: - raise QiskitOptimizationError("Wrong number of arguments.") + self._index = NameIndex() + + keys = self._index.convert(*args) + if isinstance(keys, int): + keys = [keys] + for i in sorted(keys, reverse=True): + del self._rhs[i] + del self._senses[i] + del self._names[i] + del self._lin_expr[i] + del self._range_values[i] + self._index.build(self._names) def set_rhs(self, *args): """Sets the righthand side of a set of linear constraints. @@ -303,16 +269,9 @@ def set_rhs(self, *args): """ def _set(i, v): - self._rhs[convert(i, self._linconsgetindexfunc)] = v + self._rhs[self._index.convert(i)] = v - if len(args) == 2: - _set(args[0], args[1]) - elif len(args) == 1: - args = listify(args[0]) - for (i, v) in args: - _set(i, v) - else: - raise QiskitOptimizationError("Wrong number of arguments.") + self._setter(_set, *args) def set_names(self, *args): """Sets the name of a linear constraint or set of linear constraints. @@ -342,16 +301,9 @@ def set_names(self, *args): """ def _set(i, v): - self._names[convert(i, self._linconsgetindexfunc)] = v + self._names[self._index.convert(i)] = v - if len(args) == 2: - _set(args[0], args[1]) - elif len(args) == 1: - args = listify(args[0]) - for (i, v) in args: - _set(i, v) - else: - raise QiskitOptimizationError("Wrong number of arguments.") + self._setter(_set, *args) def set_senses(self, *args): """Sets the sense of a linear constraint or set of linear constraints. @@ -387,20 +339,13 @@ def set_senses(self, *args): """ def _set(i, v): - v = v.upper().strip() + v = v.upper() if v in ["G", "L", "E", "R"]: - self._senses[convert(i, getindexfunc=self._linconsgetindexfunc)] = v + self._senses[self._index.convert(i)] = v else: - raise QiskitOptimizationError("Wrong sense!") + raise QiskitOptimizationError("Wrong sense {}".format(v)) - if len(args) == 2: - _set(args[0], args[1]) - elif len(args) == 1: - args = listify(args[0]) - for (i, v) in args: - _set(i, v) - else: - raise QiskitOptimizationError("Wrong number of arguments.") + self._setter(_set, *args) def set_linear_components(self, *args): """Sets a linear constraint or set of linear constraints. @@ -433,30 +378,19 @@ def set_linear_components(self, *args): """ def _set(i, v): - i = convert(i, self._linconsgetindexfunc) if isinstance(v, SparsePair): - ind, val = SparsePair.unpack(v) - for j, w in zip(ind, val): - j = convert(j, self._varsgetindexfunc) - self._lin_expr[i][j] = w - elif isinstance(v, Sequence): - if len(v) != 2: - raise QiskitOptimizationError( - "Wrong linear expression. A SparsePair or a pair of indices and values is expected!") - for j, w in zip(v[0], v[1]): - j = convert(j, self._varsgetindexfunc) - self._lin_expr[i][j] = w + zip_iter = zip(v.ind, v.val) + elif isinstance(v, Sequence) and len(v) == 2: + zip_iter = zip(v[0], v[1]) else: - raise QiskitOptimizationError("Wrong linear expression. A SparsePair is expected!") + raise QiskitOptimizationError( + "Wrong linear expression. A SparsePair is expected: {}".format(v)) + i = self._index.convert(i) + for j, w in zip_iter: + j = self._varindex(j) + self._lin_expr[i][j] = w - if len(args) == 2: - _set(args[0], args[1]) - elif len(args) == 1: - args = listify(args[0]) - for (i, v) in args: - _set(i, v) - else: - raise QiskitOptimizationError("Wrong number of arguments.") + self._setter(_set, *args) def set_range_values(self, *args): """Sets the range values for a set of linear constraints. @@ -505,17 +439,10 @@ def set_range_values(self, *args): """ def _set(i, v): - self._range_values[convert(i, getindexfunc=self._linconsgetindexfunc)] = v + self._range_values[self._index.convert(i)] = v # TODO: raise QiskitOptimizationError("Wrong range!") - if len(args) == 2: - _set(args[0], args[1]) - elif len(args) == 1: - args = listify(args[0]) - for (i, v) in args: - _set(i, v) - else: - raise QiskitOptimizationError("Wrong number of arguments.") + self._setter(_set, *args) def set_coefficients(self, *args): """Sets individual coefficients of the linear constraint matrix. @@ -545,14 +472,14 @@ def set_coefficients(self, *args): """ if len(args) == 3: arg_list = [args] - elif len(args) == 1: - arg_list = listify(args[0]) + elif len(args) == 1 and isinstance(args[0], Sequence): + arg_list = args[0] else: - raise QiskitOptimizationError("Wrong number of arguments") - for ijv in arg_list: - i = convert(ijv[0], self._linconsgetindexfunc) - j = convert(ijv[1], self._varsgetindexfunc) - self._lin_expr[i][j] = ijv[2] + raise QiskitOptimizationError("Invalid arguments {}".format(args)) + for i, j, v in arg_list: + i = self._index.convert(i) + j = self._varindex(j) + self._lin_expr[i][j] = v def get_rhs(self, *args): """Returns the righthand side of constraints from the problem. @@ -588,22 +515,12 @@ def get_rhs(self, *args): """ def _get(i): - i = convert(i, self._linconsgetindexfunc) return self._rhs[i] - out = [] if len(args) == 0: return copy.deepcopy(self._rhs) - elif len(args) == 1: - if isinstance(args[0], Sequence): - for i in args[0]: - out.append(_get(i)) - else: - return _get(args[0]) - else: - for i in args: - out.append(_get(i)) - return out + keys = self._index.convert(*args) + return self._getter(_get, keys) def get_senses(self, *args): """Returns the senses of constraints from the problem. @@ -639,22 +556,12 @@ def get_senses(self, *args): """ def _get(i): - i = convert(i, self._linconsgetindexfunc) return self._senses[i] - out = [] if len(args) == 0: return copy.deepcopy(self._senses) - elif len(args) == 1: - if isinstance(args[0], Sequence): - for i in args[0]: - out.append(_get(i)) - else: - return _get(args[0]) - else: - for i in args: - out.append(_get(i)) - return out + keys = self._index.convert(*args) + return self._getter(_get, keys) def get_range_values(self, *args): """Returns the range values of linear constraints from the problem. @@ -703,22 +610,12 @@ def get_range_values(self, *args): """ def _get(i): - i = convert(i, self._linconsgetindexfunc) return self._range_values[i] - out = [] if len(args) == 0: return copy.deepcopy(self._range_values) - elif len(args) == 1: - if isinstance(args[0], Sequence): - for i in args[0]: - out.append(_get(i)) - else: - return _get(args[0]) - else: - for i in args: - out.append(_get(i)) - return out + keys = self._index.convert(*args) + return self._getter(_get, keys) def get_coefficients(self, *args): """Returns coefficients by row, column coordinates. @@ -748,27 +645,25 @@ def get_coefficients(self, *args): [2.0, -1.0] """ - def _get(i, j): - i = convert(i, self._linconsgetindexfunc) - j = convert(j, self._varsgetindexfunc) + def _get(args): + i, j = args return self._lin_expr[i].get(j, 0) if len(args) == 0: return copy.deepcopy(self._lin_expr) - elif len(args) == 1: - if isinstance(args[0], Sequence): - out = [] - for (i, j) in args[0]: - out.append(_get(i, j)) - return out - else: - raise QiskitOptimizationError( - "Wrong type of arguments. Single argument must be of list type.") + elif len(args) == 1 and isinstance(args[0], Sequence): + i, j = zip(*args[0]) + i = self._index.convert(i) + j = self._varindex(j) + return self._getter(_get, *zip(i, j)) elif len(args) == 2: - return _get(args[0], args[1]) + i, j = args + i = self._index.convert(i) + j = self._varindex(j) + return _get((i, j)) else: raise QiskitOptimizationError( - "Wrong number of arguments. Please use 2 or one list of pairs.") + "Wrong number of arguments. Please use 2 or one list of pairs: {}".format(args)) def get_rows(self, *args): """Returns a set of rows of the linear constraint matrix. @@ -883,19 +778,9 @@ def get_names(self, *args): """ def _get(i): - i = convert(i, self._linconsgetindexfunc) return self._names[i] - out = [] if len(args) == 0: - return copy.deepcopy(self._names) - elif len(args) == 1: - if isinstance(args[0], Sequence): - for i in args[0]: - out.append(_get(i)) - else: - return _get(args[0]) - else: - for i in args: - out.append(_get(i)) - return out + return self._names + keys = self._index.convert(*args) + return self._getter(_get, keys) diff --git a/qiskit/optimization/problems/objective.py b/qiskit/optimization/problems/objective.py index d782bd8150..efcb2dd13f 100644 --- a/qiskit/optimization/problems/objective.py +++ b/qiskit/optimization/problems/objective.py @@ -416,7 +416,7 @@ def _get(args): if len(args) == 0: return copy.deepcopy(self._quadratic) - elif len(args) == 1: + elif len(args) == 1 and isinstance(args[0], Sequence): i, j = zip(*args[0]) i = self._varindex(i) j = self._varindex(j) diff --git a/qiskit/optimization/problems/optimization_problem.py b/qiskit/optimization/problems/optimization_problem.py index 441a29d014..b6067415c3 100755 --- a/qiskit/optimization/problems/optimization_problem.py +++ b/qiskit/optimization/problems/optimization_problem.py @@ -65,7 +65,7 @@ def __init__(self, *args): """See `qiskit.optimization.VariablesInterface()` """ self.linear_constraints = LinearConstraintInterface( - varsgetindexfunc=self.variables._varsgetindexfunc) + varindex=self.variables.get_indices) """See `qiskit.optimization.LinearConstraintInterface()` """ self.quadratic_constraints = QuadraticConstraintInterface( diff --git a/qiskit/optimization/utils/base.py b/qiskit/optimization/utils/base.py index 12c57145b3..58cd8783da 100755 --- a/qiskit/optimization/utils/base.py +++ b/qiskit/optimization/utils/base.py @@ -48,12 +48,12 @@ def get_indices(self, *args): Example usage: - >>> from qiskit.optimization.problems import OptimizationProblem - >>> c = cplex.Cplex() - >>> indices = c.variables.add(names=["a", "b"]) - >>> c.variables.get_indices("a") + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() + >>> indices = op.variables.add(names=["a", "b"]) + >>> op.variables.get_indices("a") 0 - >>> c.variables.get_indices(["a", "b"]) + >>> op.variables.get_indices(["a", "b"]) [0, 1] """ return self._index.convert(*args) From 28190b48f29cc95948077de108627240df1ddd38 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 12 Mar 2020 12:06:49 +0900 Subject: [PATCH 050/323] fixes linear_constraints --- .../problems/linear_constraint.py | 135 +++++----- test/optimization/test_linear_constraints.py | 253 ++++++++---------- 2 files changed, 180 insertions(+), 208 deletions(-) mode change 100755 => 100644 qiskit/optimization/problems/linear_constraint.py diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py old mode 100755 new mode 100644 index 0466873c30..03e8fef2cd --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -18,7 +18,7 @@ from cplex import SparsePair from qiskit.optimization.utils.base import BaseInterface -from qiskit.optimization.utils.helpers import init_list_args, convert, NameIndex +from qiskit.optimization.utils.helpers import init_list_args, NameIndex from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError # TODO: can we delete these? @@ -59,6 +59,7 @@ def get_num(self): Example usage: + >>> from qiskit.optimization import OptimizationProblem >>> op = qiskit.optimization.OptimizationProblem() >>> indices = op.linear_constraints.add(names = ["c1", "c2", "c3"]) >>> op.linear_constraints.get_num() @@ -106,6 +107,7 @@ def add(self, lin_expr=None, senses="", rhs=None, range_values=None, Returns an iterator containing the indices of the added linear constraints. + >>> from qiskit.optimization import OptimizationProblem >>> op = qiskit.optimization.OptimizationProblem() >>> indices = op.variables.add(names = ["x1", "x2", "x3"]) >>> indices = op.linear_constraints.add(\ @@ -129,44 +131,43 @@ def add(self, lin_expr=None, senses="", rhs=None, range_values=None, for arg_length in arg_lengths: if arg_length > 0 and arg_length != max_length: raise QiskitOptimizationError("inconsistent arguments in linear_constraints.add().") + assert max_length > 0 - if max_length > 0: - - if not rhs: - rhs = [0.0] * max_length - self._rhs.extend(rhs) - - if not senses: - senses = "E" * max_length - self._senses.extend(senses) - - if not range_values: - range_values = [0.0] * max_length - self._range_values.extend(range_values) - - if not names: - names = ["c" + str(cnt) for cnt in range(len(self._names), - len(self._names) + max_length)] - self._names.extend(names) - self._index.build(self._names) - - if not lin_expr: - lin_expr = [SparsePair()] * max_length - for sp in lin_expr: - lin_expr_dict = {} - if isinstance(sp, SparsePair): - zip_iter = zip(sp.ind, sp.val) - elif isinstance(sp, Sequence) and len(sp) == 2: - zip_iter = zip(sp[0], sp[1]) - else: - raise QiskitOptimizationError('Invalid lin_expr: {}'.format(lin_expr)) - for i, val in zip_iter: - i = self._varindex(i) - if i in lin_expr_dict: - raise QiskitOptimizationError( - 'Variables should only appear once in linear constraint.') - lin_expr_dict[i] = val - self._lin_expr += [lin_expr_dict] + if not rhs: + rhs = [0.0] * max_length + self._rhs.extend(rhs) + + if not senses: + senses = "E" * max_length + self._senses.extend(senses) + + if not range_values: + range_values = [0.0] * max_length + self._range_values.extend(range_values) + + if not names: + names = ["c" + str(cnt) for cnt in range(len(self._names), + len(self._names) + max_length)] + self._names.extend(names) + self._index.build(self._names) + + if not lin_expr: + lin_expr = [SparsePair()] * max_length + for sp in lin_expr: + lin_expr_dict = {} + if isinstance(sp, SparsePair): + zip_iter = zip(sp.ind, sp.val) + elif isinstance(sp, Sequence) and len(sp) == 2: + zip_iter = zip(sp[0], sp[1]) + else: + raise QiskitOptimizationError('Invalid lin_expr: {}'.format(lin_expr)) + for i, val in zip_iter: + i = self._varindex(i) + if i in lin_expr_dict: + raise QiskitOptimizationError( + 'Variables should only appear once in linear constraint.') + lin_expr_dict[i] = val + self._lin_expr.append(lin_expr_dict) return range(len(self._names) - max_length, len(self._names)) @@ -201,6 +202,7 @@ def delete(self, *args): Example usage: + >>> from qiskit.optimization import OptimizationProblem >>> op = qiskit.optimization.OptimizationProblem() >>> indices = op.linear_constraints.add(names=[str(i) for i in range(10)]) >>> op.linear_constraints.get_num() @@ -219,7 +221,7 @@ def delete(self, *args): [] """ if len(args) == 0: - # Delete All: + # Delete all self._rhs = [] self._senses = [] self._names = [] @@ -256,6 +258,7 @@ def set_rhs(self, *args): the corresponding values. Equivalent to [linear_constraints.set_rhs(pair[0], pair[1]) for pair in seq_of_pairs]. + >>> from qiskit.optimization import OptimizationProblem >>> op = qiskit.optimization.OptimizationProblem() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) >>> op.linear_constraints.get_rhs() @@ -290,6 +293,7 @@ def set_names(self, *args): corresponding strings. Equivalent to [linear_constraints.set_names(pair[0], pair[1]) for pair in seq_of_pairs]. + >>> from qiskit.optimization import OptimizationProblem >>> op = qiskit.optimization.OptimizationProblem() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) >>> op.linear_constraints.set_names("c1", "second") @@ -326,6 +330,7 @@ def set_senses(self, *args): and 'R', indicating greater-than, less-than, equality, and ranged constraints, respectively. + >>> from qiskit.optimization import OptimizationProblem >>> op = qiskit.optimization.OptimizationProblem() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) >>> op.linear_constraints.get_senses() @@ -365,6 +370,7 @@ def set_linear_components(self, *args): to the corresponding vector. Equivalent to [linear_constraints.set_linear_components(pair[0], pair[1]) for pair in seq_of_pairs]. + >>> from qiskit.optimization import OptimizationProblem >>> op = qiskit.optimization.OptimizationProblem() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) >>> indices = op.variables.add(names = ["x0", "x1"]) @@ -428,6 +434,7 @@ def set_range_values(self, *args): the corresponding values. Equivalent to [linear_constraints.set_range_values(pair[0], pair[1]) for pair in seq_of_pairs]. + >>> from qiskit.optimization import OptimizationProblem >>> op = qiskit.optimization.OptimizationProblem() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) >>> op.linear_constraints.set_range_values("c1", 1.0) @@ -459,6 +466,7 @@ def set_coefficients(self, *args): coefficients must be a list of (row, col, val) triples as described above. + >>> from qiskit.optimization import OptimizationProblem >>> op = qiskit.optimization.OptimizationProblem() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) >>> indices = op.variables.add(names = ["x0", "x1"]) @@ -501,6 +509,7 @@ def get_rhs(self, *args): indices the members of s. Equivalent to [linear_constraints.get_rhs(i) for i in s] + >>> from qiskit.optimization import OptimizationProblem >>> op = qiskit.optimization.OptimizationProblem() >>> indices = op.linear_constraints.add(rhs = [1.5 * i for i in range(10)],\ names = [str(i) for i in range(10)]) @@ -541,6 +550,7 @@ def get_senses(self, *args): the members of s. Equivalent to [linear_constraints.get_senses(i) for i in s] + >>> from qiskit.optimization import OptimizationProblem >>> op = qiskit.optimization.OptimizationProblem() >>> indices = op.linear_constraints.add( ... senses=["E", "G", "L", "R"], @@ -549,6 +559,8 @@ def get_senses(self, *args): 4 >>> op.linear_constraints.get_senses(1) 'G' + >>> op.linear_constraints.get_senses("1",3) + ['G', 'L', 'R'] >>> op.linear_constraints.get_senses([2,"0",1]) ['L', 'E', 'G'] >>> op.linear_constraints.get_senses() @@ -594,6 +606,7 @@ def get_range_values(self, *args): indices the members of s. Equivalent to [linear_constraints.get_range_values(i) for i in s] + >>> from qiskit.optimization import OptimizationProblem >>> op = qiskit.optimization.OptimizationProblem() >>> indices = op.linear_constraints.add(\ range_values = [1.5 * i for i in range(10)],\ @@ -603,6 +616,8 @@ def get_range_values(self, *args): 10 >>> op.linear_constraints.get_range_values(8) 12.0 + >>> op.linear_constraints.get_range_values("1",3) + [1.5, 3.0, 4.5] >>> op.linear_constraints.get_range_values([2,"0",5]) [3.0, 0.0, 7.5] >>> op.linear_constraints.get_range_values() @@ -634,6 +649,7 @@ def get_coefficients(self, *args): linear_constraints.get_coefficients(sequence_of_pairs) returns a list of coefficients. + >>> from qiskit.optimization import OptimizationProblem >>> op = qiskit.optimization.OptimizationProblem() >>> indices = op.variables.add(names = ["x0", "x1"]) >>> indices = op.linear_constraints.add(\ @@ -686,6 +702,7 @@ def get_rows(self, *args): of s. Equivalent to [linear_constraints.get_rows(i) for i in s] + >>> from qiskit.optimization import OptimizationProblem >>> op = qiskit.optimization.OptimizationProblem() >>> indices = op.variables.add(names = ["x1", "x2", "x3"]) >>> indices = op.linear_constraints.add(\ @@ -703,36 +720,19 @@ def get_rows(self, *args): """ def _get(i): - i = convert(i, self._linconsgetindexfunc) - keys = self._lin_expr[i].keys() - keys = sorted(keys) - values = [self._lin_expr[i][k] for k in keys] - s = SparsePair(keys, values) - return s - - out = [] - if len(args) == 0: - for i in range(len(self._lin_expr)): - out.append(_get(i)) - return out - elif len(args) == 1: - if isinstance(args[0], str): - return _get(args[0]) - if isinstance(args[0], Sequence): - for i in args[0]: - out.append(_get(i)) - return out - else: - return _get(args[0]) - else: - raise QiskitOptimizationError( - "Wrong number of arguments. Please use 0 or 1. If there is 1, it can be iterable.") + keys = list(self._lin_expr[i].keys()) + keys.sort() + return SparsePair(keys, [self._lin_expr[i][k] for k in keys]) + + keys = self._index.convert(*args) + return self._getter(_get, keys) def get_num_nonzeros(self): """Returns the number of nonzeros in the linear constraint matrix. Example usage: + >>> from qiskit.optimization import OptimizationProblem >>> op = qiskit.optimization.OptimizationProblem() >>> indices = op.variables.add(names = ["x1", "x2", "x3"]) >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"],\ @@ -745,8 +745,9 @@ def get_num_nonzeros(self): """ nnz = 0 for c in self._lin_expr: - for v in c.values(): - nnz += 1 * (v != 0.0) + for e in c.values(): + if e != 0.0: + nnz += 1 return nnz def get_names(self, *args): @@ -765,6 +766,7 @@ def get_names(self, *args): the linear constraints with indices the members of s. Equivalent to [linear_constraints.get_names(i) for i in s] + >>> from qiskit.optimization import OptimizationProblem >>> op = qiskit.optimization.OptimizationProblem() >>> indices = op.linear_constraints.add(names = ["c" + str(i) for i in range(10)]) >>> op.linear_constraints.get_num() @@ -784,3 +786,6 @@ def _get(i): return self._names keys = self._index.convert(*args) return self._getter(_get, keys) + + def get_histogram(self): + raise QiskitOptimizationError("Not implemented") diff --git a/test/optimization/test_linear_constraints.py b/test/optimization/test_linear_constraints.py index 6f73bd1e27..3fab820bb2 100644 --- a/test/optimization/test_linear_constraints.py +++ b/test/optimization/test_linear_constraints.py @@ -14,14 +14,11 @@ """ Test LinearConstraintInterface """ -import numpy as np -import os.path -import tempfile from cplex import SparsePair -from qiskit.optimization.problems import OptimizationProblem -from cplex import infinity -from test.optimization.common import QiskitOptimizationTestCase + +from qiskit.optimization import OptimizationProblem from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError +from test.optimization.common import QiskitOptimizationTestCase class TestLinearConstraints(QiskitOptimizationTestCase): @@ -30,13 +27,12 @@ class TestLinearConstraints(QiskitOptimizationTestCase): def setUp(self): super().setUp() - def test_initial1(self): + def test_get_num(self): op = OptimizationProblem() op.linear_constraints.add(names=["c1", "c2", "c3"]) self.assertEqual(op.linear_constraints.get_num(), 3) - def test_initial2(self): - rhs = [0.0, 1.0, -1.0, 2.0] + def test_add(self): op = OptimizationProblem() op.variables.add(names=["x1", "x2", "x3"]) op.linear_constraints.add( @@ -45,183 +41,137 @@ def test_initial2(self): SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])], senses=["E", "L", "G", "R"], - rhs=rhs, + rhs=[0.0, 1.0, -1.0, 2.0], range_values=[0.0, 0.0, 0.0, -10.0], names=["c0", "c1", "c2", "c3"]) - self.assertEqual(len(op.linear_constraints.get_rhs()), 4) - for i, v in enumerate(rhs): - self.assertAlmostEqual(op.linear_constraints.get_rhs(i), v) - self.assertAlmostEqual(op.linear_constraints.get_rhs()[i], v) + self.assertListEqual(op.linear_constraints.get_rhs(), [0.0, 1.0, -1.0, 2.0]) - def test_initial3(self): + def test_delete(self): op = OptimizationProblem() op.linear_constraints.add(names=[str(i) for i in range(10)]) self.assertEqual(op.linear_constraints.get_num(), 10) op.linear_constraints.delete(8) - self.assertEqual(len(op.linear_constraints.get_names()), 9) - self.assertEqual(op.linear_constraints.get_names()[0], '0') - # ['0', '1', '2', '3', '4', '5', '6', '7', '9'] + self.assertListEqual(op.linear_constraints.get_names(), + ['0', '1', '2', '3', '4', '5', '6', '7', '9']) op.linear_constraints.delete("1", 3) - self.assertEqual(len(op.linear_constraints.get_names()), 6) - # ['0', '4', '5', '6', '7', '9'] + self.assertListEqual(op.linear_constraints.get_names(), ['0', '4', '5', '6', '7', '9']) op.linear_constraints.delete([2, "0", 5]) - self.assertEqual(len(op.linear_constraints.get_names()), 3) - self.assertEqual(op.linear_constraints.get_names()[0], '4') - # ['4', '6', '7'] + self.assertListEqual(op.linear_constraints.get_names(), ['4', '6', '7']) op.linear_constraints.delete() - self.assertEqual(len(op.linear_constraints.get_names()), 0) - # [] + self.assertListEqual(op.linear_constraints.get_names(), []) - def test_rhs1(self): + def test_rhs(self): op = OptimizationProblem() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) - self.assertEqual(len(op.linear_constraints.get_rhs()), 4) - self.assertAlmostEqual(op.linear_constraints.get_rhs()[0], 0.0) - # [0.0, 0.0, 0.0, 0.0] + self.assertListEqual(op.linear_constraints.get_rhs(), [0.0, 0.0, 0.0, 0.0]) op.linear_constraints.set_rhs("c1", 1.0) - self.assertEqual(len(op.linear_constraints.get_rhs()), 4) - self.assertAlmostEqual(op.linear_constraints.get_rhs()[1], 1.0) - # [0.0, 1.0, 0.0, 0.0] + self.assertListEqual(op.linear_constraints.get_rhs(), [0.0, 1.0, 0.0, 0.0]) op.linear_constraints.set_rhs([("c3", 2.0), (2, -1.0)]) - self.assertEqual(len(op.linear_constraints.get_rhs()), 4) - self.assertAlmostEqual(op.linear_constraints.get_rhs()[2], -1.0) - self.assertAlmostEqual(op.linear_constraints.get_rhs()[3], 2.0) - # [0.0, 1.0, -1.0, 2.0] + self.assertListEqual(op.linear_constraints.get_rhs(), [0.0, 1.0, -1.0, 2.0]) - def test_names(self): + def test_set_names(self): op = OptimizationProblem() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) op.linear_constraints.set_names("c1", "second") self.assertEqual(op.linear_constraints.get_names(1), 'second') op.linear_constraints.set_names([("c3", "last"), (2, "middle")]) - op.linear_constraints.get_names() - self.assertEqual(len(op.linear_constraints.get_names()), 4) - self.assertEqual(op.linear_constraints.get_names()[0], 'c0') - self.assertEqual(op.linear_constraints.get_names()[1], 'second') - self.assertEqual(op.linear_constraints.get_names()[2], 'middle') - self.assertEqual(op.linear_constraints.get_names()[3], 'last') - # ['c0', 'second', 'middle', 'last'] + self.assertListEqual(op.linear_constraints.get_names(), ['c0', 'second', 'middle', 'last']) - def test_senses1(self): + def test_set_senses(self): op = OptimizationProblem() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) - op.linear_constraints.get_senses() - self.assertEqual(len(op.linear_constraints.get_senses()), 4) - self.assertEqual(op.linear_constraints.get_senses()[0], 'E') - # ['E', 'E', 'E', 'E'] + self.assertListEqual(op.linear_constraints.get_senses(), ['E', 'E', 'E', 'E']) op.linear_constraints.set_senses("c1", "G") self.assertEqual(op.linear_constraints.get_senses(1), 'G') op.linear_constraints.set_senses([("c3", "L"), (2, "R")]) - # ['E', 'G', 'R', 'L'] - self.assertEqual(op.linear_constraints.get_senses()[0], 'E') - self.assertEqual(op.linear_constraints.get_senses()[1], 'G') - self.assertEqual(op.linear_constraints.get_senses()[2], 'R') - self.assertEqual(op.linear_constraints.get_senses()[3], 'L') + self.assertListEqual(op.linear_constraints.get_senses(), ['E', 'G', 'R', 'L']) - def test_linear_components(self): + def test_set_linear_components(self): op = OptimizationProblem() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) op.variables.add(names=["x0", "x1"]) op.linear_constraints.set_linear_components("c0", [["x0"], [1.0]]) - self.assertEqual(op.linear_constraints.get_rows("c0").ind[0], 0) - self.assertAlmostEqual(op.linear_constraints.get_rows("c0").val[0], 1.0) - # SparsePair(ind = [0], val = [1.0]) - op.linear_constraints.set_linear_components([("c3", SparsePair(ind=["x1"], val=[-1.0])), - (2, [[0, 1], [-2.0, 3.0]])]) - op.linear_constraints.get_rows() - # [SparsePair(ind = [0], val = [1.0]), - # SparsePair(ind = [], val = []), - # SparsePair(ind = [0, 1], val = [-2.0, 3.0]), - # SparsePair(ind = [1], val = [-1.0])] - self.assertEqual(op.linear_constraints.get_rows()[0].ind[0], 0) - self.assertAlmostEqual(op.linear_constraints.get_rows()[0].val[0], 1.0) - self.assertEqual(op.linear_constraints.get_rows()[2].ind[0], 0) - self.assertAlmostEqual(op.linear_constraints.get_rows()[2].val[0], -2.0) - - def test_linear_components_ranges(self): + sp = op.linear_constraints.get_rows("c0") + self.assertListEqual(sp.ind, [0]) + self.assertListEqual(sp.val, [1.0]) + op.linear_constraints.set_linear_components( + [("c3", SparsePair(ind=["x1"], val=[-1.0])), + (2, [[0, 1], [-2.0, 3.0]])] + ) + sp = op.linear_constraints.get_rows("c3") + self.assertListEqual(sp.ind, [1]) + self.assertListEqual(sp.val, [-1.0]) + sp = op.linear_constraints.get_rows(2) + self.assertListEqual(sp.ind, [0, 1]) + self.assertListEqual(sp.val, [-2.0, 3.0]) + + def test_set_range_values(self): op = OptimizationProblem() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) op.linear_constraints.set_range_values("c1", 1.0) - self.assertEqual(len(op.linear_constraints.get_range_values()), 4) - self.assertAlmostEqual(op.linear_constraints.get_range_values()[0], 0.0) - # [0.0, 1.0, 0.0, 0.0] + self.assertListEqual(op.linear_constraints.get_range_values(), [0.0, 1.0, 0.0, 0.0]) op.linear_constraints.set_range_values([("c3", 2.0), (2, -1.0)]) - # [0.0, 1.0, -1.0, 2.0] - self.assertEqual(len(op.linear_constraints.get_range_values()), 4) - self.assertAlmostEqual(op.linear_constraints.get_range_values()[2], -1.0) - self.assertAlmostEqual(op.linear_constraints.get_range_values()[3], 2.0) + self.assertListEqual(op.linear_constraints.get_range_values(), [0.0, 1.0, -1.0, 2.0]) - def test_rows(self): + def test_set_coeffients(self): op = OptimizationProblem() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) op.variables.add(names=["x0", "x1"]) op.linear_constraints.set_coefficients("c0", "x1", 1.0) - self.assertEqual(op.linear_constraints.get_rows(0).ind[0], 1) - self.assertAlmostEqual(op.linear_constraints.get_rows(0).val[0], 1.0) - # SparsePair(ind = [1], val = [1.0]) + sp = op.linear_constraints.get_rows(0) + self.assertListEqual(sp.ind, [1]) + self.assertListEqual(sp.val, [1.0]) op.linear_constraints.set_coefficients([("c2", "x0", 2.0), ("c2", "x1", -1.0)]) - # SparsePair(ind = [0, 1], val = [2.0, -1.0]) - self.assertEqual(op.linear_constraints.get_rows("c2").ind[0], 0) - self.assertAlmostEqual(op.linear_constraints.get_rows("c2").val[0], 2.0) - self.assertEqual(op.linear_constraints.get_rows("c2").ind[1], 1) - self.assertAlmostEqual(op.linear_constraints.get_rows("c2").val[1], -1.0) + sp = op.linear_constraints.get_rows("c2") + self.assertListEqual(sp.ind, [0, 1]) + self.assertListEqual(sp.val, [2.0, -1.0]) - def test_rhs2(self): + def test_get_rhs(self): op = OptimizationProblem() op.linear_constraints.add(rhs=[1.5 * i for i in range(10)], names=[str(i) for i in range(10)]) self.assertEqual(op.linear_constraints.get_num(), 10) self.assertAlmostEqual(op.linear_constraints.get_rhs(8), 12.0) - self.assertAlmostEqual(op.linear_constraints.get_rhs([2, "0", 5])[0], 3.0) - self.assertAlmostEqual(op.linear_constraints.get_rhs([2, "0", 5])[1], 0.0) - self.assertAlmostEqual(op.linear_constraints.get_rhs([2, "0", 5])[2], 7.5) - # [3.0, 0.0, 7.5] - self.assertEqual(len(op.linear_constraints.get_rhs()), 10) - self.assertEqual(sum(op.linear_constraints.get_rhs()), 67.5) - # [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] + self.assertListEqual(op.linear_constraints.get_rhs([2, "0", 5]), [3.0, 0.0, 7.5]) + self.assertEqual(op.linear_constraints.get_rhs(), + [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5]) - def test_senses2(self): + def test_get_senses(self): op = OptimizationProblem() op.linear_constraints.add( senses=["E", "G", "L", "R"], names=[str(i) for i in range(4)]) self.assertEqual(op.linear_constraints.get_num(), 4) self.assertEqual(op.linear_constraints.get_senses(1), 'G') - self.assertEqual(op.linear_constraints.get_senses([2, "0", 1])[0], 'L') - self.assertEqual(op.linear_constraints.get_senses([2, "0", 1])[1], 'E') - self.assertEqual(op.linear_constraints.get_senses([2, "0", 1])[2], 'G') - # ['L', 'E', 'G'] - self.assertEqual(op.linear_constraints.get_senses()[0], 'E') - self.assertEqual(op.linear_constraints.get_senses()[1], 'G') - # ['E', 'G', 'L', 'R'] + self.assertListEqual(op.linear_constraints.get_senses("1", 3), ['G', 'L', 'R']) + self.assertListEqual(op.linear_constraints.get_senses([2, "0", 1]), ['L', 'E', 'G']) + self.assertListEqual(op.linear_constraints.get_senses(), ['E', 'G', 'L', 'R']) - def test_range_values(self): + def test_get_range_values(self): op = OptimizationProblem() op.linear_constraints.add( range_values=[1.5 * i for i in range(10)], senses=["R"] * 10, names=[str(i) for i in range(10)]) self.assertEqual(op.linear_constraints.get_num(), 10) - self.assertAlmostEqual(op.linear_constraints.get_range_values(8), 12.0) - self.assertAlmostEqual(sum(op.linear_constraints.get_range_values([2, "0", 5])), 10.5) - # [3.0, 0.0, 7.5] - self.assertAlmostEqual(sum(op.linear_constraints.get_range_values()), 67.5) - # [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] + self.assertEqual(op.linear_constraints.get_range_values(8), 12.0) + self.assertListEqual(op.linear_constraints.get_range_values("1", 3), [1.5, 3.0, 4.5]) + self.assertListEqual(op.linear_constraints.get_range_values([2, "0", 5]), [3.0, 0.0, 7.5]) + self.assertListEqual(op.linear_constraints.get_range_values(), + [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5]) - def test_coefficients(self): + def test_get_coefficients(self): op = OptimizationProblem() op.variables.add(names=["x0", "x1"]) op.linear_constraints.add( names=["c0", "c1"], lin_expr=[[[1], [1.0]], [[0, 1], [2.0, -1.0]]]) self.assertAlmostEqual(op.linear_constraints.get_coefficients("c0", "x1"), 1.0) - self.assertAlmostEqual(op.linear_constraints.get_coefficients( - [("c1", "x0"), ("c1", "x1")])[0], 2.0) - self.assertAlmostEqual(op.linear_constraints.get_coefficients( - [("c1", "x0"), ("c1", "x1")])[1], -1.0) + self.assertListEqual( + op.linear_constraints.get_coefficients([("c1", "x0"), ("c1", "x1")]), [2.0, -1.0]) - def test_rows2(self): + def test_get_rows(self): op = OptimizationProblem() op.variables.add(names=["x1", "x2", "x3"]) op.linear_constraints.add( @@ -230,39 +180,56 @@ def test_rows2(self): SparsePair(ind=["x1", "x2"], val=[1.0, 1.0]), SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])]) - self.assertEqual(op.linear_constraints.get_rows(0).ind[0], 0) - self.assertAlmostEqual(op.linear_constraints.get_rows(0).val[0], 1.0) - self.assertEqual(op.linear_constraints.get_rows(0).ind[1], 2) - self.assertAlmostEqual(op.linear_constraints.get_rows(0).val[1], -1.0) - # SparsePair(ind = [0, 2], val = [1.0, -1.0]) - self.assertEqual(op.linear_constraints.get_rows("c2").ind[0], 0) - self.assertAlmostEqual(op.linear_constraints.get_rows("c2").val[0], -1.0) - self.assertEqual(op.linear_constraints.get_rows("c2").ind[1], 1) - self.assertAlmostEqual(op.linear_constraints.get_rows("c2").val[1], -1.0) - # [SparsePair(ind = [0, 1, 2], val = [-1.0, -1.0, -1.0]), - # SparsePair(ind = [0, 2], val = [1.0, -1.0])] - self.assertEqual(len(op.linear_constraints.get_rows()), 4) - # [SparsePair(ind = [0, 2], val = [1.0, -1.0]), - # SparsePair(ind = [0, 1], val = [1.0, 1.0]), - # SparsePair(ind = [0, 1, 2], val = [-1.0, -1.0, -1.0]), - # SparsePair(ind = [1, 2], val = [10.0, -2.0])] - - def test_nnz(self): + sp = op.linear_constraints.get_rows(0) + self.assertListEqual(sp.ind, [0, 2]) + self.assertListEqual(sp.val, [1.0, -1.0]) + + sp = op.linear_constraints.get_rows(1, 3) + self.assertListEqual(sp[0].ind, [0, 1]) + self.assertListEqual(sp[0].val, [1.0, 1.0]) + self.assertListEqual(sp[1].ind, [0, 1, 2]) + self.assertListEqual(sp[1].val, [-1.0, -1.0, -1.0]) + self.assertListEqual(sp[2].ind, [1, 2]) + self.assertListEqual(sp[2].val, [10.0, -2.0]) + + sp = op.linear_constraints.get_rows(['c2', 0]) + self.assertListEqual(sp[0].ind, [0, 1, 2]) + self.assertListEqual(sp[0].val, [-1.0, -1.0, -1.0]) + self.assertListEqual(sp[1].ind, [0, 2]) + self.assertListEqual(sp[1].val, [1.0, -1.0]) + + sp = op.linear_constraints.get_rows() + self.assertListEqual(sp[0].ind, [0, 2]) + self.assertListEqual(sp[0].val, [1.0, -1.0]) + self.assertListEqual(sp[1].ind, [0, 1]) + self.assertListEqual(sp[1].val, [1.0, 1.0]) + self.assertListEqual(sp[2].ind, [0, 1, 2]) + self.assertListEqual(sp[2].val, [-1.0, -1.0, -1.0]) + self.assertListEqual(sp[3].ind, [1, 2]) + self.assertListEqual(sp[3].val, [10.0, -2.0]) + + def test_get_num_nonzeros(self): op = OptimizationProblem() op.variables.add(names=["x1", "x2", "x3"]) - op.linear_constraints.add(names=["c0", "c1", "c2", "c3"], - lin_expr=[SparsePair(ind=["x1", "x3"], val=[1.0, -1.0]), - SparsePair(ind=["x1", "x2"], val=[1.0, 1.0]), - SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), - SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])]) + op.linear_constraints.add( + names=["c0", "c1", "c2", "c3"], + lin_expr=[SparsePair(ind=["x1", "x3"], val=[1.0, -1.0]), + SparsePair(ind=["x1", "x2"], val=[1.0, 1.0]), + SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), + SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])]) self.assertEqual(op.linear_constraints.get_num_nonzeros(), 9) + op.linear_constraints.set_coefficients("c0", "x3", 0) + self.assertEqual(op.linear_constraints.get_num_nonzeros(), 8) - def test_names2(self): + def test_get_names(self): op = OptimizationProblem() op.linear_constraints.add(names=["c" + str(i) for i in range(10)]) self.assertEqual(op.linear_constraints.get_num(), 10) self.assertEqual(op.linear_constraints.get_names(8), 'c8') - self.assertEqual(op.linear_constraints.get_names([2, 0, 5])[0], 'c2') - # ['c2', 'c0', 'c5'] - self.assertEqual(len(op.linear_constraints.get_names()), 10) - # ['c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9'] + self.assertListEqual(op.linear_constraints.get_names([2, 0, 5]), ['c2', 'c0', 'c5']) + self.assertEqual(op.linear_constraints.get_names(), + ['c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9']) + + def test_get_histogram(self): + op = OptimizationProblem() + self.assertRaises(QiskitOptimizationError, lambda: op.linear_constraints.get_histogram()) From dd1da266f929411e78f56463646372ba44f2162c Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 12 Mar 2020 12:25:07 +0900 Subject: [PATCH 051/323] refactor optimization problem --- .../problems/linear_constraint.py | 2 + qiskit/optimization/problems/objective.py | 2 +- .../problems/optimization_problem.py | 65 ++++---- qiskit/optimization/utils/helpers.py | 153 +----------------- 4 files changed, 37 insertions(+), 185 deletions(-) mode change 100755 => 100644 qiskit/optimization/problems/optimization_problem.py mode change 100755 => 100644 qiskit/optimization/utils/helpers.py diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index 03e8fef2cd..d805552dce 100644 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -131,6 +131,8 @@ def add(self, lin_expr=None, senses="", rhs=None, range_values=None, for arg_length in arg_lengths: if arg_length > 0 and arg_length != max_length: raise QiskitOptimizationError("inconsistent arguments in linear_constraints.add().") + if max_length == 0: + return range(len(self._names), len(self._names)) assert max_length > 0 if not rhs: diff --git a/qiskit/optimization/problems/objective.py b/qiskit/optimization/problems/objective.py index efcb2dd13f..d4f4aa5b11 100644 --- a/qiskit/optimization/problems/objective.py +++ b/qiskit/optimization/problems/objective.py @@ -57,7 +57,7 @@ class ObjectiveInterface(BaseInterface): sense = ObjSense() # See `ObjSense()` - def __init__(self, varindex: Callable[[Union[str, List[str]]], int]): + def __init__(self, varindex: Callable): super(ObjectiveInterface, self).__init__() self._linear = {} self._quadratic = {} diff --git a/qiskit/optimization/problems/optimization_problem.py b/qiskit/optimization/problems/optimization_problem.py old mode 100755 new mode 100644 index b6067415c3..a90010d670 --- a/qiskit/optimization/problems/optimization_problem.py +++ b/qiskit/optimization/problems/optimization_problem.py @@ -13,16 +13,16 @@ # that they have been altered from the originals. -from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError -from qiskit.optimization.utils.helpers import convert -from qiskit.optimization.problems.variables import VariablesInterface +from cplex import Cplex, SparsePair +from cplex.exceptions import CplexSolverError + from qiskit.optimization.problems.linear_constraint import LinearConstraintInterface -from qiskit.optimization.problems.quadratic_constraint import QuadraticConstraintInterface from qiskit.optimization.problems.objective import ObjectiveInterface from qiskit.optimization.problems.problem_type import ProblemType +from qiskit.optimization.problems.quadratic_constraint import QuadraticConstraintInterface +from qiskit.optimization.problems.variables import VariablesInterface from qiskit.optimization.results.solution import SolutionInterface -from cplex import Cplex, SparsePair -from cplex.exceptions import CplexSolverError +from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError class OptimizationProblem(object): @@ -97,10 +97,10 @@ def from_cplex(self, op): self._disposed = False self._name = None self.variables = VariablesInterface() - self.linear_constraints = LinearConstraintInterface( - varsgetindexfunc=self.variables._varsgetindexfunc) - self.quadratic_constraints = QuadraticConstraintInterface() - self.objective = ObjectiveInterface(varsgetindexfunc=self.variables._varsgetindexfunc) + self.linear_constraints = LinearConstraintInterface(varindex=self.variables.get_indices) + self.quadratic_constraints = QuadraticConstraintInterface( + varindex=self.variables.get_indices) + self.objective = ObjectiveInterface(varindex=self.variables.get_indices) self.solution = SolutionInterface() # set problem name @@ -364,15 +364,15 @@ def substitute_variables(self, constants=None, variables=None): vars_to_be_replaced = {} if constants is not None: for i, v in zip(constants.ind, constants.val): - i = convert(i, self.variables._varsgetindexfunc) + i = self.variables.get_indices(i) name = self.variables.get_names(i) if i in vars_to_be_replaced: raise QiskitOptimizationError('cannot substitute the same variable twice') vars_to_be_replaced[name] = [v] if variables is not None: for i, j, v in zip(variables.ind1, variables.ind2, variables.val): - i = convert(i, self.variables._varsgetindexfunc) - j = convert(j, self.variables._varsgetindexfunc) + i = self.variables.get_indices(i) + j = self.variables.get_indices(j) name1 = self.variables.get_names(i) name2 = self.variables.get_names(j) if name1 in vars_to_be_replaced: @@ -416,7 +416,7 @@ def substitute_variables(self, constants=None, variables=None): # construct linear part of objective for i, v in self.objective.get_linear().items(): - i = convert(i, self.variables._varsgetindexfunc) + i = self.variables.get_indices(i) i_name = self.variables.get_names(i) i_repl = vars_to_be_replaced.get(i_name, None) if i_repl is not None: @@ -433,8 +433,8 @@ def substitute_variables(self, constants=None, variables=None): # construct quadratic part of objective for i, vi in self.objective.get_quadratic().items(): for j, v in vi.items(): - i = convert(i, self.variables._varsgetindexfunc) - j = convert(j, self.variables._varsgetindexfunc) + i = self.variables.get_indices(i) + j = self.variables.get_indices(j) i_name = self.variables.get_names(i) j_name = self.variables.get_names(j) i_repl = vars_to_be_replaced.get(i_name, None) @@ -447,7 +447,7 @@ def substitute_variables(self, constants=None, variables=None): w_j += i_repl[0] * w_ij / 2 op.objective.set_linear(j_name, w_j) else: # len == 2 - k = convert(i_repl[0], self.variables._varsgetindexfunc) + k = self.variables.get_indices(i_repl[0]) k_name = self.variables.get_names(k) if k_name in vars_to_be_replaced.keys(): raise QiskitOptimizationError( @@ -462,7 +462,7 @@ def substitute_variables(self, constants=None, variables=None): w_i += j_repl[0] * w_ij / 2 op.objective.set_linear(i_name, w_i) else: # len == 2 - k = convert(j_repl[0], self.variables._varsgetindexfunc) + k = self.variables.get_indices(j_repl[0]) k_name = self.variables.get_names(k) if k_name in vars_to_be_replaced.keys(): raise QiskitOptimizationError( @@ -474,7 +474,7 @@ def substitute_variables(self, constants=None, variables=None): if len(i_repl) == 1 and len(j_repl) == 1: offset += w_ij * i_repl[0] * j_repl[0] / 2 elif len(i_repl) == 1 and len(j_repl) == 2: - k = convert(j_repl[0], self.variables._varsgetindexfunc) + k = self.variables.get_indices(j_repl[0]) k_name = self.variables.get_names(k) if k_name in vars_to_be_replaced.keys(): raise QiskitOptimizationError( @@ -483,7 +483,7 @@ def substitute_variables(self, constants=None, variables=None): w_k += w_ij * i_repl[0] * j_repl[1] / 2 op.objective.set_linear(k_name, w_k) elif len(i_repl) == 2 and len(j_repl) == 1: - k = convert(i_repl[0], self.variables._varsgetindexfunc) + k = self.variables.get_indices(i_repl[0]) k_name = self.variables.get_names(k) if k_name in vars_to_be_replaced.keys(): raise QiskitOptimizationError( @@ -492,12 +492,12 @@ def substitute_variables(self, constants=None, variables=None): w_k += w_ij * j_repl[0] * i_repl[1] / 2 op.objective.set_linear(k_name, w_k) else: # both len(repl) == 2 - k = convert(i_repl[0], self.variables._varsgetindexfunc) + k = self.variables.get_indices(i_repl[0]) k_name = self.variables.get_names(k) if k_name in vars_to_be_replaced.keys(): raise QiskitOptimizationError( 'Cannot substitute by variable that gets substituted itself.') - l = convert(j_repl[0], self.variables._varsgetindexfunc) + l = self.variables.get_indices(j_repl[0]) l_name = self.variables.get_names(l) if l_name in vars_to_be_replaced.keys(): raise QiskitOptimizationError( @@ -508,11 +508,11 @@ def substitute_variables(self, constants=None, variables=None): else: # nothing to be replaced, just copy coefficients if i == j: - w_ij = self.objective.get_quadratic_coefficients( - i_name, j_name) + op.objective.get_quadratic_coefficients(i_name, j_name) + w_ij = sum(self.objective.get_quadratic_coefficients(i_name, j_name), + op.objective.get_quadratic_coefficients(i_name, j_name)) else: - w_ij = self.objective.get_quadratic_coefficients( - i_name, j_name)/2 + op.objective.get_quadratic_coefficients(i_name, j_name) + w_ij = sum(self.objective.get_quadratic_coefficients(i_name, j_name) / 2, + op.objective.get_quadratic_coefficients(i_name, j_name)) op.objective.set_quadratic_coefficients(i_name, j_name, w_ij) # set offset @@ -529,24 +529,25 @@ def substitute_variables(self, constants=None, variables=None): # print(name, row, rhs, sense, range_value) new_vals = {} for i, v in zip(row.ind, row.val): - i = convert(i, self.variables._varsgetindexfunc) + i = self.variables.get_indices(i) i_name = self.variables.get_names(i) i_repl = vars_to_be_replaced.get(i_name, None) if i_repl is not None: if len(i_repl) == 1: - rhs -= v*i_repl[0] + rhs -= v * i_repl[0] else: - j = convert(i_repl[0], self.variables._varsgetindexfunc) + j = self.variables.get_indices(i_repl[0]) j_name = self.variables.get_names(j) - new_vals[j_name] = v*i_repl[1] + new_vals.get(i_name, 0) + new_vals[j_name] = v * i_repl[1] + new_vals.get(i_name, 0) else: # nothing to replace, just add value new_vals[i_name] = v + new_vals.get(i_name, 0) new_ind = list(new_vals.keys()) new_val = [new_vals[i] for i in new_ind] new_row = SparsePair(new_ind, new_val) - op.linear_constraints.add(lin_expr=[new_row], senses=[sense], rhs=[ - rhs], range_values=[range_value], names=[name]) + op.linear_constraints.add( + lin_expr=[new_row], senses=[sense], rhs=[rhs], range_values=[range_value], + names=[name]) # TODO: quadratic constraints diff --git a/qiskit/optimization/utils/helpers.py b/qiskit/optimization/utils/helpers.py old mode 100755 new mode 100644 index fdcf3ce994..4a29ed336e --- a/qiskit/optimization/utils/helpers.py +++ b/qiskit/optimization/utils/helpers.py @@ -13,80 +13,10 @@ # that they have been altered from the originals. -from typing import Union, List, Tuple, Dict, Sequence - -from cplex import SparsePair, SparseTriple +from typing import Union, List, Dict, Sequence from qiskit.optimization.utils import QiskitOptimizationError -_defaultgetindex = {} - - -def _defaultgetindexfunc(item): - if item not in _defaultgetindex: - _defaultgetindex[item] = len(_defaultgetindex) - return _defaultgetindex[item] - - -def _cachelookup(item, getindexfunc, cache): - if item not in cache: - cache[item] = getindexfunc(item) - return cache[item] - - -def _convert_sequence(seq, getindexfunc, cache): - results = [] - for item in seq: - if isinstance(item, str): - idx = _cachelookup(item, getindexfunc, cache) - results.append(idx) - else: - results.append(item) - return results - - -def listify(x): - """Returns [x] if x isn't already a list. - - This is used to wrap arguments for functions that require lists. - """ - # Assumes name to index conversions already done. - assert not isinstance(x, str) - if isinstance(x, Sequence): - return x - else: - return [x] - - -def convert(name, getindexfunc=_defaultgetindexfunc, cache=None): - """Converts from names to indices as necessary. - - If name is a string, an index is returned. - - If name is a sequence, a sequence of indices is returned. - - If name is neither (i.e., it's an integer), then that is returned - as is. - - getindexfunc is a function that takes a name and returns an index. - - The optional cache argument allows for further localized - caching (e.g., within a loop). - """ - # In some cases, it can be beneficial to cache lookups. - if cache is None: - cache = {} - if isinstance(name, str): - return _cachelookup(name, getindexfunc, cache) - elif isinstance(name, Sequence): - # It's tempting to use a recursive solution here, but that kills - # performance for the case where all indices are passed in (i.e., - # no names). This is due to the fact that we end up doing the - # extra check for sequence types over and over (above). - return _convert_sequence(name, getindexfunc, cache) - else: - return name - class NameIndex: def __init__(self): @@ -132,84 +62,3 @@ def convert(self, *args) -> Union[int, List[int]]: def init_list_args(*args): """Initialize default arguments with empty lists if necessary.""" return tuple([] if a is None else a for a in args) - - -if __debug__: - - # With non-optimzied bytecode, validate_arg_lengths actually does - # something. - def validate_arg_lengths(arg_list, allow_empty=True): - """Checks for equivalent argument lengths. - - If allow_empty is True (the default), then empty arguments are not - checked against the max length of non-empty arguments. Some functions - allow NULL arguments in the Callable Library, for example. - """ - arg_lengths = [len(x) for x in arg_list] - if allow_empty: - arg_lengths = [x for x in arg_lengths if x > 0] - if len(arg_lengths) == 0: - return - max_length = max(arg_lengths) - for arg_length in arg_lengths: - if arg_length != max_length: - raise QiskitOptimizationError("inconsistent arguments") - -else: - - # A no-op if using python -O or the PYTHONOPTIMIZE environment - # variable is defined as a non-empty string. - def validate_arg_lengths(arg_list, allow_empty=True): - pass - - -def unpack_pair(item: Union[SparsePair, List, Tuple]) -> Tuple[List[int], List[float]]: - """Extracts the indices and values from an object. - - The argument item can either be an instance of SparsePair or a - sequence of length two. - - Example usage: - - >>> sp = SparsePair() - >>> ind, val = unpack_pair(sp) - >>> lin_expr = [[], []] - >>> ind, val = unpack_pair(lin_expr) - """ - if isinstance(item, SparsePair): - assert item.isvalid() - ind, val = item.unpack() - elif isinstance(item, (tuple, list)): - ind, val = item[0:2] - else: - raise QiskitOptimizationError('Invalid object for unpack_pair {}'.format(item)) - validate_arg_lengths([ind, val]) - return ind, val - - -def unpack_triple(item: Union[SparseTriple, List, Tuple]) \ - -> Tuple[List[int], List[int], List[float]]: - """Extracts the indices and values from an object. - - The argument item can either be an instance of SparseTriple or a - sequence of length three. - - Example usage: - - >>> st = SparseTriple() - >>> ind1, ind2, val = unpack_triple(st) - >>> quad_expr = [[], [], []] - >>> ind1, ind2, val = unpack_triple(quad_expr) - """ - if isinstance(item, SparseTriple): - assert item.isvalid() - ind1, ind2, val = item.unpack() - elif isinstance(item, (list, tuple)): - ind1, ind2, val = item[0:3] - validate_arg_lengths([ind1, ind2, val]) - return ind1, ind2, val - - -def max_arg_length(arg_list): - """Returns the max length of the arguments in arg_list.""" - return max(len(x) for x in arg_list) From abbb13705ccdd2034f228a6e9a6386bdf6cbf75e Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 12 Mar 2020 17:02:53 +0900 Subject: [PATCH 052/323] revise unit tests --- .../problems/linear_constraint.py | 14 +- qiskit/optimization/problems/objective.py | 18 +-- .../problems/quadratic_constraint.py | 98 ++++++++----- qiskit/optimization/problems/variables.py | 53 ++++--- qiskit/optimization/utils/base.py | 42 ++++-- qiskit/optimization/utils/helpers.py | 44 +++++- test/optimization/test_helpers.py | 46 ++++++ test/optimization/test_linear_constraints.py | 3 +- .../test_quadratic_constraints.py | 133 +++++++++++++++--- test/optimization/test_variables.py | 63 +++++---- 10 files changed, 375 insertions(+), 139 deletions(-) create mode 100644 test/optimization/test_helpers.py diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index d805552dce..8e3936e4c0 100644 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -396,7 +396,11 @@ def _set(i, v): i = self._index.convert(i) for j, w in zip_iter: j = self._varindex(j) - self._lin_expr[i][j] = w + if w == 0: + if j in self._lin_expr[i]: + del self._lin_expr[i][j] + else: + self._lin_expr[i][j] = w self._setter(_set, *args) @@ -489,7 +493,11 @@ def set_coefficients(self, *args): for i, j, v in arg_list: i = self._index.convert(i) j = self._varindex(j) - self._lin_expr[i][j] = v + if v == 0: + if j in self._lin_expr[i]: + del self._lin_expr[i][j] + else: + self._lin_expr[i][j] = v def get_rhs(self, *args): """Returns the righthand side of constraints from the problem. @@ -790,4 +798,4 @@ def _get(i): return self._getter(_get, keys) def get_histogram(self): - raise QiskitOptimizationError("Not implemented") + raise NotImplementedError("histrogram is not implemented") diff --git a/qiskit/optimization/problems/objective.py b/qiskit/optimization/problems/objective.py index d4f4aa5b11..3b3fe61cc7 100644 --- a/qiskit/optimization/problems/objective.py +++ b/qiskit/optimization/problems/objective.py @@ -16,12 +16,11 @@ import numbers from collections.abc import Sequence from logging import getLogger -from typing import Callable, Union, List +from typing import Callable, List from cplex import SparsePair from qiskit.optimization.utils import BaseInterface, QiskitOptimizationError -from qiskit.optimization.utils.helpers import NameIndex CPX_MAX = -1 CPX_MIN = 1 @@ -55,7 +54,8 @@ def __getitem__(self, item): class ObjectiveInterface(BaseInterface): """Contains methods for querying and modifying the objective function.""" - sense = ObjSense() # See `ObjSense()` + sense = ObjSense() + """See `ObjSense()`""" def __init__(self, varindex: Callable): super(ObjectiveInterface, self).__init__() @@ -64,7 +64,6 @@ def __init__(self, varindex: Callable): self._name = None self._sense = ObjSense.minimize self._offset = 0.0 - self._index = NameIndex() self._varindex = varindex def set_linear(self, *args): @@ -100,7 +99,7 @@ def set_linear(self, *args): def _set(i, v): i = self._varindex(i) - if v == 0.0 and i in self._linear: + if v == 0 and i in self._linear: del self._linear[i] else: self._linear[i] = v @@ -148,7 +147,7 @@ def set_quadratic(self, args: List): self._quadratic = {} def _set(i, j, val): - if val == 0 or val == 0.0: + if val == 0: return i = self._varindex(i) j = self._varindex(j) @@ -244,7 +243,7 @@ def _set(i, j, val): self._quadratic[i][j] = self._quadratic[j][i] = val if (len(args) == 1 and isinstance(args[0], Sequence)) or len(args) == 3: - # valid arguments + # valid arguments. go through. pass else: raise QiskitOptimizationError("Wrong number of arguments: {}".format(args)) @@ -452,11 +451,14 @@ def get_name(self): Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> op.objective.set_name("cost") >>> op.objective.get_name() 'cost' """ + if not self._name: + logger.warning('No name of exists for objective') return self._name def get_num_quadratic_variables(self): diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index 71d85b7ea7..ecf4000faf 100755 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -50,7 +50,7 @@ def get_num(self) -> int: Example usage: - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> indices = op.variables.add(names = ['x','y']) >>> l = SparsePair(ind = ['x'], val = [1.0]) @@ -99,7 +99,7 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): Raises: QiskitOptimizationError: if invalid argument is given. - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> indices = op.variables.add(names = ['x','y']) >>> l = SparsePair(ind = ['x'], val = [1.0]) @@ -202,7 +202,7 @@ def delete(self, *args): Example usage: - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> indices = op.variables.add(names=['x', 'y']) >>> l = SparsePair(ind=['x'], val=[1.0]) @@ -274,7 +274,7 @@ def get_rhs(self, *args): end. Equivalent to quadratic_constraints.get_rhs(range(begin, end + 1)). - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> indices = op.variables.add(names = [str(i) for i in range(10)]) >>> [op.quadratic_constraints.add(rhs=1.5 * i, name=str(i)) @@ -326,7 +326,7 @@ def get_senses(self, *args): end. Equivalent to quadratic_constraints.get_senses(range(begin, end + 1)). - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> indices = op.variables.add(names = ["x0"]) >>> [op.quadratic_constraints.add(name=str(i), sense=j) @@ -379,7 +379,7 @@ def get_linear_num_nonzeros(self, *args): inclusive of end. Equivalent to quadratic_constraints.get_linear_num_nonzeros(range(begin, end + 1)). - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> indices = op.variables.add(names = [str(i) for i in range(11)], types = "B" * 11) >>> [op.quadratic_constraints.add( @@ -435,25 +435,36 @@ def get_linear_components(self, *args): inclusive of end. Equivalent to quadratic_constraints.get_linear_components(range(begin, end + 1)). - >>> from qiskit.optimization.problems import OptimizationProblem + Examples: + + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() - >>> indices = op.variables.add(names = [str(i) for i in range(11)], types = "B" * 11) + >>> indices = op.variables.add( + ... names=[str(i) for i in range(4)], + ... types="B" * 4 + ... ) >>> [op.quadratic_constraints.add( - ... name = str(i), - ... lin_expr = [range(i), [1.0 * (j+1.0) for j in range(i)]]) - ... for i in range(10)] - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + ... name=str(i), + ... lin_expr=[range(i), [1.0 * (j+1.0) for j in range(i)]]) + ... for i in range(3)] + [0, 1, 2] >>> op.quadratic_constraints.get_num() - 10 - >>> op.quadratic_constraints.get_linear_components(8) - SparsePair(ind = [0, 1, 2, 3, 4, 5, 6, 7], val = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]) - >>> op.quadratic_constraints.get_linear_components("1",3) - [SparsePair(ind = [0], val = [1.0]), SparsePair(ind = [0, 1], val = [1.0, 2.0]), SparsePair(ind = [0, 1, 2], val = [1.0, 2.0, 3.0])] - >>> op.quadratic_constraints.get_linear_components([2,"0",5]) - [SparsePair(ind = [0, 1], val = [1.0, 2.0]), SparsePair(ind = [], val = []), SparsePair(ind = [0, 1, 2, 3, 4], val = [1.0, 2.0, 3.0, 4.0, 5.0])] - >>> op.quadratic_constraints.delete(4,9) - >>> op.quadratic_constraints.get_linear_components() - [SparsePair(ind = [], val = []), SparsePair(ind = [0], val = [1.0]), SparsePair(ind = [0, 1], val = [1.0, 2.0]), SparsePair(ind = [0, 1, 2], val = [1.0, 2.0, 3.0])] + 3 + >>> op.quadratic_constraints.get_linear_components(2) + SparsePair(ind = [0, 1], val = [1.0, 2.0]) + >>> for row in op.quadratic_constraints.get_linear_components("0", 1): + ... print(row) + SparsePair(ind = [], val = []) + SparsePair(ind = [0], val = [1.0]) + >>> for row in op.quadratic_constraints.get_linear_components([1, "0"]): + ... print(row) + SparsePair(ind = [0], val = [1.0]) + SparsePair(ind = [], val = []) + >>> for row in op.quadratic_constraints.get_linear_components(): + ... print(row) + SparsePair(ind = [], val = []) + SparsePair(ind = [0], val = [1.0]) + SparsePair(ind = [0, 1], val = [1.0, 2.0]) """ def _linear_component(i) -> SparsePair: @@ -490,7 +501,7 @@ def get_quad_num_nonzeros(self, *args): inclusive of end. Equivalent to quadratic_constraints.get_quad_num_nonzeros(range(begin, end + 1)). - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> indices = op.variables.add(names = [str(i) for i in range(11)]) >>> [op.quadratic_constraints.add( @@ -544,24 +555,33 @@ def get_quadratic_components(self, *args): inclusive of end. Equivalent to quadratic_constraints.get_quadratic_components(range(begin, end + 1)). - >>> op = qiskit.optimization.OptimizationProblem() - >>> indices = op.variables.add(names = [str(i) for i in range(11)], types = "B" * 11) + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() + >>> indices = op.variables.add( + ... names=[str(i) for i in range(4)] + ... ) >>> [op.quadratic_constraints.add( - ... name = str(i), - ... quad_expr = [range(i), range(i), [1.0 * (j+1.0) for j in range(i)]]) - ... for i in range(1, 11)] - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + ... name="q{0}".format(i), + ... quad_expr=[range(i), range(i), + ... [1.0 * (j+1.0) for j in range(i)]]) + ... for i in range(1, 3)] + [0, 1] >>> op.quadratic_constraints.get_num() - 10 - >>> op.quadratic_constraints.get_quadratic_components(8) - SparseTriple(ind1 = [0, 1, 2, 3, 4, 5, 6, 7, 8], ind2 = [0, 1, 2, 3, 4, 5, 6, 7, 8], val = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]) - >>> op.quadratic_constraints.get_quadratic_components("1",3) - [SparseTriple(ind1 = [0], ind2 = [0], val = [1.0]), SparseTriple(ind1 = [0, 1], ind2 = [0, 1], val = [1.0, 2.0]), SparseTriple(ind1 = [0, 1, 2], ind2 = [0, 1, 2], val = [1.0, 2.0, 3.0]), SparseTriple(ind1 = [0, 1, 2, 3], ind2 = [0, 1, 2, 3], val = [1.0, 2.0, 3.0, 4.0])] - >>> op.quadratic_constraints.get_quadratic_components([2,"1",5]) - [SparseTriple(ind1 = [0, 1, 2], ind2 = [0, 1, 2], val = [1.0, 2.0, 3.0]), SparseTriple(ind1 = [0], ind2 = [0], val = [1.0]), SparseTriple(ind1 = [0, 1, 2, 3, 4, 5], ind2 = [0, 1, 2, 3, 4, 5], val = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0])] - >>> op.quadratic_constraints.delete(4,9) - >>> op.quadratic_constraints.get_quadratic_components() - [SparseTriple(ind1 = [0], ind2 = [0], val = [1.0]), SparseTriple(ind1 = [0, 1], ind2 = [0, 1], val = [1.0, 2.0]), SparseTriple(ind1 = [0, 1, 2], ind2 = [0, 1, 2], val = [1.0, 2.0, 3.0]), SparseTriple(ind1 = [0, 1, 2, 3], ind2 = [0, 1, 2, 3], val = [1.0, 2.0, 3.0, 4.0])] + 2 + >>> op.quadratic_constraints.get_quadratic_components(1) + SparseTriple(ind1 = [0, 1], ind2 = [0, 1], val = [1.0, 2.0]) + >>> for quad in op.quadratic_constraints.get_quadratic_components("q1", 1): + ... print(quad) + SparseTriple(ind1 = [0], ind2 = [0], val = [1.0]) + SparseTriple(ind1 = [0, 1], ind2 = [0, 1], val = [1.0, 2.0]) + >>> for quad in op.quadratic_constraints.get_quadratic_components(["q2", 0]): + ... print(quad) + SparseTriple(ind1 = [0, 1], ind2 = [0, 1], val = [1.0, 2.0]) + SparseTriple(ind1 = [0], ind2 = [0], val = [1.0]) + >>> for quad in op.quadratic_constraints.get_quadratic_components(): + ... print(quad) + SparseTriple(ind1 = [0], ind2 = [0], val = [1.0]) + SparseTriple(ind1 = [0, 1], ind2 = [0, 1], val = [1.0, 2.0]) """ def _quadratic_component(i) -> SparseTriple: diff --git a/qiskit/optimization/problems/variables.py b/qiskit/optimization/problems/variables.py index 97024427aa..19adc76370 100644 --- a/qiskit/optimization/problems/variables.py +++ b/qiskit/optimization/problems/variables.py @@ -68,7 +68,7 @@ class VariablesInterface(BaseInterface): Example usage: - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> indices = op.variables.add(names = ["x0", "x1", "x2"]) >>> # default values for lower_bounds are 0.0 @@ -77,6 +77,9 @@ class VariablesInterface(BaseInterface): >>> # values can be set either one at a time or many at a time >>> op.variables.set_lower_bounds(0, 1.0) >>> op.variables.set_lower_bounds([("x1", -1.0), (2, 3.0)]) + >>> # values can be queried as a range + >>> op.variables.get_lower_bounds(0, "x1") + [1.0, -1.0] >>> # values can be queried as a sequence in arbitrary order >>> op.variables.get_lower_bounds(["x1", "x2", 0]) [-1.0, 3.0, 1.0] @@ -106,14 +109,13 @@ def __init__(self): # self._obj = [] # self._columns = [] self._index = NameIndex() - self._varsgetindexfunc = {} # TODO: to be deleted def get_num(self): """Returns the number of variables in the problem. Example usage: - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) @@ -127,7 +129,7 @@ def get_num_continuous(self): Example usage: - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) @@ -141,7 +143,7 @@ def get_num_integer(self): Example usage: - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) @@ -155,7 +157,7 @@ def get_num_binary(self): Example usage: - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.semi_continuous, t.binary, t.integer]) @@ -169,7 +171,7 @@ def get_num_semicontinuous(self): Example usage: - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.semi_continuous, t.semi_integer, t.semi_integer]) @@ -183,7 +185,7 @@ def get_num_semiinteger(self): Example usage: - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.semi_continuous, t.semi_integer, t.semi_integer]) @@ -192,8 +194,7 @@ def get_num_semiinteger(self): """ return self._types.count(VarTypes.semi_integer) - def add(self, obj=None, lb=None, ub=None, types="", names=None, - columns=None): + def add(self, obj=None, lb=None, ub=None, types="", names=None, columns=None): """Adds variables and related data to the problem. variables.add accepts the keyword arguments obj, lb, ub, types, names, and columns. @@ -233,7 +234,7 @@ def add(self, obj=None, lb=None, ub=None, types="", names=None, Example usage: - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> from cplex import SparsePair, infinity >>> op = OptimizationProblem() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2"]) @@ -317,7 +318,7 @@ def delete(self, *args): Example usage: - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> indices = op.variables.add(names=[str(i) for i in range(10)]) >>> op.variables.get_num() @@ -376,7 +377,7 @@ def set_lower_bounds(self, *args): the corresponding values. Equivalent to [variables.set_lower_bounds(pair[0], pair[1]) for pair in seq_of_pairs]. - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> indices = op.variables.add(names = ["x0", "x1", "x2"]) >>> op.variables.set_lower_bounds(0, 1.0) @@ -410,7 +411,7 @@ def set_upper_bounds(self, *args): the corresponding values. Equivalent to [variables.set_upper_bounds(pair[0], pair[1]) for pair in seq_of_pairs]. - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> indices = op.variables.add(names = ["x0", "x1", "x2"]) >>> op.variables.set_upper_bounds(0, 1.0) @@ -441,7 +442,7 @@ def set_names(self, *args): corresponding strings. Equivalent to [variables.set_names(pair[0], pair[1]) for pair in seq_of_pairs]. - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) @@ -478,7 +479,7 @@ def set_types(self, *args): If the types are set, the problem will be treated as a MIP, even if all variable types are continuous. - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> indices = op.variables.add(names = [str(i) for i in range(5)]) >>> op.variables.set_types(0, op.variables.type.continuous) @@ -518,7 +519,7 @@ def get_lower_bounds(self, *args): of s. Equivalent to [variables.get_lower_bounds(i) for i in s] - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> indices = op.variables.add(lb = [1.5 * i for i in range(10)],\ names = [str(i) for i in range(10)]) @@ -526,6 +527,8 @@ def get_lower_bounds(self, *args): 10 >>> op.variables.get_lower_bounds(8) 12.0 + >>> op.variables.get_lower_bounds("1",3) + [1.5, 3.0, 4.5] >>> op.variables.get_lower_bounds([2,"0",5]) [3.0, 0.0, 7.5] >>> op.variables.get_lower_bounds() @@ -564,7 +567,7 @@ def get_upper_bounds(self, *args): begin and end, inclusive of end. Equivalent to variables.get_upper_bounds(range(begin, end + 1)). - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> indices = op.variables.add(ub = [(1.5 * i) + 1.0 for i in range(10)],\ names = [str(i) for i in range(10)]) @@ -572,6 +575,8 @@ def get_upper_bounds(self, *args): 10 >>> op.variables.get_upper_bounds(8) 13.0 + >>> op.variables.get_upper_bounds("1",3) + [2.5, 4.0, 5.5] >>> op.variables.get_upper_bounds([2,"0",5]) [4.0, 1.0, 8.5] >>> op.variables.get_upper_bounds() @@ -602,13 +607,15 @@ def get_names(self, *args): names of the variables with indices the members of s. Equivalent to [variables.get_names(i) for i in s] - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> indices = op.variables.add(names = ['x' + str(i) for i in range(10)]) >>> op.variables.get_num() 10 >>> op.variables.get_names(8) 'x8' + >>> op.variables.get_names(1,3) + ['x1', 'x2', 'x3'] >>> op.variables.get_names([2,0,5]) ['x2', 'x0', 'x5'] >>> op.variables.get_names() @@ -640,7 +647,7 @@ def get_types(self, *args): the types of the variables with indices the members of s. Equivalent to [variables.get_types(i) for i in s] - >>> from qiskit.optimization.problems import OptimizationProblem + >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() >>> t = op.variables.type >>> indices = op.variables.add(names = [str(i) for i in range(5)],\ @@ -650,6 +657,8 @@ def get_types(self, *args): 5 >>> op.variables.get_types(3) 'S' + >>> op.variables.get_types(1,3) + ['I', 'B', 'S'] >>> op.variables.get_types([2,0,4]) ['B', 'C', 'N'] >>> op.variables.get_types() @@ -665,7 +674,7 @@ def _get(i): return self._getter(_get, keys) def get_cols(self, *args): - raise QiskitOptimizationError("Please use LinearConstraintInterface instead.") + raise NotImplementedError("Please use LinearConstraintInterface instead.") def get_obj(self, *args): - raise QiskitOptimizationError("Please use ObjectiveInterface instead.") + raise NotImplementedError("Please use ObjectiveInterface instead.") diff --git a/qiskit/optimization/utils/base.py b/qiskit/optimization/utils/base.py index 58cd8783da..f52e3c5342 100755 --- a/qiskit/optimization/utils/base.py +++ b/qiskit/optimization/utils/base.py @@ -13,7 +13,7 @@ # that they have been altered from the originals. -from typing import Callable, Sequence, Union, Any +from typing import Callable, Sequence, Union, Any, List from qiskit.optimization.utils.helpers import NameIndex from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError @@ -32,7 +32,7 @@ def __init__(self): raise TypeError("BaseInterface must be sub-classed") self._index = NameIndex() - def get_indices(self, *args): + def get_indices(self, *name) -> Union[int, List[int]]: """Converts from names to indices. If name is a string, get_indices returns the index of the @@ -43,6 +43,8 @@ def get_indices(self, *args): of the indices corresponding to the strings in name. Equivalent to map(self.get_indices, name). + See `NameIndex.convert` for details. + If the subclass does not provide an index function (i.e., the interface is not indexed), then a NotImplementedError is raised. @@ -56,22 +58,44 @@ def get_indices(self, *args): >>> op.variables.get_indices(["a", "b"]) [0, 1] """ - return self._index.convert(*args) + return self._index.convert(*name) @staticmethod - def _setter(setfunc: Callable[[int, Any], None], *args): + def _setter(setfunc: Callable[[Union[str, int], Any], None], *args) -> None: + """A generic setter method + + Args: + setfunc(index, val): A setter function of two parameters: `index` and `val`. + Since `index` can be a string, users need to convert it into an appropriate index + by applying `NameIndex.convert`. + *args: A pair of index and value or a list of pairs of index and value. + `setfunc` is invoked with `args`. + + Returns: + None + """ # check for all elements in args whether they are types - if len(args) == 1 and all(isinstance(el, Sequence) and len(el) == 2 for el in args[0]): - for el in args[0]: - setfunc(*el) + if len(args) == 1 and \ + all(isinstance(pair, Sequence) and len(pair) == 2 for pair in args[0]): + for pair in args[0]: + setfunc(*pair) elif len(args) == 2: setfunc(*args) else: raise QiskitOptimizationError("Invalid arguments: {}".format(args)) @staticmethod - def _getter(getfunc: Callable[[int], Any], *args): - # `args` should be already converted into `int` by `get_indices`. + def _getter(getfunc: Callable[[int], Any], *args) -> Any: + """A generic getter method + + Args: + getfunc(index): A getter function with an argument `index`. + `index` should be already converted by `NameIndex.convert`. + *args: A single index or a list of indices. `getfunc` is invoked with args. + + Returns: if `args` is a single index, this returns a single value genereted by `getfunc`. + If `args` is a list of indices, this returns a list of values. + """ if len(args) == 0: raise QiskitOptimizationError('Empty arguments should be handled in the caller') if len(args) == 1: diff --git a/qiskit/optimization/utils/helpers.py b/qiskit/optimization/utils/helpers.py index 4a29ed336e..35d54c1bde 100644 --- a/qiskit/optimization/utils/helpers.py +++ b/qiskit/optimization/utils/helpers.py @@ -18,17 +18,39 @@ from qiskit.optimization.utils import QiskitOptimizationError -class NameIndex: +class NameIndex(object): + """Convert a string name into an integer index. + This is used for the implementation of `BaseInterface.get_indices`. + """ + def __init__(self): + """Initialize a dictionary of name and index""" self._dict = {} def to_dict(self) -> Dict[str, int]: + """ + Returns: A dictionary of name and index + """ return self._dict - def __contains__(self, item: str) -> bool: - return item in self._dict + def __contains__(self, name: str) -> bool: + """Check a name is registered or not. + + Args: + name: a string name + + Returns: + This returns True if the name has been registered. Otherwise it returns False. + + """ + return name in self._dict + + def build(self, names: List[str]) -> None: + """Build a dictionary from scratch. - def build(self, names: List[str]): + Args: + names: a list of names + """ self._dict = {e: i for i, e in enumerate(names)} def _convert_one(self, arg: Union[str, int]) -> int: @@ -41,6 +63,20 @@ def _convert_one(self, arg: Union[str, int]) -> int: return self._dict[arg] def convert(self, *args) -> Union[int, List[int]]: + """Convert a set of names into a set of indices. + There are three types of arguments. + + - `convert()` returns all indices. + + - `convert(Union[str, int])` return an index corresponding to the argument. + If the argument is already integer, this returns the same integer value. + + - `convert(List[Union[str, int]])` returns a list of indices + + - `convert(begin, end)` return a list of indices in a range starting from `begin` to `end`, + which includes both `begin` and `end`. + Note that it behaves similar to `range(begin, end+1)` + """ if len(args) == 0: return list(self._dict.values()) elif len(args) == 1: diff --git a/test/optimization/test_helpers.py b/test/optimization/test_helpers.py new file mode 100644 index 0000000000..81b7f22f54 --- /dev/null +++ b/test/optimization/test_helpers.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" Test helpers """ + +from qiskit.optimization.utils.helpers import NameIndex, init_list_args +from test.optimization.common import QiskitOptimizationTestCase + + +class TestHelpers(QiskitOptimizationTestCase): + """Test helpers.""" + + def setUp(self): + super().setUp() + + def test_init_list_args(self): + a = init_list_args(1, [2], None) + self.assertTupleEqual(a, (1, [2], [])) + + def test_name_index1(self): + a = NameIndex() + self.assertEqual(a.convert('1'), 0) + self.assertListEqual(a.convert(['2', '3']), [1, 2]) + self.assertEqual(a.convert('1'), 0) + self.assertListEqual(a.convert(), [0, 1, 2]) + self.assertListEqual(a.convert('1', '3'), [0, 1, 2]) + self.assertListEqual(a.convert('1', '2'), [0, 1]) + + def test_name_index2(self): + a = NameIndex() + a.build(['1', '2', '3']) + self.assertEqual(a.convert('1'), 0) + self.assertListEqual(a.convert(), [0, 1, 2]) + self.assertListEqual(a.convert('1', '3'), [0, 1, 2]) + self.assertListEqual(a.convert('1', '2'), [0, 1]) diff --git a/test/optimization/test_linear_constraints.py b/test/optimization/test_linear_constraints.py index 3fab820bb2..02fac9a575 100644 --- a/test/optimization/test_linear_constraints.py +++ b/test/optimization/test_linear_constraints.py @@ -17,7 +17,6 @@ from cplex import SparsePair from qiskit.optimization import OptimizationProblem -from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError from test.optimization.common import QiskitOptimizationTestCase @@ -232,4 +231,4 @@ def test_get_names(self): def test_get_histogram(self): op = OptimizationProblem() - self.assertRaises(QiskitOptimizationError, lambda: op.linear_constraints.get_histogram()) + self.assertRaises(NotImplementedError, lambda: op.linear_constraints.get_histogram()) diff --git a/test/optimization/test_quadratic_constraints.py b/test/optimization/test_quadratic_constraints.py index 2799415c97..ecce171472 100644 --- a/test/optimization/test_quadratic_constraints.py +++ b/test/optimization/test_quadratic_constraints.py @@ -63,6 +63,24 @@ def test_initial2(self): self.assertListEqual(q[0].ind2, [0, 1]) self.assertListEqual(q[0].val, [1.0, -1.0]) + def test_get_num(self): + op = OptimizationProblem() + op.variables.add(names=['x', 'y']) + l = SparsePair(ind=['x'], val=[1.0]) + q = SparseTriple(ind1=['x'], ind2=['y'], val=[1.0]) + n = 10 + for i in range(n): + self.assertEqual(op.quadratic_constraints.add(name=str(i), lin_expr=l, quad_expr=q), i) + self.assertEqual(op.quadratic_constraints.get_num(), n) + + def test_add(self): + op = OptimizationProblem() + op.variables.add(names=['x', 'y']) + l = SparsePair(ind=['x'], val=[1.0]) + q = SparseTriple(ind1=['x'], ind2=['y'], val=[1.0]) + self.assertEqual(op.quadratic_constraints.add( + name='my quad', lin_expr=l, quad_expr=q, rhs=1.0, sense='G'), 0) + def test_delete(self): op = OptimizationProblem() q0 = [op.quadratic_constraints.add(name=str(i)) for i in range(10)] @@ -78,7 +96,7 @@ def test_delete(self): q.delete() self.assertListEqual(q.get_names(), []) - def test_rhs(self): + def test_get_rhs(self): op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(10)]) q0 = [op.quadratic_constraints.add(rhs=1.5 * i, name=str(i)) for i in range(10)] @@ -90,21 +108,7 @@ def test_rhs(self): self.assertListEqual(q.get_rhs([2, '0', 5]), [3.0, 0.0, 7.5]) self.assertListEqual(q.get_rhs(), [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5]) - def test_names(self): - op = OptimizationProblem() - op.variables.add(names=[str(i) for i in range(11)]) - q = op.quadratic_constraints - [q.add(name="q" + str(i), - quad_expr=[range(i), range(i), [1.0 * (j + 1.0) for j in range(i)]]) - for i in range(1, 11)] - self.assertEqual(q.get_num(), 10) - self.assertEqual(q.get_names(8), 'q9') - self.assertListEqual(q.get_names(1, 3), ['q2', 'q3', 'q4']) - self.assertListEqual(q.get_names([2, 0, 5]), ['q3', 'q1', 'q6']) - self.assertListEqual(q.get_names(), - ['q1', 'q2', 'q3', 'q4', 'q5', 'q6', 'q7', 'q8', 'q9', 'q10']) - - def test_senses(self): + def test_get_senses(self): op = OptimizationProblem() op.variables.add(names=["x0"]) q = op.quadratic_constraints @@ -115,20 +119,54 @@ def test_senses(self): self.assertListEqual(q.get_senses([2, '0', 1]), ['L', 'G', 'G']) self.assertListEqual(q.get_senses(), ['G', 'G', 'L', 'L']) - def test_linear_num_nonzeros(self): + def test_get_linear_num_nonzeros(self): op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(11)], types="B" * 11) q = op.quadratic_constraints + n = 10 [q.add(name=str(i), lin_expr=[range(i), [1.0 * (j + 1.0) for j in range(i)]]) - for i in range(10)] + for i in range(n)] + self.assertEqual(q.get_num(), n) self.assertEqual(q.get_linear_num_nonzeros(8), 8) self.assertListEqual(q.get_linear_num_nonzeros('1', 3), [1, 2, 3]) - self.assertListEqual(q.get_linear_num_nonzeros('1', 3), [1, 2, 3]) self.assertListEqual(q.get_linear_num_nonzeros([2, '0', 5]), [2, 0, 5]) self.assertListEqual(q.get_linear_num_nonzeros(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - def test_linear_components(self): + def test_get_linear_components(self): + op = OptimizationProblem() + op.variables.add(names=[str(i) for i in range(4)], types="B" * 4) + q = op.quadratic_constraints + z = [q.add(name=str(i), + lin_expr=[range(i), [1.0 * (j + 1.0) for j in range(i)]]) for i in range(3)] + self.assertListEqual(z, [0, 1, 2]) + self.assertEqual(q.get_num(), 3) + + sp = q.get_linear_components(2) + self.assertListEqual(sp.ind, [0, 1]) + self.assertListEqual(sp.val, [1.0, 2.0]) + + sp = q.get_linear_components('0', 1) + self.assertListEqual(sp[0].ind, []) + self.assertListEqual(sp[0].val, []) + self.assertListEqual(sp[1].ind, [0]) + self.assertListEqual(sp[1].val, [1.0]) + + sp = q.get_linear_components([1, '0']) + self.assertListEqual(sp[0].ind, [0]) + self.assertListEqual(sp[0].val, [1.0]) + self.assertListEqual(sp[1].ind, []) + self.assertListEqual(sp[1].val, []) + + sp = q.get_linear_components() + self.assertListEqual(sp[0].ind, []) + self.assertListEqual(sp[0].val, []) + self.assertListEqual(sp[1].ind, [0]) + self.assertListEqual(sp[1].val, [1.0]) + self.assertListEqual(sp[2].ind, [0, 1]) + self.assertListEqual(sp[2].val, [1.0, 2.0]) + + def test_get_linear_components2(self): op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(11)], types="B" * 11) q = op.quadratic_constraints @@ -182,7 +220,46 @@ def test_quad_num_nonzeros(self): self.assertListEqual(q.get_quad_num_nonzeros([2, '1', 5]), [3, 1, 6]) self.assertListEqual(q.get_quad_num_nonzeros(), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) - def test_quadratic_components(self): + def test_get_quadratic_components(self): + op = OptimizationProblem() + op.variables.add(names=[str(i) for i in range(4)]) + q = op.quadratic_constraints + z = [q.add(name="q{0}".format(i), + quad_expr=[range(i), range(i), [1.0 * (j + 1.0) for j in range(i)]]) + for i in range(1, 3)] + self.assertListEqual(z, [0, 1]) + self.assertEqual(q.get_num(), 2) + + st = q.get_quadratic_components(1) + self.assertListEqual(st.ind1, [0, 1]) + self.assertListEqual(st.ind2, [0, 1]) + self.assertListEqual(st.val, [1.0, 2.0]) + + st = q.get_quadratic_components('q1', 1) + self.assertListEqual(st[0].ind1, [0]) + self.assertListEqual(st[0].ind2, [0]) + self.assertListEqual(st[0].val, [1.0]) + self.assertListEqual(st[1].ind1, [0, 1]) + self.assertListEqual(st[1].ind2, [0, 1]) + self.assertListEqual(st[1].val, [1.0, 2.0]) + + st = q.get_quadratic_components(['q2', 0]) + self.assertListEqual(st[0].ind1, [0, 1]) + self.assertListEqual(st[0].ind2, [0, 1]) + self.assertListEqual(st[0].val, [1.0, 2.0]) + self.assertListEqual(st[1].ind1, [0]) + self.assertListEqual(st[1].ind2, [0]) + self.assertListEqual(st[1].val, [1.0]) + + st = q.get_quadratic_components() + self.assertListEqual(st[0].ind1, [0]) + self.assertListEqual(st[0].ind2, [0]) + self.assertListEqual(st[0].val, [1.0]) + self.assertListEqual(st[1].ind1, [0, 1]) + self.assertListEqual(st[1].ind2, [0, 1]) + self.assertListEqual(st[1].val, [1.0, 2.0]) + + def test_get_quadratic_components2(self): op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(11)]) q = op.quadratic_constraints @@ -236,3 +313,17 @@ def test_quadratic_components(self): self.assertListEqual(s[3].ind1, [0, 1, 2, 3]) self.assertListEqual(s[3].ind2, [0, 1, 2, 3]) self.assertListEqual(s[3].val, [1.0, 2.0, 3.0, 4.0]) + + def test_get_names(self): + op = OptimizationProblem() + op.variables.add(names=[str(i) for i in range(11)]) + q = op.quadratic_constraints + [q.add(name="q" + str(i), + quad_expr=[range(i), range(i), [1.0 * (j + 1.0) for j in range(i)]]) + for i in range(1, 11)] + self.assertEqual(q.get_num(), 10) + self.assertEqual(q.get_names(8), 'q9') + self.assertListEqual(q.get_names(1, 3), ['q2', 'q3', 'q4']) + self.assertListEqual(q.get_names([2, 0, 5]), ['q3', 'q1', 'q6']) + self.assertListEqual(q.get_names(), + ['q1', 'q2', 'q3', 'q4', 'q5', 'q6', 'q7', 'q8', 'q9', 'q10']) diff --git a/test/optimization/test_variables.py b/test/optimization/test_variables.py index 76aff1fba1..77e59f36d6 100644 --- a/test/optimization/test_variables.py +++ b/test/optimization/test_variables.py @@ -35,18 +35,11 @@ def test_type(self): def test_initial(self): op = OptimizationProblem() op.variables.add(names=["x0", "x1", "x2"]) - # default values for lower_bounds are 0.0 - self.assertAlmostEqual(sum(op.variables.get_lower_bounds()), 0.0) - # values can be set either one at a time or many at a time + self.assertListEqual(op.variables.get_lower_bounds(), [0.0, 0.0, 0.0]) op.variables.set_lower_bounds(0, 1.0) - self.assertAlmostEqual(sum(op.variables.get_lower_bounds()), 1.0) op.variables.set_lower_bounds([("x1", -1.0), (2, 3.0)]) - self.assertAlmostEqual(sum(op.variables.get_lower_bounds()), 3.0) - # values can be queried as a sequence in arbitrary order - self.assertAlmostEqual(op.variables.get_lower_bounds(["x1", "x2", 0])[0], -1.0) - self.assertAlmostEqual(op.variables.get_lower_bounds(["x1", "x2", 0])[1], 3.0) - self.assertAlmostEqual(op.variables.get_lower_bounds(["x1", "x2", 0])[2], 1.0) - # can query the number of variables + self.assertListEqual(op.variables.get_lower_bounds(0, "x1"), [1.0, -1.0]) + self.assertListEqual(op.variables.get_lower_bounds(["x1", "x2", 0]), [-1.0, 3.0, 1.0]) self.assertEqual(op.variables.get_num(), 3) op.variables.set_types(0, op.variables.type.binary) self.assertEqual(op.variables.get_num_binary(), 1) @@ -97,12 +90,14 @@ def test_add(self): types=[op.variables.type.integer] * 3, names=["0", "1", "2"] ) - self.assertListEqual(op.variables.get_lower_bounds(), - [0.0, 0.0, 0.0, -1.0, 1.0, 0.0]) - self.assertListEqual(op.variables.get_upper_bounds(), - [infinity, infinity, infinity, 100.0, infinity, infinity]) + self.assertListEqual( + op.variables.get_lower_bounds(), + [0.0, 0.0, 0.0, -1.0, 1.0, 0.0]) + self.assertListEqual( + op.variables.get_upper_bounds(), + [infinity, infinity, infinity, 100.0, infinity, infinity]) - def test_delete1(self): + def test_delete(self): op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(10)]) self.assertEqual(op.variables.get_num(), 10) @@ -118,7 +113,7 @@ def test_delete1(self): op.variables.delete() self.assertListEqual(op.variables.get_names(), []) - def test_lower_bounds(self): + def test_set_lower_bounds(self): op = OptimizationProblem() op.variables.add(names=["x0", "x1", "x2"]) op.variables.set_lower_bounds(0, 1.0) @@ -126,14 +121,14 @@ def test_lower_bounds(self): op.variables.set_lower_bounds([(2, 3.0), ("x1", -1.0)]) self.assertListEqual(op.variables.get_lower_bounds(), [1.0, -1.0, 3.0]) - def test_upper_bounds(self): + def test_set_upper_bounds(self): op = OptimizationProblem() op.variables.add(names=["x0", "x1", "x2"]) op.variables.set_upper_bounds(0, 1.0) op.variables.set_upper_bounds([("x1", 10.0), (2, 3.0)]) self.assertListEqual(op.variables.get_upper_bounds(), [1.0, 10.0, 3.0]) - def test_names(self): + def test_set_names(self): op = OptimizationProblem() t = op.variables.type op.variables.add(types=[t.continuous, t.binary, t.integer]) @@ -141,7 +136,7 @@ def test_names(self): op.variables.set_names([(2, "third"), (1, "second")]) self.assertListEqual(op.variables.get_names(), ['first', 'second', 'third']) - def test_lower_bounds1(self): + def test_set_types(self): op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(5)]) op.variables.set_types(0, op.variables.type.continuous) @@ -150,38 +145,41 @@ def test_lower_bounds1(self): ("3", op.variables.type.semi_continuous), ("4", op.variables.type.semi_integer)]) self.assertListEqual(op.variables.get_types(), ['C', 'I', 'B', 'S', 'N']) + self.assertEqual(op.variables.type[op.variables.get_types(0)], 'continuous') - def test_lower_bounds2(self): + def test_get_lower_bounds(self): op = OptimizationProblem() op.variables.add(lb=[1.5 * i for i in range(10)], names=[str(i) for i in range(10)]) self.assertEqual(op.variables.get_num(), 10) - self.assertAlmostEqual(op.variables.get_lower_bounds(8), 12.0) + self.assertEqual(op.variables.get_lower_bounds(8), 12.0) + self.assertListEqual(op.variables.get_lower_bounds('1', 3), [1.5, 3.0, 4.5]) self.assertListEqual(op.variables.get_lower_bounds([2, "0", 5]), [3.0, 0.0, 7.5]) self.assertEqual(op.variables.get_lower_bounds(), [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5]) - def test_upper_bounds2(self): + def test_get_upper_bounds(self): op = OptimizationProblem() op.variables.add(ub=[(1.5 * i) + 1.0 for i in range(10)], names=[str(i) for i in range(10)]) self.assertEqual(op.variables.get_num(), 10) - self.assertAlmostEqual(op.variables.get_upper_bounds(8), 13.0) + self.assertEqual(op.variables.get_upper_bounds(8), 13.0) + self.assertListEqual(op.variables.get_upper_bounds('1', 3), [2.5, 4.0, 5.5]) self.assertListEqual(op.variables.get_upper_bounds([2, "0", 5]), [4.0, 1.0, 8.5]) self.assertListEqual(op.variables.get_upper_bounds(), [1.0, 2.5, 4.0, 5.5, 7.0, 8.5, 10.0, 11.5, 13.0, 14.5]) - self.assertAlmostEqual(op.variables.get_upper_bounds()[0], 1.0) - def test_names2(self): + def test_get_names(self): op = OptimizationProblem() op.variables.add(names=['x' + str(i) for i in range(10)]) self.assertEqual(op.variables.get_num(), 10) self.assertEqual(op.variables.get_names(8), 'x8') + self.assertListEqual(op.variables.get_names(1, 3), ['x1', 'x2', 'x3']) self.assertListEqual(op.variables.get_names([2, 0, 5]), ['x2', 'x0', 'x5']) self.assertListEqual(op.variables.get_names(), ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9']) - def test_types(self): + def test_set_types(self): op = OptimizationProblem() t = op.variables.type op.variables.add(names=[str(i) for i in range(5)], @@ -190,24 +188,27 @@ def test_types(self): self.assertEqual(op.variables.get_num(), 5) self.assertEqual(op.variables.get_types(3), 'S') + types = op.variables.get_types(1, 3) + self.assertListEqual(types, ['I', 'B', 'S']) + types = op.variables.get_types([2, 0, 4]) self.assertListEqual(types, ['B', 'C', 'N']) types = op.variables.get_types() self.assertEqual(types, ['C', 'I', 'B', 'S', 'N']) - def test_cols(self): + def test_get_cols(self): op = OptimizationProblem() - with self.assertRaises(QiskitOptimizationError): + with self.assertRaises(NotImplementedError): op.variables.get_cols() - def test_obj(self): + def test_get_obj(self): op = OptimizationProblem() - with self.assertRaises(QiskitOptimizationError): + with self.assertRaises(NotImplementedError): op.variables.get_obj() def test_get_indices(self): op = OptimizationProblem() op.variables.add(names=['a', 'b']) self.assertEqual(op.variables.get_indices('a'), 0) - self.assertListEqual(op.variables.get_indices(), [0, 1]) + self.assertListEqual(op.variables.get_indices(['a', 'b']), [0, 1]) From e30f86a887d454f19a3f3c91b6798b5966e38fff Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 12 Mar 2020 17:14:17 +0900 Subject: [PATCH 053/323] add tests of NameIndex --- qiskit/optimization/utils/helpers.py | 6 ------ test/optimization/test_helpers.py | 8 ++++++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/qiskit/optimization/utils/helpers.py b/qiskit/optimization/utils/helpers.py index 35d54c1bde..e564b01b99 100644 --- a/qiskit/optimization/utils/helpers.py +++ b/qiskit/optimization/utils/helpers.py @@ -27,12 +27,6 @@ def __init__(self): """Initialize a dictionary of name and index""" self._dict = {} - def to_dict(self) -> Dict[str, int]: - """ - Returns: A dictionary of name and index - """ - return self._dict - def __contains__(self, name: str) -> bool: """Check a name is registered or not. diff --git a/test/optimization/test_helpers.py b/test/optimization/test_helpers.py index 81b7f22f54..2079674de0 100644 --- a/test/optimization/test_helpers.py +++ b/test/optimization/test_helpers.py @@ -15,6 +15,7 @@ """ Test helpers """ from qiskit.optimization.utils.helpers import NameIndex, init_list_args +from qiskit.optimization import QiskitOptimizationError from test.optimization.common import QiskitOptimizationTestCase @@ -44,3 +45,10 @@ def test_name_index2(self): self.assertListEqual(a.convert(), [0, 1, 2]) self.assertListEqual(a.convert('1', '3'), [0, 1, 2]) self.assertListEqual(a.convert('1', '2'), [0, 1]) + + def test_name_index3(self): + a = NameIndex() + with self.assertRaises(QiskitOptimizationError): + a.convert({}) + with self.assertRaises(QiskitOptimizationError): + a.convert(1, 2, 3) From 33f3a907cb2c3a95c83112c2db4f72fff17002b2 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 12 Mar 2020 17:16:02 +0900 Subject: [PATCH 054/323] adjust file mode --- qiskit/optimization/problems/__init__.py | 0 qiskit/optimization/problems/problem_type.py | 0 qiskit/optimization/problems/quadratic_constraint.py | 0 qiskit/optimization/utils/__init__.py | 0 qiskit/optimization/utils/base.py | 0 5 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 qiskit/optimization/problems/__init__.py mode change 100755 => 100644 qiskit/optimization/problems/problem_type.py mode change 100755 => 100644 qiskit/optimization/problems/quadratic_constraint.py mode change 100755 => 100644 qiskit/optimization/utils/__init__.py mode change 100755 => 100644 qiskit/optimization/utils/base.py diff --git a/qiskit/optimization/problems/__init__.py b/qiskit/optimization/problems/__init__.py old mode 100755 new mode 100644 diff --git a/qiskit/optimization/problems/problem_type.py b/qiskit/optimization/problems/problem_type.py old mode 100755 new mode 100644 diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py old mode 100755 new mode 100644 diff --git a/qiskit/optimization/utils/__init__.py b/qiskit/optimization/utils/__init__.py old mode 100755 new mode 100644 diff --git a/qiskit/optimization/utils/base.py b/qiskit/optimization/utils/base.py old mode 100755 new mode 100644 From 7cc92a5949d7d03984846ed6dc44c327c13afbde Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 12 Mar 2020 19:20:10 +0900 Subject: [PATCH 055/323] fix converters and ub of binary variable --- .../inequality_to_equality_converter.py | 33 +++++++++++-------- .../converters/integer_to_binary_converter.py | 7 ++-- .../optimization_problem_to_operator.py | 2 +- qiskit/optimization/problems/variables.py | 7 ++-- test/optimization/test_converters.py | 8 ++--- test/optimization/test_variables.py | 19 ++++++++++- 6 files changed, 51 insertions(+), 25 deletions(-) diff --git a/qiskit/optimization/converters/inequality_to_equality_converter.py b/qiskit/optimization/converters/inequality_to_equality_converter.py index ce1ed5fd03..19a245f7b7 100644 --- a/qiskit/optimization/converters/inequality_to_equality_converter.py +++ b/qiskit/optimization/converters/inequality_to_equality_converter.py @@ -46,7 +46,8 @@ def __init__(self): self._conv: Dict[str, List[Tuple[str, int]]] = {} # e.g., self._conv = {'c1': [c1@slack_var]} - def encode(self, op: OptimizationProblem, name: str = None, mode: str = 'auto') -> OptimizationProblem: + def encode(self, op: OptimizationProblem, name: str = None, + mode: str = 'auto') -> OptimizationProblem: """ Convert a problem with inequality constraints into new one with only equality constraints. @@ -116,11 +117,14 @@ def encode(self, op: OptimizationProblem, name: str = None, mode: str = 'auto') # with slack variables which represent [lb, ub] = [0, constant - the lower bound of lhs] elif senses[i] == 'L': if mode == 'integer': - self._add_int_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], sense=senses[i]) + self._add_int_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], + sense=senses[i]) elif mode == 'continuous': - self._add_continuous_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], sense=senses[i]) + self._add_continuous_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], + sense=senses[i]) elif mode == 'auto': - self._add_auto_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], sense=senses[i]) + self._add_auto_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], + sense=senses[i]) else: raise AquaError('Unsupported mode is selected' + mode) @@ -128,11 +132,14 @@ def encode(self, op: OptimizationProblem, name: str = None, mode: str = 'auto') # with slack variables which represent [lb, ub] = [0, the upper bound of lhs] elif senses[i] == 'G': if mode == 'integer': - self._add_int_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], sense=senses[i]) + self._add_int_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], + sense=senses[i]) elif mode == 'continuous': - self._add_continuous_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], sense=senses[i]) + self._add_continuous_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], + sense=senses[i]) elif mode == 'auto': - self._add_auto_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], sense=senses[i]) + self._add_auto_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], + sense=senses[i]) else: raise AquaError('Unsupported mode is selected' + mode) @@ -207,12 +214,12 @@ def _add_int_slack_var_constraint(self, name, row, rhs, sense): new_ind = copy.deepcopy(row.ind) new_val = copy.deepcopy(row.val) - new_ind.append(self._dst.variables._varsgetindex[slack_name]) + new_ind.append(self._dst.variables.get_indices(slack_name)) new_val.append(sign) # Add a new equality constraint. - self._dst.linear_constraints.add(lin_expr=[SparsePair(ind=new_ind, val=new_val)], senses=['E'], - rhs=[new_rhs], names=[name]) + self._dst.linear_constraints.add(lin_expr=[SparsePair(ind=new_ind, val=new_val)], + senses=['E'], rhs=[new_rhs], names=[name]) def _add_continuous_slack_var_constraint(self, name, row, rhs, sense): slack_name = name + self._delimiter + 'continuous_slack' @@ -235,12 +242,12 @@ def _add_continuous_slack_var_constraint(self, name, row, rhs, sense): new_ind = copy.deepcopy(row.ind) new_val = copy.deepcopy(row.val) - new_ind.append(self._dst.variables._varsgetindex[slack_name]) + new_ind.append(self._dst.variables.get_indices(slack_name)) new_val.append(sign) # Add a new equality constraint. - self._dst.linear_constraints.add(lin_expr=[SparsePair(ind=new_ind, val=new_val)], senses=['E'], - rhs=[rhs], names=[name]) + self._dst.linear_constraints.add(lin_expr=[SparsePair(ind=new_ind, val=new_val)], + senses=['E'], rhs=[rhs], names=[name]) def _add_auto_slack_var_constraint(self, name, row, rhs, sense): # If a coefficient that is not integer exist, use a continuous slack variable diff --git a/qiskit/optimization/converters/integer_to_binary_converter.py b/qiskit/optimization/converters/integer_to_binary_converter.py index a2ffd2e63d..08cc7123d7 100644 --- a/qiskit/optimization/converters/integer_to_binary_converter.py +++ b/qiskit/optimization/converters/integer_to_binary_converter.py @@ -75,7 +75,7 @@ def encode(self, op: OptimizationProblem, name: str = None) -> OptimizationProbl self._dst.variables.add( names=[name for name, _ in new_vars], types='B' * len(new_vars)) else: - self._dst.variables.add(names=[name], types=typ) + self._dst.variables.add(names=[name], types=typ, lb=[lb[i]], ub=[ub[i]]) # replace integer variables with binary variables in the objective function # self.objective.subs(self._conv) @@ -135,7 +135,6 @@ def _substitute_int_var(self): num_var = self._dst.variables.get_num() new_quad = np.zeros((num_var, num_var)) - index_dict = self._dst.variables._varsgetindex for row in src_obj_quad: for col in src_obj_quad[row]: @@ -155,8 +154,8 @@ def _substitute_int_var(self): for new_row, row_coef in row_vars: for new_col, col_coef in col_vars: - row_index = index_dict[new_row] - col_index = index_dict[new_col] + row_index = self._dst.variables.get_indices(new_row) + col_index = self._dst.variables.get_indices(new_col) new_quad[row_index, col_index] = coef * row_coef * col_coef ind = list(range(num_var)) diff --git a/qiskit/optimization/converters/optimization_problem_to_operator.py b/qiskit/optimization/converters/optimization_problem_to_operator.py index 7e6278ba82..0e3796dd0b 100644 --- a/qiskit/optimization/converters/optimization_problem_to_operator.py +++ b/qiskit/optimization/converters/optimization_problem_to_operator.py @@ -63,7 +63,7 @@ def encode(self, op: OptimizationProblem) -> Tuple[WeightedPauliOperator, float] _q_d = {} qubit_index = 0 for name in self._src.variables.get_names(): - var_index = self._src.variables._varsgetindex[name] + var_index = self._src.variables.get_indices(name) _q_d[var_index] = qubit_index qubit_index += 1 diff --git a/qiskit/optimization/problems/variables.py b/qiskit/optimization/problems/variables.py index 19adc76370..cc4726be6a 100644 --- a/qiskit/optimization/problems/variables.py +++ b/qiskit/optimization/problems/variables.py @@ -278,14 +278,17 @@ def add(self, obj=None, lb=None, ub=None, types="", names=None, columns=None): self._ub.extend(ub) if not types: - types = [VarTypes().continuous] * max_length + types = [VarTypes.continuous] * max_length + for i, t in enumerate(types): + if t == VarTypes.binary: + self._ub[i] = 1.0 self._types.extend(types) if not names: names = ["x" + str(cnt) for cnt in range(len(self._names), len(self._names) + max_length)] self._names.extend(names) - self._index.build(names) + self._index.build(self._names) return range(len(self._names) - max_length, len(self._names)) diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index cd8f454c91..2619e90f75 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -136,8 +136,8 @@ def test_integer_to_binary(self): self.assertIn('x', names) self.assertIn('z', names) vars = op2.variables - self.assertEqual(vars.get_lower_bounds('x')[0], 0.0) - self.assertEqual(vars.get_lower_bounds('z')[0], 0.0) - self.assertEqual(vars.get_upper_bounds('x')[0], 1.0) - self.assertEqual(vars.get_upper_bounds('z')[0], 10.0) + self.assertEqual(vars.get_lower_bounds('x'), 0.0) + self.assertEqual(vars.get_lower_bounds('z'), 0.0) + self.assertEqual(vars.get_upper_bounds('x'), 1.0) + self.assertEqual(vars.get_upper_bounds('z'), 10.0) self.assertListEqual(vars.get_types(['x', 'z']), ['B', 'C']) diff --git a/test/optimization/test_variables.py b/test/optimization/test_variables.py index 77e59f36d6..18f1cbd93b 100644 --- a/test/optimization/test_variables.py +++ b/test/optimization/test_variables.py @@ -17,7 +17,6 @@ from cplex import infinity from qiskit.optimization.problems import OptimizationProblem -from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError from test.optimization.common import QiskitOptimizationTestCase @@ -212,3 +211,21 @@ def test_get_indices(self): op.variables.add(names=['a', 'b']) self.assertEqual(op.variables.get_indices('a'), 0) self.assertListEqual(op.variables.get_indices(['a', 'b']), [0, 1]) + + def test_add2(self): + op = OptimizationProblem() + op.variables.add(names=['x']) + self.assertEqual(op.variables.get_indices('x'), 0) + self.assertListEqual(op.variables.get_indices(), [0]) + op.variables.add(names=['y']) + self.assertEqual(op.variables.get_indices('x'), 0) + self.assertEqual(op.variables.get_indices('y'), 1) + self.assertListEqual(op.variables.get_indices(), [0, 1]) + + def test_default_bounds(self): + op = OptimizationProblem() + types = ['B', 'I', 'C', 'S', 'N'] + op.variables.add(names=types, types=types) + self.assertListEqual(op.variables.get_lower_bounds(), [0.0] * 5) + # the upper bound of binary variable is 1. + self.assertListEqual(op.variables.get_upper_bounds(), [1.0] + [infinity] * 4) From 76f6cba78928faf3c5d5edc168a2a0d47ca923ee Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Thu, 12 Mar 2020 15:26:03 -0400 Subject: [PATCH 056/323] Style Recommendations from Optimization Team, Use Typing, GMF Derives Optimization Algorithm --- .../algorithms/grover_minimum_finder.py | 153 ++++++++----- ...zation_problem_to_negative_value_oracle.py | 206 ++++++++++-------- .../results/grover_optimization_results.py | 43 ++-- .../test_grover_minimum_finder.py | 108 ++++----- 4 files changed, 265 insertions(+), 245 deletions(-) diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index 52e0c514be..9371cb7332 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -17,44 +17,100 @@ import random import math import numpy as np +import logging +from typing import Optional, Dict from qiskit.optimization.converters import OptimizationProblemToNegativeValueOracle from qiskit.optimization.results import GroverOptimizationResults +from qiskit.optimization.algorithms import OptimizationAlgorithm +from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.results import OptimizationResult +from qiskit.optimization.utils import QiskitOptimizationError from qiskit.optimization.util import get_qubo_solutions from qiskit.aqua.algorithms.amplitude_amplifiers.grover import Grover -from qiskit.visualization import plot_histogram -from qiskit import Aer, execute +from qiskit import Aer, execute, QuantumCircuit +from qiskit.providers import BaseBackend -class GroverMinimumFinder: +class GroverMinimumFinder(OptimizationAlgorithm): """Uses Grover Adaptive Search (GAS) to find the minimum of a QUBO function.""" - def __init__(self, num_iterations=3, backend=Aer.get_backend('statevector_simulator'), - verbose=False): + def __init__(self, num_iterations: int = 3, backend: Optional[BaseBackend] = None) -> None: """ - Constructor. Args: - num_iterations (int, optional): The number of iterations the algorithm will search with + num_iterations: The number of iterations the algorithm will search with no improvement. - backend (str, optional): Instance of selected backend. - verbose (bool, optional): Verbose flag - prints/plots state at each iteration of GAS. + backend: Instance of selected backend. """ self._n_iterations = num_iterations - self._verbose = verbose - self._backend = backend + if backend is None: + self._backend = Aer.get_backend('statevector_simulator') + self._logger = logging.getLogger(__name__) - def solve(self, quadratic, linear, constant, num_output_qubits): + def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + """Checks whether a given problem can be solved with this optimizer. + + Checks whether the given problem is compatible, i.e., whether the problem contains only + binary and integer variables as well as linear equality constraints, and otherwise, + returns a message explaining the incompatibility. + + Args: + problem: The optimization problem to check compatibility. + + Returns: + Returns ``None`` if the problem is compatible and else a string with the error message. """ - Given the coefficients and constants of a QUBO function, find the minimum output value. + # initialize message + msg = '' + + # check whether there are incompatible variable types + if problem.variables.get_num_semiinteger() > 0: + # TODO: to be removed once semi-integer to binary mapping is introduced + msg += 'Semi-integer variables are not supported! ' + + # if an error occurred, return error message, otherwise, return None + if len(msg) > 0: + return msg.strip() + else: + return None + + def solve(self, problem: OptimizationProblem) -> OptimizationResult: + """Tries to solves the given problem using the optimizer. + + Runs the optimizer to try to solve the optimization problem. If problem is not convex, + this optimizer may raise an exception due to incompatibility, depending on the settings. + Args: - quadratic (np.array): The quadratic coefficients of the QUBO. - linear (np.array): The linear coefficients of the QUBO. - constant (int): The constant of the QUBO. - num_output_qubits (int): The number of qubits used to represent the output values. + problem: The problem to be solved. + Returns: - GroverOptimizationResults: A results object containing information about the run, - including the solution. + The result of the optimizer applied to the problem. + + Raises: + QiskitOptimizationError: If the problem is incompatible with the optimizer. """ + # check compatibility and raise exception if incompatible + msg = self.is_compatible(problem) + if msg is not None: + raise QiskitOptimizationError('Incompatible problem: %s' % msg) + + # TODO: Best practice for getting the linear coefficients as a list? + linear_dicts = problem.linear_constraints.get_coefficients() + linear = [0 for _ in range(len(linear_dicts))] + for i in range(len(linear_dicts)): + for key in linear_dicts[i]: + linear[key] = linear_dicts[i][key] + + # TODO: Best practice for getting the quadratic coefficients as a dict? + quadratic_dicts = problem.quadratic_constraints.get_quadratic_components() + quadratic = {} + for pair in quadratic_dicts: + idx1, idx2, val = pair.unpack() + quadratic[(idx1[0], idx2[0])] = val[0] + + constant = 0 # TODO: How to get from Optimization Problem? + num_output_qubits = 6 # TODO: How to get from Optimization Problem? + # Variables for tracking the optimum. optimum_found = False optimum_key = math.inf @@ -78,7 +134,6 @@ def solve(self, quadratic, linear, constant, num_output_qubits): # Initialize oracle helper object. opt_prob_converter = OptimizationProblemToNegativeValueOracle(n_value, - verbose=self._verbose, backend=self._backend) while not optimum_found: @@ -106,27 +161,24 @@ def solve(self, quadratic, linear, constant, num_output_qubits): circuit = a_operator._circuit # Get the next outcome. - outcome = self.__measure(circuit, n_key, n_value, self._backend, - verbose=self._verbose) + outcome = self._measure(circuit, n_key, n_value, self._backend) k = int(outcome[0:n_key], 2) v = outcome[n_key:n_key + n_value] # Convert the binary string to integer. - int_v = self.__bin_to_int(v, n_value) + threshold - v = self.__twos_complement(int_v, n_value) + int_v = self._bin_to_int(v, n_value) + threshold + v = self._twos_complement(int_v, n_value) - if self._verbose: - print("Iterations:", rotation_count) - print("Outcome:", outcome) - print("Value:", v, "=", int_v) + self._logger.info('Iterations: {}'.format(rotation_count)) + self._logger.info('Outcome: {}'.format(outcome)) + self._logger.info('Value: {} = {}'.format(v, int_v)) # If the value is an improvement, we update the iteration parameters (e.g. oracle). if int_v < optimum_value: optimum_key = k optimum_value = int_v - if self._verbose: - print("Current Optimum Key:", optimum_key) - print("Current Optimum:", optimum_value) + self._logger.info('Current Optimum Key: {}'.format(optimum_key)) + self._logger.info('Current Optimum Value: {}'.format(optimum_value)) if v.startswith("1"): improvement_found = True threshold = optimum_value @@ -138,9 +190,7 @@ def solve(self, quadratic, linear, constant, num_output_qubits): # Using Durr and Hoyer method, increase m. m = int(np.ceil(min(m * 8/7, 2**(n_key / 2)))) - if self._verbose: - print("No Improvement.") - print("M:", m) + self._logger.info('No Improvement. M: {}'.format(m)) # Check if we've already seen this value. if k not in keys_measured: @@ -155,10 +205,7 @@ def solve(self, quadratic, linear, constant, num_output_qubits): operations = circuit.count_ops() operation_count[iteration] = operations iteration += 1 - - if self._verbose: - print("Operation Count:", operations) - print("\n") + self._logger.info('Operation Count: {}\n'.format(operations)) # Get original key and value pairs. func_dict[-1] = constant @@ -168,30 +215,30 @@ def solve(self, quadratic, linear, constant, num_output_qubits): if optimum_value >= 0 and constant == 0: optimum_key = 0 - return GroverOptimizationResults(optimum_key, solutions[optimum_key], operation_count, - rotations, n_key, n_value, func_dict) + # Build the results object. + grover_results = GroverOptimizationResults(operation_count, rotations, n_key, n_value, + func_dict) + result = OptimizationResult(x=optimum_key, fval=solutions[optimum_key], + results=grover_results) + + return result - def __measure(self, circuit, n_key, n_value, backend, shots=1024, verbose=False): + def _measure(self, circuit: QuantumCircuit, n_key: int, n_value: int, backend: BaseBackend, + shots: Optional[int] = 1024) -> str: """Get probabilities from the given backend, and picks a random outcome.""" - probs = self.__get_probs(n_key, n_value, circuit, backend, shots) + probs = self._get_probs(n_key, n_value, circuit, backend, shots) freq = sorted(probs.items(), key=lambda x: x[1], reverse=True) # Pick a random outcome. freq[len(freq)-1] = (freq[len(freq)-1][0], 1 - sum([x[1] for x in freq[0:len(freq)-1]])) idx = np.random.choice(len(freq), 1, p=[x[1] for x in freq])[0] - if verbose: - print("Frequencies:", freq) - outcomes = {} - for label in probs: - key = str(int(label[:n_key], 2)) + " -> " +\ - str(self.__bin_to_int(label[n_key:n_key + n_value], n_value)) - outcomes[key] = probs[label] - plot_histogram(outcomes).show() + self._logger.info('Frequencies: {}'.format(freq)) return freq[idx][0] @staticmethod - def __get_probs(n_key, n_value, qc, backend, shots): + def _get_probs(n_key: int, n_value: int, qc: QuantumCircuit, backend: BaseBackend, + shots: int) -> Dict[str, float]: """Gets probabilities from a given backend.""" # Execute job and filter results. job = execute(qc, backend=backend, shots=shots) @@ -217,7 +264,7 @@ def __get_probs(n_key, n_value, qc, backend, shots): return hist @staticmethod - def __twos_complement(v, n_bits): + def _twos_complement(v: int, n_bits: int) -> str: """Converts an integer into a binary string of n bits using two's complement.""" assert -2**n_bits <= v < 2**n_bits @@ -231,7 +278,7 @@ def __twos_complement(v, n_bits): return bin_v @staticmethod - def __bin_to_int(v, num_value_bits): + def _bin_to_int(v: str, num_value_bits: int) -> int: """Converts a binary string of n bits using two's complement to an integer.""" if v.startswith("1"): int_v = int(v, 2) - 2 ** num_value_bits diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index a4a6a03bb0..30d04ddb8d 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -14,43 +14,49 @@ """OptimizationProblemToNegativeValueOracle module""" +import logging import numpy as np -from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister +from typing import Optional, Tuple, Dict, Union, Callable, Any +from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, Aer from qiskit.aqua.components.oracles import CustomCircuitOracle from qiskit.aqua.components.initial_states import Custom from qiskit.aqua.components.iqfts import Standard as IQFT +from qiskit.providers import BaseBackend class OptimizationProblemToNegativeValueOracle: - """ - Converts an optimization problem (QUBO) to a negative value oracle and state preparation - operators. + """Converts an optimization problem (QUBO) to a negative value oracle. + + In addition, a state preparation operator is generated from the coefficients and constant of a + QUBO, which can be used to encode the function into a quantum state. In conjuction, this oracle + and operator can be used to flag the negative values of a QUBO encoded in a quantum state. """ - def __init__(self, num_output_qubits, verbose=False, backend='statevector_simulator'): + def __init__(self, num_output_qubits: int, backend: Optional[BaseBackend] = None) -> None: """ - Constructor. Args: - num_output_qubits (int): The number of qubits required to represent the output. - verbose (bool, optional): Verbose flag - prints/plots state at each iteration of GAS. - backend (str, optional): A string corresponding to the name of the selected backend. + num_output_qubits: The number of qubits required to represent the output. + backend: Instance of selected backend. """ self._num_value = num_output_qubits - self._verbose = verbose - self._backend = backend + if backend is None: + self._backend = Aer.get_backend('statevector_simulator') + else: + self._backend = backend + self._logger = logging.getLogger(__name__) + + def encode(self, linear_coeff: np.array, quadratic_coeff: np.array, constant: int) -> \ + Tuple[Custom, CustomCircuitOracle, Dict[Union[int, Tuple[int, int]], int]]: + """ A helper function that converts a QUBO into an oracle that recognizes negative numbers. - def encode(self, linear_coeff, quadratic_coeff, constant): - """ - A helper function that converts a quadratic function into a state preparation operator A and - oracle O that recognizes negative numbers. Args: - linear_coeff (np.array): The linear coefficients of the QUBO. - quadratic_coeff (np.array): The quadratic coefficients of the QUBO. - constant (int): The constant of the QUBO. + linear_coeff: The linear coefficients of the QUBO. + quadratic_coeff: The quadratic coefficients of the QUBO. + constant: The constant of the QUBO. + Returns: - Tuple(InitialState.Custom, CustomCircuitOracle, dict): A state preparation operator A - (InitialState), an oracle O (CustomCircuitOracle) that recognizes negative numbers, and + A state preparation operator A, an oracle O that recognizes negative numbers, and a dictionary representation of the function coefficients, where the key -1 represents the constant. """ @@ -59,9 +65,8 @@ def encode(self, linear_coeff, quadratic_coeff, constant): num_ancilla = max(num_key, self._num_value) - 1 # Get the function dictionary. - func = self.__get_function(num_key, linear_coeff, quadratic_coeff, constant) - if self._verbose: - print("f:", func, "\n") + func = self._get_function(num_key, linear_coeff, quadratic_coeff, constant) + self._logger.info("Function: {}\n", func) # Define state preparation operator A from function. quantum_dict = QQUBODictionary(num_key, self._num_value, num_ancilla, @@ -76,38 +81,42 @@ def encode(self, linear_coeff, quadratic_coeff, constant): key_val = reg_map["key_value"] anc = reg_map["ancilla"] + # TODO: Can we use LogicalExpressionOracle instead? # Build negative value oracle O. oracle_bit = QuantumRegister(1, "oracle") oracle_circuit = QuantumCircuit(key_val, anc, oracle_bit) - self.__cxzxz(oracle_circuit, key_val[num_key], oracle_bit[0]) + self._cxzxz(oracle_circuit, key_val[num_key], oracle_bit[0]) oracle = CustomCircuitOracle(variable_register=key_val, output_register=oracle_bit, ancillary_register=anc, circuit=oracle_circuit, - evaluate_classically_callback=self.__evaluate_classically) + evaluate_classically_callback=self._evaluate_classically) return a_operator, oracle, func @staticmethod - def __get_function(num_assets, linear, quadratic, constant): + def _get_function(num_assets: int, linear: np.array, quadratic: np.array, constant: int) -> \ + Dict[Union[int, Tuple[int, int]], int]: """Convert the problem to a dictionary format.""" func = {-1: constant} for i in range(num_assets): - func[i] = -linear[i] + func[i] = linear[i] for j in range(i): - func[(i, j)] = int(quadratic[i, j]) + if (i, j) in quadratic: + func[(i, j)] = int(quadratic[(i, j)]) return func @staticmethod - def __cxzxz(circuit, ctrl, tgt): + def _cxzxz(circuit: QuantumCircuit, ctrl: QuantumRegister, tgt: QuantumRegister) -> None: """Multiplies by -1.""" circuit.cx(ctrl, tgt) circuit.cz(ctrl, tgt) circuit.cx(ctrl, tgt) circuit.cz(ctrl, tgt) - def __evaluate_classically(self, measurement): + def _evaluate_classically(self, measurement): + # TODO: Typing for this method? Still not sure what it's used for. Required by Grover. """ evaluate classical """ assignment = [(var + 1) * (int(tf) * 2 - 1) for tf, var in zip(measurement[::-1], range(len(measurement)))] @@ -118,113 +127,122 @@ def __evaluate_classically(self, measurement): class QuantumDictionary: - """ - A parent class that defines a Quantum Dictionary, which encodes key-value pairs into the - quantum state. See https://arxiv.org/abs/1912.04088 for a formal definition. + + """Defines a Quantum Dictionary, which encodes key-value pairs into the quantum state. + + See https://arxiv.org/abs/1912.04088 for a formal definition. """ - def __init__(self, key_bits, value_bits, ancilla_bits, func_dict, prepare, - backend="statevector_simulator"): + def __init__(self, key_bits: int, value_bits: int, ancilla_bits: int, + func_dict: Dict[Union[int, Tuple[int, int]], int], + prepare: Callable[[Any, QuantumCircuit, QuantumRegister], None], + backend: Optional[BaseBackend] = None) -> None: """ - Constructor. Args: - key_bits (int): The number of key bits. - value_bits (int): The number of value bits. - ancilla_bits (int): The number of precision (result) bits. - func_dict (dict): The dictionary of function coefficients to encode. - prepare (function): A method that encodes f into the quantum state. - backend (str, optional): A string corresponding to the name of the selected backend. - """ - self.key_bits = key_bits - self.value_bits = value_bits - self.ancilla_bits = ancilla_bits - self.func_dict = func_dict - self.prepare = prepare - self.backend = backend - - def construct_circuit(self): + key_bits: The number of key bits. + value_bits: The number of value bits. + ancilla_bits: The number of precision (result) bits. + func_dict: The dictionary of function coefficients to encode. + prepare: A method that encodes f into the quantum state. + backend: Instance of selected backend. """ - Creates a circuit for the initialized Quantum Dictionary. + self._key_bits = key_bits + self._value_bits = value_bits + self._ancilla_bits = ancilla_bits + self._func_dict = func_dict + self._prepare = prepare + if backend is None: + self._backend = Aer.get_backend('statevector_simulator') + else: + self._backend = backend + + def construct_circuit(self) -> QuantumCircuit: + """Creates a circuit for the initialized Quantum Dictionary. + Returns: - QuantumCircuit: Circuit object describing the Quantum Dictionary. + Circuit object describing the Quantum Dictionary. """ - key_val = QuantumRegister(self.key_bits + self.value_bits, "key_value") - ancilla = QuantumRegister(self.ancilla_bits, "ancilla") + key_val = QuantumRegister(self._key_bits + self._value_bits, "key_value") + ancilla = QuantumRegister(self._ancilla_bits, "ancilla") - if self.backend == "statevector_simulator": + if self._backend.name == "statevector_simulator": circuit = QuantumCircuit(key_val, ancilla) else: - measure = ClassicalRegister(self.key_bits + self.value_bits) + measure = ClassicalRegister(self._key_bits + self._value_bits) circuit = QuantumCircuit(key_val, ancilla, measure) - self.prepare(self.func_dict, circuit, key_val) + self._prepare(self._func_dict, circuit, key_val) return circuit class QQUBODictionary(QuantumDictionary): - """ - A Quantum Dictionary that creates a state preparation operator for a given QUBO problem. + + """ A Quantum Dictionary that creates a state preparation operator for a given QUBO problem. """ - def __init__(self, key_bits, value_bits, ancilla_bits, func_dict, - backend="statevector_simulator"): + def __init__(self, key_bits: int, value_bits: int, ancilla_bits: int, + func_dict: Dict[Union[int, Tuple[int, int]], int], + backend: Optional[BaseBackend] = None) -> None: """ - Constructor. Args: - key_bits (int): The number of key bits. - value_bits (int): The number of value bits. - ancilla_bits (int): The number of precision (result) bits. - func_dict (dict): The dictionary of function coefficients to encode. - backend (str, optional): A string corresponding to the name of the selected backend. + key_bits: The number of key bits. + value_bits: The number of value bits. + ancilla_bits: The number of ancilla bits. + func_dict: The dictionary of function coefficients to encode. + backend: Instance of selected backend. """ + if backend is None: + self._backend = Aer.get_backend('statevector_simulator') + else: + self._backend = backend QuantumDictionary.__init__(self, key_bits, value_bits, ancilla_bits, func_dict, self.prepare_quadratic, backend=backend) self._circuit = None @property - def circuit(self): - """ - Provides the circuit of the Quantum Dictionary. Will construct one if not yet created. + def circuit(self) -> QuantumCircuit: + """ Provides the circuit of the Quantum Dictionary. Will construct one if not yet created. + Returns: - QuantumCircuit: Circuit object describing the Quantum Dictionary. + Circuit object describing the Quantum Dictionary. """ if self._circuit is None: self._circuit = self.construct_circuit() return self._circuit - def prepare_quadratic(self, func_dict, circuit, key_val): - """ - Encodes a QUBO in the proper dictionary format into the state of a given register. + def prepare_quadratic(self, func_dict: Dict[Union[int, Tuple[int, int]], int], + circuit: QuantumCircuit, key_val: QuantumRegister) -> None: + """Encodes a QUBO in the proper dictionary format into the state of a given register. + Args: - func_dict (dict): The QUBO problem. The keys should be subscripts of the coefficients - (e.g. x_1 -> 1), with the constant (if present) being represented with a key of -1 - (i.e. d[-1] = constant). Quadratic coefficients should use a tuple for the key, with - the corresponding subscripts inside (e.g. 2*x_1*x_2 -> d[(1,2)]=2). - circuit (QuantumCircuit): The circuit to apply the operator to. - key_val (QuantumRegister): Register containing the key and value qubits. They are - combined here to follow the register format expected by algorithms like Qiskit - Aqua's Grover. + func_dict: Representation of the QUBO problem. The keys should be subscripts of the + coefficients (e.g. x_1 -> 1), with the constant (if present) being represented with + a key of -1 (i.e. d[-1] = constant). Quadratic coefficients should use a tuple for + the key, with the corresponding subscripts inside (e.g. 2*x_1*x_2 -> d[(1,2)]=2). + circuit: The circuit to apply the operator to. + key_val: Register containing the key and value qubits. They are combined here to follow + the register format expected by algorithms like Qiskit Aqua's Grover. """ circuit.h(key_val) # Linear Coefficients - for i in range(self.value_bits): + for i in range(self._value_bits): if func_dict.get(-1, 0) != 0: - circuit.u1(1 / 2 ** self.value_bits * 2 * np.pi * 2 ** i * func_dict[-1], - key_val[self.key_bits + i]) - for j in range(self.key_bits): + circuit.u1(1 / 2 ** self._value_bits * 2 * np.pi * 2 ** i * func_dict[-1], + key_val[self._key_bits + i]) + for j in range(self._key_bits): if func_dict.get(j, 0) != 0: - circuit.cu1(1 / 2 ** self.value_bits * 2 * np.pi * 2 ** i * func_dict[j], - key_val[j], key_val[self.key_bits + i]) + circuit.cu1(1 / 2 ** self._value_bits * 2 * np.pi * 2 ** i * func_dict[j], + key_val[j], key_val[self._key_bits + i]) # Quadratic Coefficients - for i in range(self.value_bits): + for i in range(self._value_bits): for k, v in func_dict.items(): if isinstance(k, tuple): - circuit.mcu1(1/2 ** self.value_bits * 2 * np.pi * 2 ** i * v, - [key_val[k[0]], key_val[k[1]]], key_val[self.key_bits + i]) + circuit.mcu1(1 / 2 ** self._value_bits * 2 * np.pi * 2 ** i * v, + [key_val[k[0]], key_val[k[1]]], key_val[self._key_bits + i]) - iqft = IQFT(self.value_bits) - value = [key_val[v] for v in range(self.key_bits, self.key_bits + self.value_bits)] + iqft = IQFT(self._value_bits) + value = [key_val[v] for v in range(self._key_bits, self._key_bits + self._value_bits)] iqft.construct_circuit(qubits=value, circuit=circuit) diff --git a/qiskit/optimization/results/grover_optimization_results.py b/qiskit/optimization/results/grover_optimization_results.py index 0b9489c5aa..0aee0e205f 100644 --- a/qiskit/optimization/results/grover_optimization_results.py +++ b/qiskit/optimization/results/grover_optimization_results.py @@ -14,28 +14,25 @@ """GroverOptimizationResults module""" +from typing import Dict, Tuple, Union + class GroverOptimizationResults: """A results object for Grover Optimization methods.""" - def __init__(self, optimum_input, optimum_output, operation_counts, rotations, n_input_qubits, - n_output_qubits, func_dict): + def __init__(self, operation_counts: Dict[int, Dict[str, int]], rotations: int, + n_input_qubits: int, n_output_qubits: int, + func_dict: Dict[Union[int, Tuple[int, int]], int]) -> None: """ - Constructor. - Args: - optimum_input (int): The input that corresponds to the optimum output. - optimum_output (int): The optimum output value. - operation_counts (dict): The counts of each operation performed per iteration. - rotations (int): The total number of Grover rotations performed. - n_input_qubits (int): The number of qubits used to represent the input. - n_output_qubits (int): The number of qubits used to represent the output. - func_dict (dict): A dictionary representation of the function, where the keys correspond + operation_counts: The counts of each operation performed per iteration. + rotations: The total number of Grover rotations performed. + n_input_qubits: The number of qubits used to represent the input. + n_output_qubits: The number of qubits used to represent the output. + func_dict: A dictionary representation of the function, where the keys correspond to a variable, and the values are the corresponding coefficients. """ - self._optimum_input = optimum_input - self._optimum_output = optimum_output self._operation_counts = operation_counts self._rotations = rotations self._n_input_qubits = n_input_qubits @@ -43,36 +40,26 @@ def __init__(self, optimum_input, optimum_output, operation_counts, rotations, n self._func_dict = func_dict @property - def optimum_input(self): - """Getter of optimum_input""" - return self._optimum_input - - @property - def optimum_output(self): - """Getter of optimum_output""" - return self._optimum_output - - @property - def operation_counts(self): + def operation_counts(self) -> Dict[int, Dict[str, int]]: """Getter of operation_counts""" return self._operation_counts @property - def rotation_count(self): + def rotation_count(self) -> int: """Getter of rotation_count""" return self._rotations @property - def n_input_qubits(self): + def n_input_qubits(self) -> int: """Getter of n_input_qubits""" return self._n_input_qubits @property - def n_output_qubits(self): + def n_output_qubits(self) -> int: """Getter of n_output_qubits""" return self._n_output_qubits @property - def func_dict(self): + def func_dict(self) -> Dict[Union[int, Tuple[int, int]], int]: """Getter of func_dict""" return self._func_dict diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index ebede016dd..455a7fa278 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -16,12 +16,11 @@ from test.optimization import QiskitOptimizationTestCase import numpy as np +from cplex import SparsePair, SparseTriple from qiskit.optimization.algorithms import GroverMinimumFinder +from qiskit.optimization.problems import OptimizationProblem from qiskit.optimization.util import get_qubo_solutions -# Flag for verbosity in all units under test. -VERBOSE = False - class TestGroverMinimumFinder(QiskitOptimizationTestCase): """GroverMinimumFinder Tests""" @@ -29,12 +28,14 @@ class TestGroverMinimumFinder(QiskitOptimizationTestCase): def validate_results(self, results, max_iterations): """Validate the results object returned by GroverMinimumFinder.""" # Get measured values. - n_key = results.n_input_qubits - op_key = results.optimum_input - op_value = results.optimum_output - iterations = len(results.operation_counts) - rot = results.rotation_count - func = results.func_dict + grover_results = results.results + n_key = grover_results.n_input_qubits + op_key = results.x + op_value = results.fval + iterations = len(grover_results.operation_counts) + rot = grover_results.rotation_count + func = grover_results.func_dict + print("Function", func) print("Optimum Key:", op_key, "Optimum Value:", op_value, "Rotations:", rot, "\n") # Get expected value. @@ -54,16 +55,17 @@ def test_qubo_gas_int_zero(self): num_value = 4 # Input. - linear = np.array([0, 0]) - quadratic = np.array([[0, 0], - [0, 0]]) - constant = 0 + op = OptimizationProblem() + _ = op.variables.add(names=["x0", "x1"]) + x0_linear = SparsePair(ind=['x0'], val=[0]) + x1_linear = SparsePair(ind=['x1'], val=[0]) + op.linear_constraints.add(lin_expr=[x0_linear, x1_linear]) # Will not find a negative, should return 0. - gmf = GroverMinimumFinder(num_iterations=1, verbose=VERBOSE) - results = gmf.solve(quadratic, linear, constant, num_value) - self.assertEqual(results.optimum_input, 0) - self.assertEqual(int(results.optimum_output), 0) + gmf = GroverMinimumFinder(num_iterations=1) + results = gmf.solve(op) + self.assertEqual(results.x, 0) + self.assertEqual(int(results.fval), 0) def test_qubo_gas_int_simple(self): """ Test for simple case, with 2 linear coeffs and no quadratic coeffs or constants """ @@ -71,54 +73,16 @@ def test_qubo_gas_int_simple(self): num_value = 4 # Input. - linear = np.array([1, -2]) - quadratic = np.array([[2, 0], - [0, 2]]) - q = 0.5 - quadratic = quadratic.dot(q) - - # Get the optimum key and value. - n_iter = 8 - gmf = GroverMinimumFinder(num_iterations=n_iter, verbose=VERBOSE) - results = gmf.solve(quadratic, linear, 0, num_value) - self.validate_results(results, n_iter) - - def test_qubo_gas_int_simple_pos_constant(self): - """ Test for a positive constant """ - # Circuit parameters. - num_value = 4 - - # Input. - linear = np.array([1, -2]) - quadratic = np.array([[2, 0], - [0, 2]]) - q = 0.5 - quadratic = quadratic.dot(q) - constant = 2 - - # Get the optimum key and value. - n_iter = 8 - gmf = GroverMinimumFinder(num_iterations=n_iter, verbose=VERBOSE) - results = gmf.solve(quadratic, linear, constant, num_value) - self.validate_results(results, n_iter) - - def test_qubo_gas_int_simple_neg_constant(self): - """ Test for a negative constant """ - # Circuit parameters. - num_value = 4 - - # Input. - linear = np.array([1, -2]) - quadratic = np.array([[2, 0], - [0, 2]]) - q = 0.5 - quadratic = quadratic.dot(q) - constant = -2 + op = OptimizationProblem() + _ = op.variables.add(names=["x0", "x1"]) + x0_linear = SparsePair(ind=['x0'], val=[-1]) + x1_linear = SparsePair(ind=['x1'], val=[2]) + op.linear_constraints.add(lin_expr=[x0_linear, x1_linear]) # Get the optimum key and value. n_iter = 8 - gmf = GroverMinimumFinder(num_iterations=n_iter, verbose=VERBOSE) - results = gmf.solve(quadratic, linear, constant, num_value) + gmf = GroverMinimumFinder(num_iterations=n_iter) + results = gmf.solve(op) self.validate_results(results, n_iter) def test_qubo_gas_int_paper_example(self): @@ -127,15 +91,19 @@ def test_qubo_gas_int_paper_example(self): num_value = 5 # Input. - linear = np.array([1, -2, 3]) - quadratic = np.array([[2, 0, -4], - [0, 4, -2], - [-4, -2, 10]]) - q = 0.5 - quadratic = quadratic.dot(q) + op = OptimizationProblem() + _ = op.variables.add(names=["x0", "x1", "x2"]) + x0_linear = SparsePair(ind=['x0'], val=[-1]) + x1_linear = SparsePair(ind=['x1'], val=[2]) + x2_linear = SparsePair(ind=['x2'], val=[-3]) + x0_x2 = SparseTriple(ind1=['x0'], ind2=['x2'], val=[-2]) + x1_x2 = SparseTriple(ind1=['x1'], ind2=['x2'], val=[-1]) + op.quadratic_constraints.add(name='x0x2', quad_expr=x0_x2) + op.quadratic_constraints.add(name='x1x2', quad_expr=x1_x2) + op.linear_constraints.add(lin_expr=[x0_linear, x1_linear, x2_linear]) # Get the optimum key and value. n_iter = 10 - gmf = GroverMinimumFinder(num_iterations=n_iter, verbose=VERBOSE) - results = gmf.solve(quadratic, linear, 0, num_value) + gmf = GroverMinimumFinder(num_iterations=n_iter) + results = gmf.solve(op) self.validate_results(results, 10) From da026d5627d4c28c0d76a6e009c807b47df264fc Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Thu, 12 Mar 2020 21:36:55 +0100 Subject: [PATCH 057/323] add parent classes to grover minimum finder --- .../algorithms/grover_minimum_finder.py | 23 +++++++++++++++---- .../results/grover_optimization_results.py | 5 +++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index 52e0c514be..86c3830831 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -17,6 +17,8 @@ import random import math import numpy as np +from qiskit.optimization.algorithms import OptimizationAlgorithm +from qiskit.optimization.problems import OptimizationProblem from qiskit.optimization.converters import OptimizationProblemToNegativeValueOracle from qiskit.optimization.results import GroverOptimizationResults from qiskit.optimization.util import get_qubo_solutions @@ -25,11 +27,11 @@ from qiskit import Aer, execute -class GroverMinimumFinder: +class GroverMinimumFinder(OptimizationAlgorithm): """Uses Grover Adaptive Search (GAS) to find the minimum of a QUBO function.""" - def __init__(self, num_iterations=3, backend=Aer.get_backend('statevector_simulator'), + def __init__(self, num_output_qubits, num_iterations=3, backend=Aer.get_backend('statevector_simulator'), verbose=False): """ Constructor. @@ -39,11 +41,16 @@ def __init__(self, num_iterations=3, backend=Aer.get_backend('statevector_simula backend (str, optional): Instance of selected backend. verbose (bool, optional): Verbose flag - prints/plots state at each iteration of GAS. """ + self._num_output_qubits = num_output_qubits self._n_iterations = num_iterations self._verbose = verbose self._backend = backend - def solve(self, quadratic, linear, constant, num_output_qubits): + def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + # TODO + return True + + def solve(self, problem: OptimizationProblem) -> OptimizationResult: """ Given the coefficients and constants of a QUBO function, find the minimum output value. Args: @@ -55,13 +62,21 @@ def solve(self, quadratic, linear, constant, num_output_qubits): GroverOptimizationResults: A results object containing information about the run, including the solution. """ + + # map to QUBO or throw exception + + # extract + quadratic = problem.objective.get_quadratic() + linear = problem.objective.get_linear() + constant = problem.objective.get_offset() + # Variables for tracking the optimum. optimum_found = False optimum_key = math.inf optimum_value = math.inf threshold = 0 n_key = len(linear) - n_value = num_output_qubits + n_value = self._num_output_qubits # Variables for tracking the solutions encountered. num_solutions = 2**n_key diff --git a/qiskit/optimization/results/grover_optimization_results.py b/qiskit/optimization/results/grover_optimization_results.py index 0b9489c5aa..030db0a774 100644 --- a/qiskit/optimization/results/grover_optimization_results.py +++ b/qiskit/optimization/results/grover_optimization_results.py @@ -14,8 +14,10 @@ """GroverOptimizationResults module""" +from qiskit.optimization.results import OptimizationResult -class GroverOptimizationResults: + +class GroverOptimizationResults(OptimizationResult): """A results object for Grover Optimization methods.""" @@ -34,6 +36,7 @@ def __init__(self, optimum_input, optimum_output, operation_counts, rotations, n func_dict (dict): A dictionary representation of the function, where the keys correspond to a variable, and the values are the corresponding coefficients. """ + super().__init__(optimum_input, optimum_output) self._optimum_input = optimum_input self._optimum_output = optimum_output self._operation_counts = operation_counts From a06e7c4a35e30beeb5e1557054683d197ec1793f Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Fri, 13 Mar 2020 00:06:47 +0100 Subject: [PATCH 058/323] update gas --- .../algorithms/grover_minimum_finder.py | 99 ++++++++++++----- ...zation_problem_to_negative_value_oracle.py | 34 ++++-- .../problems/optimization_problem.py | 21 +++- .../results/grover_optimization_results.py | 6 +- qiskit/optimization/util.py | 21 +--- .../test_grover_minimum_finder.py | 103 ++++++++++++------ 6 files changed, 191 insertions(+), 93 deletions(-) diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index 300b8d6045..1f468ffe79 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -21,7 +21,9 @@ import numpy as np from qiskit.optimization.algorithms import OptimizationAlgorithm from qiskit.optimization.problems import OptimizationProblem -from qiskit.optimization.converters import OptimizationProblemToNegativeValueOracle +from qiskit.optimization.converters import (IntegerToBinaryConverter, + PenalizeLinearEqualityConstraints, + OptimizationProblemToNegativeValueOracle) from qiskit.optimization.results import GroverOptimizationResults from qiskit.optimization.results import OptimizationResult from qiskit.optimization.utils import QiskitOptimizationError @@ -45,11 +47,50 @@ def __init__(self, num_iterations: int = 3, backend: Optional[BaseBackend] = Non self._n_iterations = num_iterations if backend is None: self._backend = Aer.get_backend('statevector_simulator') + else: + self._backend = backend self._logger = logging.getLogger(__name__) def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: - # TODO - return True + """Checks whether a given problem can be solved with this optimizer. + + Checks whether the given problem is compatible, i.e., whether the problem contains only + binary and integer variables as well as linear equality constraints, and otherwise, + returns a message explaining the incompatibility. + + Args: + problem: The optization problem to check compatibility. + + Returns: + Returns ``None`` if the problem is compatible and else a string with the error message. + """ + + # initialize message + msg = '' + + # check whether there are incompatible variable types + if problem.variables.get_num_continuous() > 0: + msg += 'Continuous variables are not supported! ' + if problem.variables.get_num_semicontinuous() > 0: + msg += 'Semi-continuous variables are not supported! ' + # if problem.variables.get_num_integer() > 0: + # # TODO: to be removed once integer to binary mapping is introduced + # msg += 'Integer variables are not supported! ' + if problem.variables.get_num_semiinteger() > 0: + # TODO: to be removed once semi-integer to binary mapping is introduced + msg += 'Semi-integer variables are not supported! ' + + # check whether there are incompatible constraint types + if not all([sense == 'E' for sense in problem.linear_constraints.get_senses()]): + msg += 'Only linear equality constraints are supported.' + + # TODO: check for quadratic constraints + + # if an error occurred, return error message, otherwise, return None + if len(msg) > 0: + return msg.strip() + else: + return None def solve(self, problem: OptimizationProblem) -> OptimizationResult: """Tries to solves the given problem using the optimizer. @@ -66,35 +107,36 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: Raises: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ - # check compatibility and raise exception if incompatible + + # analyze compatibility of problem msg = self.is_compatible(problem) if msg is not None: raise QiskitOptimizationError('Incompatible problem: %s' % msg) - # TODO: Best practice for getting the linear coefficients as a list? - linear_dicts = problem.linear_constraints.get_coefficients() - linear = [0 for _ in range(len(linear_dicts))] - for i in range(len(linear_dicts)): - for key in linear_dicts[i]: - linear[key] = linear_dicts[i][key] + # map integer variables to binary variables + int_to_bin_converter = IntegerToBinaryConverter() + problem_ = int_to_bin_converter.encode(problem) - # TODO: Best practice for getting the quadratic coefficients as a dict? - quadratic_dicts = problem.quadratic_constraints.get_quadratic_components() - quadratic = {} - for pair in quadratic_dicts: - idx1, idx2, val = pair.unpack() - quadratic[(idx1[0], idx2[0])] = val[0] + # penalize linear equality constraints with only binary variables + penalty = 2 # TODO + # if self._penalty is None: + # # TODO: should be derived from problem + # penalty = 1e5 + # else: + # penalty = self._penalty + lin_eq_converter = PenalizeLinearEqualityConstraints() + problem_ = lin_eq_converter.encode(problem_, penalty_factor=penalty) - constant = 0 # TODO: How to get from Optimization Problem? - num_output_qubits = 6 # TODO: How to get from Optimization Problem? + # TODO: How to get from Optimization Problem? + num_output_qubits = 6 # Variables for tracking the optimum. optimum_found = False optimum_key = math.inf optimum_value = math.inf threshold = 0 - n_key = len(linear) - n_value = self._num_output_qubits + n_key = problem_.variables.get_num() + n_value = num_output_qubits # Variables for tracking the solutions encountered. num_solutions = 2**n_key @@ -110,9 +152,9 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: max_rotations = int(np.ceil(100*np.pi/4)) # Initialize oracle helper object. + orig_constant = problem_.objective.get_offset() opt_prob_converter = OptimizationProblemToNegativeValueOracle(n_value, backend=self._backend) - while not optimum_found: m = 1 improvement_found = False @@ -126,8 +168,8 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: rotations += rotation_count # Get state preparation operator A and oracle O for the current threshold. - a_operator, oracle, func_dict = opt_prob_converter.encode(linear, quadratic, - constant - threshold) + problem_.objective.set_offset(orig_constant - threshold) + a_operator, oracle, func_dict = opt_prob_converter.encode(problem_) # Apply Grover's Algorithm to find values below the threshold. if rotation_count > 0: @@ -185,19 +227,24 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: self._logger.info('Operation Count: {}\n'.format(operations)) # Get original key and value pairs. - func_dict[-1] = constant + func_dict[-1] = orig_constant solutions = get_qubo_solutions(func_dict, n_key) # If the constant is 0 and we didn't find a negative, the answer is likely 0. - if optimum_value >= 0 and constant == 0: + if optimum_value >= 0 and orig_constant == 0: optimum_key = 0 + opt_x = [1 if s == '1' else 0 for s in ('{0:%sb}' % n_key).format(optimum_key)] # Build the results object. grover_results = GroverOptimizationResults(operation_count, rotations, n_key, n_value, func_dict) - result = OptimizationResult(x=optimum_key, fval=solutions[optimum_key], + result = OptimizationResult(x=opt_x, fval=solutions[optimum_key], results=grover_results) + # cast binaries back to integers + print(result) + result = int_to_bin_converter.decode(result) + return result def _measure(self, circuit: QuantumCircuit, n_key: int, n_value: int, backend: BaseBackend, diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index 30d04ddb8d..24657435e3 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -22,6 +22,7 @@ from qiskit.aqua.components.initial_states import Custom from qiskit.aqua.components.iqfts import Standard as IQFT from qiskit.providers import BaseBackend +from qiskit.optimization.problems import OptimizationProblem class OptimizationProblemToNegativeValueOracle: @@ -46,7 +47,7 @@ def __init__(self, num_output_qubits: int, backend: Optional[BaseBackend] = None self._backend = backend self._logger = logging.getLogger(__name__) - def encode(self, linear_coeff: np.array, quadratic_coeff: np.array, constant: int) -> \ + def encode(self, problem: OptimizationProblem) -> \ Tuple[Custom, CustomCircuitOracle, Dict[Union[int, Tuple[int, int]], int]]: """ A helper function that converts a QUBO into an oracle that recognizes negative numbers. @@ -60,6 +61,22 @@ def encode(self, linear_coeff: np.array, quadratic_coeff: np.array, constant: in a dictionary representation of the function coefficients, where the key -1 represents the constant. """ + + # get linear part of objective + linear_dict = problem.objective.get_linear() + linear_coeff = np.zeros(problem.variables.get_num()) + for i, v in linear_dict.items(): + linear_coeff[i] = v + + # get quadratic part of objective + quadratic_dict = problem.objective.get_quadratic() + quadratic_coeff = {} + for i, jv in quadratic_dict.items(): + for j, v in jv.items(): + quadratic_coeff[(i, j)] = v + + constant = problem.objective.get_offset() + # Get circuit requirements from input. num_key = len(linear_coeff) num_ancilla = max(num_key, self._num_value) - 1 @@ -79,16 +96,17 @@ def encode(self, linear_coeff: np.array, quadratic_coeff: np.array, constant: in for reg in a_operator_circuit.qregs: reg_map[reg.name] = reg key_val = reg_map["key_value"] - anc = reg_map["ancilla"] + # anc = reg_map["ancilla"] # TODO: Can we use LogicalExpressionOracle instead? # Build negative value oracle O. oracle_bit = QuantumRegister(1, "oracle") - oracle_circuit = QuantumCircuit(key_val, anc, oracle_bit) + oracle_circuit = QuantumCircuit(key_val, oracle_bit) + # oracle_circuit = QuantumCircuit(key_val, anc, oracle_bit) # TODO self._cxzxz(oracle_circuit, key_val[num_key], oracle_bit[0]) oracle = CustomCircuitOracle(variable_register=key_val, output_register=oracle_bit, - ancillary_register=anc, + # ancillary_register=anc, # TODO circuit=oracle_circuit, evaluate_classically_callback=self._evaluate_classically) @@ -163,13 +181,15 @@ def construct_circuit(self) -> QuantumCircuit: Circuit object describing the Quantum Dictionary. """ key_val = QuantumRegister(self._key_bits + self._value_bits, "key_value") - ancilla = QuantumRegister(self._ancilla_bits, "ancilla") + # ancilla = QuantumRegister(self._ancilla_bits, "ancilla") # TODO if self._backend.name == "statevector_simulator": - circuit = QuantumCircuit(key_val, ancilla) + # circuit = QuantumCircuit(key_val, ancilla) # TODO + circuit = QuantumCircuit(key_val) else: measure = ClassicalRegister(self._key_bits + self._value_bits) - circuit = QuantumCircuit(key_val, ancilla, measure) + # circuit = QuantumCircuit(key_val, ancilla, measure) # TODO + circuit = QuantumCircuit(key_val, measure) self._prepare(self._func_dict, circuit, key_val) diff --git a/qiskit/optimization/problems/optimization_problem.py b/qiskit/optimization/problems/optimization_problem.py index a90010d670..3275157258 100644 --- a/qiskit/optimization/problems/optimization_problem.py +++ b/qiskit/optimization/problems/optimization_problem.py @@ -12,7 +12,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. - +from docplex.mp.model import Model from cplex import Cplex, SparsePair from cplex.exceptions import CplexSolverError @@ -95,7 +95,10 @@ def from_cplex(self, op): # make sure current problem is clean self._disposed = False - self._name = None + try: + self._name = op.get_problem_name() + except CplexSolverError: + self._name = None self.variables = VariablesInterface() self.linear_constraints = LinearConstraintInterface(varindex=self.variables.get_indices) self.quadratic_constraints = QuadraticConstraintInterface( @@ -130,7 +133,7 @@ def from_cplex(self, op): # set objective name try: self.objective.set_name(op.objective.get_name()) - except: + except CplexSolverError: pass # set linear objective terms @@ -156,8 +159,14 @@ def from_cplex(self, op): # TODO: add quadratic constraints - def from_docplex(self, op): - self.from_cplex(op.get_cplex()) + def from_docplex(self, model: Model): + cplex = model.get_cplex() + try: + cplex.set_problem_name(model.get_name()) + except CplexSolverError: + cplex.set_problem_name('') + cplex.objective.set_name('Objective') + self.from_cplex(cplex) def to_cplex(self): @@ -165,6 +174,8 @@ def to_cplex(self): op = Cplex() if self.get_problem_name() is not None: op.set_problem_name(self.get_problem_name()) + else: + op.set_problem_name('') # TODO: what about problem type? # set variables (obj is set via objective interface) diff --git a/qiskit/optimization/results/grover_optimization_results.py b/qiskit/optimization/results/grover_optimization_results.py index f134d94b8c..721cc759ce 100644 --- a/qiskit/optimization/results/grover_optimization_results.py +++ b/qiskit/optimization/results/grover_optimization_results.py @@ -15,14 +15,13 @@ """GroverOptimizationResults module""" from typing import Dict, Tuple, Union -from qiskit.optimization.results import OptimizationResult -class GroverOptimizationResults(OptimizationResult): +class GroverOptimizationResults(): """A results object for Grover Optimization methods.""" - def __init__(self, x, fval, operation_counts: Dict[int, Dict[str, int]], rotations: int, + def __init__(self, operation_counts: Dict[int, Dict[str, int]], rotations: int, n_input_qubits: int, n_output_qubits: int, func_dict: Dict[Union[int, Tuple[int, int]], int]) -> None: """ @@ -34,7 +33,6 @@ def __init__(self, x, fval, operation_counts: Dict[int, Dict[str, int]], rotatio func_dict: A dictionary representation of the function, where the keys correspond to a variable, and the values are the corresponding coefficients. """ - super().__init__(x, fval) self._operation_counts = operation_counts self._rotations = rotations self._n_input_qubits = n_input_qubits diff --git a/qiskit/optimization/util.py b/qiskit/optimization/util.py index cdf54505a0..423fb3aea8 100644 --- a/qiskit/optimization/util.py +++ b/qiskit/optimization/util.py @@ -15,25 +15,6 @@ """ Optimization Utilities module """ import datetime -from qiskit.finance.data_providers import RandomDataProvider - - -def get_mu_sigma(num_assets): - """ - Generate expected return and covariance matrix from (random) time-series. - Args: - num_assets (int): The number of assets to generate values for. - Returns: - Tuple(np.array, np.array): mu (linear coefficients) and sigma (quadratic coefficients) - """ - stocks = [("TICKER%s" % i) for i in range(num_assets)] - data = RandomDataProvider(tickers=stocks, start=datetime.datetime(2020, 1, 1), - end=datetime.datetime(2020, 1, 30)) - data.run() - linear = data.get_period_return_mean_vector() - quadratic = data.get_period_return_covariance_matrix() - - return linear, quadratic def get_qubo_solutions(function_dict, n_key, print_solutions=False): @@ -83,7 +64,7 @@ def get_qubo_solutions(function_dict, n_key, print_solutions=False): print(spacer + str(i), "=", bin_key, "->" + value_spacer + str(round(solution, 4))) # Record solution. - solutions[i] = str(round(solution, 4)) + solutions[i] = solution if print_solutions: print() diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index 455a7fa278..a46b497866 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -16,8 +16,11 @@ from test.optimization import QiskitOptimizationTestCase import numpy as np +from docplex.mp.model import Model from cplex import SparsePair, SparseTriple -from qiskit.optimization.algorithms import GroverMinimumFinder +from qiskit.finance.applications.ising import portfolio +from qiskit.aqua.algorithms import NumPyMinimumEigensolver +from qiskit.optimization.algorithms import GroverMinimumFinder, MinimumEigenOptimizer from qiskit.optimization.problems import OptimizationProblem from qiskit.optimization.util import get_qubo_solutions @@ -25,7 +28,7 @@ class TestGroverMinimumFinder(QiskitOptimizationTestCase): """GroverMinimumFinder Tests""" - def validate_results(self, results, max_iterations): + def validate_results(self, problem, results, max_iterations): """Validate the results object returned by GroverMinimumFinder.""" # Get measured values. grover_results = results.results @@ -39,6 +42,8 @@ def validate_results(self, results, max_iterations): print("Optimum Key:", op_key, "Optimum Value:", op_value, "Rotations:", rot, "\n") # Get expected value. + solver = MinimumEigenOptimizer(NumPyMinimumEigensolver()) + comp_result = solver.solve(problem) solutions = get_qubo_solutions(func, n_key, print_solutions=False) min_key = min(solutions, key=lambda key: int(solutions[key])) min_value = solutions[min_key] @@ -46,64 +51,100 @@ def validate_results(self, results, max_iterations): # Validate results. max_hit = max_rotations <= rot or max_iterations <= iterations - self.assertTrue(min_key == op_key or max_hit) - self.assertTrue(min_value == op_value or max_hit) + # self.assertTrue(min_key == op_key or max_hit) + # self.assertTrue(min_value == op_value or max_hit) + self.assertTrue(comp_result.x == results.x or max_hit) + self.assertTrue(comp_result.fval == results.fval or max_hit) def test_qubo_gas_int_zero(self): """ Test for when the answer is zero """ - # Circuit parameters. - num_value = 4 # Input. op = OptimizationProblem() - _ = op.variables.add(names=["x0", "x1"]) - x0_linear = SparsePair(ind=['x0'], val=[0]) - x1_linear = SparsePair(ind=['x1'], val=[0]) - op.linear_constraints.add(lin_expr=[x0_linear, x1_linear]) + op.variables.add(names=["x0", "x1"], types='BB') + linear = [("x0", 0), ("x1", 0)] + op.objective.set_linear(linear) # Will not find a negative, should return 0. gmf = GroverMinimumFinder(num_iterations=1) results = gmf.solve(op) - self.assertEqual(results.x, 0) - self.assertEqual(int(results.fval), 0) + self.assertEqual(results.x, [0, 0]) + self.assertEqual(results.fval, 0.0) def test_qubo_gas_int_simple(self): """ Test for simple case, with 2 linear coeffs and no quadratic coeffs or constants """ - # Circuit parameters. - num_value = 4 # Input. op = OptimizationProblem() - _ = op.variables.add(names=["x0", "x1"]) - x0_linear = SparsePair(ind=['x0'], val=[-1]) - x1_linear = SparsePair(ind=['x1'], val=[2]) - op.linear_constraints.add(lin_expr=[x0_linear, x1_linear]) + op.variables.add(names=["x0", "x1"], types='BB') + linear = [("x0", -1), ("x1", 2)] + op.objective.set_linear(linear) # Get the optimum key and value. n_iter = 8 gmf = GroverMinimumFinder(num_iterations=n_iter) results = gmf.solve(op) - self.validate_results(results, n_iter) + self.validate_results(op, results, n_iter) def test_qubo_gas_int_paper_example(self): """ Test the example from https://arxiv.org/abs/1912.04088 """ - # Circuit parameters. - num_value = 5 # Input. op = OptimizationProblem() - _ = op.variables.add(names=["x0", "x1", "x2"]) - x0_linear = SparsePair(ind=['x0'], val=[-1]) - x1_linear = SparsePair(ind=['x1'], val=[2]) - x2_linear = SparsePair(ind=['x2'], val=[-3]) - x0_x2 = SparseTriple(ind1=['x0'], ind2=['x2'], val=[-2]) - x1_x2 = SparseTriple(ind1=['x1'], ind2=['x2'], val=[-1]) - op.quadratic_constraints.add(name='x0x2', quad_expr=x0_x2) - op.quadratic_constraints.add(name='x1x2', quad_expr=x1_x2) - op.linear_constraints.add(lin_expr=[x0_linear, x1_linear, x2_linear]) + op.variables.add(names=["x0", "x1", "x2"], types='BBB') + + linear = [("x0", -1), ("x1", 2), ("x2", -3)] + op.objective.set_linear(linear) + op.objective.set_quadratic_coefficients('x0', 'x2', -2) + op.objective.set_quadratic_coefficients('x1', 'x2', -1) # Get the optimum key and value. n_iter = 10 gmf = GroverMinimumFinder(num_iterations=n_iter) results = gmf.solve(op) - self.validate_results(results, 10) + self.validate_results(op, results, 10) + + def test_gas_portfolio(self): + + # specify problem + n = 2 + mu, sigma = portfolio.random_model(n, seed=42) + budget = n//2 + q = 0.5 + penalty = n + + # round to integer (for Grover) + sigma = 2*np.round(2*sigma) + mu = np.round(2*mu) + + # initialize docplex model + mdl = Model('portfolio_optimization') + + # create binary variables + x = {} + for i in range(n): + x[i] = mdl.integer_var(name='x%s' % i, lb=0, ub=2) + + # construct objective + ret = mdl.sum([mu[i] * x[i] for i in range(n)]) + var = mdl.sum([sigma[i, j] * x[i] * x[j] for i in range(n) for j in range(n)]) + objective = q * var - ret + mdl.minimize(objective) + + # construct budget constraint + cost = mdl.sum([x[i] for i in range(n)]) + mdl.add_constraint(cost == budget, ctname='budget') + + # print model + mdl.pprint() + + # create optimization problem from docplex model + problem = OptimizationProblem() + problem.from_docplex(mdl) + + # print problem + print(problem.write_as_string()) + + grover_optimizer = GroverMinimumFinder(num_iterations=6) + result = grover_optimizer.solve(problem) + print(result) From 7f30c62dd18e21894e32a8fa4bee55163d16b204 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Fri, 13 Mar 2020 10:00:50 +0100 Subject: [PATCH 059/323] bug fix grover optimization --- .../algorithms/grover_minimum_finder.py | 2 +- ...zation_problem_to_negative_value_oracle.py | 23 +++++++++++-------- qiskit/optimization/problems/objective.py | 2 +- .../test_grover_minimum_finder.py | 14 +++++++++++ 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index 1f468ffe79..9ea2ab5ccd 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -168,6 +168,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: rotations += rotation_count # Get state preparation operator A and oracle O for the current threshold. + # TODO: can the conversion go to before the while-loop? problem_.objective.set_offset(orig_constant - threshold) a_operator, oracle, func_dict = opt_prob_converter.encode(problem_) @@ -242,7 +243,6 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: results=grover_results) # cast binaries back to integers - print(result) result = int_to_bin_converter.decode(result) return result diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index 24657435e3..9afed5a030 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -73,16 +73,21 @@ def encode(self, problem: OptimizationProblem) -> \ quadratic_coeff = {} for i, jv in quadratic_dict.items(): for j, v in jv.items(): - quadratic_coeff[(i, j)] = v + coeff = quadratic_coeff.get((j, i), 0) + if i <= j: + quadratic_coeff[(i, j)] = v / 2 + coeff # divide by 2 since problem considers xQx/2. + else: + quadratic_coeff[(j, i)] = v / 2 + coeff constant = problem.objective.get_offset() # Get circuit requirements from input. num_key = len(linear_coeff) - num_ancilla = max(num_key, self._num_value) - 1 + # num_ancilla = max(num_key, self._num_value) - 1 # TODO: ancillas are not used, are they? + num_ancilla = 0 # Get the function dictionary. - func = self._get_function(num_key, linear_coeff, quadratic_coeff, constant) + func = self._get_function(linear_coeff, quadratic_coeff, constant) self._logger.info("Function: {}\n", func) # Define state preparation operator A from function. @@ -113,15 +118,15 @@ def encode(self, problem: OptimizationProblem) -> \ return a_operator, oracle, func @staticmethod - def _get_function(num_assets: int, linear: np.array, quadratic: np.array, constant: int) -> \ + def _get_function(linear: np.array, quadratic: np.array, constant: int) -> \ Dict[Union[int, Tuple[int, int]], int]: """Convert the problem to a dictionary format.""" func = {-1: constant} - for i in range(num_assets): - func[i] = linear[i] - for j in range(i): - if (i, j) in quadratic: - func[(i, j)] = int(quadratic[(i, j)]) + for i, v in enumerate(linear): + func[i] = v + for ij, v in quadratic.items(): + i, j = ij + func[(i, j)] = int(quadratic[(i, j)]) return func diff --git a/qiskit/optimization/problems/objective.py b/qiskit/optimization/problems/objective.py index 3b3fe61cc7..84a6fbd123 100644 --- a/qiskit/optimization/problems/objective.py +++ b/qiskit/optimization/problems/objective.py @@ -61,7 +61,7 @@ def __init__(self, varindex: Callable): super(ObjectiveInterface, self).__init__() self._linear = {} self._quadratic = {} - self._name = None + self._name = 'Objective' self._sense = ObjSense.minimize self._offset = 0.0 self._varindex = varindex diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index a46b497866..abeb146356 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -148,3 +148,17 @@ def test_gas_portfolio(self): grover_optimizer = GroverMinimumFinder(num_iterations=6) result = grover_optimizer.solve(problem) print(result) + + def test_gas_2(self): + + op = OptimizationProblem() + op.variables.add(names=["x0", "x1", "x2"], types='BBB') + + linear = [("x0", -1), ("x1", 2), ("x2", -3)] + op.objective.set_linear(linear) + op.objective.set_quadratic_coefficients('x0', 'x2', -2) + op.objective.set_quadratic_coefficients('x1', 'x2', -1) + + grover_optimizer = GroverMinimumFinder(num_iterations=8) + result = grover_optimizer.solve(op) + print(result) From 01f14108bd452d3b0242c85f47d3322fa1f8f650 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Fri, 13 Mar 2020 11:21:05 +0100 Subject: [PATCH 060/323] bug fix recursive optimization --- .../algorithms/recursive_minimum_eigen_optimizer.py | 4 ++-- .../optimization_problem_to_negative_value_oracle.py | 5 ++++- qiskit/optimization/problems/optimization_problem.py | 8 ++++---- test/optimization/test_min_eigen_optimizer.py | 4 ++-- test/optimization/test_recursive_optimization.py | 4 ++-- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index acb28469d4..1fc5468577 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -185,6 +185,6 @@ def _construct_correlations(self, states, probs): def _find_strongest_correlation(self, M): m_max = np.argmax(np.abs(M.flatten())) - i = m_max // len(M) - j = m_max - i*len(M) + i = int(m_max // len(M)) + j = int(m_max - i*len(M)) return (i, j) diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index 9afed5a030..70e46215cd 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -126,7 +126,10 @@ def _get_function(linear: np.array, quadratic: np.array, constant: int) -> \ func[i] = v for ij, v in quadratic.items(): i, j = ij - func[(i, j)] = int(quadratic[(i, j)]) + if i != j: + func[(i, j)] = int(quadratic[(i, j)]) + else: + func[i] += v return func diff --git a/qiskit/optimization/problems/optimization_problem.py b/qiskit/optimization/problems/optimization_problem.py index 3275157258..44ee057877 100644 --- a/qiskit/optimization/problems/optimization_problem.py +++ b/qiskit/optimization/problems/optimization_problem.py @@ -519,11 +519,11 @@ def substitute_variables(self, constants=None, variables=None): else: # nothing to be replaced, just copy coefficients if i == j: - w_ij = sum(self.objective.get_quadratic_coefficients(i_name, j_name), - op.objective.get_quadratic_coefficients(i_name, j_name)) + w_ij = sum([self.objective.get_quadratic_coefficients(i_name, j_name), + op.objective.get_quadratic_coefficients(i_name, j_name)]) else: - w_ij = sum(self.objective.get_quadratic_coefficients(i_name, j_name) / 2, - op.objective.get_quadratic_coefficients(i_name, j_name)) + w_ij = sum([self.objective.get_quadratic_coefficients(i_name, j_name) / 2, + op.objective.get_quadratic_coefficients(i_name, j_name)]) op.objective.set_quadratic_coefficients(i_name, j_name, w_ij) # set offset diff --git a/test/optimization/test_min_eigen_optimizer.py b/test/optimization/test_min_eigen_optimizer.py index 7b2fc8b980..a6bf97acde 100644 --- a/test/optimization/test_min_eigen_optimizer.py +++ b/test/optimization/test_min_eigen_optimizer.py @@ -21,7 +21,7 @@ from qiskit import BasicAer from qiskit.aqua import QuantumInstance -from qiskit.aqua.algorithms import ClassicalMinimumEigensolver +from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.aqua.algorithms import QAOA from qiskit.aqua.components.optimizers import COBYLA @@ -42,7 +42,7 @@ def setUp(self): self.min_eigen_solvers = {} # exact eigen solver - self.min_eigen_solvers['exact'] = ClassicalMinimumEigensolver() + self.min_eigen_solvers['exact'] = NumPyMinimumEigensolver() # QAOA optimizer = COBYLA() diff --git a/test/optimization/test_recursive_optimization.py b/test/optimization/test_recursive_optimization.py index 430f7c2c3d..25cc753960 100755 --- a/test/optimization/test_recursive_optimization.py +++ b/test/optimization/test_recursive_optimization.py @@ -21,7 +21,7 @@ from qiskit import BasicAer from qiskit.aqua import QuantumInstance -from qiskit.aqua.algorithms import ClassicalMinimumEigensolver +from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.aqua.algorithms import QAOA from qiskit.aqua.components.optimizers import COBYLA @@ -43,7 +43,7 @@ def setUp(self): self.min_eigen_solvers = {} # exact eigen solver - self.min_eigen_solvers['exact'] = ClassicalMinimumEigensolver() + self.min_eigen_solvers['exact'] = NumPyMinimumEigensolver() # QAOA optimizer = COBYLA() From 95ac536fe40b5c1aac48c3d24e0885b19f1ba09d Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Fri, 13 Mar 2020 09:09:56 -0400 Subject: [PATCH 061/323] Condense QD Functionality into Negative Oracle Converter --- .../algorithms/grover_minimum_finder.py | 13 +- qiskit/optimization/converters/__init__.py | 25 +-- ...zation_problem_to_negative_value_oracle.py | 151 ++++-------------- qiskit/optimization/results/__init__.py | 3 +- qiskit/optimization/util.py | 16 +- .../test_grover_minimum_finder.py | 8 - 6 files changed, 52 insertions(+), 164 deletions(-) diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index 9ea2ab5ccd..207d45fa9b 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -59,7 +59,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: returns a message explaining the incompatibility. Args: - problem: The optization problem to check compatibility. + problem: The optimization problem to check compatibility. Returns: Returns ``None`` if the problem is compatible and else a string with the error message. @@ -155,10 +155,15 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: orig_constant = problem_.objective.get_offset() opt_prob_converter = OptimizationProblemToNegativeValueOracle(n_value, backend=self._backend) + while not optimum_found: m = 1 improvement_found = False + # Get oracle O and the state preparation operator A for the current threshold. + problem_.objective.set_offset(orig_constant - threshold) + a_operator, oracle, func_dict = opt_prob_converter.encode(problem_) + # Iterate until we measure a negative. loops_with_no_improvement = 0 while not improvement_found: @@ -167,11 +172,6 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: rotation_count = int(np.ceil(random.uniform(0, m-1))) rotations += rotation_count - # Get state preparation operator A and oracle O for the current threshold. - # TODO: can the conversion go to before the while-loop? - problem_.objective.set_offset(orig_constant - threshold) - a_operator, oracle, func_dict = opt_prob_converter.encode(problem_) - # Apply Grover's Algorithm to find values below the threshold. if rotation_count > 0: grover = Grover(oracle, init_state=a_operator, num_iterations=rotation_count) @@ -209,6 +209,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: optimum_found = True # Using Durr and Hoyer method, increase m. + # TODO: Give option for a rotation schedule, or for different lambda's. m = int(np.ceil(min(m * 8/7, 2**(n_key / 2)))) self._logger.info('No Improvement. M: {}'.format(m)) diff --git a/qiskit/optimization/converters/__init__.py b/qiskit/optimization/converters/__init__.py index 4bc48e2b91..3b7ab3321c 100644 --- a/qiskit/optimization/converters/__init__.py +++ b/qiskit/optimization/converters/__init__.py @@ -13,30 +13,6 @@ # that they have been altered from the originals. """ -Optimization Converters (:mod:`qiskit.optimization.converters`) -Converters for optimization problems - -.. currentmodule:: qiskit.optimization.converters - -Converters for Operators and Oracles -========================================= - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - optimization_problem_to_negative_value_oracle - -Utilities/Results Objects -========================= - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - grover_optimization_results - portfolio_util - ======================================================== Optimization stack for Aqua (:mod:`qiskit.optimization`) ======================================================== @@ -62,5 +38,6 @@ "InequalityToEqualityConverter", "IntegerToBinaryConverter", "OptimizationProblemToOperator", + "OptimizationProblemToNegativeValueOracle", "PenalizeLinearEqualityConstraints" ] diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index 70e46215cd..f1299bf3fb 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -40,6 +40,7 @@ def __init__(self, num_output_qubits: int, backend: Optional[BaseBackend] = None num_output_qubits: The number of qubits required to represent the output. backend: Instance of selected backend. """ + self._num_key = 0 self._num_value = num_output_qubits if backend is None: self._backend = Aer.get_backend('statevector_simulator') @@ -52,9 +53,7 @@ def encode(self, problem: OptimizationProblem) -> \ """ A helper function that converts a QUBO into an oracle that recognizes negative numbers. Args: - linear_coeff: The linear coefficients of the QUBO. - quadratic_coeff: The quadratic coefficients of the QUBO. - constant: The constant of the QUBO. + problem: The problem to be solved. Returns: A state preparation operator A, an oracle O that recognizes negative numbers, and @@ -75,25 +74,22 @@ def encode(self, problem: OptimizationProblem) -> \ for j, v in jv.items(): coeff = quadratic_coeff.get((j, i), 0) if i <= j: - quadratic_coeff[(i, j)] = v / 2 + coeff # divide by 2 since problem considers xQx/2. + # divide by 2 since problem considers xQx/2. + quadratic_coeff[(i, j)] = v / 2 + coeff else: quadratic_coeff[(j, i)] = v / 2 + coeff constant = problem.objective.get_offset() # Get circuit requirements from input. - num_key = len(linear_coeff) - # num_ancilla = max(num_key, self._num_value) - 1 # TODO: ancillas are not used, are they? - num_ancilla = 0 + self._num_key = len(linear_coeff) # Get the function dictionary. func = self._get_function(linear_coeff, quadratic_coeff, constant) self._logger.info("Function: {}\n", func) # Define state preparation operator A from function. - quantum_dict = QQUBODictionary(num_key, self._num_value, num_ancilla, - func, backend=self._backend) - a_operator_circuit = quantum_dict.circuit + a_operator_circuit = self._build_operator(func) a_operator = Custom(a_operator_circuit.width(), circuit=a_operator_circuit) # Get registers from the A operator circuit. @@ -101,17 +97,14 @@ def encode(self, problem: OptimizationProblem) -> \ for reg in a_operator_circuit.qregs: reg_map[reg.name] = reg key_val = reg_map["key_value"] - # anc = reg_map["ancilla"] # TODO: Can we use LogicalExpressionOracle instead? # Build negative value oracle O. oracle_bit = QuantumRegister(1, "oracle") oracle_circuit = QuantumCircuit(key_val, oracle_bit) - # oracle_circuit = QuantumCircuit(key_val, anc, oracle_bit) # TODO - self._cxzxz(oracle_circuit, key_val[num_key], oracle_bit[0]) + self._cxzxz(oracle_circuit, key_val[self._num_key], oracle_bit[0]) oracle = CustomCircuitOracle(variable_register=key_val, output_register=oracle_bit, - # ancillary_register=anc, # TODO circuit=oracle_circuit, evaluate_classically_callback=self._evaluate_classically) @@ -151,126 +144,48 @@ def _evaluate_classically(self, measurement): assignment_dict[v] = bool(v < 0) return assignment_dict, assignment + def _build_operator(self, func_dict: Dict[Union[int, Tuple[int, int]], int]) -> QuantumCircuit: + """Creates a circuit for the state preparation operator. -class QuantumDictionary: - - """Defines a Quantum Dictionary, which encodes key-value pairs into the quantum state. - - See https://arxiv.org/abs/1912.04088 for a formal definition. - """ - - def __init__(self, key_bits: int, value_bits: int, ancilla_bits: int, - func_dict: Dict[Union[int, Tuple[int, int]], int], - prepare: Callable[[Any, QuantumCircuit, QuantumRegister], None], - backend: Optional[BaseBackend] = None) -> None: - """ Args: - key_bits: The number of key bits. - value_bits: The number of value bits. - ancilla_bits: The number of precision (result) bits. - func_dict: The dictionary of function coefficients to encode. - prepare: A method that encodes f into the quantum state. - backend: Instance of selected backend. - """ - self._key_bits = key_bits - self._value_bits = value_bits - self._ancilla_bits = ancilla_bits - self._func_dict = func_dict - self._prepare = prepare - if backend is None: - self._backend = Aer.get_backend('statevector_simulator') - else: - self._backend = backend - - def construct_circuit(self) -> QuantumCircuit: - """Creates a circuit for the initialized Quantum Dictionary. + func_dict: Representation of the QUBO problem. The keys should be subscripts of the + coefficients (e.g. x_1 -> 1), with the constant (if present) being represented with + a key of -1 (i.e. d[-1] = constant). Quadratic coefficients should use a tuple for + the key, with the corresponding subscripts inside (e.g. 2*x_1*x_2 -> d[(1,2)]=2). Returns: - Circuit object describing the Quantum Dictionary. + Circuit object describing the state preparation operator. """ - key_val = QuantumRegister(self._key_bits + self._value_bits, "key_value") - # ancilla = QuantumRegister(self._ancilla_bits, "ancilla") # TODO + # Build initial circuit. + key_val = QuantumRegister(self._num_key + self._num_value, "key_value") if self._backend.name == "statevector_simulator": - # circuit = QuantumCircuit(key_val, ancilla) # TODO circuit = QuantumCircuit(key_val) else: - measure = ClassicalRegister(self._key_bits + self._value_bits) - # circuit = QuantumCircuit(key_val, ancilla, measure) # TODO + measure = ClassicalRegister(self._num_key + self._num_value) circuit = QuantumCircuit(key_val, measure) - - self._prepare(self._func_dict, circuit, key_val) - - return circuit - - -class QQUBODictionary(QuantumDictionary): - - """ A Quantum Dictionary that creates a state preparation operator for a given QUBO problem. - """ - - def __init__(self, key_bits: int, value_bits: int, ancilla_bits: int, - func_dict: Dict[Union[int, Tuple[int, int]], int], - backend: Optional[BaseBackend] = None) -> None: - """ - Args: - key_bits: The number of key bits. - value_bits: The number of value bits. - ancilla_bits: The number of ancilla bits. - func_dict: The dictionary of function coefficients to encode. - backend: Instance of selected backend. - """ - if backend is None: - self._backend = Aer.get_backend('statevector_simulator') - else: - self._backend = backend - QuantumDictionary.__init__(self, key_bits, value_bits, ancilla_bits, func_dict, - self.prepare_quadratic, backend=backend) - self._circuit = None - - @property - def circuit(self) -> QuantumCircuit: - """ Provides the circuit of the Quantum Dictionary. Will construct one if not yet created. - - Returns: - Circuit object describing the Quantum Dictionary. - """ - if self._circuit is None: - self._circuit = self.construct_circuit() - return self._circuit - - def prepare_quadratic(self, func_dict: Dict[Union[int, Tuple[int, int]], int], - circuit: QuantumCircuit, key_val: QuantumRegister) -> None: - """Encodes a QUBO in the proper dictionary format into the state of a given register. - - Args: - func_dict: Representation of the QUBO problem. The keys should be subscripts of the - coefficients (e.g. x_1 -> 1), with the constant (if present) being represented with - a key of -1 (i.e. d[-1] = constant). Quadratic coefficients should use a tuple for - the key, with the corresponding subscripts inside (e.g. 2*x_1*x_2 -> d[(1,2)]=2). - circuit: The circuit to apply the operator to. - key_val: Register containing the key and value qubits. They are combined here to follow - the register format expected by algorithms like Qiskit Aqua's Grover. - """ circuit.h(key_val) - # Linear Coefficients - for i in range(self._value_bits): + # Linear Coefficients. + for i in range(self._num_value): if func_dict.get(-1, 0) != 0: - circuit.u1(1 / 2 ** self._value_bits * 2 * np.pi * 2 ** i * func_dict[-1], - key_val[self._key_bits + i]) - for j in range(self._key_bits): + circuit.u1(1 / 2 ** self._num_value * 2 * np.pi * 2 ** i * func_dict[-1], + key_val[self._num_key + i]) + for j in range(self._num_key): if func_dict.get(j, 0) != 0: - circuit.cu1(1 / 2 ** self._value_bits * 2 * np.pi * 2 ** i * func_dict[j], - key_val[j], key_val[self._key_bits + i]) + circuit.cu1(1 / 2 ** self._num_value * 2 * np.pi * 2 ** i * func_dict[j], + key_val[j], key_val[self._num_key + i]) - # Quadratic Coefficients - for i in range(self._value_bits): + # Quadratic Coefficients. + for i in range(self._num_value): for k, v in func_dict.items(): if isinstance(k, tuple): - circuit.mcu1(1 / 2 ** self._value_bits * 2 * np.pi * 2 ** i * v, - [key_val[k[0]], key_val[k[1]]], key_val[self._key_bits + i]) + circuit.mcu1(1 / 2 ** self._num_value * 2 * np.pi * 2 ** i * v, + [key_val[k[0]], key_val[k[1]]], key_val[self._num_key + i]) - iqft = IQFT(self._value_bits) - value = [key_val[v] for v in range(self._key_bits, self._key_bits + self._value_bits)] + # Add IQFT. + iqft = IQFT(self._num_value) + value = [key_val[v] for v in range(self._num_key, self._num_key + self._num_value)] iqft.construct_circuit(qubits=value, circuit=circuit) + + return circuit diff --git a/qiskit/optimization/results/__init__.py b/qiskit/optimization/results/__init__.py index 2eba1794a6..cbb700a70d 100644 --- a/qiskit/optimization/results/__init__.py +++ b/qiskit/optimization/results/__init__.py @@ -50,4 +50,5 @@ from qiskit.optimization.results.solution_status import SolutionStatus from qiskit.optimization.results.optimization_result import OptimizationResult -__all__ = ["SolutionStatus", "QualityMetrics", "SolutionStatus", "OptimizationResult"] +__all__ = ["SolutionStatus", "QualityMetrics", "SolutionStatus", "OptimizationResult", + "GroverOptimizationResults"] diff --git a/qiskit/optimization/util.py b/qiskit/optimization/util.py index 423fb3aea8..0ed984b42e 100644 --- a/qiskit/optimization/util.py +++ b/qiskit/optimization/util.py @@ -14,17 +14,19 @@ """ Optimization Utilities module """ -import datetime +from typing import Dict, Union, Tuple, Optional -def get_qubo_solutions(function_dict, n_key, print_solutions=False): - """ - Calculates all of the outputs of a QUBO function representable by n key qubits. +def get_qubo_solutions(function_dict: Dict[Union[int, Tuple[int, int]], int], n_key: int, + print_solutions: Optional[bool] = False): + """ Calculates all of the outputs of a QUBO function representable by n key qubits. + Args: - function_dict (dict): A dictionary representation of the function, where the keys correspond + function_dict: A dictionary representation of the function, where the keys correspond to a variable, and the values are the corresponding coefficients. - n_key (int): The number of key qubits. - print_solutions (bool, optional): If true, the solutions will be formatted and printed. + n_key: The number of key qubits. + print_solutions: If true, the solutions will be formatted and printed. + Returns: dict: A dictionary of the inputs (keys) and outputs (values) of the QUBO function. """ diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index abeb146356..f8beb1f5e4 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -17,12 +17,10 @@ from test.optimization import QiskitOptimizationTestCase import numpy as np from docplex.mp.model import Model -from cplex import SparsePair, SparseTriple from qiskit.finance.applications.ising import portfolio from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.optimization.algorithms import GroverMinimumFinder, MinimumEigenOptimizer from qiskit.optimization.problems import OptimizationProblem -from qiskit.optimization.util import get_qubo_solutions class TestGroverMinimumFinder(QiskitOptimizationTestCase): @@ -32,7 +30,6 @@ def validate_results(self, problem, results, max_iterations): """Validate the results object returned by GroverMinimumFinder.""" # Get measured values. grover_results = results.results - n_key = grover_results.n_input_qubits op_key = results.x op_value = results.fval iterations = len(grover_results.operation_counts) @@ -44,15 +41,10 @@ def validate_results(self, problem, results, max_iterations): # Get expected value. solver = MinimumEigenOptimizer(NumPyMinimumEigensolver()) comp_result = solver.solve(problem) - solutions = get_qubo_solutions(func, n_key, print_solutions=False) - min_key = min(solutions, key=lambda key: int(solutions[key])) - min_value = solutions[min_key] max_rotations = int(np.ceil(100*np.pi/4)) # Validate results. max_hit = max_rotations <= rot or max_iterations <= iterations - # self.assertTrue(min_key == op_key or max_hit) - # self.assertTrue(min_value == op_value or max_hit) self.assertTrue(comp_result.x == results.x or max_hit) self.assertTrue(comp_result.fval == results.fval or max_hit) From 49f028a9b7846b558b56c2d508d1474040e00683 Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Fri, 13 Mar 2020 09:49:54 -0400 Subject: [PATCH 062/323] Remove CXZXZ from Negative Oracle Converter --- ...optimization_problem_to_negative_value_oracle.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index f1299bf3fb..1f7644e278 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -16,7 +16,7 @@ import logging import numpy as np -from typing import Optional, Tuple, Dict, Union, Callable, Any +from typing import Optional, Tuple, Dict, Union from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, Aer from qiskit.aqua.components.oracles import CustomCircuitOracle from qiskit.aqua.components.initial_states import Custom @@ -98,11 +98,10 @@ def encode(self, problem: OptimizationProblem) -> \ reg_map[reg.name] = reg key_val = reg_map["key_value"] - # TODO: Can we use LogicalExpressionOracle instead? # Build negative value oracle O. oracle_bit = QuantumRegister(1, "oracle") oracle_circuit = QuantumCircuit(key_val, oracle_bit) - self._cxzxz(oracle_circuit, key_val[self._num_key], oracle_bit[0]) + oracle_circuit.z(key_val[self._num_key]) # recognize negative values. oracle = CustomCircuitOracle(variable_register=key_val, output_register=oracle_bit, circuit=oracle_circuit, @@ -126,14 +125,6 @@ def _get_function(linear: np.array, quadratic: np.array, constant: int) -> \ return func - @staticmethod - def _cxzxz(circuit: QuantumCircuit, ctrl: QuantumRegister, tgt: QuantumRegister) -> None: - """Multiplies by -1.""" - circuit.cx(ctrl, tgt) - circuit.cz(ctrl, tgt) - circuit.cx(ctrl, tgt) - circuit.cz(ctrl, tgt) - def _evaluate_classically(self, measurement): # TODO: Typing for this method? Still not sure what it's used for. Required by Grover. """ evaluate classical """ From 753acaeb8daed247434ba8a15758da9c0117a675 Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Mon, 16 Mar 2020 09:18:31 -0400 Subject: [PATCH 063/323] Update qiskit/optimization/algorithms/grover_minimum_finder.py Co-Authored-By: Julien Gacon --- qiskit/optimization/algorithms/grover_minimum_finder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index 207d45fa9b..1acbe2e001 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -42,7 +42,7 @@ def __init__(self, num_iterations: int = 3, backend: Optional[BaseBackend] = Non Args: num_iterations: The number of iterations the algorithm will search with no improvement. - backend: Instance of selected backend. + backend: Instance of selected backend, defaults to Aer's statevector simulator. """ self._n_iterations = num_iterations if backend is None: From 9fc6351d656f61d9bdf4bc685e836146f5fc11ce Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Mon, 16 Mar 2020 09:18:42 -0400 Subject: [PATCH 064/323] Update qiskit/optimization/algorithms/grover_minimum_finder.py Co-Authored-By: Julien Gacon --- qiskit/optimization/algorithms/grover_minimum_finder.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index 1acbe2e001..fb6f36284a 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -34,7 +34,6 @@ class GroverMinimumFinder(OptimizationAlgorithm): - """Uses Grover Adaptive Search (GAS) to find the minimum of a QUBO function.""" def __init__(self, num_iterations: int = 3, backend: Optional[BaseBackend] = None) -> None: From bbb192be4d093d299be899808422b1a07c7d0bf0 Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Mon, 16 Mar 2020 09:18:50 -0400 Subject: [PATCH 065/323] Update qiskit/optimization/algorithms/grover_minimum_finder.py Co-Authored-By: Julien Gacon --- qiskit/optimization/algorithms/grover_minimum_finder.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index fb6f36284a..361f3d1d7f 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -44,10 +44,7 @@ def __init__(self, num_iterations: int = 3, backend: Optional[BaseBackend] = Non backend: Instance of selected backend, defaults to Aer's statevector simulator. """ self._n_iterations = num_iterations - if backend is None: - self._backend = Aer.get_backend('statevector_simulator') - else: - self._backend = backend + self._backend = backend or Aer.get_backend('statevector_simulator') self._logger = logging.getLogger(__name__) def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: From 952c34c430a23fe15a09f75c68ab3a690b90d941 Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Mon, 16 Mar 2020 09:18:57 -0400 Subject: [PATCH 066/323] Update qiskit/optimization/algorithms/grover_minimum_finder.py Co-Authored-By: Julien Gacon --- qiskit/optimization/algorithms/grover_minimum_finder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index 361f3d1d7f..587270a627 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -106,7 +106,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: # analyze compatibility of problem msg = self.is_compatible(problem) - if msg is not None: + if msg: raise QiskitOptimizationError('Incompatible problem: %s' % msg) # map integer variables to binary variables From 0d99869522f2b3666d3b9408ec7edd8679c9aa7c Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Mon, 16 Mar 2020 09:19:07 -0400 Subject: [PATCH 067/323] Update qiskit/optimization/algorithms/__init__.py Co-Authored-By: Julien Gacon --- qiskit/optimization/algorithms/__init__.py | 33 ++++++++++++++-------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/qiskit/optimization/algorithms/__init__.py b/qiskit/optimization/algorithms/__init__.py index 0359d36359..458c816513 100644 --- a/qiskit/optimization/algorithms/__init__.py +++ b/qiskit/optimization/algorithms/__init__.py @@ -13,26 +13,37 @@ # that they have been altered from the originals. """ -Optimization Algorithms (:mod:`qiskit.optimization.algorithms`) -Algorithms for optimization problems +======================================================== +Optimization stack for Aqua (:mod:`qiskit.optimization`) +======================================================== + +Algorithms for optimization problems. .. currentmodule:: qiskit.optimization.algorithms -Algorithms -============= +Base class +========== .. autosummary:: :toctree: ../stubs/ :nosignatures: + + OptimizationAlgorithm - grover_minimum_finder - - -======================================================== -Optimization stack for Aqua (:mod:`qiskit.optimization`) -======================================================== +Algorithms +========== -.. currentmodule:: qiskit.optimization.algorithms +.. autosummary:: + :toctree: ../stubs/ + :nosignatures: + + ADMMOptimizer + CobylaOptimizer + CplexOptimizer + GroverMinimumFinder + MinimumEigenOptimizer + RecursiveMinimumEigenOptimizer + Structures for defining an optimization algorithms ========== From ac46485b8bb8b0e105e2aece33efa2ded09179eb Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Mon, 16 Mar 2020 09:19:26 -0400 Subject: [PATCH 068/323] Update qiskit/optimization/algorithms/__init__.py Co-Authored-By: Julien Gacon --- qiskit/optimization/algorithms/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/qiskit/optimization/algorithms/__init__.py b/qiskit/optimization/algorithms/__init__.py index 458c816513..43365111ae 100644 --- a/qiskit/optimization/algorithms/__init__.py +++ b/qiskit/optimization/algorithms/__init__.py @@ -44,10 +44,6 @@ MinimumEigenOptimizer RecursiveMinimumEigenOptimizer -Structures for defining an optimization algorithms -========== - - """ from qiskit.optimization.algorithms.optimization_algorithm import OptimizationAlgorithm From d87aad007845f22bf206aa9bc9b23e93b2067244 Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Mon, 16 Mar 2020 09:19:34 -0400 Subject: [PATCH 069/323] Update qiskit/optimization/results/grover_optimization_results.py Co-Authored-By: Julien Gacon --- qiskit/optimization/results/grover_optimization_results.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qiskit/optimization/results/grover_optimization_results.py b/qiskit/optimization/results/grover_optimization_results.py index 721cc759ce..eb7c7ad378 100644 --- a/qiskit/optimization/results/grover_optimization_results.py +++ b/qiskit/optimization/results/grover_optimization_results.py @@ -41,7 +41,11 @@ def __init__(self, operation_counts: Dict[int, Dict[str, int]], rotations: int, @property def operation_counts(self) -> Dict[int, Dict[str, int]]: - """Getter of operation_counts""" + """Get the of operation_counts. + + Returns: + The counts of each operation performed per iteration. + """ return self._operation_counts @property From 455080533855040fa1753a8aac8198cbd3c494fc Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Mon, 16 Mar 2020 09:20:02 -0400 Subject: [PATCH 070/323] Update qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py Co-Authored-By: Julien Gacon --- .../converters/optimization_problem_to_negative_value_oracle.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index 1f7644e278..1ec547eefb 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -26,7 +26,6 @@ class OptimizationProblemToNegativeValueOracle: - """Converts an optimization problem (QUBO) to a negative value oracle. In addition, a state preparation operator is generated from the coefficients and constant of a From a08231d4d2a2e9e2adeef61bb0f13ddc7f647ecf Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Mon, 16 Mar 2020 09:20:15 -0400 Subject: [PATCH 071/323] Update qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py Co-Authored-By: Julien Gacon --- .../optimization_problem_to_negative_value_oracle.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index 1ec547eefb..21faab400d 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -69,14 +69,14 @@ def encode(self, problem: OptimizationProblem) -> \ # get quadratic part of objective quadratic_dict = problem.objective.get_quadratic() quadratic_coeff = {} - for i, jv in quadratic_dict.items(): - for j, v in jv.items(): + for i, j_value_dict in quadratic_dict.items(): + for j, value in j_value_dict.items(): coeff = quadratic_coeff.get((j, i), 0) if i <= j: # divide by 2 since problem considers xQx/2. - quadratic_coeff[(i, j)] = v / 2 + coeff + quadratic_coeff[(i, j)] = value / 2 + coeff else: - quadratic_coeff[(j, i)] = v / 2 + coeff + quadratic_coeff[(j, i)] = value / 2 + coeff constant = problem.objective.get_offset() From 1ba96a1e861d7aea3c26ac3ccfc4f209ac556f10 Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Mon, 16 Mar 2020 09:20:58 -0400 Subject: [PATCH 072/323] Update qiskit/optimization/results/grover_optimization_results.py Co-Authored-By: Julien Gacon --- qiskit/optimization/results/grover_optimization_results.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiskit/optimization/results/grover_optimization_results.py b/qiskit/optimization/results/grover_optimization_results.py index eb7c7ad378..2b7c90c8c0 100644 --- a/qiskit/optimization/results/grover_optimization_results.py +++ b/qiskit/optimization/results/grover_optimization_results.py @@ -17,8 +17,7 @@ from typing import Dict, Tuple, Union -class GroverOptimizationResults(): - +class GroverOptimizationResults: """A results object for Grover Optimization methods.""" def __init__(self, operation_counts: Dict[int, Dict[str, int]], rotations: int, From 6fb43887c9cd90db85747f565729d7494b6e35c6 Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Mon, 16 Mar 2020 09:27:52 -0400 Subject: [PATCH 073/323] Return docs for GroverOptimizationResults --- .../results/grover_optimization_results.py | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/qiskit/optimization/results/grover_optimization_results.py b/qiskit/optimization/results/grover_optimization_results.py index 2b7c90c8c0..a97801ad81 100644 --- a/qiskit/optimization/results/grover_optimization_results.py +++ b/qiskit/optimization/results/grover_optimization_results.py @@ -40,7 +40,7 @@ def __init__(self, operation_counts: Dict[int, Dict[str, int]], rotations: int, @property def operation_counts(self) -> Dict[int, Dict[str, int]]: - """Get the of operation_counts. + """Get the operation counts. Returns: The counts of each operation performed per iteration. @@ -49,20 +49,38 @@ def operation_counts(self) -> Dict[int, Dict[str, int]]: @property def rotation_count(self) -> int: - """Getter of rotation_count""" + """Getter of rotation_count + + Returns: + The total number of Grover rotations. + """ return self._rotations @property def n_input_qubits(self) -> int: - """Getter of n_input_qubits""" + """Getter of n_input_qubits + + Returns: + The number of qubits used to represent the input. + """ return self._n_input_qubits @property def n_output_qubits(self) -> int: - """Getter of n_output_qubits""" + """Getter of n_output_qubits + + Returns: + The number of qubits used to represent the output. + """ return self._n_output_qubits @property def func_dict(self) -> Dict[Union[int, Tuple[int, int]], int]: - """Getter of func_dict""" + """Getter of func_dict + + Returns: + A dictionary of coefficients describing a function, where the keys are the subscripts + of the variables (e.g. x1), and the values are the corresponding coefficients. If there + is a constant term, it is referenced by key -1. + """ return self._func_dict From a4d6b5f10906678ad225c7a4602fa459027b9623 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Mon, 16 Mar 2020 23:27:07 +0100 Subject: [PATCH 074/323] add qubo converter to streamline code --- .../algorithms/grover_minimum_finder.py | 66 ++------- .../algorithms/minimum_eigen_optimizer.py | 69 ++-------- .../recursive_minimum_eigen_optimizer.py | 91 +++++++----- qiskit/optimization/converters/__init__.py | 8 +- ...zation_problem_to_negative_value_oracle.py | 2 +- .../optimization_problem_to_qubo.py | 130 ++++++++++++++++++ 6 files changed, 215 insertions(+), 151 deletions(-) create mode 100644 qiskit/optimization/converters/optimization_problem_to_qubo.py diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index 207d45fa9b..1ca699d193 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -21,12 +21,10 @@ import numpy as np from qiskit.optimization.algorithms import OptimizationAlgorithm from qiskit.optimization.problems import OptimizationProblem -from qiskit.optimization.converters import (IntegerToBinaryConverter, - PenalizeLinearEqualityConstraints, +from qiskit.optimization.converters import (OptimizationProblemToQubo, OptimizationProblemToNegativeValueOracle) from qiskit.optimization.results import GroverOptimizationResults from qiskit.optimization.results import OptimizationResult -from qiskit.optimization.utils import QiskitOptimizationError from qiskit.optimization.util import get_qubo_solutions from qiskit.aqua.algorithms.amplitude_amplifiers.grover import Grover from qiskit import Aer, execute, QuantumCircuit @@ -34,7 +32,6 @@ class GroverMinimumFinder(OptimizationAlgorithm): - """Uses Grover Adaptive Search (GAS) to find the minimum of a QUBO function.""" def __init__(self, num_iterations: int = 3, backend: Optional[BaseBackend] = None) -> None: @@ -54,43 +51,16 @@ def __init__(self, num_iterations: int = 3, backend: Optional[BaseBackend] = Non def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: """Checks whether a given problem can be solved with this optimizer. - Checks whether the given problem is compatible, i.e., whether the problem contains only - binary and integer variables as well as linear equality constraints, and otherwise, - returns a message explaining the incompatibility. + Checks whether the given problem is compatible, i.e., whether the problem can be converted + to a QUBO, and otherwise, returns a message explaining the incompatibility. Args: - problem: The optimization problem to check compatibility. + problem: The optization problem to check compatibility. Returns: Returns ``None`` if the problem is compatible and else a string with the error message. """ - - # initialize message - msg = '' - - # check whether there are incompatible variable types - if problem.variables.get_num_continuous() > 0: - msg += 'Continuous variables are not supported! ' - if problem.variables.get_num_semicontinuous() > 0: - msg += 'Semi-continuous variables are not supported! ' - # if problem.variables.get_num_integer() > 0: - # # TODO: to be removed once integer to binary mapping is introduced - # msg += 'Integer variables are not supported! ' - if problem.variables.get_num_semiinteger() > 0: - # TODO: to be removed once semi-integer to binary mapping is introduced - msg += 'Semi-integer variables are not supported! ' - - # check whether there are incompatible constraint types - if not all([sense == 'E' for sense in problem.linear_constraints.get_senses()]): - msg += 'Only linear equality constraints are supported.' - - # TODO: check for quadratic constraints - - # if an error occurred, return error message, otherwise, return None - if len(msg) > 0: - return msg.strip() - else: - return None + return OptimizationProblemToQubo.is_compatible(problem) def solve(self, problem: OptimizationProblem) -> OptimizationResult: """Tries to solves the given problem using the optimizer. @@ -108,24 +78,9 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ - # analyze compatibility of problem - msg = self.is_compatible(problem) - if msg is not None: - raise QiskitOptimizationError('Incompatible problem: %s' % msg) - - # map integer variables to binary variables - int_to_bin_converter = IntegerToBinaryConverter() - problem_ = int_to_bin_converter.encode(problem) - - # penalize linear equality constraints with only binary variables - penalty = 2 # TODO - # if self._penalty is None: - # # TODO: should be derived from problem - # penalty = 1e5 - # else: - # penalty = self._penalty - lin_eq_converter = PenalizeLinearEqualityConstraints() - problem_ = lin_eq_converter.encode(problem_, penalty_factor=penalty) + # convert problem to QUBO + qubo_converter = OptimizationProblemToQubo() + problem_ = qubo_converter.encode(problem) # TODO: How to get from Optimization Problem? num_output_qubits = 6 @@ -241,10 +196,11 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: grover_results = GroverOptimizationResults(operation_count, rotations, n_key, n_value, func_dict) result = OptimizationResult(x=opt_x, fval=solutions[optimum_key], - results=grover_results) + results={"grover_results": grover_results, + "qubo_converter": qubo_converter}) # cast binaries back to integers - result = int_to_bin_converter.decode(result) + result = qubo_converter.decode(result) return result diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 8d2bdd8400..c81626dff5 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -16,11 +16,12 @@ """A wrapper for minimum eigen solvers from Qiskit Aqua to be used within Qiskit Optimization. Examples: - >>> problem = OptimizationProblem() >>> # specify problem here + >>> problem = OptimizationProblem() >>> # specify minimum eigen solver to be used, e.g., QAOA >>> qaoa = QAOA(...) - >>> optimizer = MinEigenOptimizer(qaoa) + >>> # construct minimum eigen optimizer + >>> optimizer = MinimumEigenOptimizer(qaoa) >>> result = optimizer.solve(problem) """ @@ -29,10 +30,7 @@ from qiskit.aqua.algorithms import MinimumEigensolver from qiskit.optimization.problems import OptimizationProblem from qiskit.optimization.algorithms import OptimizationAlgorithm -from qiskit.optimization.utils import QiskitOptimizationError -from qiskit.optimization.converters import (OptimizationProblemToOperator, - PenalizeLinearEqualityConstraints, - IntegerToBinaryConverter) +from qiskit.optimization.converters import OptimizationProblemToQubo, OptimizationProblemToOperator from qiskit.optimization.utils import eigenvector_to_solutions from qiskit.optimization.results import OptimizationResult @@ -71,9 +69,8 @@ def __init__(self, min_eigen_solver: MinimumEigensolver, penalty: Optional[float def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: """Checks whether a given problem can be solved with this optimizer. - Checks whether the given problem is compatible, i.e., whether the problem contains only - binary and integer variables as well as linear equality constraints, and otherwise, - returns a message explaining the incompatibility. + Checks whether the given problem is compatible, i.e., whether the problem can be converted + to a QUBO, and otherwise, returns a message explaining the incompatibility. Args: problem: The optization problem to check compatibility. @@ -81,33 +78,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: Returns: Returns ``None`` if the problem is compatible and else a string with the error message. """ - - # initialize message - msg = '' - - # check whether there are incompatible variable types - if problem.variables.get_num_continuous() > 0: - msg += 'Continuous variables are not supported! ' - if problem.variables.get_num_semicontinuous() > 0: - msg += 'Semi-continuous variables are not supported! ' - # if problem.variables.get_num_integer() > 0: - # # TODO: to be removed once integer to binary mapping is introduced - # msg += 'Integer variables are not supported! ' - if problem.variables.get_num_semiinteger() > 0: - # TODO: to be removed once semi-integer to binary mapping is introduced - msg += 'Semi-integer variables are not supported! ' - - # check whether there are incompatible constraint types - if not all([sense == 'E' for sense in problem.linear_constraints.get_senses()]): - msg += 'Only linear equality constraints are supported.' - - # TODO: check for quadratic constraints - - # if an error occurred, return error message, otherwise, return None - if len(msg) > 0: - return msg.strip() - else: - return None + return OptimizationProblemToQubo.is_compatible(problem) def solve(self, problem: OptimizationProblem) -> OptimizationResult: """Tries to solves the given problem using the optimizer. @@ -120,27 +91,11 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: Returns: The result of the optimizer applied to the problem. - Raises: - QiskitOptimizationError: If the problem is incompatible with the optimizer. """ - # analyze compatibility of problem - msg = self.is_compatible(problem) - if msg is not None: - raise QiskitOptimizationError('Incompatible problem: %s' % msg) - - # map integer variables to binary variables - int_to_bin_converter = IntegerToBinaryConverter() - problem_ = int_to_bin_converter.encode(problem) - - # penalize linear equality constraints with only binary variables - if self._penalty is None: - # TODO: should be derived from problem - penalty = 1e5 - else: - penalty = self._penalty - lin_eq_converter = PenalizeLinearEqualityConstraints() - problem_ = lin_eq_converter.encode(problem_, penalty_factor=penalty) + # convert problem to QUBO + qubo_converter = OptimizationProblemToQubo() + problem_ = qubo_converter.encode(problem) # construct operator and offset operator_converter = OptimizationProblemToOperator() @@ -156,8 +111,8 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: results.sort(key=lambda x: problem_.objective.get_sense() * x[1]) # translate result back to integers - opt_res = OptimizationResult(results[0][0], results[0][1], (results, int_to_bin_converter)) - opt_res = int_to_bin_converter.decode(opt_res) + opt_res = OptimizationResult(results[0][0], results[0][1], (results, qubo_converter)) + opt_res = qubo_converter.decode(opt_res) # translate results back to original problem return opt_res diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 1fc5468577..3988aa71f8 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -33,24 +33,43 @@ from qiskit.optimization.algorithms import OptimizationAlgorithm, MinimumEigenOptimizer from qiskit.optimization.problems import OptimizationProblem from qiskit.optimization.results import OptimizationResult -from qiskit.optimization.converters import (PenalizeLinearEqualityConstraints, - IntegerToBinaryConverter) +from qiskit.optimization.converters import OptimizationProblemToQubo from qiskit.aqua.algorithms import NumPyMinimumEigensolver class RecursiveMinimumEigenOptimizer(OptimizationAlgorithm): - """ - TODO + """ A meta-algorithm that applies the recursive optimization scheme introduce in + [paper] on top of ``MinimumEigenOptimizer``. """ def __init__(self, min_eigen_optimizer: MinimumEigenOptimizer, min_num_vars: int = 1, min_num_vars_optimizer: Optional[OptimizationAlgorithm] = None, penalty: Optional[float] = None) -> None: + """ Initializes the recusrive miniimum eigen optimizer. + + This initializer takes a ``MinimumEigenOptimizer``, the parameters to specify until when to + to apply the iterative scheme, and the optimizer to be applied once the threshold number of + variables is reached. + + Args: + min_eigen_optimizer: The eigen optimizer to use in every iteration. + min_num_vars: The minimum number of variables to apply the recursive scheme. If this + threshold is reached, the min_num_vars_optimizer is used. + min_num_vars_optimizer: This optimizer is used after the recursive scheme for the + problem with the remaining variables. + penalty: The factor that is used to scale the penalty terms corresponding to linear + equality constraints. + + TODO: add flag to store full history. + + Raises: + QiskitOptimizationError: In case of invalid parameters (num_min_vars < 1). """ - TODO: add flag to store full history... - """ + # TODO: should also allow function that maps problem to -correlators? - # --> would support efficient classical implementation for QAOA with depth p=1 + # --> would support efficient classical implementation for QAOA with depth p=1 + # --> add results class for MinimumEigenSolver that contains enough info to do so. + self._min_eigen_optimizer = min_eigen_optimizer if min_num_vars < 1: raise QiskitOptimizationError('Minimal problem size needs to be >= 1!') @@ -62,35 +81,35 @@ def __init__(self, min_eigen_optimizer: MinimumEigenOptimizer, min_num_vars: int self._penalty = penalty def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + """Checks whether a given problem can be solved with this optimizer. + + Checks whether the given problem is compatible, i.e., whether the problem can be converted + to a QUBO, and otherwise, returns a message explaining the incompatibility. + + Args: + problem: The optization problem to check compatibility. + + Returns: + Returns ``None`` if the problem is compatible and else a string with the error message. """ - TODO - """ - return None + return OptimizationProblemToQubo.is_compatible(problem) def solve(self, problem: OptimizationProblem) -> OptimizationResult: - """ - # handle variable replacements via variable names - # --> allows to easily adjust problems and roll-out final results - # TODO - """ + """Tries to solves the given problem using the recursive optimizer. - # analyze compatibility of problem - msg = self.is_compatible(problem) - if msg is not None: - raise QiskitOptimizationError('Incompatible problem: %s' % msg) + Runs the optimizer to try to solve the optimization problem. - # map integer variables to binary variables - int_to_bin_converter = IntegerToBinaryConverter() - problem_ = int_to_bin_converter.encode(problem) + Args: + problem: The problem to be solved. - # penalize linear equality constraints with only binary variables - if self._penalty is None: - # TODO: should be derived from problem - penalty = 1e5 - else: - penalty = self._penalty - lin_eq_converter = PenalizeLinearEqualityConstraints() - problem_ = lin_eq_converter.encode(problem_, penalty_factor=penalty) + Returns: + The result of the optimizer applied to the problem. + + """ + + # convert problem to QUBO + qubo_converter = OptimizationProblemToQubo() + problem_ = qubo_converter.encode(problem) problem_ref = deepcopy(problem_) # run recursive optimization until the resulting problem is small enough @@ -166,8 +185,8 @@ def find_value(x, replacements, var_values): # construct result x = [var_values[name] for name in problem_ref.variables.get_names()] fval = result.fval - results = OptimizationResult(x, fval, (replacements, int_to_bin_converter)) - results = int_to_bin_converter.decode(results) + results = OptimizationResult(x, fval, (replacements, qubo_converter)) + results = qubo_converter.decode(results) return results def _construct_correlations(self, states, probs): @@ -183,8 +202,8 @@ def _construct_correlations(self, states, probs): correlations[i, j] -= p return correlations - def _find_strongest_correlation(self, M): - m_max = np.argmax(np.abs(M.flatten())) - i = int(m_max // len(M)) - j = int(m_max - i*len(M)) + def _find_strongest_correlation(self, correlations): + m_max = np.argmax(np.abs(correlations.flatten())) + i = int(m_max // len(correlations)) + j = int(m_max - i*len(correlations)) return (i, j) diff --git a/qiskit/optimization/converters/__init__.py b/qiskit/optimization/converters/__init__.py index 3b7ab3321c..b80deb5224 100644 --- a/qiskit/optimization/converters/__init__.py +++ b/qiskit/optimization/converters/__init__.py @@ -24,7 +24,8 @@ """ -from .optimization_problem_to_negative_value_oracle import OptimizationProblemToNegativeValueOracle +from qiskit.optimization.converters.optimization_problem_to_negative_value_oracle import\ + OptimizationProblemToNegativeValueOracle from qiskit.optimization.converters.inequality_to_equality_converter import\ InequalityToEqualityConverter from qiskit.optimization.converters.integer_to_binary_converter import\ @@ -33,11 +34,14 @@ OptimizationProblemToOperator from qiskit.optimization.converters.penalize_linear_equality_constraints import\ PenalizeLinearEqualityConstraints +from qiskit.optimization.converters.optimization_problem_to_qubo import\ + OptimizationProblemToQubo __all__ = [ "InequalityToEqualityConverter", "IntegerToBinaryConverter", "OptimizationProblemToOperator", "OptimizationProblemToNegativeValueOracle", - "PenalizeLinearEqualityConstraints" + "PenalizeLinearEqualityConstraints", + "OptimizationProblemToQubo" ] diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index 1f7644e278..8ce9c9dd8e 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -15,8 +15,8 @@ """OptimizationProblemToNegativeValueOracle module""" import logging -import numpy as np from typing import Optional, Tuple, Dict, Union +import numpy as np from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, Aer from qiskit.aqua.components.oracles import CustomCircuitOracle from qiskit.aqua.components.initial_states import Custom diff --git a/qiskit/optimization/converters/optimization_problem_to_qubo.py b/qiskit/optimization/converters/optimization_problem_to_qubo.py new file mode 100644 index 0000000000..9fdc73696a --- /dev/null +++ b/qiskit/optimization/converters/optimization_problem_to_qubo.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +"""A converter from optimization problem to a QUBO (given as optimization problem). +""" + +from typing import Optional + +from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.results import OptimizationResult +from qiskit.optimization.converters import (PenalizeLinearEqualityConstraints, + IntegerToBinaryConverter) +from qiskit.optimization.utils import QiskitOptimizationError + + +class OptimizationProblemToQubo: + """ Convert a given optimization problem to a new problem that is a QUBO. + + Examples: + >>> problem = OptimizationProblem() + >>> # define a problem + >>> conv = OptimizationProblemToQubo() + >>> problem2 = conv.encode(problem) + """ + + def __init__(self, penalty: Optional[float] = 1e5) -> None: + """ Constructor. It initializes the internal data structure. + + Args: + penalty: Penalty factor to scale equality constraints that are added to objective. + """ + self._int_to_bin = IntegerToBinaryConverter() + self._penalize_lin_eq_constraints = PenalizeLinearEqualityConstraints() + self._penalty = penalty + + def encode(self, problem: OptimizationProblem) -> OptimizationProblem: + """ Convert a problem with inequality constraints into new one with only equality + constraints. + + Args: + problem: The problem to be solved, that may contain inequality constraints. + + Returns: + The converted problem, that contain only equality constraints. + + Raises: + QiskitOptimizationError: In case of an incompatible problem. + + """ + + # analyze compatibility of problem + msg = self.is_compatible(problem) + if msg is not None: + raise QiskitOptimizationError('Incompatible problem: %s' % msg) + + # map integer variables to binary variables + problem_ = self._int_to_bin.encode(problem) + + # penalize linear equality constraints with only binary variables + if self._penalty is None: + # TODO: should be derived from problem + penalty = 1e5 + else: + penalty = self._penalty + problem_ = self._penalize_lin_eq_constraints.encode(problem_, penalty_factor=penalty) + + # return QUBO + return problem_ + + def decode(self, result: OptimizationResult) -> OptimizationResult: + """ Convert a result of a converted problem into that of the original problem. + + Args: + result: The result of the converted problem. + + Returns: + The result of the original problem. + """ + return self._int_to_bin.decode(result) + + @staticmethod + def is_compatible(problem: OptimizationProblem) -> Optional[str]: + """Checks whether a given problem can be cast to a Quadratic Unconstrained Binary + Optimization (QUBO) problem, i.e., whether the problem contains only binary and integer + variables as well as linear equality constraints, and otherwise, returns a message + explaining the incompatibility. + + Args: + problem: The optimization problem to check compatibility. + + Returns: + Returns ``None`` if the problem is compatible and else a string with the error message. + + """ + + # initialize message + msg = '' + + # check whether there are incompatible variable types + if problem.variables.get_num_continuous() > 0: + msg += 'Continuous variables are not supported! ' + if problem.variables.get_num_semicontinuous() > 0: + # TODO: to be removed once semi-continuous to binary + continuous is introduced + msg += 'Semi-continuous variables are not supported! ' + if problem.variables.get_num_semiinteger() > 0: + # TODO: to be removed once semi-integer to binary mapping is introduced + msg += 'Semi-integer variables are not supported! ' + + # check whether there are incompatible constraint types + if not all([sense == 'E' for sense in problem.linear_constraints.get_senses()]): + msg += 'Only linear equality constraints are supported.' + if problem.quadratic_constraints.get_num() > 0: + msg += 'Quadratic constraints are not supported. ' + + # if an error occurred, return error message, otherwise, return None + if len(msg) > 0: + return msg.strip() + else: + return None From c6cd1873dd46148683e6931f6d8225a3156f8284 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Tue, 17 Mar 2020 10:35:43 +0100 Subject: [PATCH 075/323] update unit tests --- .../algorithms/cobyla_optimizer.py | 32 +++++++++- .../recursive_minimum_eigen_optimizer.py | 2 +- test/aqua/test_compute_min_eigenvalue.py | 4 +- test/aqua/test_optimizers.py | 8 ++- test/finance/test_portfolio.py | 4 +- .../finance/test_portfolio_diversification.py | 2 +- test/optimization/test_cobyla_optimizer.py | 25 +++++++- .../test_grover_minimum_finder.py | 61 +------------------ test/optimization/test_stable_set.py | 4 +- test/optimization/test_tsp.py | 4 +- 10 files changed, 72 insertions(+), 74 deletions(-) diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index d066926727..522dcb9a31 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -169,7 +169,37 @@ def objective(x): # TODO: add range constraints raise QiskitOptimizationError('Unsupported constraint type!') - # TODO: add quadratic constraints + # add quadratic constraints + for i in range(problem.quadratic_constraints.get_num()): + rhs = problem.quadratic_constraints.get_rhs(i) + sense = problem.quadratic_constraints.get_senses(i) + + linear_comp = problem.quadratic_constraints.get_linear_components(i) + quadratic_comp = problem.quadratic_constraints.get_quadratic_components(i) + + linear_array = np.zeros(num_vars) + for j, v in zip(linear_comp.ind, linear_comp.val): + linear_array[j] = v + + quadratic_array = np.zeros((num_vars, num_vars)) + for i, j, v in zip(quadratic_comp.ind1, quadratic_comp.ind2, quadratic_comp.val): + quadratic_array[i, j] = v + + def lhs(x): + return np.dot(x, linear_array) + np.dot(np.dot(x, quadratic_array), x) + + if sense == 'E': + constraints += [ + lambda x: rhs - lhs(x), + lambda x: lhs(x) - rhs + ] + elif sense == 'L': + constraints += [lambda x: rhs - lhs(x)] + elif sense == 'G': + constraints += [lambda x: lhs(x) - rhs] + else: + # TODO: add range constraints + raise QiskitOptimizationError('Unsupported constraint type!') # TODO: derive x0 from lower/upper bounds x0 = np.zeros(problem.variables.get_num()) diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 3988aa71f8..42e0e700ab 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -39,7 +39,7 @@ class RecursiveMinimumEigenOptimizer(OptimizationAlgorithm): """ A meta-algorithm that applies the recursive optimization scheme introduce in - [paper] on top of ``MinimumEigenOptimizer``. + [http://arxiv.org/abs/1910.08980] on top of ``MinimumEigenOptimizer``. """ def __init__(self, min_eigen_optimizer: MinimumEigenOptimizer, min_num_vars: int = 1, diff --git a/test/aqua/test_compute_min_eigenvalue.py b/test/aqua/test_compute_min_eigenvalue.py index 0b0a64583b..0d67358dca 100755 --- a/test/aqua/test_compute_min_eigenvalue.py +++ b/test/aqua/test_compute_min_eigenvalue.py @@ -23,7 +23,7 @@ from qiskit.aqua.components.variational_forms import RY, RYRZ from qiskit.aqua.components.optimizers import L_BFGS_B, SPSA, SLSQP from qiskit.aqua.components.initial_states import Zero -from qiskit.aqua.algorithms import VQE, ClassicalMinimumEigensolver +from qiskit.aqua.algorithms import VQE, NumPyMinimumEigensolver class TestComputeMinEigenvalue(QiskitAquaTestCase): @@ -77,7 +77,7 @@ def test_vqe_qasm(self): def test_ee(self): """ EE test """ dummy_operator = MatrixOperator([[1]]) - ee = ClassicalMinimumEigensolver() + ee = NumPyMinimumEigensolver() output = ee.compute_minimum_eigenvalue(self.qubit_op) self.assertAlmostEqual(output.eigenvalue, -1.85727503) diff --git a/test/aqua/test_optimizers.py b/test/aqua/test_optimizers.py index 6d854eefa9..0dab11ee4c 100644 --- a/test/aqua/test_optimizers.py +++ b/test/aqua/test_optimizers.py @@ -22,7 +22,7 @@ from qiskit.aqua import aqua_globals from qiskit.aqua.components.optimizers import (ADAM, CG, COBYLA, L_BFGS_B, P_BFGS, NELDER_MEAD, - POWELL, SLSQP, SPSA, TNC) + POWELL, SLSQP, SPSA, TNC, GSLS) class TestOptimizers(QiskitAquaTestCase): @@ -99,6 +99,12 @@ def test_tnc(self): res = self._optimize(optimizer) self.assertLessEqual(res[2], 10000) + def test_gsls(self): + """ gsls test """ + optimizer = GSLS() + res = self._optimize(optimizer) + self.assertLessEqual(res[2], 10000) + if __name__ == '__main__': unittest.main() diff --git a/test/finance/test_portfolio.py b/test/finance/test_portfolio.py index e237cf2c84..1f6462dc8a 100644 --- a/test/finance/test_portfolio.py +++ b/test/finance/test_portfolio.py @@ -24,9 +24,9 @@ from qiskit.aqua import aqua_globals, QuantumInstance from qiskit.aqua.algorithms import NumPyMinimumEigensolver, QAOA from qiskit.aqua.components.optimizers import COBYLA -from qiskit.finance.ising import portfolio +from qiskit.finance.applications.ising import portfolio from qiskit.finance.data_providers import RandomDataProvider -from qiskit.optimization.ising.common import sample_most_likely +from qiskit.optimization.applications.ising.common import sample_most_likely class TestPortfolio(QiskitFinanceTestCase): diff --git a/test/finance/test_portfolio_diversification.py b/test/finance/test_portfolio_diversification.py index b8ab4bcd21..58fac37783 100644 --- a/test/finance/test_portfolio_diversification.py +++ b/test/finance/test_portfolio_diversification.py @@ -22,7 +22,7 @@ from qiskit.aqua import aqua_globals from qiskit.aqua.algorithms import NumPyMinimumEigensolver -from qiskit.finance.ising.portfolio_diversification import \ +from qiskit.finance.applications.ising.portfolio_diversification import \ (get_portfoliodiversification_solution, get_operator, get_portfoliodiversification_value) diff --git a/test/optimization/test_cobyla_optimizer.py b/test/optimization/test_cobyla_optimizer.py index a2b16bc1f9..9b7e990499 100644 --- a/test/optimization/test_cobyla_optimizer.py +++ b/test/optimization/test_cobyla_optimizer.py @@ -14,13 +14,15 @@ """ Test Cobyla Optimizer """ + from test.optimization.common import QiskitOptimizationTestCase -import numpy as np + +from ddt import ddt, data from qiskit.optimization.algorithms import CobylaOptimizer from qiskit.optimization.problems import OptimizationProblem -from ddt import ddt, data +from cplex import SparsePair, SparseTriple @ddt @@ -51,3 +53,22 @@ def test_cobyla_optimizer(self, config): # analyze results self.assertAlmostEqual(result.fval, fval) + + def test_cobyla_optimizer_with_quadratic_constraint(self): + """ Cobyla Optimizer Test """ + + # load optimization problem + problem = OptimizationProblem() + problem.variables.add(lb=[0, 0], ub=[1, 1], types='CC') + problem.objective.set_linear([(0, 1), (1, 1)]) + + qc = problem.quadratic_constraints + linear = SparsePair(ind=[0, 1], val=[-1, -1]) + quadratic = SparseTriple(ind1=[0, 1], ind2=[0, 1], val=[1, 1]) + qc.add(name='qc', lin_expr=linear, quad_expr=quadratic, rhs=-1/2) + + # solve problem with cobyla + result = self.cobyla_optimizer.solve(problem) + + # analyze results + self.assertAlmostEqual(result.fval, 1.0, places=2) diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index f8beb1f5e4..5aed7962ad 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -29,7 +29,7 @@ class TestGroverMinimumFinder(QiskitOptimizationTestCase): def validate_results(self, problem, results, max_iterations): """Validate the results object returned by GroverMinimumFinder.""" # Get measured values. - grover_results = results.results + grover_results = results.results['grover_results'] op_key = results.x op_value = results.fval iterations = len(grover_results.operation_counts) @@ -95,62 +95,3 @@ def test_qubo_gas_int_paper_example(self): gmf = GroverMinimumFinder(num_iterations=n_iter) results = gmf.solve(op) self.validate_results(op, results, 10) - - def test_gas_portfolio(self): - - # specify problem - n = 2 - mu, sigma = portfolio.random_model(n, seed=42) - budget = n//2 - q = 0.5 - penalty = n - - # round to integer (for Grover) - sigma = 2*np.round(2*sigma) - mu = np.round(2*mu) - - # initialize docplex model - mdl = Model('portfolio_optimization') - - # create binary variables - x = {} - for i in range(n): - x[i] = mdl.integer_var(name='x%s' % i, lb=0, ub=2) - - # construct objective - ret = mdl.sum([mu[i] * x[i] for i in range(n)]) - var = mdl.sum([sigma[i, j] * x[i] * x[j] for i in range(n) for j in range(n)]) - objective = q * var - ret - mdl.minimize(objective) - - # construct budget constraint - cost = mdl.sum([x[i] for i in range(n)]) - mdl.add_constraint(cost == budget, ctname='budget') - - # print model - mdl.pprint() - - # create optimization problem from docplex model - problem = OptimizationProblem() - problem.from_docplex(mdl) - - # print problem - print(problem.write_as_string()) - - grover_optimizer = GroverMinimumFinder(num_iterations=6) - result = grover_optimizer.solve(problem) - print(result) - - def test_gas_2(self): - - op = OptimizationProblem() - op.variables.add(names=["x0", "x1", "x2"], types='BBB') - - linear = [("x0", -1), ("x1", 2), ("x2", -3)] - op.objective.set_linear(linear) - op.objective.set_quadratic_coefficients('x0', 'x2', -2) - op.objective.set_quadratic_coefficients('x1', 'x2', -1) - - grover_optimizer = GroverMinimumFinder(num_iterations=8) - result = grover_optimizer.solve(op) - print(result) diff --git a/test/optimization/test_stable_set.py b/test/optimization/test_stable_set.py index 1072162882..65dc0d7752 100644 --- a/test/optimization/test_stable_set.py +++ b/test/optimization/test_stable_set.py @@ -19,8 +19,8 @@ from qiskit import BasicAer from qiskit.aqua import aqua_globals, QuantumInstance -from qiskit.optimization.ising import stable_set -from qiskit.optimization.ising.common import random_graph, sample_most_likely +from qiskit.optimization.applications.ising import stable_set +from qiskit.optimization.applications.ising.common import random_graph, sample_most_likely from qiskit.aqua.algorithms import NumPyMinimumEigensolver, VQE from qiskit.aqua.components.optimizers import L_BFGS_B from qiskit.aqua.components.variational_forms import RYRZ diff --git a/test/optimization/test_tsp.py b/test/optimization/test_tsp.py index 9c5fe80d20..58c43ab147 100644 --- a/test/optimization/test_tsp.py +++ b/test/optimization/test_tsp.py @@ -18,8 +18,8 @@ import numpy as np from qiskit.aqua import aqua_globals -from qiskit.optimization.ising import tsp -from qiskit.optimization.ising.common import sample_most_likely +from qiskit.optimization.applications.ising import tsp +from qiskit.optimization.applications.ising.common import sample_most_likely from qiskit.aqua.algorithms import NumPyMinimumEigensolver From 70d878a53e33c865124144560506942da610ea33 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Tue, 17 Mar 2020 12:34:02 +0100 Subject: [PATCH 076/323] add min eigen optimizer results --- .../algorithms/cplex_optimizer.py | 5 +-- .../algorithms/minimum_eigen_optimizer.py | 33 +++++++++++++++---- .../recursive_minimum_eigen_optimizer.py | 6 ++-- .../converters/integer_to_binary_converter.py | 7 ++-- test/optimization/test_min_eigen_optimizer.py | 2 -- 5 files changed, 34 insertions(+), 19 deletions(-) diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index 3296c85b1c..c32265cd07 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -24,12 +24,13 @@ from typing import Optional +from cplex import ParameterSet +from cplex.exceptions import CplexSolverError + from qiskit.optimization import QiskitOptimizationError from qiskit.optimization.algorithms import OptimizationAlgorithm from qiskit.optimization.results import OptimizationResult from qiskit.optimization.problems import OptimizationProblem -from cplex import ParameterSet -from cplex.exceptions import CplexSolverError class CplexOptimizer(OptimizationAlgorithm): diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index c81626dff5..c6779af8d3 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -25,7 +25,7 @@ >>> result = optimizer.solve(problem) """ -from typing import Optional +from typing import Optional, Any from qiskit.aqua.algorithms import MinimumEigensolver from qiskit.optimization.problems import OptimizationProblem @@ -35,6 +35,25 @@ from qiskit.optimization.results import OptimizationResult +class MinimumEigenOptimizerResult(OptimizationResult): + """ Minimum Eigen Optimizer Result.""" + + def __init__(self, x: Optional[Any] = None, fval: Optional[Any] = None, + samples: Optional[Any] = None, results: Optional[Any] = None) -> None: + super().__init__(x, fval, results) + self._samples = samples + + @property + def samples(self) -> Any: + """ returns samples """ + return self._samples + + @samples.setter + def samples(self, samples: Any) -> None: + """ set samples """ + self._samples = samples + + class MinimumEigenOptimizer(OptimizationAlgorithm): """A wrapper for minimum eigen solvers from Qiskit Aqua to be used within Qiskit Optimization. @@ -80,7 +99,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: """ return OptimizationProblemToQubo.is_compatible(problem) - def solve(self, problem: OptimizationProblem) -> OptimizationResult: + def solve(self, problem: OptimizationProblem) -> MinimumEigenOptimizerResult: """Tries to solves the given problem using the optimizer. Runs the optimizer to try to solve the optimization problem. @@ -105,13 +124,13 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: eigen_results = self._min_eigen_solver.compute_minimum_eigenvalue(operator) # analyze results - results = eigenvector_to_solutions(eigen_results.eigenstate, operator) - results = [(res[0], problem_.objective.get_sense() * (res[1] + offset), res[2]) - for res in results] - results.sort(key=lambda x: problem_.objective.get_sense() * x[1]) + samples = eigenvector_to_solutions(eigen_results.eigenstate, operator) + samples = [(res[0], problem_.objective.get_sense() * (res[1] + offset), res[2]) + for res in samples] + samples.sort(key=lambda x: problem_.objective.get_sense() * x[1]) # translate result back to integers - opt_res = OptimizationResult(results[0][0], results[0][1], (results, qubo_converter)) + opt_res = MinimumEigenOptimizerResult(samples[0][0], samples[0][1], samples, qubo_converter) opt_res = qubo_converter.decode(opt_res) # translate results back to original problem diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 42e0e700ab..377b139b90 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -118,11 +118,11 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: # solve current problem with optimizer result = self._min_eigen_optimizer.solve(problem_) - details = result.results[0] + samples = result.samples # analyze results to get strongest correlation - states = [v[0] for v in details] - probs = [v[2] for v in details] + states = [v[0] for v in samples] + probs = [v[2] for v in samples] correlations = self._construct_correlations(states, probs) i, j = self._find_strongest_correlation(correlations) diff --git a/qiskit/optimization/converters/integer_to_binary_converter.py b/qiskit/optimization/converters/integer_to_binary_converter.py index 08cc7123d7..9808dc20c4 100644 --- a/qiskit/optimization/converters/integer_to_binary_converter.py +++ b/qiskit/optimization/converters/integer_to_binary_converter.py @@ -203,14 +203,11 @@ def decode(self, result: OptimizationResult) -> OptimizationResult: Returns: The result of the original problem. """ - new_result = OptimizationResult() names = self._dst.variables.get_names() vals = result.x new_vals = self._decode_var(names, vals) - new_result.x = new_vals - new_result.fval = result.fval - new_result.results = result.results - return new_result + result.x = new_vals + return result def _decode_var(self, names, vals) -> List[int]: # decode integer values diff --git a/test/optimization/test_min_eigen_optimizer.py b/test/optimization/test_min_eigen_optimizer.py index a6bf97acde..ab0e43a142 100644 --- a/test/optimization/test_min_eigen_optimizer.py +++ b/test/optimization/test_min_eigen_optimizer.py @@ -15,12 +15,10 @@ """ Test Min Eigen Optimizer """ from test.optimization.common import QiskitOptimizationTestCase -import numpy as np from ddt import ddt, data from qiskit import BasicAer -from qiskit.aqua import QuantumInstance from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.aqua.algorithms import QAOA from qiskit.aqua.components.optimizers import COBYLA From 47c844a1d3f70c742f776df5756e47009c0d8547 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Tue, 17 Mar 2020 12:38:19 +0100 Subject: [PATCH 077/323] Update test_recursive_optimization.py --- test/optimization/test_recursive_optimization.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/optimization/test_recursive_optimization.py b/test/optimization/test_recursive_optimization.py index 25cc753960..49f5426e4a 100755 --- a/test/optimization/test_recursive_optimization.py +++ b/test/optimization/test_recursive_optimization.py @@ -15,12 +15,9 @@ """ Test Recursive Min Eigen Optimizer """ from test.optimization.common import QiskitOptimizationTestCase -import numpy as np from ddt import ddt, data from qiskit import BasicAer - -from qiskit.aqua import QuantumInstance from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.aqua.algorithms import QAOA from qiskit.aqua.components.optimizers import COBYLA From 24c8f45a282a676cc8863ae464f8d1eb7a512342 Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Tue, 17 Mar 2020 09:21:29 -0400 Subject: [PATCH 078/323] Converter Tests Use OptimizationProblem --- ...zation_problem_to_negative_value_oracle.py | 9 ++- ...zation_problem_to_negative_value_oracle.py | 80 ++++++++----------- 2 files changed, 39 insertions(+), 50 deletions(-) diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index 21faab400d..7ce9653bc5 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -71,11 +71,12 @@ def encode(self, problem: OptimizationProblem) -> \ quadratic_coeff = {} for i, j_value_dict in quadratic_dict.items(): for j, value in j_value_dict.items(): - coeff = quadratic_coeff.get((j, i), 0) if i <= j: # divide by 2 since problem considers xQx/2. + coeff = quadratic_coeff.get((i, j), 0) quadratic_coeff[(i, j)] = value / 2 + coeff else: + coeff = quadratic_coeff.get((j, i), 0) quadratic_coeff[(j, i)] = value / 2 + coeff constant = problem.objective.get_offset() @@ -112,15 +113,15 @@ def encode(self, problem: OptimizationProblem) -> \ def _get_function(linear: np.array, quadratic: np.array, constant: int) -> \ Dict[Union[int, Tuple[int, int]], int]: """Convert the problem to a dictionary format.""" - func = {-1: constant} + func = {-1: int(constant)} for i, v in enumerate(linear): - func[i] = v + func[i] = int(v) for ij, v in quadratic.items(): i, j = ij if i != j: func[(i, j)] = int(quadratic[(i, j)]) else: - func[i] += v + func[i] += int(v) return func diff --git a/test/optimization/test_optimization_problem_to_negative_value_oracle.py b/test/optimization/test_optimization_problem_to_negative_value_oracle.py index fa77ed653c..472828d281 100644 --- a/test/optimization/test_optimization_problem_to_negative_value_oracle.py +++ b/test/optimization/test_optimization_problem_to_negative_value_oracle.py @@ -19,19 +19,22 @@ from qiskit.optimization.converters import OptimizationProblemToNegativeValueOracle from qiskit.optimization.util import get_qubo_solutions from qiskit import QuantumCircuit, Aer, execute +from qiskit.optimization.problems import OptimizationProblem class TestOptimizationProblemToNegativeValueOracle(QiskitOptimizationTestCase): """OPtNVO Tests""" - def _validate_function(self, func_dict, linear, quadratic, constant): + def _validate_function(self, func_dict, problem): + linear = problem.objective.get_linear() + quadratic = problem.objective.get_quadratic() for key in func_dict: if isinstance(key, int) and key >= 0: - self.assertEqual(-1 * linear[key], func_dict[key]) + self.assertEqual(linear[key], func_dict[key]) elif isinstance(key, tuple): self.assertEqual(quadratic[key[0]][key[1]], func_dict[key]) else: - self.assertEqual(constant, func_dict[key]) + self.assertEqual(problem.objective.get_offset(), func_dict[key]) def _validate_operator(self, func_dict, n_key, n_value, operator): # Get expected results. @@ -74,40 +77,24 @@ def _bin_to_int(v, num_value_bits): return int_v - def test_optnvo_2_key(self): - """ Test with 2 linear coefficients, no quadratic or constant """ + def test_optnvo_3_linear_2_quadratic_no_constant(self): + """ Test with 3 linear coefficients, 2 quadratic, and no constant """ # Circuit parameters. num_value = 4 # Input. - linear = np.array([1, -2]) - quadratic = np.array([[1, 0], - [0, 1]]) - constant = 0 - - # Convert to dictionary format with operator/oracle. - converter = OptimizationProblemToNegativeValueOracle(num_value) - a_operator, _, func_dict = converter.encode(linear, quadratic, constant) - - self._validate_function(func_dict, linear, quadratic, constant) - self._validate_operator(func_dict, len(linear), num_value, a_operator) - - def test_optnvo_2_key_w_constant(self): - """ Test with 2 linear coefficients, no quadratic, simple constant """ - # Circuit parameters. - num_value = 4 - - # Input. - linear = np.array([1, -2]) - quadratic = np.array([[1, 0], - [0, 1]]) - constant = 1 + problem = OptimizationProblem() + problem.variables.add(names=["x0", "x1", "x2"], types='BBB') + linear = [("x0", -1), ("x1", 2), ("x2", -3)] + problem.objective.set_linear(linear) + problem.objective.set_quadratic_coefficients('x0', 'x2', -2) + problem.objective.set_quadratic_coefficients('x1', 'x2', -1) # Convert to dictionary format with operator/oracle. converter = OptimizationProblemToNegativeValueOracle(num_value) - a_operator, _, func_dict = converter.encode(linear, quadratic, constant) + a_operator, _, func_dict = converter.encode(problem) - self._validate_function(func_dict, linear, quadratic, constant) + self._validate_function(func_dict, problem) self._validate_operator(func_dict, len(linear), num_value, a_operator) def test_optnvo_4_key_all_negative(self): @@ -116,18 +103,20 @@ def test_optnvo_4_key_all_negative(self): num_value = 5 # Input. - linear = np.array([1, 1, 1, 1]) - quadratic = np.array([[-1, -1, -1, -1], - [-1, -1, -1, -1], - [-1, -1, -1, -1], - [-1, -1, -1, -1]]) - constant = -1 + problem = OptimizationProblem() + problem.variables.add(names=["x0", "x1", "x2"], types='BBB') + linear = [("x0", -1), ("x1", -2), ("x2", -1)] + problem.objective.set_linear(linear) + problem.objective.set_quadratic_coefficients('x0', 'x1', -1) + problem.objective.set_quadratic_coefficients('x0', 'x2', -2) + problem.objective.set_quadratic_coefficients('x1', 'x2', -1) + problem.objective.set_offset(-1) # Convert to dictionary format with operator/oracle. converter = OptimizationProblemToNegativeValueOracle(num_value) - a_operator, _, func_dict = converter.encode(linear, quadratic, constant) + a_operator, _, func_dict = converter.encode(problem) - self._validate_function(func_dict, linear, quadratic, constant) + self._validate_function(func_dict, problem) self._validate_operator(func_dict, len(linear), num_value, a_operator) def test_optnvo_6_key(self): @@ -136,18 +125,17 @@ def test_optnvo_6_key(self): num_value = 4 # Input. - linear = np.array([1, -2, -1, 0, 1, 2]) - quadratic = np.array([[1, 0, 0, -1, 0, 0], - [0, 1, 0, 0, 0, -2], - [0, 0, 1, 0, 0, 0], - [-1, 0, 0, 1, 0, 0], - [0, 0, 0, 0, 1, 0], - [0, -2, 0, 0, 0, 1]]) + problem = OptimizationProblem() + problem.variables.add(names=["x0", "x1", "x2", "x3", "x4", "x5"], types='BBBBBB') + linear = [("x0", -1), ("x1", -2), ("x2", -1), ("x3", 0), ("x4", 1), ("x5", 2)] + problem.objective.set_linear(linear) + problem.objective.set_quadratic_coefficients('x0', 'x3', -1) + problem.objective.set_quadratic_coefficients('x1', 'x5', -2) constant = 0 # Convert to dictionary format with operator/oracle. converter = OptimizationProblemToNegativeValueOracle(num_value) - a_operator, _, func_dict = converter.encode(linear, quadratic, constant) + a_operator, _, func_dict = converter.encode(problem) - self._validate_function(func_dict, linear, quadratic, constant) + self._validate_function(func_dict, problem) self._validate_operator(func_dict, len(linear), num_value, a_operator) From 1cf6119daa71a56ea5a7ea02aab368fbf46f5b98 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Wed, 18 Mar 2020 11:12:37 +0100 Subject: [PATCH 079/323] replace backend by qinstance, lint fixes Aqua (currently still) runs on the quantum instance, basically a elaborate wrapper around the backend. You can still provide a backend as input but intenally a QuantumInstance is used. Also removed passing of args which are class attributes. Also fixed some lint errors. --- .../algorithms/grover_minimum_finder.py | 57 ++++++++++--------- ...zation_problem_to_negative_value_oracle.py | 27 ++++----- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index 587270a627..ed6e7e6531 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -15,10 +15,11 @@ """GroverMinimumFinder module""" import logging -from typing import Optional, Dict +from typing import Optional, Dict, Union import random import math import numpy as np +from qiskit.aqua import QuantumInstance from qiskit.optimization.algorithms import OptimizationAlgorithm from qiskit.optimization.problems import OptimizationProblem from qiskit.optimization.converters import (IntegerToBinaryConverter, @@ -29,22 +30,26 @@ from qiskit.optimization.utils import QiskitOptimizationError from qiskit.optimization.util import get_qubo_solutions from qiskit.aqua.algorithms.amplitude_amplifiers.grover import Grover -from qiskit import Aer, execute, QuantumCircuit +from qiskit import Aer, QuantumCircuit from qiskit.providers import BaseBackend class GroverMinimumFinder(OptimizationAlgorithm): """Uses Grover Adaptive Search (GAS) to find the minimum of a QUBO function.""" - def __init__(self, num_iterations: int = 3, backend: Optional[BaseBackend] = None) -> None: + def __init__(self, num_iterations: int = 3, + quantum_instance: Optional[Union[BaseBackend, QuantumInstance]] = None) -> None: """ Args: num_iterations: The number of iterations the algorithm will search with no improvement. - backend: Instance of selected backend, defaults to Aer's statevector simulator. + quantum_instance: Instance of selected backend, defaults to Aer's statevector simulator. """ self._n_iterations = num_iterations - self._backend = backend or Aer.get_backend('statevector_simulator') + if quantum_instance is None or isinstance(quantum_instance, BaseBackend): + backend = quantum_instance or Aer.get_backend('statevector_simulator') + quantum_instance = QuantumInstance(backend) + self._quantum_instance = quantum_instance self._logger = logging.getLogger(__name__) def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: @@ -149,8 +154,9 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: # Initialize oracle helper object. orig_constant = problem_.objective.get_offset() + measurement = not self._quantum_instance.is_statevector opt_prob_converter = OptimizationProblemToNegativeValueOracle(n_value, - backend=self._backend) + measurement) while not optimum_found: m = 1 @@ -172,12 +178,14 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: if rotation_count > 0: grover = Grover(oracle, init_state=a_operator, num_iterations=rotation_count) circuit = grover.construct_circuit( - measurement=self._backend.name() != "statevector_simulator") + measurement=self._quantum_instance.is_statevector + ) + else: circuit = a_operator._circuit # Get the next outcome. - outcome = self._measure(circuit, n_key, n_value, self._backend) + outcome = self._measure(circuit, n_key, n_value) k = int(outcome[0:n_key], 2) v = outcome[n_key:n_key + n_value] @@ -185,17 +193,17 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: int_v = self._bin_to_int(v, n_value) + threshold v = self._twos_complement(int_v, n_value) - self._logger.info('Iterations: {}'.format(rotation_count)) - self._logger.info('Outcome: {}'.format(outcome)) - self._logger.info('Value: {} = {}'.format(v, int_v)) + self._logger.info('Iterations: %s', rotation_count) + self._logger.info('Outcome: %s', outcome) + self._logger.info('Value: %s = %s', v, int_v) # If the value is an improvement, we update the iteration parameters (e.g. oracle). if int_v < optimum_value: optimum_key = k optimum_value = int_v - self._logger.info('Current Optimum Key: {}'.format(optimum_key)) - self._logger.info('Current Optimum Value: {}'.format(optimum_value)) - if v.startswith("1"): + self._logger.info('Current Optimum Key: %s', optimum_key) + self._logger.info('Current Optimum Value: %s', optimum_value) + if v.startswith('1'): improvement_found = True threshold = optimum_value else: @@ -207,7 +215,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: # Using Durr and Hoyer method, increase m. # TODO: Give option for a rotation schedule, or for different lambda's. m = int(np.ceil(min(m * 8/7, 2**(n_key / 2)))) - self._logger.info('No Improvement. M: {}'.format(m)) + self._logger.info('No Improvement. M: %s', m) # Check if we've already seen this value. if k not in keys_measured: @@ -222,7 +230,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: operations = circuit.count_ops() operation_count[iteration] = operations iteration += 1 - self._logger.info('Operation Count: {}\n'.format(operations)) + self._logger.info('Operation Count: %s\n', operations) # Get original key and value pairs. func_dict[-1] = orig_constant @@ -244,27 +252,23 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: return result - def _measure(self, circuit: QuantumCircuit, n_key: int, n_value: int, backend: BaseBackend, - shots: Optional[int] = 1024) -> str: + def _measure(self, circuit: QuantumCircuit, n_key: int, n_value: int) -> str: """Get probabilities from the given backend, and picks a random outcome.""" - probs = self._get_probs(n_key, n_value, circuit, backend, shots) + probs = self._get_probs(n_key, n_value, circuit) freq = sorted(probs.items(), key=lambda x: x[1], reverse=True) # Pick a random outcome. freq[len(freq)-1] = (freq[len(freq)-1][0], 1 - sum([x[1] for x in freq[0:len(freq)-1]])) idx = np.random.choice(len(freq), 1, p=[x[1] for x in freq])[0] - self._logger.info('Frequencies: {}'.format(freq)) + self._logger.info('Frequencies: %s', freq) return freq[idx][0] - @staticmethod - def _get_probs(n_key: int, n_value: int, qc: QuantumCircuit, backend: BaseBackend, - shots: int) -> Dict[str, float]: + def _get_probs(self, n_key: int, n_value: int, qc: QuantumCircuit) -> Dict[str, float]: """Gets probabilities from a given backend.""" # Execute job and filter results. - job = execute(qc, backend=backend, shots=shots) - result = job.result() - if backend.name() == 'statevector_simulator': + result = self._quantum_instance.execute(qc) + if self._quantum_instance.is_statevector: state = np.round(result.get_statevector(qc), 5) keys = [bin(i)[2::].rjust(int(np.log2(len(state))), '0')[::-1] for i in range(0, len(state))] @@ -276,6 +280,7 @@ def _get_probs(n_key: int, n_value: int, qc: QuantumCircuit, backend: BaseBacken hist[new_key] = f_hist[key] else: state = result.get_counts(qc) + shots = self._quantum_instance.run_config.shots hist = {} for key in state: hist[key[:n_key] + key[n_key:n_key+n_value][::-1] + key[n_key+n_value:]] = \ diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index 7ce9653bc5..152e589773 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -15,13 +15,13 @@ """OptimizationProblemToNegativeValueOracle module""" import logging +from typing import Tuple, Dict, Union import numpy as np -from typing import Optional, Tuple, Dict, Union -from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, Aer + +from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister from qiskit.aqua.components.oracles import CustomCircuitOracle from qiskit.aqua.components.initial_states import Custom from qiskit.aqua.components.iqfts import Standard as IQFT -from qiskit.providers import BaseBackend from qiskit.optimization.problems import OptimizationProblem @@ -33,18 +33,15 @@ class OptimizationProblemToNegativeValueOracle: and operator can be used to flag the negative values of a QUBO encoded in a quantum state. """ - def __init__(self, num_output_qubits: int, backend: Optional[BaseBackend] = None) -> None: + def __init__(self, num_output_qubits: int, measurement: bool = False) -> None: """ Args: num_output_qubits: The number of qubits required to represent the output. - backend: Instance of selected backend. + measurement: Whether the A operator contains measurements. """ self._num_key = 0 self._num_value = num_output_qubits - if backend is None: - self._backend = Aer.get_backend('statevector_simulator') - else: - self._backend = backend + self._measurement = measurement self._logger = logging.getLogger(__name__) def encode(self, problem: OptimizationProblem) -> \ @@ -86,7 +83,7 @@ def encode(self, problem: OptimizationProblem) -> \ # Get the function dictionary. func = self._get_function(linear_coeff, quadratic_coeff, constant) - self._logger.info("Function: {}\n", func) + self._logger.info("Function: %s\n", func) # Define state preparation operator A from function. a_operator_circuit = self._build_operator(func) @@ -116,8 +113,7 @@ def _get_function(linear: np.array, quadratic: np.array, constant: int) -> \ func = {-1: int(constant)} for i, v in enumerate(linear): func[i] = int(v) - for ij, v in quadratic.items(): - i, j = ij + for (i, j), v in quadratic.items(): if i != j: func[(i, j)] = int(quadratic[(i, j)]) else: @@ -150,11 +146,10 @@ def _build_operator(self, func_dict: Dict[Union[int, Tuple[int, int]], int]) -> # Build initial circuit. key_val = QuantumRegister(self._num_key + self._num_value, "key_value") - if self._backend.name == "statevector_simulator": - circuit = QuantumCircuit(key_val) - else: + circuit = QuantumCircuit(key_val) + if self._measurement: measure = ClassicalRegister(self._num_key + self._num_value) - circuit = QuantumCircuit(key_val, measure) + circuit.add_register(measure) circuit.h(key_val) # Linear Coefficients. From 3be390993b69f9237500e532784d841bbabff49b Mon Sep 17 00:00:00 2001 From: Cryoris Date: Wed, 18 Mar 2020 14:48:41 +0100 Subject: [PATCH 080/323] fix style and lint in multiple files --- .../algorithms/cobyla_optimizer.py | 30 ++--- .../algorithms/cplex_optimizer.py | 14 +-- .../algorithms/minimum_eigen_optimizer.py | 16 +-- .../algorithms/optimization_algorithm.py | 16 ++- .../recursive_minimum_eigen_optimizer.py | 91 ++++++++------ qiskit/optimization/applications/__init__.py | 0 .../applications/ising/__init__.py | 6 +- .../inequality_to_equality_converter.py | 115 +++++++++--------- .../converters/integer_to_binary_converter.py | 4 +- test/optimization/test_min_eigen_optimizer.py | 8 +- .../test_recursive_optimization.py | 8 +- test/optimization/test_stable_set.py | 4 +- test/optimization/test_tsp.py | 4 +- 13 files changed, 164 insertions(+), 152 deletions(-) delete mode 100644 qiskit/optimization/applications/__init__.py diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index d066926727..da52cd12ac 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -3,7 +3,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2020. # # 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 @@ -24,15 +24,15 @@ from typing import Optional +import numpy as np +from scipy.optimize import fmin_cobyla + from qiskit.optimization.algorithms import OptimizationAlgorithm from qiskit.optimization.results import OptimizationResult from qiskit.optimization.problems import OptimizationProblem from qiskit.optimization import QiskitOptimizationError from qiskit.optimization import infinity -import numpy as np -from scipy.optimize import fmin_cobyla - class CobylaOptimizer(OptimizationAlgorithm): """The COBYLA optimizer wrapped to be used within Qiskit Optimization. @@ -111,8 +111,8 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: # check compatibility and raise exception if incompatible msg = self.is_compatible(problem) - if msg is not None: - raise QiskitOptimizationError('Incompatible problem: %s' % msg) + if msg: + raise QiskitOptimizationError('Incompatible problem: {}'.format(msg)) # get number of variables num_vars = problem.variables.get_num() @@ -125,8 +125,8 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: quadratic = np.zeros((num_vars, num_vars)) for i, v in linear_dict.items(): linear[i] = v - for i, vi in quadratic_dict.items(): - for j, v in vi.items(): + for i, v_i in quadratic_dict.items(): + for j, v in v_i.items(): quadratic[i, j] = v def objective(x): @@ -158,24 +158,24 @@ def objective(x): if sense == 'E': constraints += [ - lambda x: rhs - np.dot(x, row_array), - lambda x: np.dot(x, row_array) - rhs + lambda x, rhs=rhs, row_array=row_array: rhs - np.dot(x, row_array), + lambda x, rhs=rhs, row_array=row_array: np.dot(x, row_array) - rhs ] elif sense == 'L': - constraints += [lambda x: rhs - np.dot(x, row_array)] + constraints += [lambda x, rhs=rhs, row_array=row_array: rhs - np.dot(x, row_array)] elif sense == 'G': - constraints += [lambda x: np.dot(x, row_array) - rhs] + constraints += [lambda x, rhs=rhs, row_array=row_array: np.dot(x, row_array) - rhs] else: # TODO: add range constraints raise QiskitOptimizationError('Unsupported constraint type!') # TODO: add quadratic constraints - # TODO: derive x0 from lower/upper bounds - x0 = np.zeros(problem.variables.get_num()) + # TODO: derive x_0 from lower/upper bounds + x_0 = np.zeros(problem.variables.get_num()) # run optimization - x = fmin_cobyla(objective, x0, constraints, rhobeg=self._rhobeg, rhoend=self._rhoend, + x = fmin_cobyla(objective, x_0, constraints, rhobeg=self._rhobeg, rhoend=self._rhoend, maxfun=self._maxfun, disp=self._disp, catol=self._catol) fval = problem.objective.get_sense() * objective(x) diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index 3296c85b1c..83bad0104e 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -3,7 +3,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2020. # # 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 @@ -23,14 +23,14 @@ """ from typing import Optional - -from qiskit.optimization import QiskitOptimizationError -from qiskit.optimization.algorithms import OptimizationAlgorithm -from qiskit.optimization.results import OptimizationResult -from qiskit.optimization.problems import OptimizationProblem from cplex import ParameterSet from cplex.exceptions import CplexSolverError +from .optimization_algorithm import OptimizationAlgorithm +from ..utils import QiskitOptimizationError +from ..results import OptimizationResult +from ..problems import OptimizationProblem + class CplexOptimizer(OptimizationAlgorithm): """The CPLEX optimizer wrapped to be used within the Qiskit Optimization. @@ -110,7 +110,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: try: cplex.solve() except CplexSolverError: - raise QiskitOptimizationError("Non convex/symmetric matrix.") + raise QiskitOptimizationError('Non convex/symmetric matrix.') # process results sol = cplex.solution diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 8d2bdd8400..b91eda72eb 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -3,7 +3,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2020. # # 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 @@ -27,14 +27,14 @@ from typing import Optional from qiskit.aqua.algorithms import MinimumEigensolver -from qiskit.optimization.problems import OptimizationProblem -from qiskit.optimization.algorithms import OptimizationAlgorithm -from qiskit.optimization.utils import QiskitOptimizationError -from qiskit.optimization.converters import (OptimizationProblemToOperator, + +from .optimization_algorithm import OptimizationAlgorithm +from ..problems import OptimizationProblem +from ..utils import QiskitOptimizationError, eigenvector_to_solutions +from ..converters import (OptimizationProblemToOperator, PenalizeLinearEqualityConstraints, IntegerToBinaryConverter) -from qiskit.optimization.utils import eigenvector_to_solutions -from qiskit.optimization.results import OptimizationResult +from ..results import OptimizationResult class MinimumEigenOptimizer(OptimizationAlgorithm): @@ -126,7 +126,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: # analyze compatibility of problem msg = self.is_compatible(problem) - if msg is not None: + if msg: raise QiskitOptimizationError('Incompatible problem: %s' % msg) # map integer variables to binary variables diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index 2869fc8bb0..f733d3b7b1 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -3,7 +3,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2020. # # 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 @@ -13,20 +13,18 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""An abstract class for optimization algorithms in Qiskit Optimization. -""" +"""An abstract class for optimization algorithms in Qiskit Optimization.""" from abc import abstractmethod from typing import Optional -from qiskit.optimization.problems import OptimizationProblem -from qiskit.optimization.results import OptimizationResult +from ..problems import OptimizationProblem +from ..results import OptimizationResult class OptimizationAlgorithm: - """An abstract class for optimization algorithms in Qiskit Optimization. - """ + """An abstract class for optimization algorithms in Qiskit Optimization.""" @abstractmethod def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: @@ -38,7 +36,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: Returns: Returns ``None`` if the problem is compatible and else a string with the error message. """ - pass + raise NotImplementedError @abstractmethod def solve(self, problem: OptimizationProblem) -> OptimizationResult: @@ -55,4 +53,4 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: Raises: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ - pass + raise NotImplementedError diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index acb28469d4..cedd3b79e7 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -3,7 +3,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2020. # # 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 @@ -29,25 +29,32 @@ import numpy as np from cplex import SparseTriple -from qiskit.optimization import QiskitOptimizationError -from qiskit.optimization.algorithms import OptimizationAlgorithm, MinimumEigenOptimizer -from qiskit.optimization.problems import OptimizationProblem -from qiskit.optimization.results import OptimizationResult -from qiskit.optimization.converters import (PenalizeLinearEqualityConstraints, - IntegerToBinaryConverter) from qiskit.aqua.algorithms import NumPyMinimumEigensolver +from .optimization_algorithm import OptimizationAlgorithm +from .minimum_eigen_optimizer import MinimumEigenOptimizer +from ..utils import QiskitOptimizationError +from ..problems import OptimizationProblem +from ..results import OptimizationResult +from ..converters import PenalizeLinearEqualityConstraints, IntegerToBinaryConverter + class RecursiveMinimumEigenOptimizer(OptimizationAlgorithm): - """ - TODO - """ + """TODO""" def __init__(self, min_eigen_optimizer: MinimumEigenOptimizer, min_num_vars: int = 1, min_num_vars_optimizer: Optional[OptimizationAlgorithm] = None, penalty: Optional[float] = None) -> None: - """ - TODO: add flag to store full history... + """TODO: add flag to store full history... + + Args: + min_eigen_optimizer: The minimum eigen optimizer, which is used recursively. + min_num_vars: The minimal problem size. + min_num_vars_optimizer: The optimizer used to solve the reduced problem. + penalty: Penalty to penalize linear equality constraints. + + Raises: + QiskitOptimizationError: If the minimal problem size is smaller than 1. """ # TODO: should also allow function that maps problem to -correlators? # --> would support efficient classical implementation for QAOA with depth p=1 @@ -62,9 +69,18 @@ def __init__(self, min_eigen_optimizer: MinimumEigenOptimizer, min_num_vars: int self._penalty = penalty def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + """Check whether ``problem`` is compatible with this algorithm. + + Args: + problem: The problem to check the compatibility for. + + Returns: + None if the problem is compatible, otherwise the error message. """ - TODO - """ + msg = '' + if len(msg) > 0: + return msg.strip() + return None def solve(self, problem: OptimizationProblem) -> OptimizationResult: @@ -76,7 +92,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: # analyze compatibility of problem msg = self.is_compatible(problem) - if msg is not None: + if msg: raise QiskitOptimizationError('Incompatible problem: %s' % msg) # map integer variables to binary variables @@ -84,6 +100,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: problem_ = int_to_bin_converter.encode(problem) # penalize linear equality constraints with only binary variables + penalty = self._penalty or 1e5 if self._penalty is None: # TODO: should be derived from problem penalty = 1e5 @@ -107,16 +124,16 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: correlations = self._construct_correlations(states, probs) i, j = self._find_strongest_correlation(correlations) - xi = problem_.variables.get_names(i) - xj = problem_.variables.get_names(j) + x_i = problem_.variables.get_names(i) + x_j = problem_.variables.get_names(j) if correlations[i, j] > 0: - # set xi = xj + # set x_i = x_j problem_ = problem_.substitute_variables(variables=SparseTriple([i], [j], [1])) - replacements[xi] = (xj, 1) + replacements[x_i] = (x_j, 1) else: - # set xi = 1 - xj, this is done in two steps: - # 1. set xi = 1 + xi - # 2. set xi = -xj + # set x_i = 1 - x_j, this is done in two steps: + # 1. set x_i = 1 + x_i + # 2. set x_i = -x_j # 1a. get additional offset offset = problem_.objective.get_offset() @@ -131,9 +148,10 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: coeff += problem_.objective.get_linear(k) problem_.objective.set_linear(k, coeff) - # 2. replace xi by -xj - problem_ = problem_.substitute_variables(variables=SparseTriple([i], [j], [-1])) - replacements[xi] = (xj, -1) + # 2. replace x_i by -x_j + problem_ = problem_.substitute_variables( + variables=SparseTriple([i], [j], [-1])) + replacements[x_i] = (x_j, -1) # solve remaining problem result = self._min_num_vars_optimizer.solve(problem_) @@ -159,32 +177,33 @@ def find_value(x, replacements, var_values): raise QiskitOptimizationError('Invalid values!') # loop over all variables to set their values - for xi in problem_ref.variables.get_names(): - if xi not in var_values: - find_value(xi, replacements, var_values) + for x_i in problem_ref.variables.get_names(): + if x_i not in var_values: + find_value(x_i, replacements, var_values) # construct result x = [var_values[name] for name in problem_ref.variables.get_names()] fval = result.fval - results = OptimizationResult(x, fval, (replacements, int_to_bin_converter)) + results = OptimizationResult( + x, fval, (replacements, int_to_bin_converter)) results = int_to_bin_converter.decode(results) return results def _construct_correlations(self, states, probs): n = len(states[0]) correlations = np.zeros((n, n)) - for k, p in enumerate(probs): + for k, prob in enumerate(probs): b = states[k] for i in range(n): for j in range(i): if b[i] == b[j]: - correlations[i, j] += p + correlations[i, j] += prob else: - correlations[i, j] -= p + correlations[i, j] -= prob return correlations - def _find_strongest_correlation(self, M): - m_max = np.argmax(np.abs(M.flatten())) - i = m_max // len(M) - j = m_max - i*len(M) + def _find_strongest_correlation(self, correlations): + m_max = np.argmax(np.abs(correlations.flatten())) + i = m_max // len(correlations) + j = m_max - i*len(correlations) return (i, j) diff --git a/qiskit/optimization/applications/__init__.py b/qiskit/optimization/applications/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/qiskit/optimization/applications/ising/__init__.py b/qiskit/optimization/applications/ising/__init__.py index 04813e82af..6f2ec2f8bb 100644 --- a/qiskit/optimization/applications/ising/__init__.py +++ b/qiskit/optimization/applications/ising/__init__.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2019, 2020. # # 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 @@ -13,8 +13,8 @@ # that they have been altered from the originals. """ -Ising Models (:mod:`qiskit.optimization.ising`) -=============================================== +Ising Models (:mod:`qiskit.optimization.applications.ising`) +============================================================ Ising models for optimization problems .. currentmodule:: qiskit.optimization.ising diff --git a/qiskit/optimization/converters/inequality_to_equality_converter.py b/qiskit/optimization/converters/inequality_to_equality_converter.py index 19a245f7b7..6427eed220 100644 --- a/qiskit/optimization/converters/inequality_to_equality_converter.py +++ b/qiskit/optimization/converters/inequality_to_equality_converter.py @@ -12,22 +12,21 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""The inequality to equality converter.""" import copy import math -from typing import List, Tuple, Dict +from typing import List, Tuple, Dict, Optional -import numpy as np from cplex import SparsePair -from qiskit.optimization.problems import OptimizationProblem -from qiskit.optimization.results import OptimizationResult -from qiskit.optimization.utils import QiskitOptimizationError -from qiskit.aqua import AquaError +from ..problems import OptimizationProblem +from ..results import OptimizationResult +from ..utils import QiskitOptimizationError class InequalityToEqualityConverter: - """ Convert inequality constraints into equality constraints by introducing slack variables. + """Convert inequality constraints into equality constraints by introducing slack variables. Examples: >>> problem = OptimizationProblem() @@ -38,17 +37,17 @@ class InequalityToEqualityConverter: _delimiter = '@' # users are supposed not to use this character in variable names - def __init__(self): - """ Constructor. It initializes the internal data structure. No args. - """ + def __init__(self) -> None: + """Initialize the interal variables.""" + self._src = None self._dst = None self._conv: Dict[str, List[Tuple[str, int]]] = {} # e.g., self._conv = {'c1': [c1@slack_var]} - def encode(self, op: OptimizationProblem, name: str = None, + def encode(self, op: OptimizationProblem, name: Optional[str] = None, mode: str = 'auto') -> OptimizationProblem: - """ Convert a problem with inequality constraints into new one with only equality + """Convert a problem with inequality constraints into new one with only equality constraints. Args: @@ -57,11 +56,17 @@ def encode(self, op: OptimizationProblem, name: str = None, mode: To chose the type of slack variables. There are 3 options for mode. - 'integer': All slack variables will be integer variables. - 'continuous': All slack variables will be continuous variables - - 'auto': Try to use integer variables but if it's not possible, use continuous variables + - 'auto': Try to use integer variables but if it's not possible, + use continuous variables Returns: The converted problem, that contain only equality constraints. + Raises: + QiskitOptimizationError: If a variable type is not supported. + QiskitOptimizationError: If an unsupported mode is selected. + QiskitOptimizationError: If an unsupported sense is specified. + """ self._src = copy.deepcopy(op) self._dst = OptimizationProblem() @@ -69,15 +74,17 @@ def encode(self, op: OptimizationProblem, name: str = None, # declare variables names = self._src.variables.get_names() types = self._src.variables.get_types() - lb = self._src.variables.get_lower_bounds() - ub = self._src.variables.get_upper_bounds() + lower_bounds = self._src.variables.get_lower_bounds() + upper_bounds = self._src.variables.get_upper_bounds() - for i, name in enumerate(names): + for i, variable in enumerate(names): typ = types[i] if typ == 'B': - self._dst.variables.add(names=[name], types='B') - elif typ == 'C' or typ == 'I': - self._dst.variables.add(names=[name], types=typ, lb=[lb[i]], ub=[ub[i]]) + self._dst.variables.add(names=[variable], types='B') + elif typ in ['C', 'I']: + self._dst.variables.add(names=[variable], types=typ, + lb=[lower_bounds[i]], + ub=[upper_bounds[i]]) else: raise QiskitOptimizationError('Variable type not supported: ' + typ) @@ -98,8 +105,8 @@ def encode(self, op: OptimizationProblem, name: str = None, self._dst.objective.set_linear(i, v) # set quadratic objective terms - for i, vi in self._src.objective.get_quadratic().items(): - for j, v in vi.items(): + for i, v_i in self._src.objective.get_quadratic().items(): + for j, v in v_i.items(): self._dst.objective.set_quadratic_coefficients(i, j, v) # set linear constraints @@ -108,55 +115,55 @@ def encode(self, op: OptimizationProblem, name: str = None, senses = self._src.linear_constraints.get_senses() rhs = self._src.linear_constraints.get_rhs() - for i, name in enumerate(names): + for i, variable in enumerate(names): # Copy equality constraints into self._dst if senses[i] == 'E': self._dst.linear_constraints.add(lin_expr=[rows[i]], senses=[senses[i]], - rhs=[rhs[i]], names=[names[i]]) + rhs=[rhs[i]], names=[variable]) # When the type of a constraint is L, make an equality constraint # with slack variables which represent [lb, ub] = [0, constant - the lower bound of lhs] elif senses[i] == 'L': if mode == 'integer': - self._add_int_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], + self._add_int_slack_var_constraint(name=variable, row=rows[i], rhs=rhs[i], sense=senses[i]) elif mode == 'continuous': - self._add_continuous_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], - sense=senses[i]) + self._add_continuous_slack_var_constraint(name=variable, row=rows[i], + rhs=rhs[i], sense=senses[i]) elif mode == 'auto': - self._add_auto_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], + self._add_auto_slack_var_constraint(name=variable, row=rows[i], rhs=rhs[i], sense=senses[i]) else: - raise AquaError('Unsupported mode is selected' + mode) + raise QiskitOptimizationError('Unsupported mode is selected' + mode) # When the type of a constraint is G, make an equality constraint # with slack variables which represent [lb, ub] = [0, the upper bound of lhs] elif senses[i] == 'G': if mode == 'integer': - self._add_int_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], + self._add_int_slack_var_constraint(name=variable, row=rows[i], rhs=rhs[i], sense=senses[i]) elif mode == 'continuous': - self._add_continuous_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], - sense=senses[i]) + self._add_continuous_slack_var_constraint(name=variable, row=rows[i], + rhs=rhs[i], sense=senses[i]) elif mode == 'auto': - self._add_auto_slack_var_constraint(name=name, row=rows[i], rhs=rhs[i], + self._add_auto_slack_var_constraint(name=variable, row=rows[i], rhs=rhs[i], sense=senses[i]) else: - raise AquaError('Unsupported mode is selected' + mode) + raise QiskitOptimizationError( + 'Unsupported mode is selected' + mode) else: - raise QiskitOptimizationError('The type of Sense in ' + name + 'is not supported') + raise QiskitOptimizationError('Type of sense in ' + variable + 'is not supported') return self._dst def decode(self, result: OptimizationResult) -> OptimizationResult: - """ Convert a result of a converted problem into that of the original problem. + """Convert a result of a converted problem into that of the original problem. Args: result: The result of the converted problem. Returns: The result of the original problem. - """ new_result = OptimizationResult() # convert the optimization result into that of the original problem @@ -186,7 +193,7 @@ def _decode_var(self, names, vals) -> List[int]: def _add_int_slack_var_constraint(self, name, row, rhs, sense): # If a coefficient that is not integer exist, raise error if any(isinstance(coef, float) and not coef.is_integer() for coef in row.val): - raise AquaError('Can not use a slack variable for ' + name) + raise QiskitOptimizationError('Can not use a slack variable for ' + name) slack_name = name + self._delimiter + 'int_slack' lhs_lb, lhs_ub = self._calc_bounds(row) @@ -200,12 +207,10 @@ def _add_int_slack_var_constraint(self, name, row, rhs, sense): # Add a new integer variable. if sense == 'L': sign = 1 - self._dst.variables.add(names=[slack_name], - lb=[0], ub=[new_rhs - lhs_lb], types=['I']) + self._dst.variables.add(names=[slack_name], lb=[0], ub=[new_rhs - lhs_lb], types=['I']) elif sense == 'G': sign = -1 - self._dst.variables.add(names=[slack_name], - lb=[0], ub=[lhs_ub - new_rhs], types=['I']) + self._dst.variables.add(names=[slack_name], lb=[0], ub=[lhs_ub - new_rhs], types=['I']) else: raise QiskitOptimizationError('The type of Sense in ' + name + 'is not supported') @@ -227,15 +232,12 @@ def _add_continuous_slack_var_constraint(self, name, row, rhs, sense): if sense == 'L': sign = 1 - self._dst.variables.add(names=[slack_name], - lb=[0], ub=[rhs - lhs_lb], types=['C']) + self._dst.variables.add(names=[slack_name], lb=[0], ub=[rhs - lhs_lb], types=['C']) elif sense == 'G': sign = -1 - print(lhs_ub - rhs) - self._dst.variables.add(names=[slack_name], - lb=[0], ub=[lhs_ub - rhs], types=['C']) + self._dst.variables.add(names=[slack_name], lb=[0], ub=[lhs_ub - rhs], types=['C']) else: - raise QiskitOptimizationError('The type of Sense in ' + name + 'is not supported') + raise QiskitOptimizationError('Type of Sense in ' + name + 'is not supported') self._conv[name] = slack_name @@ -252,24 +254,21 @@ def _add_continuous_slack_var_constraint(self, name, row, rhs, sense): def _add_auto_slack_var_constraint(self, name, row, rhs, sense): # If a coefficient that is not integer exist, use a continuous slack variable if any(isinstance(coef, float) and not coef.is_integer() for coef in row.val): - self._add_continuous_slack_var_constraint(name=name, row=row, rhs=rhs, - sense=sense) + self._add_continuous_slack_var_constraint(name=name, row=row, rhs=rhs, sense=sense) # Else use an integer slack variable else: - self._add_int_slack_var_constraint(name=name, row=row, rhs=rhs, - sense=sense) + self._add_int_slack_var_constraint(name=name, row=row, rhs=rhs, sense=sense) def _calc_bounds(self, row): - lhs_lb = 0 - lhs_ub = 0 + lhs_lb, lhs_ub = 0, 0 for ind, val in zip(row.ind, row.val): if self._dst.variables.get_types(ind) == 'B': - ub = 1 + upper_bound = 1 else: - ub = self._dst.variables.get_upper_bounds(ind) - lb = self._dst.variables.get_lower_bounds(ind) + upper_bound = self._dst.variables.get_upper_bounds(ind) + lower_bound = self._dst.variables.get_lower_bounds(ind) - lhs_lb += min(lb * val, ub * val) - lhs_ub += max(lb * val, ub * val) + lhs_lb += min(lower_bound * val, upper_bound * val) + lhs_ub += max(lower_bound * val, upper_bound * val) return lhs_lb, lhs_ub diff --git a/qiskit/optimization/converters/integer_to_binary_converter.py b/qiskit/optimization/converters/integer_to_binary_converter.py index 08cc7123d7..0d68b919ef 100644 --- a/qiskit/optimization/converters/integer_to_binary_converter.py +++ b/qiskit/optimization/converters/integer_to_binary_converter.py @@ -19,8 +19,8 @@ import numpy as np from cplex import SparsePair -from qiskit.optimization.problems.optimization_problem import OptimizationProblem -from qiskit.optimization.results.optimization_result import OptimizationResult +from ..problems.optimization_problem import OptimizationProblem +from ..results.optimization_result import OptimizationResult class IntegerToBinaryConverter: diff --git a/test/optimization/test_min_eigen_optimizer.py b/test/optimization/test_min_eigen_optimizer.py index 7b2fc8b980..ef411df21a 100644 --- a/test/optimization/test_min_eigen_optimizer.py +++ b/test/optimization/test_min_eigen_optimizer.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2018, 2020. # # 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 @@ -15,13 +15,11 @@ """ Test Min Eigen Optimizer """ from test.optimization.common import QiskitOptimizationTestCase -import numpy as np from ddt import ddt, data from qiskit import BasicAer -from qiskit.aqua import QuantumInstance -from qiskit.aqua.algorithms import ClassicalMinimumEigensolver +from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.aqua.algorithms import QAOA from qiskit.aqua.components.optimizers import COBYLA @@ -42,7 +40,7 @@ def setUp(self): self.min_eigen_solvers = {} # exact eigen solver - self.min_eigen_solvers['exact'] = ClassicalMinimumEigensolver() + self.min_eigen_solvers['exact'] = NumPyMinimumEigensolver() # QAOA optimizer = COBYLA() diff --git a/test/optimization/test_recursive_optimization.py b/test/optimization/test_recursive_optimization.py index 430f7c2c3d..52bf2a6a56 100755 --- a/test/optimization/test_recursive_optimization.py +++ b/test/optimization/test_recursive_optimization.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2018, 2020. # # 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 @@ -15,13 +15,11 @@ """ Test Recursive Min Eigen Optimizer """ from test.optimization.common import QiskitOptimizationTestCase -import numpy as np from ddt import ddt, data from qiskit import BasicAer -from qiskit.aqua import QuantumInstance -from qiskit.aqua.algorithms import ClassicalMinimumEigensolver +from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.aqua.algorithms import QAOA from qiskit.aqua.components.optimizers import COBYLA @@ -43,7 +41,7 @@ def setUp(self): self.min_eigen_solvers = {} # exact eigen solver - self.min_eigen_solvers['exact'] = ClassicalMinimumEigensolver() + self.min_eigen_solvers['exact'] = NumPyMinimumEigensolver() # QAOA optimizer = COBYLA() diff --git a/test/optimization/test_stable_set.py b/test/optimization/test_stable_set.py index 1072162882..65dc0d7752 100644 --- a/test/optimization/test_stable_set.py +++ b/test/optimization/test_stable_set.py @@ -19,8 +19,8 @@ from qiskit import BasicAer from qiskit.aqua import aqua_globals, QuantumInstance -from qiskit.optimization.ising import stable_set -from qiskit.optimization.ising.common import random_graph, sample_most_likely +from qiskit.optimization.applications.ising import stable_set +from qiskit.optimization.applications.ising.common import random_graph, sample_most_likely from qiskit.aqua.algorithms import NumPyMinimumEigensolver, VQE from qiskit.aqua.components.optimizers import L_BFGS_B from qiskit.aqua.components.variational_forms import RYRZ diff --git a/test/optimization/test_tsp.py b/test/optimization/test_tsp.py index 9c5fe80d20..58c43ab147 100644 --- a/test/optimization/test_tsp.py +++ b/test/optimization/test_tsp.py @@ -18,8 +18,8 @@ import numpy as np from qiskit.aqua import aqua_globals -from qiskit.optimization.ising import tsp -from qiskit.optimization.ising.common import sample_most_likely +from qiskit.optimization.applications.ising import tsp +from qiskit.optimization.applications.ising.common import sample_most_likely from qiskit.aqua.algorithms import NumPyMinimumEigensolver From 94a53e0ba5c816636fb259874f0fb4899306a50e Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Wed, 18 Mar 2020 14:47:36 +0000 Subject: [PATCH 081/323] handling objective sense --- .../optimization/algorithms/admm_optimizer.py | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 1fa0b37fcd..16aa4cc7eb 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -215,8 +215,6 @@ def solve(self, problem: OptimizationProblem): it = 0 r = 1.e+2 - # TODO: Handle objective sense. This has to be feed to the solvers of the subproblems. - while (it < self._max_iter and r > self._tol) and (elapsed_time < self._max_time): op1 = self._create_step1_problem() @@ -255,8 +253,7 @@ def solve(self, problem: OptimizationProblem): print("cost_iterate, cr, merit", cost_iterate, cr, merit) # costs and merits are saved with their original sign - # TODO: obtain the sense, and update cost iterates and merits - self._state.cost_iterates.append(cost_iterate) + self._state.cost_iterates.append(self._state.sense * cost_iterate) self._state.residuals.append(r) self._state.dual_residuals.append(s) self._state.cons_r.append(cr) @@ -273,7 +270,7 @@ def solve(self, problem: OptimizationProblem): it += 1 elapsed_time = time.time() - start_time - sol, sol_val = self.get_min_mer_sol() + sol, sol_val = self.get_best_mer_sol() # third parameter is our internal state of computations result = OptimizationResult(sol, sol_val, self._state) @@ -568,22 +565,23 @@ def update_y(self, op3): # TODO: Check output type return np.asarray(self._continuous_solver.solve(op3).x) - def get_min_mer_sol(self): + def get_best_mer_sol(self): """ - The ADMM solution is that for which the merit value is the least - * sol: Iterate with the least merit value + The ADMM solution is that for which the merit value is the best (least for min problems, greatest for max problems) + * sol: Iterate with the best merit value * sol_val: Value of sol, according to the original objective Returns: A tuple of (sol, sol_val), where - * sol: Iterate with the least merit value + * sol: Iterate with the best merit value * sol_val: Value of sol, according to the original objective """ - it_min_merits = self._state.merits.index(min(self._state.merits)) - x0 = self._state.x0_saved[it_min_merits] - u = self._state.u_saved[it_min_merits] + + it_best_merits = self._state.merits.index(min(list(map(lambda x: self._state.sense * x, self._state.merits)))) + x0 = self._state.x0_saved[it_best_merits] + u = self._state.u_saved[it_best_merits] sol = [x0, u] - sol_val = self._state.cost_iterates[it_min_merits] + sol_val = self._state.cost_iterates[it_best_merits] return sol, sol_val def update_lambda_mult(self): From 567f6eff74890fde4e4aafd38e73c44b9a77d645 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Wed, 18 Mar 2020 16:08:33 +0100 Subject: [PATCH 082/323] style and lint for int to bin converter --- .../converters/integer_to_binary_converter.py | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/qiskit/optimization/converters/integer_to_binary_converter.py b/qiskit/optimization/converters/integer_to_binary_converter.py index 0d68b919ef..dadd7ecaf8 100644 --- a/qiskit/optimization/converters/integer_to_binary_converter.py +++ b/qiskit/optimization/converters/integer_to_binary_converter.py @@ -12,6 +12,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""The converter to convert an integer problem to a binary problem.""" import copy from typing import List, Tuple, Dict @@ -24,35 +25,33 @@ class IntegerToBinaryConverter: - """ Convert an `OptimizationProblem` into new one by encoding integer variables - with binary variables. - - Examples: - >>> problem = OptimizationProblem() - >>> problem.variables.add(names=['x'], types=['I'], lb=[0], ub=[10]) - >>> conv = IntegerToBinaryConverter() - >>> problem2 = conv.encode(problem) + """Convert an `OptimizationProblem` into new one by encoding integer with binary variables. + + Examples: + >>> problem = OptimizationProblem() + >>> problem.variables.add(names=['x'], types=['I'], lb=[0], ub=[10]) + >>> conv = IntegerToBinaryConverter() + >>> problem2 = conv.encode(problem) """ _delimiter = '@' # users are supposed not to use this character in variable names - def __init__(self): - """ Constructor. It initializes the internal data structure. No args. - """ + def __init__(self) -> None: + """Initializes the internal data structure.""" self._src = None self._dst = None self._conv: Dict[str, List[Tuple[str, int]]] = {} # e.g., self._conv = {'x': [('x@1', 1), ('x@2', 2)]} def encode(self, op: OptimizationProblem, name: str = None) -> OptimizationProblem: - """ Convert a problem into a new one by encoding integer variables - with binary variables. It does not change the input. - Args: - op: The problem to be solved, that may contain integer variables. - name: The name of the converted problem. - - Returns: - The converted problem, that contains no integer variables. + """Convert an integer problem into a new problem with binary variables. + + Args: + op: The problem to be solved, that may contain integer variables. + name: The name of the converted problem. + + Returns: + The converted problem, that contains no integer variables. """ self._src = copy.deepcopy(op) @@ -65,17 +64,20 @@ def encode(self, op: OptimizationProblem, name: str = None) -> OptimizationProbl # declare variables names = self._src.variables.get_names() types = self._src.variables.get_types() - lb = self._src.variables.get_lower_bounds() - ub = self._src.variables.get_upper_bounds() - for i, name in enumerate(names): + lower_bounds = self._src.variables.get_lower_bounds() + upper_bounds = self._src.variables.get_upper_bounds() + for i, variable in enumerate(names): typ = types[i] if typ == 'I': - new_vars: List[Tuple[str, int]] = self._encode_var(name=name, lb=lb[i], ub=ub[i]) + new_vars: List[Tuple[str, int]] = self._encode_var(name=variable, + lower_bound=lower_bounds[i], + upper_bound=upper_bounds[i]) self._conv[name] = new_vars - self._dst.variables.add( - names=[name for name, _ in new_vars], types='B' * len(new_vars)) + self._dst.variables.add(names=[new_name for new_name, _ in new_vars], + types='B' * len(new_vars)) else: - self._dst.variables.add(names=[name], types=typ, lb=[lb[i]], ub=[ub[i]]) + self._dst.variables.add(names=[variable], types=typ, + lb=[lower_bounds[i]], ub=[upper_bounds[i]]) # replace integer variables with binary variables in the objective function # self.objective.subs(self._conv) @@ -89,9 +91,9 @@ def encode(self, op: OptimizationProblem, name: str = None) -> OptimizationProbl return self._dst - def _encode_var(self, name: str, lb: int, ub: int) -> List[Tuple[str, int]]: + def _encode_var(self, name: str, lower_bound: int, upper_bound: int) -> List[Tuple[str, int]]: # bounded-coefficient encoding proposed in arxiv:1706.01945 (Eq. (5)) - var_range = ub - lb + var_range = upper_bound - lower_bound power = int(np.log2(var_range)) bounded_coef = var_range - (2 ** power - 1) @@ -161,8 +163,7 @@ def _substitute_int_var(self): ind = list(range(num_var)) lst = [] for i in ind: - sp = SparsePair(ind=ind, val=new_quad[i].tolist()) - lst.append(sp) + lst.append(SparsePair(ind=ind, val=new_quad[i].tolist())) self._dst.objective.set_quadratic(lst) # set constraints whose integer variables are replaced with binary variables @@ -175,33 +176,32 @@ def _substitute_int_var(self): lin_expr = [] for i, linear_row in enumerate(linear_rows): - sp = SparsePair() + sparse_pair = SparsePair() for j, var_ind in enumerate(linear_row.ind): coef = linear_row.val[j] var_name = self._src.variables.get_names(var_ind) if var_name in self._conv: for converted_name, converted_coef in self._conv[var_name]: - sp.ind.append(converted_name) - sp.val.append(converted_coef * coef) + sparse_pair.ind.append(converted_name) + sparse_pair.val.append(converted_coef * coef) else: - sp.ind.append(var_name) - sp.val.append(coef) + sparse_pair.ind.append(var_name) + sparse_pair.val.append(coef) - lin_expr.append(sp) + lin_expr.append(sparse_pair) - self._dst.linear_constraints.add( - lin_expr, linear_sense, linear_rhs, linear_ranges, linear_names) + self._dst.linear_constraints.add(lin_expr, linear_sense, linear_rhs, linear_ranges, + linear_names) def decode(self, result: OptimizationResult) -> OptimizationResult: - """ Convert a result of a converted problem into that of the original problem - by decoding integer variables back. + """Convert the encoded problem (binary variables) back to the original (integer variables). - Args: - result: The result of the converted problem. + Args: + result: The result of the converted problem. - Returns: - The result of the original problem. + Returns: + The result of the original problem. """ new_result = OptimizationResult() names = self._dst.variables.get_names() @@ -216,7 +216,7 @@ def _decode_var(self, names, vals) -> List[int]: # decode integer values sol = {name: int(vals[i]) for i, name in enumerate(names)} new_vals = [] - for i, name in enumerate(self._src.variables.get_names()): + for name in self._src.variables.get_names(): if name in self._conv: new_vals.append(sum(sol[aux] * coef for aux, coef in self._conv[name])) else: From 9f218b37840f4549412b537a9a89e98663c08faf Mon Sep 17 00:00:00 2001 From: Cryoris Date: Wed, 18 Mar 2020 16:09:54 +0100 Subject: [PATCH 083/323] add optional type --- .../converters/integer_to_binary_converter.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/qiskit/optimization/converters/integer_to_binary_converter.py b/qiskit/optimization/converters/integer_to_binary_converter.py index dadd7ecaf8..58a1e47ba8 100644 --- a/qiskit/optimization/converters/integer_to_binary_converter.py +++ b/qiskit/optimization/converters/integer_to_binary_converter.py @@ -15,7 +15,7 @@ """The converter to convert an integer problem to a binary problem.""" import copy -from typing import List, Tuple, Dict +from typing import List, Tuple, Dict, Optional import numpy as np from cplex import SparsePair @@ -43,12 +43,13 @@ def __init__(self) -> None: self._conv: Dict[str, List[Tuple[str, int]]] = {} # e.g., self._conv = {'x': [('x@1', 1), ('x@2', 2)]} - def encode(self, op: OptimizationProblem, name: str = None) -> OptimizationProblem: + def encode(self, op: OptimizationProblem, name: Optional[str] = None) -> OptimizationProblem: """Convert an integer problem into a new problem with binary variables. Args: op: The problem to be solved, that may contain integer variables. - name: The name of the converted problem. + name: The name of the converted problem. If not provided, the name of the input + problem is used. Returns: The converted problem, that contains no integer variables. @@ -56,10 +57,10 @@ def encode(self, op: OptimizationProblem, name: str = None) -> OptimizationProbl self._src = copy.deepcopy(op) self._dst = OptimizationProblem() - if name is None: - self._dst.set_problem_name(self._src.get_problem_name()) - else: + if name: self._dst.set_problem_name(name) + else: + self._dst.set_problem_name(self._src.get_problem_name()) # declare variables names = self._src.variables.get_names() From 7742c5db267184879aec055837b9f818b3eda1b4 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Wed, 18 Mar 2020 16:10:57 +0100 Subject: [PATCH 084/323] fix docstrings --- .../converters/inequality_to_equality_converter.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/qiskit/optimization/converters/inequality_to_equality_converter.py b/qiskit/optimization/converters/inequality_to_equality_converter.py index 6427eed220..804a8d73b5 100644 --- a/qiskit/optimization/converters/inequality_to_equality_converter.py +++ b/qiskit/optimization/converters/inequality_to_equality_converter.py @@ -28,11 +28,11 @@ class InequalityToEqualityConverter: """Convert inequality constraints into equality constraints by introducing slack variables. - Examples: - >>> problem = OptimizationProblem() - >>> # define a problem - >>> conv = InequalityToEqualityConverter() - >>> problem2 = conv.encode(problem) + Examples: + >>> problem = OptimizationProblem() + >>> # define a problem + >>> conv = InequalityToEqualityConverter() + >>> problem2 = conv.encode(problem) """ _delimiter = '@' # users are supposed not to use this character in variable names @@ -47,8 +47,7 @@ def __init__(self) -> None: def encode(self, op: OptimizationProblem, name: Optional[str] = None, mode: str = 'auto') -> OptimizationProblem: - """Convert a problem with inequality constraints into new one with only equality - constraints. + """Convert a problem with inequality constraints into one with only equality constraints. Args: op: The problem to be solved, that may contain inequality constraints. @@ -66,7 +65,6 @@ def encode(self, op: OptimizationProblem, name: Optional[str] = None, QiskitOptimizationError: If a variable type is not supported. QiskitOptimizationError: If an unsupported mode is selected. QiskitOptimizationError: If an unsupported sense is specified. - """ self._src = copy.deepcopy(op) self._dst = OptimizationProblem() From d0396c170a0c76aafcdba8d9a08569a7227bc873 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Wed, 18 Mar 2020 16:12:21 +0100 Subject: [PATCH 085/323] fix indents --- .../algorithms/minimum_eigen_optimizer.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index b91eda72eb..3068421ec5 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -15,13 +15,13 @@ """A wrapper for minimum eigen solvers from Qiskit Aqua to be used within Qiskit Optimization. - Examples: - >>> problem = OptimizationProblem() - >>> # specify problem here - >>> # specify minimum eigen solver to be used, e.g., QAOA - >>> qaoa = QAOA(...) - >>> optimizer = MinEigenOptimizer(qaoa) - >>> result = optimizer.solve(problem) +Examples: + >>> problem = OptimizationProblem() + >>> # specify problem here + >>> # specify minimum eigen solver to be used, e.g., QAOA + >>> qaoa = QAOA(...) + >>> optimizer = MinEigenOptimizer(qaoa) + >>> result = optimizer.solve(problem) """ from typing import Optional @@ -32,8 +32,8 @@ from ..problems import OptimizationProblem from ..utils import QiskitOptimizationError, eigenvector_to_solutions from ..converters import (OptimizationProblemToOperator, - PenalizeLinearEqualityConstraints, - IntegerToBinaryConverter) + PenalizeLinearEqualityConstraints, + IntegerToBinaryConverter) from ..results import OptimizationResult @@ -49,7 +49,6 @@ class MinimumEigenOptimizer(OptimizationAlgorithm): corresponding eigenstate correspond to the optimal solution of the original optimization problem. The provided minimum eigen solver is then used to approximate the groundstate of the Hamiltonian to find a good solution for the optimization problem. - """ def __init__(self, min_eigen_solver: MinimumEigensolver, penalty: Optional[float] = None From 99ec3e380e9254a67fd0ccb4c3192f0c62562a6e Mon Sep 17 00:00:00 2001 From: Cryoris Date: Wed, 18 Mar 2020 16:15:47 +0100 Subject: [PATCH 086/323] fix opt.problem to operator --- .../optimization_problem_to_operator.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/qiskit/optimization/converters/optimization_problem_to_operator.py b/qiskit/optimization/converters/optimization_problem_to_operator.py index 0e3796dd0b..14998236d4 100644 --- a/qiskit/optimization/converters/optimization_problem_to_operator.py +++ b/qiskit/optimization/converters/optimization_problem_to_operator.py @@ -13,6 +13,8 @@ # that they have been altered from the originals. +"""The converter from an ```OptimizationProblem``` to ``Operator``.""" + from typing import Dict, Tuple import numpy as np @@ -24,25 +26,27 @@ class OptimizationProblemToOperator: - """ Convert an optimization problem into a qubit operator. - """ + """Convert an optimization problem into a qubit operator.""" - def __init__(self): - """ Constructor. Initialize the internal data structure. No args. - """ + def __init__(self) -> None: + """Initialize the internal data structure.""" self._src = None self._q_d: Dict[int, int] = {} # e.g., self._q_d = {0: 0} def encode(self, op: OptimizationProblem) -> Tuple[WeightedPauliOperator, float]: - """ Convert a problem into a qubit operator + """Convert a problem into a qubit operator Args: - op: The problem to be solved, that is an unconstrained problem with only binary variables. + op: The optimization problem to be converted. Must be an unconstrained problem with + binary variables only. Returns: The qubit operator of the problem and the shift value. + Raises: + QiskitOptimizationError: If a variable type is not binary. + QiskitOptimizationError: If constraints exist in the problem. """ self._src = op @@ -91,8 +95,8 @@ def encode(self, op: OptimizationProblem) -> Tuple[WeightedPauliOperator, float] shift += weight # convert quadratic parts of the object function into Hamiltonian. - for i, vi in self._src.objective.get_quadratic().items(): - for j, coef in vi.items(): + for i, v_i in self._src.objective.get_quadratic().items(): + for j, coef in v_i.items(): if j < i: continue qubit_index_1 = _q_d[i] From 61ffaca8e89f00cd87b61f6da6c62bdb68e687d7 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Wed, 18 Mar 2020 16:19:05 +0100 Subject: [PATCH 087/323] style and lint for penalize_linear_equality --- .../penalize_linear_equality_constraints.py | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/qiskit/optimization/converters/penalize_linear_equality_constraints.py b/qiskit/optimization/converters/penalize_linear_equality_constraints.py index 452132aba9..a4e7ecda45 100644 --- a/qiskit/optimization/converters/penalize_linear_equality_constraints.py +++ b/qiskit/optimization/converters/penalize_linear_equality_constraints.py @@ -12,26 +12,28 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""Converter to convert a problem with equality constraints to unconstrained with penalty terms.""" + +from typing import Optional + import copy from collections import defaultdict -from qiskit.optimization.problems.optimization_problem import OptimizationProblem -from qiskit.optimization.utils import QiskitOptimizationError +from ..problems.optimization_problem import OptimizationProblem +from ..utils import QiskitOptimizationError class PenalizeLinearEqualityConstraints: - """ Convert a problem with only equality constraints into an unconstrained problem - with penalty terms associated with the constraints. - """ + """Convert a problem with only equality constraints to unconstrained with penalty terms.""" def __init__(self): - """ Constructor. Initialize the internal data structure. No args. - """ + """Initialize the internal data structure.""" self._src = None self._dst = None - def encode(self, op: OptimizationProblem, penalty_factor: float = 1e5, name: str = None): - """ Convert a problem with equality constraints into an unconstrained problem. + def encode(self, op: OptimizationProblem, penalty_factor: float = 1e5, + name: Optional[str] = None) -> OptimizationProblem: + """Convert a problem with equality constraints into an unconstrained problem. Args: op: The problem to be solved, that does not contain inequality constraints. @@ -41,6 +43,8 @@ def encode(self, op: OptimizationProblem, penalty_factor: float = 1e5, name: str Returns: The converted problem, that is an unconstrained problem. + Raises: + QiskitOptimizationError: If an inequality constraint exists. """ # TODO: test compatibility, how to react in case of incompatibility? @@ -125,8 +129,8 @@ def encode(self, op: OptimizationProblem, penalty_factor: float = 1e5, name: str self._dst.objective.set_linear(i, v) # set quadratic objective terms - for i, vi in quadratic_terms.items(): - for j, v in vi.items(): + for i, v_i in quadratic_terms.items(): + for j, v in v_i.items(): self._dst.objective.set_quadratic_coefficients(i, j, v) return self._dst From 50e72023a2b29889996cb9296b44869d67f6af6e Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Wed, 18 Mar 2020 17:05:32 +0000 Subject: [PATCH 088/323] moving matrices and vectors to the state --- .../optimization/algorithms/admm_optimizer.py | 213 ++++++++++-------- 1 file changed, 114 insertions(+), 99 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 16aa4cc7eb..55d1cd027e 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -27,14 +27,12 @@ from qiskit.optimization.results.optimization_result import OptimizationResult -from qiskit.optimization.problems.objective import ObjSense - class ADMMParameters: def __init__(self, rho_initial=10000, factor_c=100000, beta=1000, max_iter=10, tol=1.e-4, max_time=1800, three_block=True, vary_rho=0, tau_incr=2, tau_decr=2, mu_res=10, - mu=1000, qubo_solver: OptimizationAlgorithm = None, - continuous_solver: OptimizationAlgorithm = None) -> None: + mu=1000, qubo_optimizer: OptimizationAlgorithm = None, + continuous_optimizer: OptimizationAlgorithm = None) -> None: """Defines parameters for ADMM optimizer and their default values. Args: @@ -52,8 +50,8 @@ def __init__(self, rho_initial=10000, factor_c=100000, beta=1000, max_iter=10, t tau_decr: Parameter used in the rho update. mu_res: Parameter used in the rho update. mu: Penalization for constraint residual. Used to compute the merit values. - qubo_solver: An instance of OptimizationAlgorithm that can effectively solve QUBO problems - continuous_solver: An instance of OptimizationAlgorithm that can solve continuous problems + qubo_optimizer: An instance of OptimizationAlgorithm that can effectively solve QUBO problems + continuous_optimizer: An instance of OptimizationAlgorithm that can solve continuous problems """ super().__init__() self.mu = mu @@ -68,8 +66,8 @@ def __init__(self, rho_initial=10000, factor_c=100000, beta=1000, max_iter=10, t self.factor_c = factor_c self.beta = beta self.rho_initial = rho_initial - self.qubo_solver = qubo_solver if qubo_solver is not None else CplexOptimizer() - self.continuous_solver = continuous_solver if continuous_solver is not None else CplexOptimizer() + self.qubo_optimizer = qubo_optimizer if qubo_optimizer is not None else CplexOptimizer() + self.continuous_optimizer = continuous_optimizer if continuous_optimizer is not None else CplexOptimizer() class ADMMState: @@ -98,6 +96,23 @@ def __init__(self, self.continuous_indices = continuous_indices self.sense = op.objective.get_sense() + # define heavily used matrix, they are used at each iteration, so let's cache them, they are ndarrays + # objective + self.q0 = None + self.c0 = None + self.q1 = None + self.c1 = None + # constraints + self.a0 = None + self.b0 = None + self.a1 = None + self.b1 = None + self.a2 = None + self.a3 = None + self.b2 = None + self.a4 = None + self.b3 = None + # These are the parameters that are updated in the ADMM iterations. binary_size = len(binary_indices) self.x0: np.ndarray = np.zeros(binary_size) @@ -141,8 +156,8 @@ def __init__(self, params: ADMMParameters = None) -> None: self._mu = params.mu self._rho_initial = params.rho_initial - self._qubo_solver = params.qubo_solver - self._continuous_solver = params.continuous_solver + self._qubo_optimizer = params.qubo_optimizer + self._continuous_optimizer = params.continuous_optimizer # internal state where we'll keep intermediate solution # here, we just declare the class variable @@ -205,6 +220,9 @@ def solve(self, problem: OptimizationProblem): # create our computation state self._state = ADMMState(problem, binary_indices, continuous_indices, self._rho_initial) + # convert optimization problem to a set of matrices and vector that are used at each iteration + self.convert_problem_representation() + # debug # self.__dump_matrices_and_vectors() @@ -288,11 +306,23 @@ def _get_variable_indices(op: OptimizationProblem, var_type: str) -> List[int]: return indices - def get_q0(self): - return self._get_q(self._state.binary_indices) + def convert_problem_representation(self) -> None: + # objective + self.get_q0() + self.get_c0() + self.get_q1() + self.get_c1() + # constraints + self.get_a0_b0() + self.get_a1_b1() + self.get_a2_a3_b2() + self.get_a4_b3() + + def get_q0(self) -> None: + self._state.q0 = self._get_q(self._state.binary_indices) - def get_q1(self): - return self._get_q(self._state.continuous_indices) + def get_q1(self) -> None: + self._state.q1 = self._get_q(self._state.continuous_indices) def _get_q(self, variable_indices: List[int]) -> np.ndarray: size = len(variable_indices) @@ -312,11 +342,11 @@ def _get_c(self, variable_indices: List[int]) -> np.ndarray: c = c * self._state.sense return c - def get_c0(self): - return self._get_c(self._state.binary_indices) + def get_c0(self) -> None: + self._state.c0 = self._get_c(self._state.binary_indices) - def get_c1(self): - return self._get_c(self._state.continuous_indices) + def get_c1(self) -> None: + self._state.c1 = self._get_c(self._state.continuous_indices) def _assign_row_values(self, matrix: List[List[float]], vector: List[float], constraint_index, variable_indices): # assign matrix row @@ -334,14 +364,15 @@ def _assign_row_values(self, matrix: List[List[float]], vector: List[float], con matrix[-1] = [-1 * el for el in matrix[-1]] vector[-1] = -1 * vector[-1] - def _create_ndarrays(self, matrix: List[List[float]], vector: List[float], size: int) -> (np.ndarray, np.ndarray): + @staticmethod + def _create_ndarrays(matrix: List[List[float]], vector: List[float], size: int) -> (np.ndarray, np.ndarray): # if we don't have such constraints, return just dummy arrays if len(matrix) != 0: return np.array(matrix), np.array(vector) else: return np.array([0] * size).reshape((1, -1)), np.zeros(shape=(1,)) - def get_a0_b0(self) -> (np.ndarray, np.ndarray): + def get_a0_b0(self) -> None: matrix = [] vector = [] @@ -359,7 +390,7 @@ def get_a0_b0(self) -> (np.ndarray, np.ndarray): "Linear constraint with the 'E' sense must contain only binary variables, " "row indices: {}, binary variable indices: {}".format(row, self._state.binary_indices)) - return self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) + self._state.a0, self._state.b0 = self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) -> (List[List[float]], List[float]): # extracting matrix and vector from constraints like Ax <= b @@ -379,16 +410,15 @@ def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) -> (Lis return matrix, vector - def get_a1_b1(self) -> (np.ndarray, np.ndarray): + def get_a1_b1(self) -> None: matrix, vector = self._get_inequality_matrix_and_vector(self._state.binary_indices) - return self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) + self._state.a1, self._state.b1 = self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) - def get_a4_b3(self) -> (np.ndarray, np.ndarray): + def get_a4_b3(self) -> None: matrix, vector = self._get_inequality_matrix_and_vector(self._state.continuous_indices) + self._state.a4, self._state.b3 = self._create_ndarrays(matrix, vector, len(self._state.continuous_indices)) - return self._create_ndarrays(matrix, vector, len(self._state.continuous_indices)) - - def get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): + def get_a2_a3_b2(self) -> None: matrix = [] vector = [] senses = self._state.op.linear_constraints.get_senses() @@ -411,9 +441,11 @@ def get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): # a2 a2 = matrix[:, 0:len(self._state.binary_indices)] a3 = matrix[:, len(self._state.binary_indices):] - return a2, a3, b2 + self._state.a2 = a2 + self._state.a3 = a3 + self._state.b2 = b2 - def _create_step1_problem(self): + def _create_step1_problem(self) -> OptimizationProblem: op1 = OptimizationProblem() binary_size = len(self._state.binary_indices) @@ -426,9 +458,8 @@ def _create_step1_problem(self): # prepare and set quadratic objective. # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. - a0, b0 = self.get_a0_b0() quadratic_objective = 2 * ( - self.get_q0() + self._factor_c / 2 * np.dot(a0.transpose(), a0) + + self._state.q0 + self._factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) + self._state.rho / 2 * np.eye(binary_size) ) for i in range(binary_size): @@ -437,13 +468,12 @@ def _create_step1_problem(self): op1.objective.set_quadratic_coefficients(j, i, quadratic_objective[i, j]) # prepare and set linear objective - c0 = self.get_c0() - linear_objective = c0 - self._factor_c * np.dot(b0, a0) + self._state.rho * (self._state.y - self._state.z) + linear_objective = self._state.c0 - self._factor_c * np.dot(self._state.b0, self._state.a0) + self._state.rho * (self._state.y - self._state.z) for i in range(binary_size): op1.objective.set_linear(i, linear_objective[i]) return op1 - def _create_step2_problem(self): + def _create_step2_problem(self) -> OptimizationProblem: op2 = OptimizationProblem() continuous_size = len(self._state.continuous_indices) @@ -464,7 +494,7 @@ def _create_step2_problem(self): # set quadratic objective coefficients for u variables if continuous_size: # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. - q_u = 2 * (self.get_q1()) + q_u = 2 * self._state.q1 for i in range(continuous_size): for j in range(i, continuous_size): # todo: verify that we don't need both calls @@ -482,7 +512,7 @@ def _create_step2_problem(self): # set linear objective for u variables if continuous_size: - linear_u = self.get_c1() + linear_u = self._state.c1 for i in range(continuous_size): op2.objective.set_linear(i, linear_u[i]) @@ -493,35 +523,36 @@ def _create_step2_problem(self): # constraints for z # A1 z <= b1 - a1, b1 = self.get_a1_b1() - constraint_count = a1.shape[0] + constraint_count = self._state.a1.shape[0] # in SparsePair val="something from numpy" causes an exception when saving a model via cplex method. # rhs="something from numpy" is ok # so, we convert every single value to python float, todo: consider removing this conversion lin_expr = [SparsePair(ind=list(range(continuous_size, continuous_size + binary_size)), - val=self._to_list(a1[i, :])) for i in range(constraint_count)] - op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, rhs=list(b1)) + val=self._to_list(self._state.a1[i, :])) for i in range(constraint_count)] + op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, rhs=list(self._state.b1)) if continuous_size: # A2 z + A3 u <= b2 - a2, a3, b2 = self.get_a2_a3_b2() - constraint_count = a2.shape[0] + constraint_count = self._state.a2.shape[0] lin_expr = [SparsePair(ind=list(range(continuous_size + binary_size)), - val=self._to_list(a3[i, :]) + self._to_list(a2[i, :])) + val=self._to_list(self._state.a3[i, :]) + self._to_list(self._state.a2[i, :])) for i in range(constraint_count)] - op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, rhs=self._to_list(b2)) + op2.linear_constraints.add(lin_expr=lin_expr, + senses=["L"] * constraint_count, + rhs=self._to_list(self._state.b2)) if continuous_size: # A4 u <= b3 - a4, b3 = self.get_a4_b3() - constraint_count = a4.shape[0] + constraint_count = self._state.a4.shape[0] lin_expr = [SparsePair(ind=list(range(continuous_size)), - val=self._to_list(a4[i, :])) for i in range(constraint_count)] - op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, rhs=self._to_list(b3)) + val=self._to_list(self._state.a4[i, :])) for i in range(constraint_count)] + op2.linear_constraints.add(lin_expr=lin_expr, + senses=["L"] * constraint_count, + rhs=self._to_list(self._state.b3)) return op2 - def _create_step3_problem(self): + def _create_step3_problem(self) -> OptimizationProblem: op3 = OptimizationProblem() # add y variables binary_size = len(self._state.binary_indices) @@ -552,10 +583,10 @@ def _to_list(self, values): def update_x0(self, op1: OptimizationProblem) -> np.ndarray: # TODO: Check output type of qubo_solver.solve(op1).x - return np.asarray(self._qubo_solver.solve(op1).x) + return np.asarray(self._qubo_optimizer.solve(op1).x) def update_x1(self, op2: OptimizationProblem) -> (np.ndarray, np.ndarray): - vars_op2 = self._continuous_solver.solve(op2).x + vars_op2 = self._continuous_optimizer.solve(op2).x # TODO: Check output type u = np.asarray(vars_op2[:len(self._state.continuous_indices)]) z = np.asarray(vars_op2[len(self._state.continuous_indices):]) @@ -563,7 +594,7 @@ def update_x1(self, op2: OptimizationProblem) -> (np.ndarray, np.ndarray): def update_y(self, op3): # TODO: Check output type - return np.asarray(self._continuous_solver.solve(op3).x) + return np.asarray(self._continuous_optimizer.solve(op3).x) def get_best_mer_sol(self): """ @@ -615,15 +646,12 @@ def get_cons_res(self): """ # TODO: think whether a0, b0 should be saved somewhere.. Might move to state? - a0, b0 = self.get_a0_b0() - cr0 = sum(np.abs(np.dot(a0, self._state.x0) - b0)) + cr0 = sum(np.abs(np.dot(self._state.a0, self._state.x0) - self._state.b0)) - a1, b1 = self.get_a1_b1() - eq1 = np.dot(a1, self._state.x0) - b1 + eq1 = np.dot(self._state.a1, self._state.x0) - self._state.b1 cr1 = sum(max(val, 0) for val in eq1) - a2, a3, b2 = self.get_a2_a3_b2() - eq2 = np.dot(a2, self._state.x0) + np.dot(a3, self._state.u) - b2 + eq2 = np.dot(self._state.a2, self._state.x0) + np.dot(self._state.a3, self._state.u) - self._state.b2 cr2 = sum(max(val, 0) for val in eq2) return cr0+cr1+cr2 @@ -641,13 +669,8 @@ def get_cost_val(self): # quadr_form = lambda A, x, c: np.dot(x.T, np.dot(A, x)) + np.dot(c.T, x) def quadratic_form(matrix, x, c): return np.dot(x.T, np.dot(matrix, x)) + np.dot(c.T, x) - q0 = self.get_q0() - q1 = self.get_q1() - c0 = self.get_c0() - c1 = self.get_c1() - - obj_val = quadratic_form(q0, self._state.x0, c0) - obj_val += quadratic_form(q1, self._state.u, c1) + obj_val = quadratic_form(self._state.q0, self._state.x0, self._state.c0) + obj_val += quadratic_form(self._state.q1, self._state.u, self._state.c1) return obj_val @@ -675,68 +698,60 @@ def get_sol_res(self, it): # only for debugging! def __dump_matrices_and_vectors(self): print("In admm_optimizer.py") - q0 = self.get_q0() print("Q0") - print(q0) + print(self._state.q0) print("Q0 shape") - print(q0.shape) - q1 = self.get_q1() + print(self._state.q0.shape) print("Q1") - print(q1) + print(self._state.q1) print("Q1") - print(q1.shape) + print(self._state.q1.shape) - c0 = self.get_c0() print("c0") - print(c0) + print(self._state.c0) print("c0 shape") - print(c0.shape) - c1 = self.get_c1() + print(self._state.c0.shape) print("c1") - print(c1) + print(self._state.c1) print("c1 shape") - print(c1.shape) + print(self._state.c1.shape) - a0, b0 = self.get_a0_b0() print("A0") - print(a0) + print(self._state.a0) print("A0") - print(a0.shape) + print(self._state.a0.shape) print("b0") - print(b0) + print(self._state.b0) print("b0 shape") - print(b0.shape) + print(self._state.b0.shape) - a1, b1 = self.get_a1_b1() print("A1") - print(a1) + print(self._state.a1) print("A1 shape") - print(a1.shape) + print(self._state.a1.shape) print("b1") - print(b1) + print(self._state.b1) print("b1 shape") - print(b1.shape) + print(self._state.b1.shape) - a4, b3 = self.get_a4_b3() print("A4") - print(a4) + print(self._state.a4) print("A4 shape") - print(a4.shape) + print(self._state.a4.shape) print("b3") - print(b3) + print(self._state.b3) print("b3 shape") - print(b3.shape) + print(self._state.b3.shape) - a2, a3, b2 = self.get_a2_a3_b2() print("A2") - print(a2) + print(self._state.a2) print("A2 shape") - print(a2.shape) + print(self._state.a2.shape) print("A3") - print(a3) + print(self._state.a3) print("A3") - print(a3.shape) + print(self._state.a3.shape) print("b2") - print(b2) + print(self._state.b2) print("b2 shape") - print(b2.shape) + print(self._state.b2.shape) From 94582e8450946938a08622092b142d5ad98690d6 Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Wed, 18 Mar 2020 17:21:42 +0000 Subject: [PATCH 089/323] testing miskp --- test/optimization/test_admm_miskp.py | 39 ++++++++++++++++++---------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/test/optimization/test_admm_miskp.py b/test/optimization/test_admm_miskp.py index c521129493..4f4e17d668 100644 --- a/test/optimization/test_admm_miskp.py +++ b/test/optimization/test_admm_miskp.py @@ -13,7 +13,7 @@ import numpy as np from qiskit import BasicAer -from qiskit.aqua.algorithms import QAOA +from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.aqua.components.optimizers import COBYLA from qiskit.optimization.algorithms import CplexOptimizer, MinimumEigenOptimizer @@ -341,18 +341,23 @@ def run_op_eigens(self): self.op.write(self.lp_folder + "miskp.lp") # QAOA - optimizer = COBYLA() - min_eigen_solver = QAOA(optimizer=optimizer) - qubo_solver = MinimumEigenOptimizer(min_eigen_solver) - + # optimizer = COBYLA() + # min_eigen_solver = QAOA(optimizer=optimizer) + # qubo_optimizer = MinimumEigenOptimizer(min_eigen_solver) # Note: a backend needs to be given, otherwise an error is raised in the _run method of VQE. - backend = 'statevector_simulator' - # backend = 'qasm_simulator' - min_eigen_solver.quantum_instance = BasicAer.get_backend(backend) + # backend = 'statevector_simulator' + # # backend = 'qasm_simulator' + # min_eigen_solver.quantum_instance = BasicAer.get_backend(backend) + + # use numpy exact diagonalization + qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) - continuous_solver = CplexOptimizer() + # Cplex + # qubo_optimizer = CplexOptimizer() - admm_params = ADMMParameters(qubo_solver=qubo_solver, continuous_solver=continuous_solver) + continuous_optimizer = CplexOptimizer() + + admm_params = ADMMParameters(qubo_optimizer=qubo_optimizer, continuous_optimizer=continuous_optimizer) solver = ADMMOptimizer(params=admm_params) solution = solver.solve(self.op) @@ -364,20 +369,26 @@ def toy_cplex_api(): K, T, P, S, D, C = get_instance_params() pb = Miskp(K, T, P, S, D, C) - out = pb.run_cplex_api() + result = pb.run_cplex_api() def toy_op(): K, T, P, S, D, C = get_instance_params() pb = Miskp(K, T, P, S, D, C) - out = pb.run_op() + result = pb.run_op() def toy_op_eigens(): K, T, P, S, D, C = get_instance_params() pb = Miskp(K, T, P, S, D, C) - out = pb.run_op_eigens() - + result = pb.run_op_eigens() + # debug + print("results") + print("x={}".format(result.x)) + print("fval={}".format(result.fval)) + # print("x0_saved={}".format(result.results.x0_saved)) + # print("u_saved={}".format(result.results.u_saved)) + # print("z_saved={}".format(result.results.z_saved)) if __name__ == '__main__': # toy_cplex_api() From f3a92f357797615a921f46b0bf7e15a56301b980 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Wed, 18 Mar 2020 18:52:14 +0100 Subject: [PATCH 090/323] relative imports skip __init__s --- .../optimization/algorithms/cplex_optimizer.py | 16 ++++++++-------- .../algorithms/minimum_eigen_optimizer.py | 13 +++++++------ .../algorithms/optimization_algorithm.py | 4 ++-- .../recursive_minimum_eigen_optimizer.py | 9 +++++---- .../inequality_to_equality_converter.py | 6 +++--- .../optimization_problem_to_operator.py | 5 +++-- .../penalize_linear_equality_constraints.py | 2 +- 7 files changed, 29 insertions(+), 26 deletions(-) diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index 83bad0104e..752b12519c 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -15,11 +15,11 @@ """The CPLEX optimizer wrapped to be used within Qiskit Optimization. - Examples: - >>> problem = OptimizationProblem() - >>> # specify problem here - >>> optimizer = CplexOptimizer() - >>> result = optimizer.solve(problem) +Examples: + >>> problem = OptimizationProblem() + >>> # specify problem here + >>> optimizer = CplexOptimizer() + >>> result = optimizer.solve(problem) """ from typing import Optional @@ -27,9 +27,9 @@ from cplex.exceptions import CplexSolverError from .optimization_algorithm import OptimizationAlgorithm -from ..utils import QiskitOptimizationError -from ..results import OptimizationResult -from ..problems import OptimizationProblem +from ..utils.qiskit_optimization_error import QiskitOptimizationError +from ..results.optimization_result import OptimizationResult +from ..problems.optimization_problem import OptimizationProblem class CplexOptimizer(OptimizationAlgorithm): diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 3068421ec5..60d2c1d60e 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -29,12 +29,13 @@ from qiskit.aqua.algorithms import MinimumEigensolver from .optimization_algorithm import OptimizationAlgorithm -from ..problems import OptimizationProblem -from ..utils import QiskitOptimizationError, eigenvector_to_solutions -from ..converters import (OptimizationProblemToOperator, - PenalizeLinearEqualityConstraints, - IntegerToBinaryConverter) -from ..results import OptimizationResult +from ..problems.optimization_problem import OptimizationProblem +from ..utils.qiskit_optimization_error import QiskitOptimizationError +from ..utils.eigenvector_to_solutions import eigenvector_to_solutions +from ..converters.optimization_problem_to_operator import OptimizationProblemToOperator +from ..converters.penalize_linear_equality_constraints import PenalizeLinearEqualityConstraints +from ..converters.integer_to_binary_converter import IntegerToBinaryConverter +from ..results.optimization_result import OptimizationResult class MinimumEigenOptimizer(OptimizationAlgorithm): diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index f733d3b7b1..4814e5852a 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -19,8 +19,8 @@ from typing import Optional -from ..problems import OptimizationProblem -from ..results import OptimizationResult +from ..problems.optimization_problem import OptimizationProblem +from ..results.optimization_result import OptimizationResult class OptimizationAlgorithm: diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index cedd3b79e7..bb6e5105ed 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -33,10 +33,11 @@ from .optimization_algorithm import OptimizationAlgorithm from .minimum_eigen_optimizer import MinimumEigenOptimizer -from ..utils import QiskitOptimizationError -from ..problems import OptimizationProblem -from ..results import OptimizationResult -from ..converters import PenalizeLinearEqualityConstraints, IntegerToBinaryConverter +from ..utils.qiskit_optimization_error import QiskitOptimizationError +from ..problems.optimization_problem import OptimizationProblem +from ..results.optimization_result import OptimizationResult +from ..converters.penalize_linear_equality_constraints import PenalizeLinearEqualityConstraints +from ..converters.integer_to_binary_converter import IntegerToBinaryConverter class RecursiveMinimumEigenOptimizer(OptimizationAlgorithm): diff --git a/qiskit/optimization/converters/inequality_to_equality_converter.py b/qiskit/optimization/converters/inequality_to_equality_converter.py index 804a8d73b5..8870e49f96 100644 --- a/qiskit/optimization/converters/inequality_to_equality_converter.py +++ b/qiskit/optimization/converters/inequality_to_equality_converter.py @@ -20,9 +20,9 @@ from cplex import SparsePair -from ..problems import OptimizationProblem -from ..results import OptimizationResult -from ..utils import QiskitOptimizationError +from ..problems.optimization_problem import OptimizationProblem +from ..results.optimization_result import OptimizationResult +from ..utils.qiskit_optimization_error import QiskitOptimizationError class InequalityToEqualityConverter: diff --git a/qiskit/optimization/converters/optimization_problem_to_operator.py b/qiskit/optimization/converters/optimization_problem_to_operator.py index 14998236d4..f059a7b9ad 100644 --- a/qiskit/optimization/converters/optimization_problem_to_operator.py +++ b/qiskit/optimization/converters/optimization_problem_to_operator.py @@ -21,8 +21,9 @@ from qiskit.quantum_info import Pauli from qiskit.aqua.operators import WeightedPauliOperator -from qiskit.optimization import OptimizationProblem -from qiskit.optimization.utils import QiskitOptimizationError + +from ..problems.optimization_problem import OptimizationProblem +from ..utils.qiskit_optimization_error import QiskitOptimizationError class OptimizationProblemToOperator: diff --git a/qiskit/optimization/converters/penalize_linear_equality_constraints.py b/qiskit/optimization/converters/penalize_linear_equality_constraints.py index a4e7ecda45..f5b43428f7 100644 --- a/qiskit/optimization/converters/penalize_linear_equality_constraints.py +++ b/qiskit/optimization/converters/penalize_linear_equality_constraints.py @@ -20,7 +20,7 @@ from collections import defaultdict from ..problems.optimization_problem import OptimizationProblem -from ..utils import QiskitOptimizationError +from ..utils.qiskit_optimization_error import QiskitOptimizationError class PenalizeLinearEqualityConstraints: From a93ab7dad4e8555cda682c4d4dfb52fa4c5fd928 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Thu, 19 Mar 2020 09:30:05 +0100 Subject: [PATCH 091/323] minor fixes of cobyla optimizer and linear constraint --- .../algorithms/cobyla_optimizer.py | 6 ++--- .../problems/linear_constraint.py | 26 ++++++++++++------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index 76f8bc848a..e2294eb676 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -182,10 +182,10 @@ def objective(x): linear_array[j] = v quadratic_array = np.zeros((num_vars, num_vars)) - for i, j, v in zip(quadratic_comp.ind1, quadratic_comp.ind2, quadratic_comp.val): - quadratic_array[i, j] = v + for j, k, v in zip(quadratic_comp.ind1, quadratic_comp.ind2, quadratic_comp.val): + quadratic_array[j, k] = v - def lhs(x): + def lhs(x, linear_array=linear_array, quadratic_array=quadratic_array): return np.dot(x, linear_array) + np.dot(np.dot(x, quadratic_array), x) if sense == 'E': diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index 8e3936e4c0..80f42407e9 100644 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -379,10 +379,12 @@ def set_linear_components(self, *args): >>> op.linear_constraints.set_linear_components("c0", [["x0"], [1.0]]) >>> op.linear_constraints.get_rows("c0") SparsePair(ind = [0], val = [1.0]) - >>> op.linear_constraints.set_linear_components([("c3", SparsePair(ind = ["x1"], val = [-1.0])),\ - (2, [[0, 1], [-2.0, 3.0]])]) + >>> op.linear_constraints.set_linear_components([ + ("c3", SparsePair(ind = ["x1"], val = [-1.0])),\ + (2, [[0, 1], [-2.0, 3.0]])]) >>> op.linear_constraints.get_rows() - [SparsePair(ind = [0], val = [1.0]), SparsePair(ind = [], val = []), SparsePair(ind = [0, 1], val = [-2.0, 3.0]), SparsePair(ind = [1], val = [-1.0])] + [SparsePair(ind = [0], val = [1.0]), SparsePair(ind = [], val = []), + SparsePair(ind = [0, 1], val = [-2.0, 3.0]), SparsePair(ind = [1], val = [-1.0])] """ def _set(i, v): @@ -724,9 +726,15 @@ def get_rows(self, *args): >>> op.linear_constraints.get_rows(0) SparsePair(ind = [0, 2], val = [1.0, -1.0]) >>> op.linear_constraints.get_rows(["c2", 0]) - [SparsePair(ind = [0, 1, 2], val = [-1.0, -1.0, -1.0]), SparsePair(ind = [0, 2], val = [1.0, -1.0])] + [SparsePair(ind = [0, 1, 2], val = [-1.0, -1.0, -1.0]), + SparsePair(ind = [0, 2], val = [1.0, -1.0])] >>> op.linear_constraints.get_rows() - [SparsePair(ind = [0, 2], val = [1.0, -1.0]), SparsePair(ind = [0, 1], val = [1.0, 1.0]), SparsePair(ind = [0, 1, 2], val = [-1.0, -1.0, -1.0]), SparsePair(ind = [1, 2], val = [10.0, -2.0])] + [ + SparsePair(ind = [0, 2], val = [1.0, -1.0]), + SparsePair(ind = [0, 1], val = [1.0, 1.0]), + SparsePair(ind = [0, 1, 2], val = [-1.0, -1.0, -1.0]), + SparsePair(ind = [1, 2], val = [10.0, -2.0]) + ] """ def _get(i): @@ -746,10 +754,10 @@ def get_num_nonzeros(self): >>> op = qiskit.optimization.OptimizationProblem() >>> indices = op.variables.add(names = ["x1", "x2", "x3"]) >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"],\ - lin_expr = [SparsePair(ind = ["x1", "x3"], val = [1.0, -1.0]),\ - SparsePair(ind = ["x1", "x2"], val = [1.0, 1.0]),\ - SparsePair(ind = ["x1", "x2", "x3"], val = [-1.0] * 3),\ - SparsePair(ind = ["x2", "x3"], val = [10.0, -2.0])]) + lin_expr = [SparsePair(ind = ["x1", "x3"], val = [1.0, -1.0]),\ + SparsePair(ind = ["x1", "x2"], val = [1.0, 1.0]),\ + SparsePair(ind = ["x1", "x2", "x3"], val = [-1.0] * 3),\ + SparsePair(ind = ["x2", "x3"], val = [10.0, -2.0])]) >>> op.linear_constraints.get_num_nonzeros() 9 """ From 14ba7726cea96eb4e01852aedec5e8a322cad45f Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Thu, 19 Mar 2020 10:45:23 +0000 Subject: [PATCH 092/323] reformatting, removed unnecessary todo --- .../optimization/algorithms/admm_optimizer.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 55d1cd027e..c08e244495 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -141,7 +141,6 @@ def __init__(self, params: ADMMParameters = None) -> None: if params is None: # create default params params = ADMMParameters() - # todo: consider keeping params as an object instead of copying self._three_block = params.three_block self._max_time = params.max_time self._tol = params.tol @@ -196,7 +195,6 @@ def is_compatible(self, problem: OptimizationProblem): # quadratic constraints are not supported return "Quadratic constraints are not supported" - # todo: verify other properties of the problem return None def solve(self, problem: OptimizationProblem): @@ -468,7 +466,9 @@ def _create_step1_problem(self) -> OptimizationProblem: op1.objective.set_quadratic_coefficients(j, i, quadratic_objective[i, j]) # prepare and set linear objective - linear_objective = self._state.c0 - self._factor_c * np.dot(self._state.b0, self._state.a0) + self._state.rho * (self._state.y - self._state.z) + linear_objective = self._state.c0 - self._factor_c * np.dot(self._state.b0, self._state.a0) + \ + self._state.rho * (self._state.y - self._state.z) + for i in range(binary_size): op1.objective.set_linear(i, linear_objective[i]) return op1 @@ -575,7 +575,8 @@ def _create_step3_problem(self) -> OptimizationProblem: # when a plain list() call is used a numpy type of values makes cplex to fail when cplex.write() is called. # for debug only, list() should be used instead - def _to_list(self, values): + @staticmethod + def _to_list(values): out_list = [] for el in values: out_list.append(float(el)) @@ -598,7 +599,8 @@ def update_y(self, op3): def get_best_mer_sol(self): """ - The ADMM solution is that for which the merit value is the best (least for min problems, greatest for max problems) + The ADMM solution is that for which the merit value is the best (least for min problems, greatest + for max problems) * sol: Iterate with the best merit value * sol_val: Value of sol, according to the original objective @@ -645,7 +647,6 @@ def get_cons_res(self): * max(body - rhs, 0) for leq constraints """ - # TODO: think whether a0, b0 should be saved somewhere.. Might move to state? cr0 = sum(np.abs(np.dot(self._state.a0, self._state.x0) - self._state.b0)) eq1 = np.dot(self._state.a1, self._state.x0) - self._state.b1 @@ -654,7 +655,7 @@ def get_cons_res(self): eq2 = np.dot(self._state.a2, self._state.x0) + np.dot(self._state.a3, self._state.u) - self._state.b2 cr2 = sum(max(val, 0) for val in eq2) - return cr0+cr1+cr2 + return cr0 + cr1 + cr2 def get_merit(self, cost_iterate, cr): """ @@ -666,6 +667,7 @@ def get_cost_val(self): """ Computes the value of the objective function. """ + # quadr_form = lambda A, x, c: np.dot(x.T, np.dot(A, x)) + np.dot(c.T, x) def quadratic_form(matrix, x, c): return np.dot(x.T, np.dot(matrix, x)) + np.dot(c.T, x) @@ -686,7 +688,7 @@ def get_sol_res(self, it): # elements = np.asarray([x0[i] - z[i] + y[i] for i in self.range_x0_vars]) r = pow(sum(e ** 2 for e in elements), 0.5) if it > 0: - elements_dual = self._state.z - self._state.z_saved[it-1] + elements_dual = self._state.z - self._state.z_saved[it - 1] else: elements_dual = self._state.z - self._state.z_init # debug From 4ca5e3d55fadac3d16eca6a7fc9052d4e871b69a Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Thu, 19 Mar 2020 12:03:23 +0000 Subject: [PATCH 093/323] reverting solution indexes --- qiskit/optimization/algorithms/admm_optimizer.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index c08e244495..51dc145c56 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -287,6 +287,7 @@ def solve(self, problem: OptimizationProblem): elapsed_time = time.time() - start_time sol, sol_val = self.get_best_mer_sol() + sol = self.revert_solution_indexes(sol) # third parameter is our internal state of computations result = OptimizationResult(sol, sol_val, self._state) @@ -304,6 +305,14 @@ def _get_variable_indices(op: OptimizationProblem, var_type: str) -> List[int]: return indices + def revert_solution_indexes(self, internal_solution) -> np.ndarray: + (x0, u) = internal_solution + solution = np.zeros(len(self._state.binary_indices) + len(self._state.continuous_indices)) + # restore solution at the original index location + [solution.itemset(binary_index, x0[i]) for i, binary_index in enumerate(self._state.binary_indices)] + [solution.itemset(continuous_index, u[i]) for i, continuous_index in enumerate(self._state.continuous_indices)] + return solution + def convert_problem_representation(self) -> None: # objective self.get_q0() From 197478958e678cbfce8219e39f1764ffe071f2f9 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Fri, 20 Mar 2020 00:41:35 +0900 Subject: [PATCH 094/323] fix a bug of int-to-bin converter --- qiskit/optimization/converters/integer_to_binary_converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/optimization/converters/integer_to_binary_converter.py b/qiskit/optimization/converters/integer_to_binary_converter.py index f1fa180125..ab51bdf794 100644 --- a/qiskit/optimization/converters/integer_to_binary_converter.py +++ b/qiskit/optimization/converters/integer_to_binary_converter.py @@ -73,7 +73,7 @@ def encode(self, op: OptimizationProblem, name: Optional[str] = None) -> Optimiz new_vars: List[Tuple[str, int]] = self._encode_var(name=variable, lower_bound=lower_bounds[i], upper_bound=upper_bounds[i]) - self._conv[name] = new_vars + self._conv[variable] = new_vars self._dst.variables.add(names=[new_name for new_name, _ in new_vars], types='B' * len(new_vars)) else: From 2231db1a6799467367889cb0f30764d1293bbc09 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Fri, 20 Mar 2020 09:52:38 +0000 Subject: [PATCH 095/323] added admm test and refactorings --- ...dmm_algorithm.py => run_admm_algorithm.py} | 0 .../{test_admm_bpp.py => run_admm_bpp.py} | 0 test/optimization/run_admm_miskp.py | 402 ++++++++++++++++ test/optimization/test_admm_miskp.py | 444 +++++------------- 4 files changed, 521 insertions(+), 325 deletions(-) rename test/optimization/{test_admm_algorithm.py => run_admm_algorithm.py} (100%) rename test/optimization/{test_admm_bpp.py => run_admm_bpp.py} (100%) create mode 100644 test/optimization/run_admm_miskp.py diff --git a/test/optimization/test_admm_algorithm.py b/test/optimization/run_admm_algorithm.py similarity index 100% rename from test/optimization/test_admm_algorithm.py rename to test/optimization/run_admm_algorithm.py diff --git a/test/optimization/test_admm_bpp.py b/test/optimization/run_admm_bpp.py similarity index 100% rename from test/optimization/test_admm_bpp.py rename to test/optimization/run_admm_bpp.py diff --git a/test/optimization/run_admm_miskp.py b/test/optimization/run_admm_miskp.py new file mode 100644 index 0000000000..4f4e17d668 --- /dev/null +++ b/test/optimization/run_admm_miskp.py @@ -0,0 +1,402 @@ +""" +Created: 2020-01-24 +@author Claudio Gambella [claudio.gambella1@ie.ibm.com] + +This solves Mixed-Integer Setup Knapsack Problem via ADMM +References: Exact and heuristic solution approaches for the mixed integer setup knapsack problem, Altay et. al, EJOR, 2008. +Gambella, C., & Simonetto, A. (2020). Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical +and Quantum Computers. arXiv preprint arXiv:2001.02069. +""" +import os +import sys +import time +import numpy as np +from qiskit import BasicAer + +from qiskit.aqua.algorithms import NumPyMinimumEigensolver +from qiskit.aqua.components.optimizers import COBYLA + +from qiskit.optimization.algorithms import CplexOptimizer, MinimumEigenOptimizer +from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters +from qiskit.optimization.problems import OptimizationProblem + + +try: + import cplex + from cplex.exceptions import CplexError +except ImportError: + print("Failed to import cplex.") + sys.exit(1) +from cplex import SparsePair + +def create_folder(folder: str): + if not os.path.exists(folder): + os.makedirs(folder) +def nm(stem, index1, index2=None, index3=None): + """A method to return a string representing the name of a decision variable or a constraint, given its indices. + Attributes: + stem: Element name. + index1, index2, index3: Element indices. + """ + if index2==None: return stem + "(" + str(index1) + ")" + if index3==None: return stem + "(" + str(index1) + "," + str(index2) + ")" + return stem + "(" + str(index1) + "," + str(index2) + "," + str(index3) + ")" + +def __dump_arrays(Q0, Q1, c0, c1, A0, b0, A1, b1, A4, b3, A2, A3, b2): + """ + For debugging purposes + :param Q0: + :param Q1: + :param c0: + :param c1: + :param A0: + :param b0: + :param A1: + :param b1: + :param A4: + :param b3: + :param A2: + :param A3: + :param b2: + :return: + """ + print("Claudio's implementation") + print("Q0") + print(Q0) + print("Q0 shape") + print(Q0.shape) + print("Q1") + print(Q1) + print("Q1 shape") + print(Q1.shape) + + print("c0") + print(c0) + print("c0 shape") + print(c0.shape) + print("c1") + print(c1) + print("c1 shape") + print(c1.shape) + + print("A0") + print(A0) + print("A0 shape") + print(A0.shape) + print("b0") + print(b0) + print("b0 shape") + print(b0.shape) + + print("A1") + print(A1) + print("A1 shape") + print(A1.shape) + print("b1") + print(b1) + print("b1 shape") + print(b1.shape) + + print("A4") + print(A4) + print("A4 shape") + print(A4.shape) + print("b3") + print(b3) + print("b3 shape") + print(b3.shape) + + print("A2") + print(A2) + print("A2 shape") + print(A2.shape) + print("A3") + print(A3) + print("A3 shape") + print(A3.shape) + print("b2") + print(b2) + print("b2 shape") + print(b2.shape) + + print("End of Claudio's implementation") + + +def get_instance_params(): + """ + Get parameters for a Mixed Integer Setup Knapsack Problem (MISKP) instance. + + :return: + """ + # + + K = 2 + T = 10 + P = 45.10 + S = np.asarray([75.61, 75.54]) + + D = np.asarray([9.78, 8.81, 9.08, 2.03, 8.9, 9, 4.12, 8.16, 6.55, 4.84, 3.78, 5.13, + 4.72, 7.6, 2.83, 1.44, 2.45, 2.24, 6.3, 5.02]).reshape((K, T)) + + C = np.asarray([-11.78, -10.81, -11.08, -4.03, -10.90, -11.00, -6.12, -10.16, -8.55, -6.84, + -5.78, -7.13, -6.72, -9.60, -4.83, -3.44, -4.45, -4.24, -8.30, -7.02]).reshape((K, T)) + + n = K # number of binary dec vars x0 (and z) + m = K * T # number of continuous dec vars u + # size of x1 is then n+m + + # Parameters of the Quadratic Programming problem in the standard form for ADMM. + ## Objective function + Q0 = np.zeros((n, n)) + c0 = S.reshape(n) + + A0 = np.zeros((1, n)) # we set here one dummy equality constraint + b0 = np.zeros(1) # we set here one dummy equality constraint + + Q1 = np.zeros((m, m)) + c1 = C.reshape(m) + + ## Constraints + # we set here one dummy A1 x0 \leq b1 constraint + A1 = np.zeros((1, n)) + b1 = np.zeros(1) + + # A_2 z + A_3 u \leq b_2 -- > - y_k <= x_{k,t} + A2 = np.zeros((m, n)) + for i in range(K): + A2[T * i:T * (i + 1), i] = - np.ones(T) + A3 = np.eye(m) + b2 = np.zeros(m) + + # A_4 u <= b_3 --> sum_k sum_t D_{kt} x_{kt} \leq P, -x_{kt} \leq 0 + block1 = D.reshape((1, m)) + block2 = -np.eye(m) + A4 = np.block([[block1], [block2]]) + b3 = np.hstack([P, np.zeros(m)]) + + __dump_arrays(Q0, Q1, c0, c1, A0, b0, A1, b1, A4, b3, A2, A3, b2) + + return K, T, P, S, D, C + + + +class Miskp: + + def __init__(self, K, T, P, S, D: np.ndarray, C: np.ndarray, verbose=False, relativeGap=0.0, + pairwise_incomp=0, multiple_choice=0): + """ + Constructor method of the class. + :param K: number of families + :param T: number of items in each family + :param C: value of including item t in family k in the knapsack + :param D: resources consumed if item t in family k is included in the knapsack + :param S: setup cost to include family k in the knapsack + :param P: capacity of the knapsack + :param verbose: + :param relativeGap: + """ + + self.multiple_choice = multiple_choice + self.pairwise_incomp = pairwise_incomp + self.P = P + self.S = S + self.D = D + self.C = C + self.T = T + self.K = K + self.relativeGap = relativeGap + self.verbose = verbose + self.lp_folder = "./lps/" + + def create_params(self): + self.range_K = range(self.K) + self.range_T = range(self.T) + + # make sure instance params are floats + + self.S = [float(val) for val in self.S] + self.C = self.C.astype(float) + self.D = self.D.astype(float) + + self.n_x_vars = self.K * self.T + self.n_y_vars = self.K + + self.range_x_vars = [(k, t) for k in self.range_K for t in self.range_T] + self.range_y_vars = self.range_K + + + def create_vars(self): + + self.op.variables.add( + lb=[0.0] * self.n_x_vars, + names=[nm("x", i, j) for i,j in self.range_x_vars]) + + self.op.variables.add( + # lb=[0.0] * self.n_y_vars, + # ub=[1.0] * self.n_y_vars, + types=["B"] * self.n_y_vars, + names=[nm("y", i) for i in self.range_y_vars]) + + def create_cons_capacity(self): + self.op.linear_constraints.add( + lin_expr=[ + SparsePair( + ind=[ + nm("x", i, j) for i,j in self.range_x_vars + ] + , + val=[self.D[i,j] for i,j in self.range_x_vars]) + ], + senses="L", + rhs=[self.P], + names=["CAPACITY"]) + + def create_cons_allocation(self): + self.op.linear_constraints.add( + lin_expr=[ + SparsePair( + ind=[nm("x", k, t)]+[nm("y", k)], + val=[1.0, -1.0]) + for k,t in self.range_x_vars + ], + senses="L" * self.n_x_vars, + rhs=[0.0] * self.n_x_vars, + names=[nm("ALLOCATION", k, t) for k, t in self.range_x_vars]) + + + def create_cons(self): + """Method to populate the constraints""" + + self.create_cons_capacity() + self.create_cons_allocation() + + def create_obj(self): + self.op.objective.set_linear([(nm("y", k), self.S[k]) for k in self.range_K] + + [(nm("x", k, t), self.C[k, t]) for k, t in self.range_x_vars] + ) + + def run_cplex_api(self): + """Main method, which populates and solve the mathematical model via Python Cplex API + + """ + + # Creation of the Cplex object + self.op = OptimizationProblem() + + + self.create_params() + self.create_vars() + self.create_obj() + start_time = time.time() + self.create_cons() + constraints_time = time.time() - start_time + if self.verbose: + print ("Time to populate constraints:", constraints_time) + + # Save the model + create_folder(self.lp_folder) + + self.op.write(self.lp_folder + "miskp.lp") + + out = 0 + return out + + def run_op(self): + """Main method, which populates and solve the OptimizationProblem model via OptimizationAlgorithm + + """ + + self.op = OptimizationProblem() + + self.create_params() + self.create_vars() + self.create_obj() + self.create_cons() + + # Save the model + create_folder(self.lp_folder) + + self.op.write(self.lp_folder + "miskp.lp") + + solver = ADMMOptimizer() + solution = solver.solve(self.op) + + return solution + + def run_op_eigens(self): + """Main method, which populates and solve the OptimizationProblem model via OptimizationAlgorithm + + """ + + self.op = OptimizationProblem() + + self.create_params() + self.create_vars() + self.create_obj() + self.create_cons() + + # Save the model + create_folder(self.lp_folder) + + self.op.write(self.lp_folder + "miskp.lp") + + # QAOA + # optimizer = COBYLA() + # min_eigen_solver = QAOA(optimizer=optimizer) + # qubo_optimizer = MinimumEigenOptimizer(min_eigen_solver) + # Note: a backend needs to be given, otherwise an error is raised in the _run method of VQE. + # backend = 'statevector_simulator' + # # backend = 'qasm_simulator' + # min_eigen_solver.quantum_instance = BasicAer.get_backend(backend) + + # use numpy exact diagonalization + qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) + + # Cplex + # qubo_optimizer = CplexOptimizer() + + continuous_optimizer = CplexOptimizer() + + admm_params = ADMMParameters(qubo_optimizer=qubo_optimizer, continuous_optimizer=continuous_optimizer) + + solver = ADMMOptimizer(params=admm_params) + solution = solver.solve(self.op) + + return solution + + +def toy_cplex_api(): + + K, T, P, S, D, C = get_instance_params() + pb = Miskp(K, T, P, S, D, C) + result = pb.run_cplex_api() + +def toy_op(): + + K, T, P, S, D, C = get_instance_params() + pb = Miskp(K, T, P, S, D, C) + result = pb.run_op() + +def toy_op_eigens(): + + K, T, P, S, D, C = get_instance_params() + pb = Miskp(K, T, P, S, D, C) + result = pb.run_op_eigens() + # debug + print("results") + print("x={}".format(result.x)) + print("fval={}".format(result.fval)) + # print("x0_saved={}".format(result.results.x0_saved)) + # print("u_saved={}".format(result.results.u_saved)) + # print("z_saved={}".format(result.results.z_saved)) + +if __name__ == '__main__': + # toy_cplex_api() + # toy_op() + toy_op_eigens() + + + + + + diff --git a/test/optimization/test_admm_miskp.py b/test/optimization/test_admm_miskp.py index 4f4e17d668..f2c93280d6 100644 --- a/test/optimization/test_admm_miskp.py +++ b/test/optimization/test_admm_miskp.py @@ -1,199 +1,95 @@ +# -*-coding: utf-8 -*- +# This code is part of Qiskit. +# +# (C) Copyright IBM 2000. +# +# 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. + +"""Tests of the ADMM algorithm. """ -Created: 2020-01-24 -@author Claudio Gambella [claudio.gambella1@ie.ibm.com] -This solves Mixed-Integer Setup Knapsack Problem via ADMM -References: Exact and heuristic solution approaches for the mixed integer setup knapsack problem, Altay et. al, EJOR, 2008. -Gambella, C., & Simonetto, A. (2020). Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical -and Quantum Computers. arXiv preprint arXiv:2001.02069. -""" -import os -import sys -import time import numpy as np -from qiskit import BasicAer - +from cplex import SparsePair from qiskit.aqua.algorithms import NumPyMinimumEigensolver -from qiskit.aqua.components.optimizers import COBYLA - from qiskit.optimization.algorithms import CplexOptimizer, MinimumEigenOptimizer from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters from qiskit.optimization.problems import OptimizationProblem +from test.optimization import QiskitOptimizationTestCase -try: - import cplex - from cplex.exceptions import CplexError -except ImportError: - print("Failed to import cplex.") - sys.exit(1) -from cplex import SparsePair -def create_folder(folder: str): - if not os.path.exists(folder): - os.makedirs(folder) -def nm(stem, index1, index2=None, index3=None): - """A method to return a string representing the name of a decision variable or a constraint, given its indices. - Attributes: - stem: Element name. - index1, index2, index3: Element indices. - """ - if index2==None: return stem + "(" + str(index1) + ")" - if index3==None: return stem + "(" + str(index1) + "," + str(index2) + ")" - return stem + "(" + str(index1) + "," + str(index2) + "," + str(index3) + ")" - -def __dump_arrays(Q0, Q1, c0, c1, A0, b0, A1, b1, A4, b3, A2, A3, b2): - """ - For debugging purposes - :param Q0: - :param Q1: - :param c0: - :param c1: - :param A0: - :param b0: - :param A1: - :param b1: - :param A4: - :param b3: - :param A2: - :param A3: - :param b2: - :return: - """ - print("Claudio's implementation") - print("Q0") - print(Q0) - print("Q0 shape") - print(Q0.shape) - print("Q1") - print(Q1) - print("Q1 shape") - print(Q1.shape) - - print("c0") - print(c0) - print("c0 shape") - print(c0.shape) - print("c1") - print(c1) - print("c1 shape") - print(c1.shape) - - print("A0") - print(A0) - print("A0 shape") - print(A0.shape) - print("b0") - print(b0) - print("b0 shape") - print(b0.shape) - - print("A1") - print(A1) - print("A1 shape") - print(A1.shape) - print("b1") - print(b1) - print("b1 shape") - print(b1.shape) - - print("A4") - print(A4) - print("A4 shape") - print(A4.shape) - print("b3") - print(b3) - print("b3 shape") - print(b3.shape) - - print("A2") - print(A2) - print("A2 shape") - print(A2.shape) - print("A3") - print(A3) - print("A3 shape") - print(A3.shape) - print("b2") - print(b2) - print("b2 shape") - print(b2.shape) - - print("End of Claudio's implementation") - - -def get_instance_params(): - """ - Get parameters for a Mixed Integer Setup Knapsack Problem (MISKP) instance. - - :return: - """ - # - - K = 2 - T = 10 - P = 45.10 - S = np.asarray([75.61, 75.54]) - - D = np.asarray([9.78, 8.81, 9.08, 2.03, 8.9, 9, 4.12, 8.16, 6.55, 4.84, 3.78, 5.13, - 4.72, 7.6, 2.83, 1.44, 2.45, 2.24, 6.3, 5.02]).reshape((K, T)) - - C = np.asarray([-11.78, -10.81, -11.08, -4.03, -10.90, -11.00, -6.12, -10.16, -8.55, -6.84, - -5.78, -7.13, -6.72, -9.60, -4.83, -3.44, -4.45, -4.24, -8.30, -7.02]).reshape((K, T)) - - n = K # number of binary dec vars x0 (and z) - m = K * T # number of continuous dec vars u - # size of x1 is then n+m - - # Parameters of the Quadratic Programming problem in the standard form for ADMM. - ## Objective function - Q0 = np.zeros((n, n)) - c0 = S.reshape(n) - - A0 = np.zeros((1, n)) # we set here one dummy equality constraint - b0 = np.zeros(1) # we set here one dummy equality constraint - - Q1 = np.zeros((m, m)) - c1 = C.reshape(m) - - ## Constraints - # we set here one dummy A1 x0 \leq b1 constraint - A1 = np.zeros((1, n)) - b1 = np.zeros(1) - - # A_2 z + A_3 u \leq b_2 -- > - y_k <= x_{k,t} - A2 = np.zeros((m, n)) - for i in range(K): - A2[T * i:T * (i + 1), i] = - np.ones(T) - A3 = np.eye(m) - b2 = np.zeros(m) - - # A_4 u <= b_3 --> sum_k sum_t D_{kt} x_{kt} \leq P, -x_{kt} \leq 0 - block1 = D.reshape((1, m)) - block2 = -np.eye(m) - A4 = np.block([[block1], [block2]]) - b3 = np.hstack([P, np.zeros(m)]) - - __dump_arrays(Q0, Q1, c0, c1, A0, b0, A1, b1, A4, b3, A2, A3, b2) - - return K, T, P, S, D, C +class TestADMMOptimizerMiskp(QiskitOptimizationTestCase): + """ADMM Optimizer Tests based on Mixed-Integer Setup Knapsack Problem""" + def setUp(self): + super().setUp() + def test_admm_optimizer_miskp_eigen(self): + """ ADMM Optimizer Test based on Mixed-Integer Setup Knapsack Problem using NumPy eigen optimizer""" + K, T, P, S, D, C = self.get_problem_params() + miskp = Miskp(K, T, P, S, D, C) + op: OptimizationProblem = miskp.create_problem() -class Miskp: + # use numpy exact diagonalization + qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) + continuous_optimizer = CplexOptimizer() + + admm_params = ADMMParameters(qubo_optimizer=qubo_optimizer, continuous_optimizer=continuous_optimizer) + + solver = ADMMOptimizer(params=admm_params) + solution = solver.solve(op) + + # debug + print("results") + print("x={}".format(solution.x)) + print("fval={}".format(solution.fval)) + + correct_solution = [0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, + 0.009127, 0.009127, 0.006151, 0.006151, 0.006151, 0.006151, 0.006151, 0.006151, + 0.006151, 0.006151, 0.006151, 0.006151, 0., 0.] + correct_objective = -1.2113693 + + np.testing.assert_almost_equal(correct_solution, solution.x, 3) + np.testing.assert_almost_equal(solution.fval, correct_objective, 3) + + def get_problem_params(self): + """ + Fills in parameters for a Mixed Integer Setup Knapsack Problem (MISKP) instance. + """ + # + + K = 2 + T = 10 + P = 45.10 + S = np.asarray([75.61, 75.54]) + + D = np.asarray([9.78, 8.81, 9.08, 2.03, 8.9, 9, 4.12, 8.16, 6.55, 4.84, 3.78, 5.13, + 4.72, 7.6, 2.83, 1.44, 2.45, 2.24, 6.3, 5.02]).reshape((K, T)) - def __init__(self, K, T, P, S, D: np.ndarray, C: np.ndarray, verbose=False, relativeGap=0.0, - pairwise_incomp=0, multiple_choice=0): + C = np.asarray([-11.78, -10.81, -11.08, -4.03, -10.90, -11.00, -6.12, -10.16, -8.55, -6.84, + -5.78, -7.13, -6.72, -9.60, -4.83, -3.44, -4.45, -4.24, -8.30, -7.02]).reshape((K, T)) + + return K, T, P, S, D, C + + +class Miskp: + def __init__(self, K, T, P, S, D: np.ndarray, C: np.ndarray, pairwise_incomp=0, multiple_choice=0): """ Constructor method of the class. - :param K: number of families - :param T: number of items in each family - :param C: value of including item t in family k in the knapsack - :param D: resources consumed if item t in family k is included in the knapsack - :param S: setup cost to include family k in the knapsack - :param P: capacity of the knapsack - :param verbose: - :param relativeGap: + + Args: + K: number of families + T: number of items in each family + C: value of including item t in family k in the knapsack + D: resources consumed if item t in family k is included in the knapsack + S: setup cost to include family k in the knapsack + P: capacity of the knapsack """ self.multiple_choice = multiple_choice @@ -204,9 +100,30 @@ def __init__(self, K, T, P, S, D: np.ndarray, C: np.ndarray, verbose=False, rela self.C = C self.T = T self.K = K - self.relativeGap = relativeGap - self.verbose = verbose - self.lp_folder = "./lps/" + + # definitions of the internal variables + self.op = None + self.range_K = None + self.range_T = None + self.n_x_vars = None + self.n_y_vars = None + self.range_x_vars = None + self.range_y_vars = None + + @staticmethod + def var_name(stem, index1, index2=None, index3=None): + """A method to return a string representing the name of a decision variable or a constraint, given its indices. + Args: + stem: Element name. + index1: Element indices + index2: Element indices + index3: Element indices + """ + if index2 is None: + return stem + "(" + str(index1) + ")" + if index3 is None: + return stem + "(" + str(index1) + "," + str(index2) + ")" + return stem + "(" + str(index1) + "," + str(index2) + "," + str(index3) + ")" def create_params(self): self.range_K = range(self.K) @@ -224,179 +141,56 @@ def create_params(self): self.range_x_vars = [(k, t) for k in self.range_K for t in self.range_T] self.range_y_vars = self.range_K - def create_vars(self): - self.op.variables.add( lb=[0.0] * self.n_x_vars, - names=[nm("x", i, j) for i,j in self.range_x_vars]) + names=[self.var_name("x", i, j) for i, j in self.range_x_vars]) self.op.variables.add( # lb=[0.0] * self.n_y_vars, # ub=[1.0] * self.n_y_vars, types=["B"] * self.n_y_vars, - names=[nm("y", i) for i in self.range_y_vars]) + names=[self.var_name("y", i) for i in self.range_y_vars]) - def create_cons_capacity(self): + def create_constraint_capacity(self): self.op.linear_constraints.add( lin_expr=[ SparsePair( - ind=[ - nm("x", i, j) for i,j in self.range_x_vars - ] + ind=[self.var_name("x", i, j) for i, j in self.range_x_vars] , - val=[self.D[i,j] for i,j in self.range_x_vars]) + val=[self.D[i, j] for i, j in self.range_x_vars]) ], senses="L", rhs=[self.P], names=["CAPACITY"]) - def create_cons_allocation(self): + def create_constraint_allocation(self): self.op.linear_constraints.add( lin_expr=[ SparsePair( - ind=[nm("x", k, t)]+[nm("y", k)], + ind=[self.var_name("x", k, t)] + [self.var_name("y", k)], val=[1.0, -1.0]) - for k,t in self.range_x_vars + for k, t in self.range_x_vars ], senses="L" * self.n_x_vars, rhs=[0.0] * self.n_x_vars, - names=[nm("ALLOCATION", k, t) for k, t in self.range_x_vars]) - - - def create_cons(self): - """Method to populate the constraints""" + names=[self.var_name("ALLOCATION", k, t) for k, t in self.range_x_vars]) - self.create_cons_capacity() - self.create_cons_allocation() + def create_constraints(self): + self.create_constraint_capacity() + self.create_constraint_allocation() - def create_obj(self): - self.op.objective.set_linear([(nm("y", k), self.S[k]) for k in self.range_K] + - [(nm("x", k, t), self.C[k, t]) for k, t in self.range_x_vars] + def create_objective(self): + self.op.objective.set_linear([(self.var_name("y", k), self.S[k]) for k in self.range_K] + + [(self.var_name("x", k, t), self.C[k, t]) for k, t in self.range_x_vars] ) - def run_cplex_api(self): - """Main method, which populates and solve the mathematical model via Python Cplex API - - """ - - # Creation of the Cplex object - self.op = OptimizationProblem() - - - self.create_params() - self.create_vars() - self.create_obj() - start_time = time.time() - self.create_cons() - constraints_time = time.time() - start_time - if self.verbose: - print ("Time to populate constraints:", constraints_time) - - # Save the model - create_folder(self.lp_folder) - - self.op.write(self.lp_folder + "miskp.lp") - - out = 0 - return out - - def run_op(self): - """Main method, which populates and solve the OptimizationProblem model via OptimizationAlgorithm - - """ - + def create_problem(self): self.op = OptimizationProblem() self.create_params() self.create_vars() - self.create_obj() - self.create_cons() - - # Save the model - create_folder(self.lp_folder) - - self.op.write(self.lp_folder + "miskp.lp") - - solver = ADMMOptimizer() - solution = solver.solve(self.op) - - return solution - - def run_op_eigens(self): - """Main method, which populates and solve the OptimizationProblem model via OptimizationAlgorithm - - """ - - self.op = OptimizationProblem() - - self.create_params() - self.create_vars() - self.create_obj() - self.create_cons() - - # Save the model - create_folder(self.lp_folder) - - self.op.write(self.lp_folder + "miskp.lp") - - # QAOA - # optimizer = COBYLA() - # min_eigen_solver = QAOA(optimizer=optimizer) - # qubo_optimizer = MinimumEigenOptimizer(min_eigen_solver) - # Note: a backend needs to be given, otherwise an error is raised in the _run method of VQE. - # backend = 'statevector_simulator' - # # backend = 'qasm_simulator' - # min_eigen_solver.quantum_instance = BasicAer.get_backend(backend) - - # use numpy exact diagonalization - qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) - - # Cplex - # qubo_optimizer = CplexOptimizer() - - continuous_optimizer = CplexOptimizer() - - admm_params = ADMMParameters(qubo_optimizer=qubo_optimizer, continuous_optimizer=continuous_optimizer) - - solver = ADMMOptimizer(params=admm_params) - solution = solver.solve(self.op) - - return solution - - -def toy_cplex_api(): - - K, T, P, S, D, C = get_instance_params() - pb = Miskp(K, T, P, S, D, C) - result = pb.run_cplex_api() - -def toy_op(): - - K, T, P, S, D, C = get_instance_params() - pb = Miskp(K, T, P, S, D, C) - result = pb.run_op() - -def toy_op_eigens(): - - K, T, P, S, D, C = get_instance_params() - pb = Miskp(K, T, P, S, D, C) - result = pb.run_op_eigens() - # debug - print("results") - print("x={}".format(result.x)) - print("fval={}".format(result.fval)) - # print("x0_saved={}".format(result.results.x0_saved)) - # print("u_saved={}".format(result.results.u_saved)) - # print("z_saved={}".format(result.results.z_saved)) - -if __name__ == '__main__': - # toy_cplex_api() - # toy_op() - toy_op_eigens() - - - - - + self.create_objective() + self.create_constraints() + return self.op From 556024179fb4676aeb23db1a0a5fb12699b543ca Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Fri, 20 Mar 2020 17:29:40 +0100 Subject: [PATCH 096/323] update GSLS optimizer and corresponding unit test --- qiskit/aqua/components/optimizers/gsls.py | 51 +++++++++++++---------- test/aqua/test_optimizers.py | 17 ++++++-- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/qiskit/aqua/components/optimizers/gsls.py b/qiskit/aqua/components/optimizers/gsls.py index 9d37a3d118..079cc01a40 100644 --- a/qiskit/aqua/components/optimizers/gsls.py +++ b/qiskit/aqua/components/optimizers/gsls.py @@ -33,25 +33,28 @@ class GSLS(Optimizer): """ - _OPTIONS = ['max_iter', 'disp', 'sampling_radius', 'sample_size_factor', - 'initial_step_size', 'min_step_size', 'step_size_multiplier', - 'armijo_parameter', 'min_gradient_norm', - 'max_failed_rejection_sampling'] + _OPTIONS = ['max_iter', 'max_eval', 'disp', 'sampling_radius', + 'sample_size_factor', 'initial_step_size', 'min_step_size', + 'step_size_multiplier', 'armijo_parameter', + 'min_gradient_norm', 'max_failed_rejection_sampling'] # pylint: disable=unused-argument def __init__(self, - max_iter: int = 1000, + max_iter: int = 10000, + max_eval: int = 10000, disp: bool = False, - sampling_radius: float = 1.0e-3, + sampling_radius: float = 1.0e-6, sample_size_factor: int = 1, - initial_step_size: float = 1.0e-1, + initial_step_size: float = 1.0e-2, min_step_size: float = 1.0e-10, - step_size_multiplier: float = 0.5, - armijo_parameter: float = 1.0e-2, - min_gradient_norm: float = 1e-5, + step_size_multiplier: float = 0.4, + armijo_parameter: float = 1.0e-1, + min_gradient_norm: float = 1e-8, max_failed_rejection_sampling: int = 50) -> None: """Args: + max_iter : Maximum number of iterations + max_eval : Maximum number of evaluations sampling_radius : Sampling radius to determine gradient estimate. sample_size_factor : The size of the sample set at each @@ -144,10 +147,15 @@ def ls_optimize(self, n, obj_fun, initial_point, var_lb, var_ub): x = initial_point x_value = obj_fun(x) n_evals += 1 - while (iter_count < self._options['max_iter'] and not stop): + while (iter_count < self._options['max_iter'] and + n_evals < self._options['max_eval'] and not stop): # Determine set of sample points u, sample_set_x = self.sample_set(n, x, var_lb, var_ub, sample_set_size) + if (n_evals + len(sample_set_x) + 1 >= self._options['max_eval']): + # The evaluation budget is too small to allow for + # another full iteration; we therefore exit now + break sample_set_y = np.array( [obj_fun(point) for point in sample_set_x]) n_evals += len(sample_set_x) @@ -168,17 +176,17 @@ def ls_optimize(self, n, obj_fun, initial_point, var_lb, var_ub): if (self._options['disp']): print('Iter {:d}'.format(iter_count)) print('Point ' + str(x) + ' obj ' + str(x_value)) - print('Gradient' + str(grad)) + print('Gradient' + str(grad)) print('Grad norm ' + str(grad_norm) + ' new_x_value ' + str(new_x_value) + ' step size ' + str(alpha)) print('Direction ' + str(d)) - # Test Armijo condition for sufficient decrease + # Test Armijo condition for sufficient decrease if (new_x_value <= x_value - - self._options['armijo_parameter'] * alpha * grad_norm): - # Accept point + self._options['armijo_parameter'] * alpha * grad_norm): + # Accept point x = new_x x_value = new_x_value - alpha /= self._options['step_size_multiplier'] + alpha /= 2*self._options['step_size_multiplier'] prev_iter_successful = True consecutive_fail_iter = 0 # Reset sample set @@ -196,7 +204,7 @@ def ls_optimize(self, n, obj_fun, initial_point, var_lb, var_ub): prev_sample_set_y = sample_set_y iter_count += 1 if (grad_norm <= self._options['min_gradient_norm'] or - alpha <= self._options['min_step_size']): + alpha <= self._options['min_step_size']): stop = True return x, x_value, n_evals, grad_norm # -- end function @@ -224,7 +232,7 @@ def sample_set(self, n, x, var_lb, var_ub, num_points): """ # Generate points uniformly on the sphere - normal_samples = np.random.normal(size=(num_points,n)) + normal_samples = np.random.normal(size=(num_points, n)) row_norms = np.sqrt(np.sum(normal_samples**2, 1, keepdims=True)) directions = normal_samples / row_norms points = x + self._options['sampling_radius'] * directions @@ -243,7 +251,7 @@ def sample_set(self, n, x, var_lb, var_ub, num_points): while (len(accepted) < num_points and num_trials < self._options['max_failed_rejection_sampling']): # Generate points uniformly on the sphere - normal_samples = np.random.normal(size=(num_points,n)) + normal_samples = np.random.normal(size=(num_points, n)) row_norms = np.sqrt(np.sum(normal_samples**2, 1, keepdims=True)) directions = normal_samples / row_norms @@ -259,7 +267,7 @@ def sample_set(self, n, x, var_lb, var_ub, num_points): # different method that guarantees finding enough points, # but they may not be uniformly distributed. if (len(accepted) < num_points): - normal_samples = np.random.normal(size=(num_points,n)) + normal_samples = np.random.normal(size=(num_points, n)) row_norms = np.sqrt(np.sum(normal_samples**2, 1, keepdims=True)) directions = normal_samples / row_norms @@ -272,7 +280,7 @@ def sample_set(self, n, x, var_lb, var_ub, num_points): accepted = np.vstack((accepted, directions[indices])) # If we still do not have enough sampling points, we have # failed. - if (len(accepted) < num_points): + if (len(accepted) < num_points): raise RuntimeError('Could not generate enough samples ' + 'within bounds; try smaller radius.') return (accepted[:num_points], @@ -306,4 +314,3 @@ def gradient_approximation(self, n, x, x_value, directions, self._options['sampling_radius'] * directions, 0)) return gradient # -- end function - diff --git a/test/aqua/test_optimizers.py b/test/aqua/test_optimizers.py index 0dab11ee4c..f2864bb71c 100644 --- a/test/aqua/test_optimizers.py +++ b/test/aqua/test_optimizers.py @@ -27,6 +27,7 @@ class TestOptimizers(QiskitAquaTestCase): """ Test Optimizers """ + def setUp(self): super().setUp() aqua_globals.random_seed = 50 @@ -101,9 +102,19 @@ def test_tnc(self): def test_gsls(self): """ gsls test """ - optimizer = GSLS() - res = self._optimize(optimizer) - self.assertLessEqual(res[2], 10000) + # We need to set our own randomness because GSLS is stochastic + rs = np.random.get_state() + np.random.seed(512310912) + optimizer = GSLS(sample_size_factor=40, + sampling_radius=1.0e-12, max_iter=10000, + max_eval=10000, min_step_size=1.0e-12) + x_0 = [1.3, 0.7, 0.8, 1.9, 1.2] + x, x_value, n_evals = optimizer.optimize(len(x_0), rosen, + initial_point=x_0) + np.random.set_state(rs) + # Ensure value is near-optimal + self.assertLessEqual(x_value, 0.01) + self.assertLessEqual(n_evals, 10000) if __name__ == '__main__': From d200272b8f1a78d9b22a57611bbdbee3dd957f4f Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Fri, 20 Mar 2020 17:38:00 +0000 Subject: [PATCH 097/323] run admm bpp --- test/optimization/run_admm_bpp.py | 56 ++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/test/optimization/run_admm_bpp.py b/test/optimization/run_admm_bpp.py index 1428948efc..9b76b803c3 100644 --- a/test/optimization/run_admm_bpp.py +++ b/test/optimization/run_admm_bpp.py @@ -7,6 +7,8 @@ import sys import numpy as np +from qiskit.aqua.algorithms import NumPyMinimumEigensolver +from qiskit.optimization.algorithms import MinimumEigenOptimizer, CplexOptimizer from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters from qiskit.optimization.problems import OptimizationProblem @@ -348,14 +350,66 @@ def run_op(self, save_vars=False): return solution + def run_op_eigens(self): + """Main method, which populates and solve the OptimizationProblem model via OptimizationAlgorithm + + """ + + self.op = OptimizationProblem() + + self.create_params() + self.create_vars() + self.create_obj() + self.create_cons() + + # Save the model + create_folder(self.lp_folder) + + self.op.write(self.lp_folder + "miskp.lp") + + # QAOA + # optimizer = COBYLA() + # min_eigen_solver = QAOA(optimizer=optimizer) + # qubo_optimizer = MinimumEigenOptimizer(min_eigen_solver) + # Note: a backend needs to be given, otherwise an error is raised in the _run method of VQE. + # backend = 'statevector_simulator' + # # backend = 'qasm_simulator' + # min_eigen_solver.quantum_instance = BasicAer.get_backend(backend) + + # use numpy exact diagonalization + # qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) + + # Cplex + qubo_optimizer = CplexOptimizer() + + continuous_optimizer = CplexOptimizer() + + admm_params = ADMMParameters(qubo_optimizer=qubo_optimizer, continuous_optimizer=continuous_optimizer) + + solver = ADMMOptimizer(params=admm_params) + solution = solver.solve(self.op) + + return solution + def toy_op(): n_items, n_bins, C, w = get_instance_params() pb = Bpp(n_items, n_bins, C, w) out = pb.run_op() +def toy_op_eigens(): + + n_items, n_bins, C, w = get_instance_params() + pb = Bpp(n_items, n_bins, C, w) + result = pb.run_op_eigens() + # debug + print("results") + print("x={}".format(result.x)) + print("fval={}".format(result.fval)) + if __name__ == '__main__': # toy_cplex_api() - toy_op() + # toy_op() + toy_op_eigens() From 9da8862309610716e0e277cab93f28c9f3b6ca5b Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Fri, 20 Mar 2020 20:49:18 +0000 Subject: [PATCH 098/323] refactorings --- .../optimization/algorithms/admm_optimizer.py | 184 +++++++----------- test/optimization/run_admm_algorithm.py | 66 ------- 2 files changed, 69 insertions(+), 181 deletions(-) delete mode 100644 test/optimization/run_admm_algorithm.py diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 51dc145c56..3b15d94876 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -29,6 +29,9 @@ class ADMMParameters: + """Defines a set of parameters for ADMM optimizer. + """ + def __init__(self, rho_initial=10000, factor_c=100000, beta=1000, max_iter=10, tol=1.e-4, max_time=1800, three_block=True, vary_rho=0, tau_incr=2, tau_decr=2, mu_res=10, mu=1000, qubo_optimizer: OptimizationAlgorithm = None, @@ -43,9 +46,8 @@ def __init__(self, rho_initial=10000, factor_c=100000, beta=1000, max_iter=10, t tol: Tolerance for the residual convergence. max_time: Maximum running time (in seconds) for ADMM. three_block: Boolean flag to select the 3-block ADMM implementation. - vary_rho: Flag to select the rule to update rho. - If set to 0, then rho increases by 10% at each iteartion. - If set to 1, then rho is modified according to primal and dual residuals. + vary_rho: Flag to select the rule to update rho. If set to 0, then rho increases by 10% at each iteration. + If set to 1, then rho is modified according to primal and dual residuals. tau_incr: Parameter used in the rho update. tau_decr: Parameter used in the rho update. mu_res: Parameter used in the rho update. @@ -71,15 +73,17 @@ def __init__(self, rho_initial=10000, factor_c=100000, beta=1000, max_iter=10, t class ADMMState: + """Internal computation state of the ADMM implementation. Here, various variables are stored that are + being updated during problem solving. The values are relevant to the problem being solved. + The state is recreated for each optimization problem. State is returned as the third value + """ + def __init__(self, op: OptimizationProblem, binary_indices: List[int], continuous_indices: List[int], rho_initial: float) -> None: - """ - Internal computation state of the ADMM implementation. Here, various variables are stored that are - being updated during problem solving. The values are relevant to the problem being solved. - The state is recreated for each optimization problem. + """Constructs an internal computation state of the ADMM implementation. Args: op: The optimization problem being solved @@ -96,7 +100,7 @@ def __init__(self, self.continuous_indices = continuous_indices self.sense = op.objective.get_sense() - # define heavily used matrix, they are used at each iteration, so let's cache them, they are ndarrays + # define heavily used matrix, they are used at each iteration, so let's cache them, they are np.ndarrays # objective self.q0 = None self.c0 = None @@ -136,7 +140,16 @@ def __init__(self, class ADMMOptimizer(OptimizationAlgorithm): + """An implementation of the ADMM algorithm. + """ + def __init__(self, params: ADMMParameters = None) -> None: + """Constructs an instance of ADMMOptimizer. + + Args: + params: An instance of ADMMParameters + """ + super().__init__() if params is None: # create default params @@ -159,7 +172,7 @@ def __init__(self, params: ADMMParameters = None) -> None: self._continuous_optimizer = params.continuous_optimizer # internal state where we'll keep intermediate solution - # here, we just declare the class variable + # here, we just declare the class variable, the variable is initialized in kept in the solve method. self._state = None def is_compatible(self, problem: OptimizationProblem): @@ -204,9 +217,7 @@ def solve(self, problem: OptimizationProblem): problem: The problem to be solved. Returns: - The result of the optimizer applied to the problem. Note that result.x it is a list [x0, u], with x0 - being the value of the binary variables in the ADMM solution, and u is the value of the continuous - variables in the ADMM solution. + The result of the optimizer applied to the problem. Raises: QiskitOptimizationError: If the problem is incompatible with the optimizer. @@ -227,51 +238,46 @@ def solve(self, problem: OptimizationProblem): start_time = time.time() # we have not stated our computations yet, so elapsed time initialized as zero elapsed_time = 0 + iteration = 0 + residual = 1.e+2 - it = 0 - r = 1.e+2 - - while (it < self._max_iter and r > self._tol) and (elapsed_time < self._max_time): - + while (iteration < self._max_iter and residual > self._tol) and (elapsed_time < self._max_time): op1 = self._create_step1_problem() # debug - op1.write("op1.lp") + # op1.write("op1.lp") self._state.x0 = self.update_x0(op1) # debug - print("x0={}".format(self._state.x0)) + # print("x0={}".format(self._state.x0)) op2 = self._create_step2_problem() - op2.write("op2.lp") + # op2.write("op2.lp") self._state.u, self._state.z = self.update_x1(op2) # debug - print("u={}".format(self._state.u)) - print("z={}".format(self._state.z)) + # print("u={}".format(self._state.u)) + # print("z={}".format(self._state.z)) if self._three_block: op3 = self._create_step3_problem() - op3.write("op3.lp") + # op3.write("op3.lp") self._state.y = self.update_y(op3) # debug - print("y={}".format(self._state.y)) + # print("y={}".format(self._state.y)) lambda_mult = self.update_lambda_mult() - cost_iterate = self.get_cost_val() - - cr = self.get_cons_res() - - r, s = self.get_sol_res(it) - + cost_iterate = self.get_objective_value() + cr = self.get_constraint_residual() + residual, dual_residual = self.get_solution_residuals(iteration) merit = self.get_merit(cost_iterate, cr) # debug - print("cost_iterate, cr, merit", cost_iterate, cr, merit) + # print("cost_iterate, cr, merit", cost_iterate, cr, merit) # costs and merits are saved with their original sign self._state.cost_iterates.append(self._state.sense * cost_iterate) - self._state.residuals.append(r) - self._state.dual_residuals.append(s) + self._state.residuals.append(residual) + self._state.dual_residuals.append(dual_residual) self._state.cons_r.append(cr) self._state.merits.append(merit) self._state.lambdas.append(np.linalg.norm(lambda_mult)) @@ -281,19 +287,19 @@ def solve(self, problem: OptimizationProblem): self._state.z_saved.append(self._state.z) self._state.z_saved.append(self._state.y) - self.update_rho(r, s) + self.update_rho(residual, dual_residual) - it += 1 + iteration += 1 elapsed_time = time.time() - start_time - sol, sol_val = self.get_best_mer_sol() - sol = self.revert_solution_indexes(sol) + solution, objective_value = self.get_best_merit_solution() + solution = self.revert_solution_indexes(solution) # third parameter is our internal state of computations - result = OptimizationResult(sol, sol_val, self._state) + result = OptimizationResult(solution, objective_value, self._state) # debug - print("sol={0}, sol_val={1}".format(sol, sol_val)) - print("it {0}, state {1}".format(it, self._state)) + # print("sol={0}, sol_val={1}".format(solution, objective_value)) + # print("it {0}, state {1}".format(iteration, self._state)) return result @staticmethod @@ -506,18 +512,14 @@ def _create_step2_problem(self) -> OptimizationProblem: q_u = 2 * self._state.q1 for i in range(continuous_size): for j in range(i, continuous_size): - # todo: verify that we don't need both calls op2.objective.set_quadratic_coefficients(i, j, q_u[i, j]) - op2.objective.set_quadratic_coefficients(j, i, q_u[i, j]) # set quadratic objective coefficients for z variables. # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. q_z = 2 * (self._state.rho / 2 * np.eye(binary_size)) for i in range(binary_size): for j in range(i, binary_size): - # todo: verify that we don't need both calls op2.objective.set_quadratic_coefficients(i + continuous_size, j + continuous_size, q_z[i, j]) - op2.objective.set_quadratic_coefficients(j + continuous_size, i + continuous_size, q_z[i, j]) # set linear objective for u variables if continuous_size: @@ -592,21 +594,18 @@ def _to_list(values): return out_list def update_x0(self, op1: OptimizationProblem) -> np.ndarray: - # TODO: Check output type of qubo_solver.solve(op1).x return np.asarray(self._qubo_optimizer.solve(op1).x) def update_x1(self, op2: OptimizationProblem) -> (np.ndarray, np.ndarray): vars_op2 = self._continuous_optimizer.solve(op2).x - # TODO: Check output type u = np.asarray(vars_op2[:len(self._state.continuous_indices)]) z = np.asarray(vars_op2[len(self._state.continuous_indices):]) return u, z def update_y(self, op3): - # TODO: Check output type return np.asarray(self._continuous_optimizer.solve(op3).x) - def get_best_mer_sol(self): + def get_best_merit_solution(self): """ The ADMM solution is that for which the merit value is the best (least for min problems, greatest for max problems) @@ -615,8 +614,8 @@ def get_best_mer_sol(self): Returns: A tuple of (sol, sol_val), where - * sol: Iterate with the best merit value - * sol_val: Value of sol, according to the original objective + * sol: Solution with the best merit value + * sol_val: Value of the objective function """ it_best_merits = self._state.merits.index(min(list(map(lambda x: self._state.sense * x, self._state.merits)))) @@ -648,12 +647,15 @@ def update_rho(self, r, s): elif s > self._mu_res * r: self._state.rho = self._tau_decr * self._state.rho - def get_cons_res(self): + def get_constraint_residual(self): """ Compute violation of the constraints of the original problem, as: * norm 1 of the body-rhs of the constraints A0 x0 - b0 * -1 * min(body - rhs, 0) for geq constraints * max(body - rhs, 0) for leq constraints + + Returns: + Violation of the constraints as a float value """ cr0 = sum(np.abs(np.dot(self._state.a0, self._state.x0) - self._state.b0)) @@ -669,12 +671,22 @@ def get_cons_res(self): def get_merit(self, cost_iterate, cr): """ Compute merit value associated with the current iterate + + Args: + cost_iterate: cost at the certain iteration + cr: value of violation of the constraints + + Returns: + merit value as a float """ return cost_iterate + self._mu * cr - def get_cost_val(self): + def get_objective_value(self): """ Computes the value of the objective function. + + Returns: + Value of the objective function as a float """ # quadr_form = lambda A, x, c: np.dot(x.T, np.dot(A, x)) + np.dot(c.T, x) @@ -685,12 +697,15 @@ def quadratic_form(matrix, x, c): return np.dot(x.T, np.dot(matrix, x)) + np.dot return obj_val - def get_sol_res(self, it): + def get_solution_residuals(self, it): """ Compute primal and dual residual. Args: - it: + it: iteration number + + Returns: + r, s as primary and dual residuals """ elements = self._state.x0 - self._state.z - self._state.y # debug @@ -705,64 +720,3 @@ def get_sol_res(self, it): s = self._state.rho * pow(sum(e ** 2 for e in elements_dual), 0.5) return r, s - - # only for debugging! - def __dump_matrices_and_vectors(self): - print("In admm_optimizer.py") - print("Q0") - print(self._state.q0) - print("Q0 shape") - print(self._state.q0.shape) - print("Q1") - print(self._state.q1) - print("Q1") - print(self._state.q1.shape) - - print("c0") - print(self._state.c0) - print("c0 shape") - print(self._state.c0.shape) - print("c1") - print(self._state.c1) - print("c1 shape") - print(self._state.c1.shape) - - print("A0") - print(self._state.a0) - print("A0") - print(self._state.a0.shape) - print("b0") - print(self._state.b0) - print("b0 shape") - print(self._state.b0.shape) - - print("A1") - print(self._state.a1) - print("A1 shape") - print(self._state.a1.shape) - print("b1") - print(self._state.b1) - print("b1 shape") - print(self._state.b1.shape) - - print("A4") - print(self._state.a4) - print("A4 shape") - print(self._state.a4.shape) - print("b3") - print(self._state.b3) - print("b3 shape") - print(self._state.b3.shape) - - print("A2") - print(self._state.a2) - print("A2 shape") - print(self._state.a2.shape) - print("A3") - print(self._state.a3) - print("A3") - print(self._state.a3.shape) - print("b2") - print(self._state.b2) - print("b2 shape") - print(self._state.b2.shape) diff --git a/test/optimization/run_admm_algorithm.py b/test/optimization/run_admm_algorithm.py deleted file mode 100644 index f57cf2549f..0000000000 --- a/test/optimization/run_admm_algorithm.py +++ /dev/null @@ -1,66 +0,0 @@ -from cplex import SparsePair -from qiskit.optimization.problems.optimization_problem import OptimizationProblem -from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer - - -def create_simple_admm_problem(): - op = OptimizationProblem() - op.variables.add(names=["x0_1"], types=["B"]) - op.variables.add(names=["x0_2"], types=["B"]) - op.variables.add(names=["u_1"], lb=[0]) - op.variables.add(names=["u_2"], lb=[0]) - - # a list with length equal to the number of variables - # list of SparsePairs - q_terms = [SparsePair(ind=(0, 1, 2, 3), val=[11, 12, 0, 0]), - SparsePair(ind=(0, 1, 2, 3), val=[12, 22, 0, 0]), - SparsePair(ind=(0, 1, 2, 3), val=[0, 0, 33, 34]), - SparsePair(ind=(0, 1, 2, 3), val=[0, 0, 34, 44])] - op.objective.set_quadratic(q_terms) - - op.objective.set_linear("x0_1", 1) - op.objective.set_linear("x0_2", 2) - - op.objective.set_linear("u_1", 3) - op.objective.set_linear("u_2", 4) - - # A0, vector b0 - op.linear_constraints.add(lin_expr=[SparsePair(ind=[0, 1], val=[1, 11])], senses="E", rhs=[11.1], - names=["constraint1"]) - op.linear_constraints.add(lin_expr=[SparsePair(ind=[0, 1], val=[2, 22])], senses="E", rhs=[22.22], - names=["constraint2"]) - - # matrix A1, vector b1 - op.linear_constraints.add(lin_expr=[SparsePair(ind=[0, 1], val=[3, 33])], senses="L", rhs=[33.33], - names=["constraint3"]) - op.linear_constraints.add(lin_expr=[SparsePair(ind=[0, 1], val=[4, 44])], senses="L", rhs=[44.44], - names=["constraint4"]) - - # matrix A2, matrix A3 - # original one - # op.linear_constraints.add(lin_expr=[SparsePair(ind=[0, 1, 2, 3], val=[5, 55, 555, 5555])], senses="L", rhs=[55.55], - # names=["constraint5"]) - op.linear_constraints.add(lin_expr=[SparsePair(ind=[0, 2, 1, 3], val=[5, 555, 55, 5555])], senses="L", rhs=[55.55], - names=["constraint5"]) - op.linear_constraints.add(lin_expr=[SparsePair(ind=[0, 1, 2, 3], val=[6, 66, 666, 6666])], senses="L", rhs=[66.66], - names=["constraint6"]) - op.linear_constraints.add(lin_expr=[SparsePair(ind=[2, 3, 0], val=[777, 7777, 7])], senses="L", rhs=[77.77], - names=["constraint7"]) - - # matrix A4, vector b3 - op.linear_constraints.add(lin_expr=[SparsePair(ind=[2, 3], val=[777, 7777])], senses="L", rhs=[77.77], - names=["constraint8"]) - op.linear_constraints.add(lin_expr=[SparsePair(ind=[2, 3], val=[888, 8888])], senses="L", rhs=[88.88], - names=["constraint9"]) - - op.write("admm-sample.lp") - - return op - - -if __name__ == '__main__': - op = create_simple_admm_problem() - solver = ADMMOptimizer() - compatible = solver.is_compatible(op) - print("Problem is compatible: {}".format(compatible)) - solution = solver.solve(op) From 6252d3a26f527e106075520c3c835815406220f3 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Fri, 20 Mar 2020 21:20:22 +0000 Subject: [PATCH 099/323] removed non-test scripts from tests --- test/optimization/run_admm_bpp.py | 415 ---------------------------- test/optimization/run_admm_miskp.py | 402 --------------------------- 2 files changed, 817 deletions(-) delete mode 100644 test/optimization/run_admm_bpp.py delete mode 100644 test/optimization/run_admm_miskp.py diff --git a/test/optimization/run_admm_bpp.py b/test/optimization/run_admm_bpp.py deleted file mode 100644 index 9b76b803c3..0000000000 --- a/test/optimization/run_admm_bpp.py +++ /dev/null @@ -1,415 +0,0 @@ -""" -Created: 2020-02-19 -@author Claudio Gambella [claudio.gambella1@ie.ibm.com] -""" -import math -import os -import sys -import numpy as np - -from qiskit.aqua.algorithms import NumPyMinimumEigensolver -from qiskit.optimization.algorithms import MinimumEigenOptimizer, CplexOptimizer -from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters -from qiskit.optimization.problems import OptimizationProblem - - - -try: - import cplex - from cplex.exceptions import CplexError -except ImportError: - print("Failed to import cplex.") - sys.exit(1) -from cplex import SparsePair - -def create_folder(folder: str): - if not os.path.exists(folder): - os.makedirs(folder) -def nm(stem, index1, index2=None, index3=None): - """A method to return a string representing the name of a decision variable or a constraint, given its indices. - Attributes: - stem: Element name. - index1, index2, index3: Element indices. - """ - if index2==None: return stem + "(" + str(index1) + ")" - if index3==None: return stem + "(" + str(index1) + "," + str(index2) + ")" - return stem + "(" + str(index1) + "," + str(index2) + "," + str(index3) + ")" - - -def __dump_arrays(Q0, Q1, c0, c1, A0, b0, A1, b1, A4, b3, A2, A3, b2): - """ - For debugging purposes - :param Q0: - :param Q1: - :param c0: - :param c1: - :param A0: - :param b0: - :param A1: - :param b1: - :param A4: - :param b3: - :param A2: - :param A3: - :param b2: - :return: - """ - print("Claudio's implementation") - print("Q0") - print(Q0) - print("Q0 shape") - print(Q0.shape) - print("Q1") - print(Q1) - print("Q1 shape") - print(Q1.shape) - - print("c0") - print(c0) - print("c0 shape") - print(c0.shape) - print("c1") - print(c1) - print("c1 shape") - print(c1.shape) - - print("A0") - print(A0) - print("A0 shape") - print(A0.shape) - print("b0") - print(b0) - print("b0 shape") - print(b0.shape) - - print("A1") - print(A1) - print("A1 shape") - print(A1.shape) - print("b1") - print(b1) - print("b1 shape") - print(b1.shape) - - print("A4") - print(A4) - print("A4 shape") - print(A4.shape) - print("b3") - print(b3) - print("b3 shape") - print(b3.shape) - - print("A2") - print(A2) - print("A2 shape") - print(A2.shape) - print("A3") - print(A3) - print("A3 shape") - print(A3.shape) - print("b2") - print(b2) - print("b2 shape") - print(b2.shape) - - print("End of Claudio's implementation") - - -def get_instance_params(): - """ - - Populate a Bin Packing Problem (BPP) instance. - - Create the instance parameters needed to apply ADMM. - NOTE: The x decision variables are thought of as array parameters. - - n number of binary dec vars x0 (and z) - m number of continuous dec vars u - - :return: n, m, Q0, c0, Q1, c1, A0, b0, A1, b1, A2, A3, b2, lb, ub ADMM parameters - """ - # - - n_items = 2 - n_bins = n_items - n_x_vars = n_items * n_bins - C = 40 - w = [35, 31] - - n = n_x_vars + n_bins # number of binary dec vars x0 (and z) - m = 0 # number of continuous dec vars u - - # Parameters of the Quadratic Programming problem in the standard form for ADMM. - Q0 = np.zeros((n, n)) - c0 = np.hstack((np.zeros(n_x_vars), np.ones(n_bins))) - - # A0 x0 = b0 --> \sum _i x_{ij} = 1 - A0 = np.tile(np.eye(n_items), n_bins) - A0 = np.hstack((A0, np.zeros((n_items, n_items - )))) - b0 = np.ones(n_items) - - Q1 = np.zeros((m, m)) - c1 = np.zeros(m) - - # A1 z \leq b1 - A1 = np.zeros((n_bins, n)) - block1 = np.zeros((n_bins, n_x_vars)) - for i in range(n_bins): - block1[i, i*n_items:(i+1)*n_items] = np.asarray(w) - A1 = np.hstack((block1, -C * np.eye(n_bins))) - b1 = np.zeros(n_bins) - - dummy_dim = 1 - A2 = np.zeros((dummy_dim, n)) - A3 = np.zeros((dummy_dim, m)) - b2 = np.zeros(dummy_dim) - - A4 = np.zeros((1,m)) - b3 = np.zeros(1) - - __dump_arrays(Q0, Q1, c0, c1, A0, b0, A1, b1, A4, b3, A2, A3, b2) - - return n_items, n_bins, C, w - -class Bpp: - - def __init__(self, n_items, n_bins, C, w, verbose=False, relativeGap=0.0): - """ - Constructor method of the class. - :param n: number of items - :param m: number of bins - :param C: capacity of the bins - :param w: weight of items - :param verbose: - :param relativeGap: - """ - self.C = C - self.relativeGap = relativeGap - self.verbose = verbose - self.w = w - self.n = n_items - self.m = n_bins - self.lp_folder = "./outputs/lps/bpp/" - - def create_params(self, save_vars=False): - self.range_n = range(self.n) - self.range_m = range(self.m) - self.range_mn = [(i,j) for i in self.range_m for j in self.range_n] - self.nm = self.n*self.m - - # make sure instance params are floats - self.w = [float(item) for item in self.w] - self.C = float(self.C) - - if save_vars: - self.l = self.get_lb_bins() - self.n_x = self.m*self.n-self.m - self.n_y = self.m-self.l - - self.range_x_vars = [(i, j) for i in self.range_m for j in self.range_n if - j != 0] # item j=0 is automatically assigned to bin 0 - self.range_y_vars = range(self.l, self.m) # y_0, ..., y_{l-1} are not needed - else: - self.l = 0 - - self.n_x = self.m * self.n - self.n_y = self.m - - self.range_x_vars = self.range_mn - self.range_y_vars = self.range_m - - def get_lb_bins(self) -> int: - """ - Return lower bound on the number of bins needed, (see, e.g., Martello and Toth 1990) - :return: - """ - return math.ceil(sum(self.w)/self.C) - - - def create_vars(self, x_var=True): - """ - - :param x_var: - :return: - """ - - if x_var: - self.op.variables.add( - types=["B"] * self.n_x, - names=[nm("x", i, j) for i,j in self.range_x_vars]) - - self.op.variables.add( - types=["B"] * self.n_y, - names=[nm("y", i) for i in self.range_y_vars]) - - - def create_cons_eq(self, save_vars=False): - - if save_vars: - self.items_to_assign = range(1, self.n) - else: - self.items_to_assign = self.range_n - - self.op.linear_constraints.add( - lin_expr=[ - SparsePair( - ind=[ - nm("x", i, j) for i in self.range_m - ], - val=[1.0] * self.m) - for j in self.items_to_assign - ], - senses="E" * len(self.items_to_assign), - rhs=[1.0] * len(self.items_to_assign), - names=[nm("ASSIGN_ITEM", j) for j in self.items_to_assign]) - - def create_cons_ineq(self, save_vars=False): - - if not save_vars: - self.op.linear_constraints.add( - lin_expr=[ - SparsePair( - ind=[ - nm("x", i, j) for j in self.range_n - ] +[nm("y", i)] - , - val=self.w + [-self.C]) - for i in self.range_m - ], - senses="L" * self.m, - rhs=[0.0] * self.m, - names=[nm("CAPACITY", i) for i in self.range_m]) - else: - #This is because x(0,0) is assigned - self.op.linear_constraints.add( - lin_expr=[ - SparsePair( - ind=[ - nm("x", 0, j) for j in self.range_n if j != 0 - ] - , - val=self.w[1:]) - ], - senses="L", - rhs=[self.C-self.w[0]], - names=[nm("CAPACITY_l", 0)]) - # Bins from 1 to l - self.op.linear_constraints.add( - lin_expr=[ - SparsePair( - ind=[ - nm("x", i, j) for j in self.range_n if j != 0 - ] - , - val=self.w[1:]) for i in range(1, self.l) - ], - senses="L" * (self.l-1), - rhs=[self.C] * (self.l-1), - names=[nm("CAPACITY_l", i) for i in range(1, self.l)]) - self.op.linear_constraints.add( - lin_expr=[ - SparsePair( - ind=[ - nm("x", i, j) for j in self.range_n if j != 0 - ] +[nm("y", i)] - , - val=self.w[1:] + [-self.C]) - for i in self.range_y_vars - ], - senses="L" * (self.m - self.l), - rhs=[0.0] * (self.m - self.l), - names=[nm("CAPACITY", i) for i in self.range_y_vars]) - - def create_cons(self, save_vars=False): - """Method to populate the constraints""" - self.create_cons_eq(save_vars) - self.create_cons_ineq(save_vars) - - def create_obj(self): - self.op.objective.set_linear([(nm("y", i), 1.0) for i in self.range_y_vars]) - - def run_op(self, save_vars=False): - """Main method, which populates and solve the mathematical model via Python Cplex API - """ - - self.op = OptimizationProblem() - - self.create_params(save_vars=save_vars) - self.create_vars() - self.create_obj() - self.create_cons(save_vars=save_vars) - - # Save the model - create_folder(self.lp_folder) - - self.op.write(self.lp_folder + "bpp.lp") - - params = ADMMParameters(max_iter=1) - solver = ADMMOptimizer(params) - solution = solver.solve(self.op) - - return solution - - def run_op_eigens(self): - """Main method, which populates and solve the OptimizationProblem model via OptimizationAlgorithm - - """ - - self.op = OptimizationProblem() - - self.create_params() - self.create_vars() - self.create_obj() - self.create_cons() - - # Save the model - create_folder(self.lp_folder) - - self.op.write(self.lp_folder + "miskp.lp") - - # QAOA - # optimizer = COBYLA() - # min_eigen_solver = QAOA(optimizer=optimizer) - # qubo_optimizer = MinimumEigenOptimizer(min_eigen_solver) - # Note: a backend needs to be given, otherwise an error is raised in the _run method of VQE. - # backend = 'statevector_simulator' - # # backend = 'qasm_simulator' - # min_eigen_solver.quantum_instance = BasicAer.get_backend(backend) - - # use numpy exact diagonalization - # qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) - - # Cplex - qubo_optimizer = CplexOptimizer() - - continuous_optimizer = CplexOptimizer() - - admm_params = ADMMParameters(qubo_optimizer=qubo_optimizer, continuous_optimizer=continuous_optimizer) - - solver = ADMMOptimizer(params=admm_params) - solution = solver.solve(self.op) - - return solution - - -def toy_op(): - n_items, n_bins, C, w = get_instance_params() - pb = Bpp(n_items, n_bins, C, w) - out = pb.run_op() - -def toy_op_eigens(): - - n_items, n_bins, C, w = get_instance_params() - pb = Bpp(n_items, n_bins, C, w) - result = pb.run_op_eigens() - # debug - print("results") - print("x={}".format(result.x)) - print("fval={}".format(result.fval)) - - -if __name__ == '__main__': - # toy_cplex_api() - # toy_op() - toy_op_eigens() - diff --git a/test/optimization/run_admm_miskp.py b/test/optimization/run_admm_miskp.py deleted file mode 100644 index 4f4e17d668..0000000000 --- a/test/optimization/run_admm_miskp.py +++ /dev/null @@ -1,402 +0,0 @@ -""" -Created: 2020-01-24 -@author Claudio Gambella [claudio.gambella1@ie.ibm.com] - -This solves Mixed-Integer Setup Knapsack Problem via ADMM -References: Exact and heuristic solution approaches for the mixed integer setup knapsack problem, Altay et. al, EJOR, 2008. -Gambella, C., & Simonetto, A. (2020). Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical -and Quantum Computers. arXiv preprint arXiv:2001.02069. -""" -import os -import sys -import time -import numpy as np -from qiskit import BasicAer - -from qiskit.aqua.algorithms import NumPyMinimumEigensolver -from qiskit.aqua.components.optimizers import COBYLA - -from qiskit.optimization.algorithms import CplexOptimizer, MinimumEigenOptimizer -from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters -from qiskit.optimization.problems import OptimizationProblem - - -try: - import cplex - from cplex.exceptions import CplexError -except ImportError: - print("Failed to import cplex.") - sys.exit(1) -from cplex import SparsePair - -def create_folder(folder: str): - if not os.path.exists(folder): - os.makedirs(folder) -def nm(stem, index1, index2=None, index3=None): - """A method to return a string representing the name of a decision variable or a constraint, given its indices. - Attributes: - stem: Element name. - index1, index2, index3: Element indices. - """ - if index2==None: return stem + "(" + str(index1) + ")" - if index3==None: return stem + "(" + str(index1) + "," + str(index2) + ")" - return stem + "(" + str(index1) + "," + str(index2) + "," + str(index3) + ")" - -def __dump_arrays(Q0, Q1, c0, c1, A0, b0, A1, b1, A4, b3, A2, A3, b2): - """ - For debugging purposes - :param Q0: - :param Q1: - :param c0: - :param c1: - :param A0: - :param b0: - :param A1: - :param b1: - :param A4: - :param b3: - :param A2: - :param A3: - :param b2: - :return: - """ - print("Claudio's implementation") - print("Q0") - print(Q0) - print("Q0 shape") - print(Q0.shape) - print("Q1") - print(Q1) - print("Q1 shape") - print(Q1.shape) - - print("c0") - print(c0) - print("c0 shape") - print(c0.shape) - print("c1") - print(c1) - print("c1 shape") - print(c1.shape) - - print("A0") - print(A0) - print("A0 shape") - print(A0.shape) - print("b0") - print(b0) - print("b0 shape") - print(b0.shape) - - print("A1") - print(A1) - print("A1 shape") - print(A1.shape) - print("b1") - print(b1) - print("b1 shape") - print(b1.shape) - - print("A4") - print(A4) - print("A4 shape") - print(A4.shape) - print("b3") - print(b3) - print("b3 shape") - print(b3.shape) - - print("A2") - print(A2) - print("A2 shape") - print(A2.shape) - print("A3") - print(A3) - print("A3 shape") - print(A3.shape) - print("b2") - print(b2) - print("b2 shape") - print(b2.shape) - - print("End of Claudio's implementation") - - -def get_instance_params(): - """ - Get parameters for a Mixed Integer Setup Knapsack Problem (MISKP) instance. - - :return: - """ - # - - K = 2 - T = 10 - P = 45.10 - S = np.asarray([75.61, 75.54]) - - D = np.asarray([9.78, 8.81, 9.08, 2.03, 8.9, 9, 4.12, 8.16, 6.55, 4.84, 3.78, 5.13, - 4.72, 7.6, 2.83, 1.44, 2.45, 2.24, 6.3, 5.02]).reshape((K, T)) - - C = np.asarray([-11.78, -10.81, -11.08, -4.03, -10.90, -11.00, -6.12, -10.16, -8.55, -6.84, - -5.78, -7.13, -6.72, -9.60, -4.83, -3.44, -4.45, -4.24, -8.30, -7.02]).reshape((K, T)) - - n = K # number of binary dec vars x0 (and z) - m = K * T # number of continuous dec vars u - # size of x1 is then n+m - - # Parameters of the Quadratic Programming problem in the standard form for ADMM. - ## Objective function - Q0 = np.zeros((n, n)) - c0 = S.reshape(n) - - A0 = np.zeros((1, n)) # we set here one dummy equality constraint - b0 = np.zeros(1) # we set here one dummy equality constraint - - Q1 = np.zeros((m, m)) - c1 = C.reshape(m) - - ## Constraints - # we set here one dummy A1 x0 \leq b1 constraint - A1 = np.zeros((1, n)) - b1 = np.zeros(1) - - # A_2 z + A_3 u \leq b_2 -- > - y_k <= x_{k,t} - A2 = np.zeros((m, n)) - for i in range(K): - A2[T * i:T * (i + 1), i] = - np.ones(T) - A3 = np.eye(m) - b2 = np.zeros(m) - - # A_4 u <= b_3 --> sum_k sum_t D_{kt} x_{kt} \leq P, -x_{kt} \leq 0 - block1 = D.reshape((1, m)) - block2 = -np.eye(m) - A4 = np.block([[block1], [block2]]) - b3 = np.hstack([P, np.zeros(m)]) - - __dump_arrays(Q0, Q1, c0, c1, A0, b0, A1, b1, A4, b3, A2, A3, b2) - - return K, T, P, S, D, C - - - -class Miskp: - - def __init__(self, K, T, P, S, D: np.ndarray, C: np.ndarray, verbose=False, relativeGap=0.0, - pairwise_incomp=0, multiple_choice=0): - """ - Constructor method of the class. - :param K: number of families - :param T: number of items in each family - :param C: value of including item t in family k in the knapsack - :param D: resources consumed if item t in family k is included in the knapsack - :param S: setup cost to include family k in the knapsack - :param P: capacity of the knapsack - :param verbose: - :param relativeGap: - """ - - self.multiple_choice = multiple_choice - self.pairwise_incomp = pairwise_incomp - self.P = P - self.S = S - self.D = D - self.C = C - self.T = T - self.K = K - self.relativeGap = relativeGap - self.verbose = verbose - self.lp_folder = "./lps/" - - def create_params(self): - self.range_K = range(self.K) - self.range_T = range(self.T) - - # make sure instance params are floats - - self.S = [float(val) for val in self.S] - self.C = self.C.astype(float) - self.D = self.D.astype(float) - - self.n_x_vars = self.K * self.T - self.n_y_vars = self.K - - self.range_x_vars = [(k, t) for k in self.range_K for t in self.range_T] - self.range_y_vars = self.range_K - - - def create_vars(self): - - self.op.variables.add( - lb=[0.0] * self.n_x_vars, - names=[nm("x", i, j) for i,j in self.range_x_vars]) - - self.op.variables.add( - # lb=[0.0] * self.n_y_vars, - # ub=[1.0] * self.n_y_vars, - types=["B"] * self.n_y_vars, - names=[nm("y", i) for i in self.range_y_vars]) - - def create_cons_capacity(self): - self.op.linear_constraints.add( - lin_expr=[ - SparsePair( - ind=[ - nm("x", i, j) for i,j in self.range_x_vars - ] - , - val=[self.D[i,j] for i,j in self.range_x_vars]) - ], - senses="L", - rhs=[self.P], - names=["CAPACITY"]) - - def create_cons_allocation(self): - self.op.linear_constraints.add( - lin_expr=[ - SparsePair( - ind=[nm("x", k, t)]+[nm("y", k)], - val=[1.0, -1.0]) - for k,t in self.range_x_vars - ], - senses="L" * self.n_x_vars, - rhs=[0.0] * self.n_x_vars, - names=[nm("ALLOCATION", k, t) for k, t in self.range_x_vars]) - - - def create_cons(self): - """Method to populate the constraints""" - - self.create_cons_capacity() - self.create_cons_allocation() - - def create_obj(self): - self.op.objective.set_linear([(nm("y", k), self.S[k]) for k in self.range_K] + - [(nm("x", k, t), self.C[k, t]) for k, t in self.range_x_vars] - ) - - def run_cplex_api(self): - """Main method, which populates and solve the mathematical model via Python Cplex API - - """ - - # Creation of the Cplex object - self.op = OptimizationProblem() - - - self.create_params() - self.create_vars() - self.create_obj() - start_time = time.time() - self.create_cons() - constraints_time = time.time() - start_time - if self.verbose: - print ("Time to populate constraints:", constraints_time) - - # Save the model - create_folder(self.lp_folder) - - self.op.write(self.lp_folder + "miskp.lp") - - out = 0 - return out - - def run_op(self): - """Main method, which populates and solve the OptimizationProblem model via OptimizationAlgorithm - - """ - - self.op = OptimizationProblem() - - self.create_params() - self.create_vars() - self.create_obj() - self.create_cons() - - # Save the model - create_folder(self.lp_folder) - - self.op.write(self.lp_folder + "miskp.lp") - - solver = ADMMOptimizer() - solution = solver.solve(self.op) - - return solution - - def run_op_eigens(self): - """Main method, which populates and solve the OptimizationProblem model via OptimizationAlgorithm - - """ - - self.op = OptimizationProblem() - - self.create_params() - self.create_vars() - self.create_obj() - self.create_cons() - - # Save the model - create_folder(self.lp_folder) - - self.op.write(self.lp_folder + "miskp.lp") - - # QAOA - # optimizer = COBYLA() - # min_eigen_solver = QAOA(optimizer=optimizer) - # qubo_optimizer = MinimumEigenOptimizer(min_eigen_solver) - # Note: a backend needs to be given, otherwise an error is raised in the _run method of VQE. - # backend = 'statevector_simulator' - # # backend = 'qasm_simulator' - # min_eigen_solver.quantum_instance = BasicAer.get_backend(backend) - - # use numpy exact diagonalization - qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) - - # Cplex - # qubo_optimizer = CplexOptimizer() - - continuous_optimizer = CplexOptimizer() - - admm_params = ADMMParameters(qubo_optimizer=qubo_optimizer, continuous_optimizer=continuous_optimizer) - - solver = ADMMOptimizer(params=admm_params) - solution = solver.solve(self.op) - - return solution - - -def toy_cplex_api(): - - K, T, P, S, D, C = get_instance_params() - pb = Miskp(K, T, P, S, D, C) - result = pb.run_cplex_api() - -def toy_op(): - - K, T, P, S, D, C = get_instance_params() - pb = Miskp(K, T, P, S, D, C) - result = pb.run_op() - -def toy_op_eigens(): - - K, T, P, S, D, C = get_instance_params() - pb = Miskp(K, T, P, S, D, C) - result = pb.run_op_eigens() - # debug - print("results") - print("x={}".format(result.x)) - print("fval={}".format(result.fval)) - # print("x0_saved={}".format(result.results.x0_saved)) - # print("u_saved={}".format(result.results.u_saved)) - # print("z_saved={}".format(result.results.z_saved)) - -if __name__ == '__main__': - # toy_cplex_api() - # toy_op() - toy_op_eigens() - - - - - - From 11af41a7f79efb91ab8de5178a3b95c083482f76 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 20 Mar 2020 22:52:42 +0100 Subject: [PATCH 100/323] review, style and lint --- qiskit/aqua/components/optimizers/gsls.py | 341 +++++++++++----------- test/aqua/test_optimizers.py | 13 +- 2 files changed, 179 insertions(+), 175 deletions(-) diff --git a/qiskit/aqua/components/optimizers/gsls.py b/qiskit/aqua/components/optimizers/gsls.py index 079cc01a40..269f761b06 100644 --- a/qiskit/aqua/components/optimizers/gsls.py +++ b/qiskit/aqua/components/optimizers/gsls.py @@ -14,7 +14,7 @@ """Line search with Gaussian-smoothed samples on a sphere.""" -from typing import Optional +from typing import Dict, Optional, Tuple, List import logging import numpy as np @@ -26,11 +26,9 @@ class GSLS(Optimizer): """Gaussian-smoothed Line Search. - An implementation of the line search algorithm described in https://arxiv.org/pdf/1905.01332.pdf, using gradient approximation based on Gaussian-smoothed samples on a sphere. - """ _OPTIONS = ['max_iter', 'max_eval', 'disp', 'sampling_radius', @@ -38,7 +36,7 @@ class GSLS(Optimizer): 'step_size_multiplier', 'armijo_parameter', 'min_gradient_norm', 'max_failed_rejection_sampling'] - # pylint: disable=unused-argument + # pylint:disable=unused-argument def __init__(self, max_iter: int = 10000, max_eval: int = 10000, @@ -51,44 +49,46 @@ def __init__(self, armijo_parameter: float = 1.0e-1, min_gradient_norm: float = 1e-8, max_failed_rejection_sampling: int = 50) -> None: - """Args: - - max_iter : Maximum number of iterations - max_eval : Maximum number of evaluations - sampling_radius : Sampling radius to determine gradient - estimate. - sample_size_factor : The size of the sample set at each - iteration is this number multiplied by - the dimension of the problem, rounded to - the nearest integer. - initial_step_size : Initial step size for the descent - algorithm. - min_step_size : Minimum step size for the descent algorithm. - step_size_multiplier : Step size reduction after unsuccessful - steps, in the interval (0, 1). - armijo_parameter : Armijo parameter for sufficient decrease - criterion, in the interval (0, 1). - min_gradient_norm : If the gradient norm is below this - threshold, the algorithm stops. - max_failed_rejection_sampling : Maximum number of attempts to - sample points within bounds. - + """ + Args: + max_iter: Maximum number of iterations. + max_eval: Maximum number of evaluations. + disp: Set to True to display convergence messages. + sampling_radius: Sampling radius to determine gradient estimate. + sample_size_factor: The size of the sample set at each iteration is this number + multiplied by the dimension of the problem, rounded to the nearest integer. + initial_step_size: Initial step size for the descent algorithm. + min_step_size: Minimum step size for the descent algorithm. + step_size_multiplier: Step size reduction after unsuccessful steps, in the + interval (0, 1). + armijo_parameter: Armijo parameter for sufficient decrease criterion, in the + interval (0, 1). + min_gradient_norm: If the gradient norm is below this threshold, the algorithm stops. + max_failed_rejection_sampling: Maximum number of attempts to sample points within + bounds. """ super().__init__() for k, v in locals().items(): if k in self._OPTIONS: self._options[k] = v - def get_support_level(self): - """ Return support level dictionary """ + def get_support_level(self) -> Dict[str, int]: + """Return support level dictionary. + + Returns: + A dictionary containing the support levels for different options. + """ return { 'gradient': Optimizer.SupportLevel.ignored, 'bounds': Optimizer.SupportLevel.supported, 'initial_point': Optimizer.SupportLevel.required } - def optimize(self, num_vars, objective_function, gradient_function=None, - variable_bounds=None, initial_point=None): + def optimize(self, num_vars: int, + objective_function: callable, + gradient_function: Optional[callable] = None, + variable_bounds: Optional[List[Tuple[float, float]]] = None, + initial_point: Optional[np.ndarray] = None) -> Tuple[np.ndarray, float, int]: super().optimize(num_vars, objective_function, gradient_function, variable_bounds, initial_point) @@ -98,219 +98,228 @@ def optimize(self, num_vars, objective_function, gradient_function=None, initial_point = np.array(initial_point) if variable_bounds is None: - var_lb = np.array([float('-inf')] * num_vars) - var_ub = np.array([float('inf')] * num_vars) + var_lb = np.array([-np.inf] * num_vars) + var_ub = np.array([np.inf] * num_vars) else: - var_lb = np.array([l for (l, u) in variable_bounds]) - var_ub = np.array([u for (l, u) in variable_bounds]) + var_lb = np.array([l for (l, _) in variable_bounds]) + var_ub = np.array([u for (_, u) in variable_bounds]) - x, x_value, n_evals, grad_nom = self.ls_optimize( - num_vars, objective_function, initial_point, var_lb, var_ub) + x, x_value, n_evals, _ = self.ls_optimize( + num_vars, objective_function, initial_point, var_lb, var_ub + ) return x, x_value, n_evals - def ls_optimize(self, n, obj_fun, initial_point, var_lb, var_ub): + def ls_optimize(self, n: int, obj_fun: callable, initial_point: np.ndarray, var_lb: np.ndarray, + var_ub: np.ndarray) -> Tuple[np.ndarray, float, int, float]: """Run the line search optimization. Args: - - n : Dimension of the problem. - obj_fun : Objective function. - initial_point : Initial point. Must be a Numpy array. - var_lb : Vector of lower bounds on the decision - variables. Vector elements can be float('-inf') if - the corresponding variable is unbounded from - below. Must be a Numpy array. - var_ub : Vector of upper bounds on the decision - variables. Vector elements can be float('inf') if - the corresponding variable is unbounded from - below. Must be a Numpy array. - - Returns: Final iterate as a vector, corresponding objective - function value, number of evaluations, and norm of the - gradient estimate. - + n: Dimension of the problem. + obj_fun: Objective function. + initial_point: Initial point. + var_lb: Vector of lower bounds on the decision variables. Vector elements can be -np.inf + if the corresponding variable is unbounded from below. + var_ub: Vector of upper bounds on the decision variables. Vector elements can be np.inf + if the corresponding variable is unbounded from below. + + Returns: + Final iterate as a vector, corresponding objective function value, + number of evaluations, and norm of the gradient estimate. + + Raises: + ValueError: If the number of dimensions mismatches the size of the initial point or + the length of the lower or upper bound. """ - assert(len(initial_point) == n) - assert(len(var_lb) == n) - assert(len(var_ub) == n) + if len(initial_point) != n: + raise ValueError('Size of the initial point mismatches the number of dimensions.') + if len(var_lb) != n: + raise ValueError('Length of the lower bound mismatches the number of dimensions.') + if len(var_ub) != n: + raise ValueError('Length of the lower bound mismatches the number of dimensions.') + # Initialize counters and data iter_count = 0 n_evals = 0 - stop = False prev_iter_successful = True + prev_directions, prev_sample_set_x, prev_sample_set_y = None, None, None consecutive_fail_iter = 0 alpha = self._options['initial_step_size'] - grad_norm = float('inf') + grad_norm = np.inf sample_set_size = int(round(self._options['sample_size_factor'] * n)) + # Initial point x = initial_point x_value = obj_fun(x) n_evals += 1 - while (iter_count < self._options['max_iter'] and - n_evals < self._options['max_eval'] and not stop): + while iter_count < self._options['max_iter'] \ + and n_evals < self._options['max_eval']: + # Determine set of sample points - u, sample_set_x = self.sample_set(n, x, var_lb, var_ub, - sample_set_size) - if (n_evals + len(sample_set_x) + 1 >= self._options['max_eval']): + directions, sample_set_x = self.sample_set(n, x, var_lb, var_ub, sample_set_size) + + if n_evals + len(sample_set_x) + 1 >= self._options['max_eval']: # The evaluation budget is too small to allow for # another full iteration; we therefore exit now break - sample_set_y = np.array( - [obj_fun(point) for point in sample_set_x]) + + sample_set_y = np.array([obj_fun(point) for point in sample_set_x]) n_evals += len(sample_set_x) + # Expand sample set if we could not improve - if (not prev_iter_successful): - u = np.vstack((prev_u, u)) + if not prev_iter_successful: + directions = np.vstack((prev_directions, directions)) sample_set_x = np.vstack((prev_sample_set_x, sample_set_x)) sample_set_y = np.hstack((prev_sample_set_y, sample_set_y)) + # Find gradient approximation and candidate point - grad = self.gradient_approximation(n, x, x_value, u, - sample_set_x, sample_set_y) - grad_norm = np.sqrt(grad.dot(grad)) - d = grad - new_x = np.clip(x - alpha * d, var_lb, var_ub) + grad = self.gradient_approximation(n, x, x_value, directions, sample_set_x, + sample_set_y) + grad_norm = np.linalg.norm(grad) + new_x = np.clip(x - alpha * grad, var_lb, var_ub) new_x_value = obj_fun(new_x) n_evals += 1 + # Print information - if (self._options['disp']): + if self._options['disp']: print('Iter {:d}'.format(iter_count)) - print('Point ' + str(x) + ' obj ' + str(x_value)) - print('Gradient' + str(grad)) - print('Grad norm ' + str(grad_norm) + ' new_x_value ' + - str(new_x_value) + ' step size ' + str(alpha)) - print('Direction ' + str(d)) + print('Point {} obj {}'.format(x, x_value)) + print('Gradient {}'.format(grad)) + print('Grad norm {} new_x_value {} step_size {}'.format(grad_norm, new_x_value, + alpha)) + print('Direction {}'.format(directions)) + # Test Armijo condition for sufficient decrease - if (new_x_value <= x_value - - self._options['armijo_parameter'] * alpha * grad_norm): + if new_x_value <= x_value - self._options['armijo_parameter'] * alpha * grad_norm: # Accept point - x = new_x - x_value = new_x_value - alpha /= 2*self._options['step_size_multiplier'] + x, x_value = new_x, new_x_value + alpha /= 2 * self._options['step_size_multiplier'] prev_iter_successful = True consecutive_fail_iter = 0 + # Reset sample set - prev_u = None - prev_sample_set_x = None - prev_sample_set_y = None + prev_directions, prev_sample_set_x, prev_sample_set_y = None, None, None else: # Do not accept point alpha *= self._options['step_size_multiplier'] prev_iter_successful = False consecutive_fail_iter += 1 + # Store sample set to enlarge it - prev_u = u - prev_sample_set_x = sample_set_x - prev_sample_set_y = sample_set_y + prev_directions = directions + prev_sample_set_x, prev_sample_set_y = sample_set_x, sample_set_y + iter_count += 1 - if (grad_norm <= self._options['min_gradient_norm'] or - alpha <= self._options['min_step_size']): - stop = True + + # Check termination criterion + if grad_norm <= self._options['min_gradient_norm'] \ + or alpha <= self._options['min_step_size']: + break + return x, x_value, n_evals, grad_norm - # -- end function - def sample_set(self, n, x, var_lb, var_ub, num_points): - """Construct sample set of given size. + def sample_points(self, n: int, x: np.ndarray, num_points: int + ) -> Tuple[np.ndarray, np.ndarray]: + """Sample ``num_points`` points around ``x`` on the ``n``-sphere of specified radius. + + The radius of the sphere is ``self._options['sampling_radius']``. Args: - n : Dimension of the problem. - x : Point around which the sample set is constructed, as a 1D - numpy.ndarray[float] - var_lb : Vector of lower bounds on the decision - variables. Vector elements can be float('-inf') if - the corresponding variable is unbounded from - below. Must be a 1D numpy.ndarray[float]. - var_lb : Vector of upper bounds on the decision - variables. Vector elements can be float('inf') if - the corresponding variable is unbounded from - below. Must be a 1D numpy.ndarray[float]. - num_points : Number of points in the sample set. - - Returns: Matrices of (unit-norm) sample directions and sample - points, one per row. Both matrices are 2D - numpy.ndarray[float]. + n: Dimension of the problem. + x: Point around which the sample set is constructed. + num_points: Number of points in the sample set. + Returns: + A tuple containing the sampling points and the directions. """ - # Generate points uniformly on the sphere normal_samples = np.random.normal(size=(num_points, n)) - row_norms = np.sqrt(np.sum(normal_samples**2, 1, keepdims=True)) + row_norms = np.linalg.norm(normal_samples, axis=1, keepdims=True) directions = normal_samples / row_norms points = x + self._options['sampling_radius'] * directions + + return points, directions + + def sample_set(self, n: int, x: np.ndarray, var_lb: np.ndarray, var_ub: np.ndarray, + num_points: int) -> Tuple[np.ndarray, np.ndarray]: + """Construct sample set of given size. + + Args: + n: Dimension of the problem. + x: Point around which the sample set is constructed. + var_lb: Vector of lower bounds on the decision variables. Vector elements can be -np.inf + if the corresponding variable is unbounded from below. + var_ub: Vector of lower bounds on the decision variables. Vector elements can be np.inf + if the corresponding variable is unbounded from above. + num_points: Number of points in the sample set. + + Returns: + Matrices of (unit-norm) sample directions and sample points, one per row. + Both matrices are 2D arrays of floats. + + Raises: + RuntimeError: If not enough samples could be generated within the bounds. + """ + # Generate points uniformly on the sphere + points, directions = self.sample_points(n, x, num_points) + # Check bounds - if ((points >= var_lb).all() and (points <= var_ub).all()): + if (points >= var_lb).all() and (points <= var_ub).all(): # If all points are within bounds, return them - return directions, (x + self._options['sampling_radius'] * - directions) + return directions, (x + self._options['sampling_radius'] * directions) else: # Otherwise we perform rejection sampling until we have # enough points that satisfy the bounds - indices = np.where((points >= var_lb).all(axis=1) & - (points <= var_ub).all(axis=1))[0] + indices = np.where((points >= var_lb).all(axis=1) & (points <= var_ub).all(axis=1))[0] accepted = directions[indices] num_trials = 0 - while (len(accepted) < num_points and - num_trials < self._options['max_failed_rejection_sampling']): + + while len(accepted) < num_points \ + and num_trials < self._options['max_failed_rejection_sampling']: # Generate points uniformly on the sphere - normal_samples = np.random.normal(size=(num_points, n)) - row_norms = np.sqrt(np.sum(normal_samples**2, 1, - keepdims=True)) - directions = normal_samples / row_norms - points = x + self._options['sampling_radius'] * directions + points, directions = self.sample_points(n, x, num_points) indices = np.where((points >= var_lb).all(axis=1) & (points <= var_ub).all(axis=1))[0] accepted = np.vstack((accepted, directions[indices])) num_trials += 1 - # When we are at a corner point, the expected fraction of - # acceptable points may be exponential small in the - # dimension of the problem. Thus, if we keep failing and - # do not have enough points by now, we switch to a - # different method that guarantees finding enough points, - # but they may not be uniformly distributed. - if (len(accepted) < num_points): - normal_samples = np.random.normal(size=(num_points, n)) - row_norms = np.sqrt(np.sum(normal_samples**2, 1, - keepdims=True)) - directions = normal_samples / row_norms - points = x + self._options['sampling_radius'] * directions + + # When we are at a corner point, the expected fraction of acceptable points may be + # exponential small in the dimension of the problem. Thus, if we keep failing and + # do not have enough points by now, we switch to a different method that guarantees + # finding enough points, but they may not be uniformly distributed. + if len(accepted) < num_points: + points, directions = self.sample_points(n, x, num_points) to_be_flipped = (points < var_lb) | (points > var_ub) directions *= np.where(to_be_flipped, -1, 1) points = x + self._options['sampling_radius'] * directions indices = np.where((points >= var_lb).all(axis=1) & (points <= var_ub).all(axis=1))[0] accepted = np.vstack((accepted, directions[indices])) - # If we still do not have enough sampling points, we have - # failed. - if (len(accepted) < num_points): - raise RuntimeError('Could not generate enough samples ' + + + # If we still do not have enough sampling points, we have failed. + if len(accepted) < num_points: + raise RuntimeError('Could not generate enough samples ' 'within bounds; try smaller radius.') + return (accepted[:num_points], - x + self._options['sampling_radius'] * - accepted[:num_points]) - # -- end function + x + self._options['sampling_radius'] * accepted[:num_points]) - def gradient_approximation(self, n, x, x_value, directions, - sample_set_x, sample_set_y): + def gradient_approximation(self, n: int, x: np.ndarray, x_value: float, directions: np.ndarray, + sample_set_x: np.ndarray, sample_set_y: np.ndarray) -> np.ndarray: """Construct gradient approximation from given sample. Args: - - n : Dimension of the problem. - x : Point around which the sample set was constructed, as a 1D - numpy.ndarray[float]. - x_value : Objective function value at x. - directions : Directions of the sample points wrt the central - point x, as a 2D numpy.ndarray[float]. - sample_set_x : x-coordinates of the sample set, one point per - row, as a 2D numpy.ndarray[float]. - sample_set_y : Objective function values of the points in - sample_set_x, as a 1D numpy.ndarray[float]. - - Returns: Gradient approximation at x, as a 1D numpy.ndarray[float]. - + n: Dimension of the problem. + x: Point around which the sample set was constructed. + x_value: Objective function value at x. + directions: Directions of the sample points wrt the central point x, as a 2D array. + sample_set_x: x-coordinates of the sample set, one point per row, as a 2D array. + sample_set_y: Objective function values of the points in sample_set_x, as a 1D array. + + Returns: + Gradient approximation at x, as a 1D array. """ - ffd = (sample_set_y - x_value) - gradient = ((float(n) / len(sample_set_y)) * np.sum( - ffd.reshape(len(sample_set_y), 1) / - self._options['sampling_radius'] * directions, 0)) + ffd = sample_set_y - x_value + gradient = float(n) / len(sample_set_y) * np.sum(ffd.reshape(len(sample_set_y), 1) / + self._options['sampling_radius'] + * directions, 0) return gradient - # -- end function diff --git a/test/aqua/test_optimizers.py b/test/aqua/test_optimizers.py index f2864bb71c..eface89b73 100644 --- a/test/aqua/test_optimizers.py +++ b/test/aqua/test_optimizers.py @@ -31,7 +31,7 @@ class TestOptimizers(QiskitAquaTestCase): def setUp(self): super().setUp() aqua_globals.random_seed = 50 - pass + np.random.seed(512310912) def _optimize(self, optimizer): x_0 = [1.3, 0.7, 0.8, 1.9, 1.2] @@ -102,16 +102,11 @@ def test_tnc(self): def test_gsls(self): """ gsls test """ - # We need to set our own randomness because GSLS is stochastic - rs = np.random.get_state() - np.random.seed(512310912) - optimizer = GSLS(sample_size_factor=40, - sampling_radius=1.0e-12, max_iter=10000, + optimizer = GSLS(sample_size_factor=40, sampling_radius=1.0e-12, max_iter=10000, max_eval=10000, min_step_size=1.0e-12) x_0 = [1.3, 0.7, 0.8, 1.9, 1.2] - x, x_value, n_evals = optimizer.optimize(len(x_0), rosen, - initial_point=x_0) - np.random.set_state(rs) + _, x_value, n_evals = optimizer.optimize(len(x_0), rosen, initial_point=x_0) + # Ensure value is near-optimal self.assertLessEqual(x_value, 0.01) self.assertLessEqual(n_evals, 10000) From 50635b905944612a987993317947ff13aab90f9f Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Mon, 23 Mar 2020 12:05:32 +0000 Subject: [PATCH 101/323] Julien requests --- Julien_requests.docx | Bin 0 -> 46984 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Julien_requests.docx diff --git a/Julien_requests.docx b/Julien_requests.docx new file mode 100644 index 0000000000000000000000000000000000000000..a07cce15902625138d7735a8da3163926e0e0ff0 GIT binary patch literal 46984 zcmeFX^LJ)V6eaw`wr$(C*|GD)wr$(C(^1DZIyO7#*fu(tyx*)@YrZvq!c6^8Yt=>7 zy>)8uv(Kqgk^=)r2S5U#0RR9AfHlcM(;NfDu8Fa((5Q0}HO z{}mbM476qM?!l4g1Yss?8A_g3h5eCRk{g1d^#X{eO-s-q=lzX?B$K^l=qNfx> zERlRA-6)uQCsJp2C5EX9%3@dj(uk%YZC$sXmI`Kt9irn~)dH%H-z8>oMxeBZy~4ak z&7A#gK1*~)e)k5U{+p%L%RREJppN9-DNDED~@~cIY&?C9}{FUG!r2)+8EKZxR6ON zk(tx|Xos}oPlpw7R-`nuOD5z^CHYS3tIwJKY=v1Y%PZE>x;{`<0usI91f4TD{;=jk z4H;|2YnDnJ99TjB8+VZ@TBtyi58jMxw&#VTxd)nEv^i z>!Ip=9!DSH2ytGMXg3?|I^gB%i`;)Zss9rn^$0%B7T^Ft2^Ii=4}b>oaBwzd`kx>% zbue+W`}So2xv>8Q7?5v2_TA6_-DNXFx2aeXIdEGw?aOf@qPu7_&2)Kfw!Z$$UZ57I3p&x}QqAVdtk zPO*?H`n(hvh};Gq7~7{mW)KJTgb?H{c5`u1t8&fWo+UQPr6OX_8#xeX;u|4M+mC zGVQPVq!)Q5ì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 B5i|e* literal 0 HcmV?d00001 From ab57574e6d20969454f010ee900d1dadf7b90233 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Mon, 23 Mar 2020 18:58:09 +0000 Subject: [PATCH 102/323] formatting, linting, typing --- .../optimization/algorithms/admm_optimizer.py | 330 ++++++++++-------- test/optimization/test_admm_miskp.py | 31 +- 2 files changed, 201 insertions(+), 160 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 3b15d94876..8a96e29231 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -1,7 +1,8 @@ -# -*-coding: utf-8 -*- +# -*- coding: utf-8 -*- + # This code is part of Qiskit. # -# (C) Copyright IBM 2000. +# (C) Copyright IBM 2020. # # 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 @@ -11,31 +12,33 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""An implementation of the ADMM algorithm. -""" +"""An implementation of the ADMM algorithm.""" import time -from typing import List +from typing import List, Optional, Any import numpy as np from cplex import SparsePair - from qiskit.optimization.algorithms import CplexOptimizer from qiskit.optimization.algorithms.optimization_algorithm import OptimizationAlgorithm from qiskit.optimization.problems.optimization_problem import OptimizationProblem from qiskit.optimization.problems.variables import CPX_BINARY, CPX_CONTINUOUS - from qiskit.optimization.results.optimization_result import OptimizationResult -class ADMMParameters: - """Defines a set of parameters for ADMM optimizer. - """ +UPDATE_RHO_BY_TEN_PERCENT = 0 +UPDATE_RHO_BY_RESIDUALS = 1 + - def __init__(self, rho_initial=10000, factor_c=100000, beta=1000, max_iter=10, tol=1.e-4, max_time=1800, - three_block=True, vary_rho=0, tau_incr=2, tau_decr=2, mu_res=10, - mu=1000, qubo_optimizer: OptimizationAlgorithm = None, - continuous_optimizer: OptimizationAlgorithm = None) -> None: +class ADMMParameters: + """Defines a set of parameters for ADMM optimizer.""" + + def __init__(self, rho_initial: float = 10000, factor_c: float = 100000, beta: float = 1000, + max_iter: int = 10, tol: float = 1.e-4, max_time: float = 1800, + three_block: bool = True, vary_rho: int = UPDATE_RHO_BY_TEN_PERCENT, + tau_incr: float = 2, tau_decr: float = 2, mu_res: float = 10, mu: float = 1000, + qubo_optimizer: Optional[OptimizationAlgorithm] = None, + continuous_optimizer: Optional[OptimizationAlgorithm] = None) -> None: """Defines parameters for ADMM optimizer and their default values. Args: @@ -46,14 +49,17 @@ def __init__(self, rho_initial=10000, factor_c=100000, beta=1000, max_iter=10, t tol: Tolerance for the residual convergence. max_time: Maximum running time (in seconds) for ADMM. three_block: Boolean flag to select the 3-block ADMM implementation. - vary_rho: Flag to select the rule to update rho. If set to 0, then rho increases by 10% at each iteration. + vary_rho: Flag to select the rule to update rho. + If set to 0, then rho increases by 10% at each iteration. If set to 1, then rho is modified according to primal and dual residuals. tau_incr: Parameter used in the rho update. tau_decr: Parameter used in the rho update. mu_res: Parameter used in the rho update. mu: Penalization for constraint residual. Used to compute the merit values. - qubo_optimizer: An instance of OptimizationAlgorithm that can effectively solve QUBO problems - continuous_optimizer: An instance of OptimizationAlgorithm that can solve continuous problems + qubo_optimizer: An instance of OptimizationAlgorithm that can effectively solve + QUBO problems + continuous_optimizer: An instance of OptimizationAlgorithm that can solve + continuous problems """ super().__init__() self.mu = mu @@ -69,13 +75,16 @@ def __init__(self, rho_initial=10000, factor_c=100000, beta=1000, max_iter=10, t self.beta = beta self.rho_initial = rho_initial self.qubo_optimizer = qubo_optimizer if qubo_optimizer is not None else CplexOptimizer() - self.continuous_optimizer = continuous_optimizer if continuous_optimizer is not None else CplexOptimizer() + self.continuous_optimizer = continuous_optimizer if continuous_optimizer is not None \ + else CplexOptimizer() class ADMMState: - """Internal computation state of the ADMM implementation. Here, various variables are stored that are - being updated during problem solving. The values are relevant to the problem being solved. - The state is recreated for each optimization problem. State is returned as the third value + """Internal computation state of the ADMM implementation. + + The state keeps track of various variables are stored that are being updated during problem + solving. The values are relevant to the problem being solved. The state is recreated for each + optimization problem. State is returned as the third value """ def __init__(self, @@ -100,7 +109,8 @@ def __init__(self, self.continuous_indices = continuous_indices self.sense = op.objective.get_sense() - # define heavily used matrix, they are used at each iteration, so let's cache them, they are np.ndarrays + # define heavily used matrix, they are used at each iteration, so let's cache them, + # they are np.ndarrays # objective self.q0 = None self.c0 = None @@ -118,6 +128,7 @@ def __init__(self, self.b3 = None # These are the parameters that are updated in the ADMM iterations. + self.u: np.ndarray = np.zeros(len(continuous_indices)) binary_size = len(binary_indices) self.x0: np.ndarray = np.zeros(binary_size) self.z: np.ndarray = np.zeros(binary_size) @@ -140,8 +151,7 @@ def __init__(self, class ADMMOptimizer(OptimizationAlgorithm): - """An implementation of the ADMM algorithm. - """ + """An implementation of the ADMM algorithm.""" def __init__(self, params: ADMMParameters = None) -> None: """Constructs an instance of ADMMOptimizer. @@ -172,10 +182,11 @@ def __init__(self, params: ADMMParameters = None) -> None: self._continuous_optimizer = params.continuous_optimizer # internal state where we'll keep intermediate solution - # here, we just declare the class variable, the variable is initialized in kept in the solve method. + # here, we just declare the class variable, the variable is initialized in kept in + # the solve method. self._state = None - def is_compatible(self, problem: OptimizationProblem): + def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: """Checks whether a given problem can be solved with the optimizer implementing this method. Args: @@ -186,9 +197,9 @@ def is_compatible(self, problem: OptimizationProblem): """ # 1. only binary and continuous variables are supported - for var_index, var_type in enumerate(problem.variables.get_types()): - if var_type != CPX_BINARY and var_type != CPX_CONTINUOUS: - # var var_index is not binary and not continuous + for var_type in problem.variables.get_types(): + if var_type not in (CPX_BINARY, CPX_CONTINUOUS): + # variable is not binary and not continuous return "Only binary and continuous variables are supported" binary_indices = self._get_variable_indices(problem, CPX_BINARY) @@ -210,7 +221,7 @@ def is_compatible(self, problem: OptimizationProblem): return None - def solve(self, problem: OptimizationProblem): + def solve(self, problem: OptimizationProblem) -> OptimizationResult: """Tries to solves the given problem using ADMM algorithm. Args: @@ -229,8 +240,9 @@ def solve(self, problem: OptimizationProblem): # create our computation state self._state = ADMMState(problem, binary_indices, continuous_indices, self._rho_initial) - # convert optimization problem to a set of matrices and vector that are used at each iteration - self.convert_problem_representation() + # convert optimization problem to a set of matrices and vector that are used + # at each iteration + self._convert_problem_representation() # debug # self.__dump_matrices_and_vectors() @@ -241,19 +253,20 @@ def solve(self, problem: OptimizationProblem): iteration = 0 residual = 1.e+2 - while (iteration < self._max_iter and residual > self._tol) and (elapsed_time < self._max_time): + while (iteration < self._max_iter and residual > self._tol) \ + and (elapsed_time < self._max_time): op1 = self._create_step1_problem() # debug # op1.write("op1.lp") - self._state.x0 = self.update_x0(op1) + self._state.x0 = self._update_x0(op1) # debug # print("x0={}".format(self._state.x0)) op2 = self._create_step2_problem() # op2.write("op2.lp") - self._state.u, self._state.z = self.update_x1(op2) + self._state.u, self._state.z = self._update_x1(op2) # debug # print("u={}".format(self._state.u)) # print("z={}".format(self._state.z)) @@ -261,16 +274,16 @@ def solve(self, problem: OptimizationProblem): if self._three_block: op3 = self._create_step3_problem() # op3.write("op3.lp") - self._state.y = self.update_y(op3) + self._state.y = self._update_y(op3) # debug # print("y={}".format(self._state.y)) - lambda_mult = self.update_lambda_mult() + lambda_mult = self._update_lambda_mult() - cost_iterate = self.get_objective_value() - cr = self.get_constraint_residual() - residual, dual_residual = self.get_solution_residuals(iteration) - merit = self.get_merit(cost_iterate, cr) + cost_iterate = self._get_objective_value() + cr = self._get_constraint_residual() + residual, dual_residual = self._get_solution_residuals(iteration) + merit = self._get_merit(cost_iterate, cr) # debug # print("cost_iterate, cr, merit", cost_iterate, cr, merit) @@ -287,13 +300,13 @@ def solve(self, problem: OptimizationProblem): self._state.z_saved.append(self._state.z) self._state.z_saved.append(self._state.y) - self.update_rho(residual, dual_residual) + self._update_rho(residual, dual_residual) iteration += 1 elapsed_time = time.time() - start_time - solution, objective_value = self.get_best_merit_solution() - solution = self.revert_solution_indexes(solution) + solution, objective_value = self._get_best_merit_solution() + solution = self._revert_solution_indexes(solution) # third parameter is our internal state of computations result = OptimizationResult(solution, objective_value, self._state) @@ -311,61 +324,68 @@ def _get_variable_indices(op: OptimizationProblem, var_type: str) -> List[int]: return indices - def revert_solution_indexes(self, internal_solution) -> np.ndarray: - (x0, u) = internal_solution + def _revert_solution_indexes(self, internal_solution: List[np.ndarray]) \ + -> np.ndarray: + """Constructs a solution array where variables are stored in the correct order. + + Args: + internal_solution: a list with two lists: solutions for binary variables and + for continuous variables. + + Returns: + a solution array + """ + binary_solutions, continuous_solutions = internal_solution solution = np.zeros(len(self._state.binary_indices) + len(self._state.continuous_indices)) # restore solution at the original index location - [solution.itemset(binary_index, x0[i]) for i, binary_index in enumerate(self._state.binary_indices)] - [solution.itemset(continuous_index, u[i]) for i, continuous_index in enumerate(self._state.continuous_indices)] + for i, binary_index in enumerate(self._state.binary_indices): + solution[binary_index] = binary_solutions[i] + for i, continuous_index in enumerate(self._state.continuous_indices): + solution[continuous_index] = continuous_solutions[i] return solution - def convert_problem_representation(self) -> None: + def _convert_problem_representation(self) -> None: + """Converts problem representation into set of matrices and vectors""" # objective - self.get_q0() - self.get_c0() - self.get_q1() - self.get_c1() - # constraints - self.get_a0_b0() - self.get_a1_b1() - self.get_a2_a3_b2() - self.get_a4_b3() - - def get_q0(self) -> None: self._state.q0 = self._get_q(self._state.binary_indices) - - def get_q1(self) -> None: + self._state.c0 = self._get_c(self._state.binary_indices) self._state.q1 = self._get_q(self._state.continuous_indices) + self._state.c1 = self._get_c(self._state.continuous_indices) + # constraints + self._state.a0, self._state.b0 = self._get_a0_b0() + self._state.a1, self._state.b1 = self._get_a1_b1() + self._state.a2, self._state.a3, self._state.b2 = self._get_a2_a3_b2() + self._state.a4, self._state.b3 = self._get_a4_b3() def _get_q(self, variable_indices: List[int]) -> np.ndarray: size = len(variable_indices) q = np.zeros(shape=(size, size)) # fill in the matrix # in fact we use re-indexed variables - [q.itemset((i, j), self._state.op.objective.get_quadratic_coefficients(var_index_i, var_index_j)) - for i, var_index_i in enumerate(variable_indices) - for j, var_index_j in enumerate(variable_indices)] - - # flip the sign, according to the optimization sense, e.g. sense == 1 if minimize, sense == -1 if maximize + for i, var_index_i in enumerate(variable_indices): + for j, var_index_j in enumerate(variable_indices): + q[i, j] = self._state.op.objective.get_quadratic_coefficients( + var_index_i, + var_index_j) + + # flip the sign, according to the optimization sense, e.g. sense == 1 if minimize, + # sense == -1 if maximize return q * self._state.sense def _get_c(self, variable_indices: List[int]) -> np.ndarray: c = np.array(self._state.op.objective.get_linear(variable_indices)) - # flip the sign, according to the optimization sense, e.g. sense == 1 if minimize, sense == -1 if maximize - c = c * self._state.sense + # flip the sign, according to the optimization sense, e.g. sense == 1 if minimize, + # sense == -1 if maximize + c *= self._state.sense return c - def get_c0(self) -> None: - self._state.c0 = self._get_c(self._state.binary_indices) - - def get_c1(self) -> None: - self._state.c1 = self._get_c(self._state.continuous_indices) - - def _assign_row_values(self, matrix: List[List[float]], vector: List[float], constraint_index, variable_indices): + def _assign_row_values(self, matrix: List[List[float]], vector: List[float], constraint_index, + variable_indices): # assign matrix row row = [] - [row.append(self._state.op.linear_constraints.get_coefficients(constraint_index, var_index)) - for var_index in variable_indices] + for var_index in variable_indices: + row.append(self._state.op + .linear_constraints.get_coefficients(constraint_index, var_index)) matrix.append(row) # assign vector row @@ -378,14 +398,15 @@ def _assign_row_values(self, matrix: List[List[float]], vector: List[float], con vector[-1] = -1 * vector[-1] @staticmethod - def _create_ndarrays(matrix: List[List[float]], vector: List[float], size: int) -> (np.ndarray, np.ndarray): + def _create_ndarrays(matrix: List[List[float]], vector: List[float], size: int) \ + -> (np.ndarray, np.ndarray): # if we don't have such constraints, return just dummy arrays if len(matrix) != 0: return np.array(matrix), np.array(vector) else: return np.array([0] * size).reshape((1, -1)), np.zeros(shape=(1,)) - def get_a0_b0(self) -> None: + def _get_a0_b0(self) -> (np.ndarray, np.ndarray): matrix = [] vector = [] @@ -397,15 +418,18 @@ def get_a0_b0(self) -> None: continue row = self._state.op.linear_constraints.get_rows(constraint_index) if set(row.ind).issubset(index_set): - self._assign_row_values(matrix, vector, constraint_index, self._state.binary_indices) + self._assign_row_values(matrix, vector, + constraint_index, self._state.binary_indices) else: raise ValueError( "Linear constraint with the 'E' sense must contain only binary variables, " - "row indices: {}, binary variable indices: {}".format(row, self._state.binary_indices)) + "row indices: {}, binary variable indices: {}" + .format(row, self._state.binary_indices)) - self._state.a0, self._state.b0 = self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) + return self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) - def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) -> (List[List[float]], List[float]): + def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) \ + -> (List[List[float]], List[float]): # extracting matrix and vector from constraints like Ax <= b matrix = [] vector = [] @@ -413,7 +437,7 @@ def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) -> (Lis index_set = set(variable_indices) for constraint_index, sense in enumerate(senses): - if sense == "E" or sense == "R": + if sense in ("E", "R"): # TODO: Ranged constraints should be supported continue # sense either G or L @@ -423,15 +447,15 @@ def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) -> (Lis return matrix, vector - def get_a1_b1(self) -> None: + def _get_a1_b1(self) -> (np.ndarray, np.ndarray): matrix, vector = self._get_inequality_matrix_and_vector(self._state.binary_indices) - self._state.a1, self._state.b1 = self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) + return self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) - def get_a4_b3(self) -> None: + def _get_a4_b3(self) -> (np.ndarray, np.ndarray): matrix, vector = self._get_inequality_matrix_and_vector(self._state.continuous_indices) - self._state.a4, self._state.b3 = self._create_ndarrays(matrix, vector, len(self._state.continuous_indices)) + return self._create_ndarrays(matrix, vector, len(self._state.continuous_indices)) - def get_a2_a3_b2(self) -> None: + def _get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): matrix = [] vector = [] senses = self._state.op.linear_constraints.get_senses() @@ -440,48 +464,49 @@ def get_a2_a3_b2(self) -> None: continuous_index_set = set(self._state.continuous_indices) all_variables = self._state.binary_indices + self._state.continuous_indices for constraint_index, sense in enumerate(senses): - if sense == "E" or sense == "R": + if sense in ("E", "R"): # TODO: Ranged constraints should be supported as well continue # sense either G or L row = self._state.op.linear_constraints.get_rows(constraint_index) row_indices = set(row.ind) - # we must have a least one binary and one continuous variable, otherwise it is another type of constraints - if len(row_indices & binary_index_set) != 0 and len(row_indices & continuous_index_set) != 0: + # we must have a least one binary and one continuous variable, + # otherwise it is another type of constraints + if len(row_indices & binary_index_set) != 0 and len( + row_indices & continuous_index_set) != 0: self._assign_row_values(matrix, vector, constraint_index, all_variables) matrix, b2 = self._create_ndarrays(matrix, vector, len(all_variables)) # a2 a2 = matrix[:, 0:len(self._state.binary_indices)] a3 = matrix[:, len(self._state.binary_indices):] - self._state.a2 = a2 - self._state.a3 = a3 - self._state.b2 = b2 + return a2, a3, b2 def _create_step1_problem(self) -> OptimizationProblem: op1 = OptimizationProblem() binary_size = len(self._state.binary_indices) # create the same binary variables - # op1.variables.add(names=["x0_" + str(i + 1) for i in range(binary_size)], types=["B"] * binary_size) op1.variables.add(names=["x0_" + str(i + 1) for i in range(binary_size)], types=["I"] * binary_size, lb=[0.] * binary_size, ub=[1.] * binary_size) # prepare and set quadratic objective. - # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. + # NOTE: The multiplication by 2 is needed for the solvers to parse + # the quadratic coefficients. quadratic_objective = 2 * ( - self._state.q0 + self._factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) + - self._state.rho / 2 * np.eye(binary_size) + self._state.q0 + + self._factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) + + self._state.rho / 2 * np.eye(binary_size) ) for i in range(binary_size): for j in range(i, binary_size): op1.objective.set_quadratic_coefficients(i, j, quadratic_objective[i, j]) - op1.objective.set_quadratic_coefficients(j, i, quadratic_objective[i, j]) # prepare and set linear objective - linear_objective = self._state.c0 - self._factor_c * np.dot(self._state.b0, self._state.a0) + \ + linear_objective = self._state.c0 - \ + self._factor_c * np.dot(self._state.b0, self._state.a0) + \ self._state.rho * (self._state.y - self._state.z) for i in range(binary_size): @@ -493,12 +518,12 @@ def _create_step2_problem(self) -> OptimizationProblem: continuous_size = len(self._state.continuous_indices) binary_size = len(self._state.binary_indices) - lb = self._state.op.variables.get_lower_bounds(self._state.continuous_indices) - ub = self._state.op.variables.get_upper_bounds(self._state.continuous_indices) + lower_bounds = self._state.op.variables.get_lower_bounds(self._state.continuous_indices) + upper_bounds = self._state.op.variables.get_upper_bounds(self._state.continuous_indices) if continuous_size: # add u variables op2.variables.add(names=["u0_" + str(i + 1) for i in range(continuous_size)], - types=["C"] * continuous_size, lb=lb, ub=ub) + types=["C"] * continuous_size, lb=lower_bounds, ub=upper_bounds) # add z variables op2.variables.add(names=["z0_" + str(i + 1) for i in range(binary_size)], @@ -508,18 +533,21 @@ def _create_step2_problem(self) -> OptimizationProblem: # set quadratic objective coefficients for u variables if continuous_size: - # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. + # NOTE: The multiplication by 2 is needed for the solvers to parse + # the quadratic coefficients. q_u = 2 * self._state.q1 for i in range(continuous_size): for j in range(i, continuous_size): op2.objective.set_quadratic_coefficients(i, j, q_u[i, j]) # set quadratic objective coefficients for z variables. - # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. + # NOTE: The multiplication by 2 is needed for the solvers to parse + # the quadratic coefficients. q_z = 2 * (self._state.rho / 2 * np.eye(binary_size)) for i in range(binary_size): for j in range(i, binary_size): - op2.objective.set_quadratic_coefficients(i + continuous_size, j + continuous_size, q_z[i, j]) + op2.objective.set_quadratic_coefficients(i + continuous_size, j + continuous_size, + q_z[i, j]) # set linear objective for u variables if continuous_size: @@ -535,18 +563,22 @@ def _create_step2_problem(self) -> OptimizationProblem: # constraints for z # A1 z <= b1 constraint_count = self._state.a1.shape[0] - # in SparsePair val="something from numpy" causes an exception when saving a model via cplex method. + # in SparsePair val="something from numpy" causes an exception + # when saving a model via cplex method. # rhs="something from numpy" is ok # so, we convert every single value to python float, todo: consider removing this conversion lin_expr = [SparsePair(ind=list(range(continuous_size, continuous_size + binary_size)), - val=self._to_list(self._state.a1[i, :])) for i in range(constraint_count)] - op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, rhs=list(self._state.b1)) + val=self._to_list(self._state.a1[i, :])) for i in + range(constraint_count)] + op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, + rhs=list(self._state.b1)) if continuous_size: # A2 z + A3 u <= b2 constraint_count = self._state.a2.shape[0] lin_expr = [SparsePair(ind=list(range(continuous_size + binary_size)), - val=self._to_list(self._state.a3[i, :]) + self._to_list(self._state.a2[i, :])) + val=self._to_list(self._state.a3[i, :]) + self._to_list( + self._state.a2[i, :])) for i in range(constraint_count)] op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, @@ -556,7 +588,8 @@ def _create_step2_problem(self) -> OptimizationProblem: # A4 u <= b3 constraint_count = self._state.a4.shape[0] lin_expr = [SparsePair(ind=list(range(continuous_size)), - val=self._to_list(self._state.a4[i, :])) for i in range(constraint_count)] + val=self._to_list(self._state.a4[i, :])) for i in + range(constraint_count)] op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, rhs=self._to_list(self._state.b3)) @@ -571,12 +604,11 @@ def _create_step3_problem(self) -> OptimizationProblem: types=["C"] * binary_size) # set quadratic objective. - # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coefficients. + # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coeff-s. q_y = 2 * (self._beta / 2 * np.eye(binary_size) + self._state.rho / 2 * np.eye(binary_size)) for i in range(binary_size): for j in range(i, binary_size): op3.objective.set_quadratic_coefficients(i, j, q_y[i, j]) - op3.objective.set_quadratic_coefficients(j, i, q_y[i, j]) linear_y = self._state.lambda_mult + self._state.rho * (self._state.x0 - self._state.z) for i in range(binary_size): @@ -584,31 +616,32 @@ def _create_step3_problem(self) -> OptimizationProblem: return op3 - # when a plain list() call is used a numpy type of values makes cplex to fail when cplex.write() is called. + # when a plain list() call is used a numpy type of values makes cplex to fail + # when cplex.write() is called. # for debug only, list() should be used instead @staticmethod - def _to_list(values): + def _to_list(values) -> List[Any]: out_list = [] - for el in values: - out_list.append(float(el)) + for element in values: + out_list.append(float(element)) return out_list - def update_x0(self, op1: OptimizationProblem) -> np.ndarray: + def _update_x0(self, op1: OptimizationProblem) -> np.ndarray: return np.asarray(self._qubo_optimizer.solve(op1).x) - def update_x1(self, op2: OptimizationProblem) -> (np.ndarray, np.ndarray): + def _update_x1(self, op2: OptimizationProblem) -> (np.ndarray, np.ndarray): vars_op2 = self._continuous_optimizer.solve(op2).x u = np.asarray(vars_op2[:len(self._state.continuous_indices)]) z = np.asarray(vars_op2[len(self._state.continuous_indices):]) return u, z - def update_y(self, op3): + def _update_y(self, op3: OptimizationProblem) -> np.ndarray: return np.asarray(self._continuous_optimizer.solve(op3).x) - def get_best_merit_solution(self): + def _get_best_merit_solution(self) -> (List[np.ndarray], float): """ - The ADMM solution is that for which the merit value is the best (least for min problems, greatest - for max problems) + The ADMM solution is that for which the merit value is the best (least for min problems, + greatest for max problems) * sol: Iterate with the best merit value * sol_val: Value of sol, according to the original objective @@ -618,36 +651,38 @@ def get_best_merit_solution(self): * sol_val: Value of the objective function """ - it_best_merits = self._state.merits.index(min(list(map(lambda x: self._state.sense * x, self._state.merits)))) + it_best_merits = self._state.merits.index( + min(list(map(lambda x: self._state.sense * x, self._state.merits)))) x0 = self._state.x0_saved[it_best_merits] u = self._state.u_saved[it_best_merits] sol = [x0, u] sol_val = self._state.cost_iterates[it_best_merits] return sol, sol_val - def update_lambda_mult(self): - return self._state.lambda_mult + self._state.rho * (self._state.x0 - self._state.z + self._state.y) + def _update_lambda_mult(self) -> np.ndarray: + return self._state.lambda_mult + \ + self._state.rho * (self._state.x0 - self._state.z + self._state.y) - def update_rho(self, r, s): + def _update_rho(self, primal_residual: float, dual_residual: float) -> None: """ Updating the rho parameter in ADMM Args: - r: primal residual - s: dual residual + primal_residual: primal residual + dual_residual: dual residual """ - if self._vary_rho == 0: + if self._vary_rho == UPDATE_RHO_BY_TEN_PERCENT: # Increase rho, to aid convergence. if self._state.rho < 1.e+10: self._state.rho *= 1.1 - elif self._vary_rho == 1: - if r > self._mu_res * s: + elif self._vary_rho == UPDATE_RHO_BY_RESIDUALS: + if primal_residual > self._mu_res * dual_residual: self._state.rho = self._tau_incr * self._state.rho - elif s > self._mu_res * r: + elif dual_residual > self._mu_res * primal_residual: self._state.rho = self._tau_decr * self._state.rho - def get_constraint_residual(self): + def _get_constraint_residual(self) -> float: """ Compute violation of the constraints of the original problem, as: * norm 1 of the body-rhs of the constraints A0 x0 - b0 @@ -663,25 +698,26 @@ def get_constraint_residual(self): eq1 = np.dot(self._state.a1, self._state.x0) - self._state.b1 cr1 = sum(max(val, 0) for val in eq1) - eq2 = np.dot(self._state.a2, self._state.x0) + np.dot(self._state.a3, self._state.u) - self._state.b2 + eq2 = np.dot(self._state.a2, self._state.x0) + np.dot(self._state.a3, + self._state.u) - self._state.b2 cr2 = sum(max(val, 0) for val in eq2) return cr0 + cr1 + cr2 - def get_merit(self, cost_iterate, cr): + def _get_merit(self, cost_iterate: float, constraint_residual: float) -> float: """ Compute merit value associated with the current iterate Args: cost_iterate: cost at the certain iteration - cr: value of violation of the constraints + constraint_residual: value of violation of the constraints Returns: merit value as a float """ - return cost_iterate + self._mu * cr + return cost_iterate + self._mu * constraint_residual - def get_objective_value(self): + def _get_objective_value(self) -> float: """ Computes the value of the objective function. @@ -689,20 +725,20 @@ def get_objective_value(self): Value of the objective function as a float """ - # quadr_form = lambda A, x, c: np.dot(x.T, np.dot(A, x)) + np.dot(c.T, x) - def quadratic_form(matrix, x, c): return np.dot(x.T, np.dot(matrix, x)) + np.dot(c.T, x) + def quadratic_form(matrix, x, c): + return np.dot(x.T, np.dot(matrix, x)) + np.dot(c.T, x) obj_val = quadratic_form(self._state.q0, self._state.x0, self._state.c0) obj_val += quadratic_form(self._state.q1, self._state.u, self._state.c1) return obj_val - def get_solution_residuals(self, it): + def _get_solution_residuals(self, iteration: int) -> (float, float): """ Compute primal and dual residual. Args: - it: iteration number + iteration: iteration number Returns: r, s as primary and dual residuals @@ -711,8 +747,8 @@ def get_solution_residuals(self, it): # debug # elements = np.asarray([x0[i] - z[i] + y[i] for i in self.range_x0_vars]) r = pow(sum(e ** 2 for e in elements), 0.5) - if it > 0: - elements_dual = self._state.z - self._state.z_saved[it - 1] + if iteration > 0: + elements_dual = self._state.z - self._state.z_saved[iteration - 1] else: elements_dual = self._state.z - self._state.z_init # debug diff --git a/test/optimization/test_admm_miskp.py b/test/optimization/test_admm_miskp.py index f2c93280d6..b7b2788229 100644 --- a/test/optimization/test_admm_miskp.py +++ b/test/optimization/test_admm_miskp.py @@ -31,7 +31,8 @@ def setUp(self): super().setUp() def test_admm_optimizer_miskp_eigen(self): - """ ADMM Optimizer Test based on Mixed-Integer Setup Knapsack Problem using NumPy eigen optimizer""" + """ ADMM Optimizer Test based on Mixed-Integer Setup Knapsack Problem + using NumPy eigen optimizer""" K, T, P, S, D, C = self.get_problem_params() miskp = Miskp(K, T, P, S, D, C) op: OptimizationProblem = miskp.create_problem() @@ -40,7 +41,8 @@ def test_admm_optimizer_miskp_eigen(self): qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) continuous_optimizer = CplexOptimizer() - admm_params = ADMMParameters(qubo_optimizer=qubo_optimizer, continuous_optimizer=continuous_optimizer) + admm_params = ADMMParameters(qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer) solver = ADMMOptimizer(params=admm_params) solution = solver.solve(op) @@ -50,19 +52,17 @@ def test_admm_optimizer_miskp_eigen(self): print("x={}".format(solution.x)) print("fval={}".format(solution.fval)) - correct_solution = [0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, - 0.009127, 0.009127, 0.006151, 0.006151, 0.006151, 0.006151, 0.006151, 0.006151, - 0.006151, 0.006151, 0.006151, 0.006151, 0., 0.] + correct_solution = [0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, + 0.009127, 0.009127, 0.009127, 0.006151, 0.006151, 0.006151, 0.006151, + 0.006151, 0.006151, 0.006151, 0.006151, 0.006151, 0.006151, + 0., 0.] correct_objective = -1.2113693 np.testing.assert_almost_equal(correct_solution, solution.x, 3) np.testing.assert_almost_equal(solution.fval, correct_objective, 3) def get_problem_params(self): - """ - Fills in parameters for a Mixed Integer Setup Knapsack Problem (MISKP) instance. - """ - # + """Fills in parameters for a Mixed Integer Setup Knapsack Problem (MISKP) instance.""" K = 2 T = 10 @@ -73,13 +73,15 @@ def get_problem_params(self): 4.72, 7.6, 2.83, 1.44, 2.45, 2.24, 6.3, 5.02]).reshape((K, T)) C = np.asarray([-11.78, -10.81, -11.08, -4.03, -10.90, -11.00, -6.12, -10.16, -8.55, -6.84, - -5.78, -7.13, -6.72, -9.60, -4.83, -3.44, -4.45, -4.24, -8.30, -7.02]).reshape((K, T)) + -5.78, -7.13, -6.72, -9.60, -4.83, -3.44, -4.45, -4.24, -8.30, -7.02]) \ + .reshape((K, T)) return K, T, P, S, D, C class Miskp: - def __init__(self, K, T, P, S, D: np.ndarray, C: np.ndarray, pairwise_incomp=0, multiple_choice=0): + def __init__(self, K: int, T: int, P: float, S: np.ndarray, D: np.ndarray, C: np.ndarray, + pairwise_incomp: int = 0, multiple_choice: int = 0): """ Constructor method of the class. @@ -112,7 +114,9 @@ def __init__(self, K, T, P, S, D: np.ndarray, C: np.ndarray, pairwise_incomp=0, @staticmethod def var_name(stem, index1, index2=None, index3=None): - """A method to return a string representing the name of a decision variable or a constraint, given its indices. + """A method to return a string representing the name of a decision variable or + a constraint, given its indices. + Args: stem: Element name. index1: Element indices @@ -182,7 +186,8 @@ def create_constraints(self): def create_objective(self): self.op.objective.set_linear([(self.var_name("y", k), self.S[k]) for k in self.range_K] + - [(self.var_name("x", k, t), self.C[k, t]) for k, t in self.range_x_vars] + [(self.var_name("x", k, t), self.C[k, t]) for k, t in + self.range_x_vars] ) def create_problem(self): From 80ef7db64ffd525002b20e1528a8e221c7ab2abb Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Tue, 24 Mar 2020 17:10:24 +0000 Subject: [PATCH 103/323] formatting, linting, typing --- Julien_requests.docx | Bin 46984 -> 39065 bytes .../optimization/algorithms/admm_optimizer.py | 199 +++++++++++++----- 2 files changed, 143 insertions(+), 56 deletions(-) diff --git a/Julien_requests.docx b/Julien_requests.docx index a07cce15902625138d7735a8da3163926e0e0ff0..7960b727a7ea2dfbaa1d61654bcd4a6f032da4e6 100644 GIT binary patch delta 31999 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

)rN|n;7MCb;C#*|#w!vtk$0B?fk^=k#k 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 E0Az&|jsO4v delta 40005 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 z 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 CLK1)g diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 8a96e29231..83b9adad96 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -13,9 +13,9 @@ # that they have been altered from the originals. """An implementation of the ADMM algorithm.""" - +import logging import time -from typing import List, Optional, Any +from typing import List, Optional, Any, Iterable import numpy as np from cplex import SparsePair @@ -153,7 +153,7 @@ def __init__(self, class ADMMOptimizer(OptimizationAlgorithm): """An implementation of the ADMM algorithm.""" - def __init__(self, params: ADMMParameters = None) -> None: + def __init__(self, params: Optional[ADMMParameters] = None) -> None: """Constructs an instance of ADMMOptimizer. Args: @@ -161,9 +161,10 @@ def __init__(self, params: ADMMParameters = None) -> None: """ super().__init__() - if params is None: - # create default params - params = ADMMParameters() + self._log = logging.getLogger(__name__) + + # create default params if not present + params = params or ADMMParameters() self._three_block = params.three_block self._max_time = params.max_time self._tol = params.tol @@ -184,7 +185,7 @@ def __init__(self, params: ADMMParameters = None) -> None: # internal state where we'll keep intermediate solution # here, we just declare the class variable, the variable is initialized in kept in # the solve method. - self._state = None + self._state: Optional[ADMMState] = None def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: """Checks whether a given problem can be solved with the optimizer implementing this method. @@ -244,9 +245,6 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: # at each iteration self._convert_problem_representation() - # debug - # self.__dump_matrices_and_vectors() - start_time = time.time() # we have not stated our computations yet, so elapsed time initialized as zero elapsed_time = 0 @@ -256,42 +254,37 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: while (iteration < self._max_iter and residual > self._tol) \ and (elapsed_time < self._max_time): op1 = self._create_step1_problem() - # debug - # op1.write("op1.lp") - self._state.x0 = self._update_x0(op1) # debug - # print("x0={}".format(self._state.x0)) + self._log.debug("x0=%s", self._state.x0) op2 = self._create_step2_problem() - # op2.write("op2.lp") - self._state.u, self._state.z = self._update_x1(op2) # debug - # print("u={}".format(self._state.u)) - # print("z={}".format(self._state.z)) + self._log.debug("u=%s", self._state.u) + self._log.debug("z=%s", self._state.z) if self._three_block: op3 = self._create_step3_problem() - # op3.write("op3.lp") self._state.y = self._update_y(op3) # debug - # print("y={}".format(self._state.y)) + self._log.debug("y=%s", self._state.y) lambda_mult = self._update_lambda_mult() cost_iterate = self._get_objective_value() - cr = self._get_constraint_residual() + constraint_residual = self._get_constraint_residual() residual, dual_residual = self._get_solution_residuals(iteration) - merit = self._get_merit(cost_iterate, cr) + merit = self._get_merit(cost_iterate, constraint_residual) # debug - # print("cost_iterate, cr, merit", cost_iterate, cr, merit) + self._log.debug("cost_iterate=%s, cr=%s, merit=%s", + cost_iterate, constraint_residual, merit) # costs and merits are saved with their original sign self._state.cost_iterates.append(self._state.sense * cost_iterate) self._state.residuals.append(residual) self._state.dual_residuals.append(dual_residual) - self._state.cons_r.append(cr) + self._state.cons_r.append(constraint_residual) self._state.merits.append(merit) self._state.lambdas.append(np.linalg.norm(lambda_mult)) @@ -311,12 +304,21 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: # third parameter is our internal state of computations result = OptimizationResult(solution, objective_value, self._state) # debug - # print("sol={0}, sol_val={1}".format(solution, objective_value)) - # print("it {0}, state {1}".format(iteration, self._state)) + self._log.debug("solution=%s, objective=%s at iteration=%s", + solution, objective_value, iteration) return result @staticmethod def _get_variable_indices(op: OptimizationProblem, var_type: str) -> List[int]: + """Returns a list of indices of the variables of the specified type + + Args: + op: Optimization problem + var_type: type of variables to look for + + Returns: + List of indices + """ indices = [] for i, variable_type in enumerate(op.variables.get_types()): if variable_type == var_type: @@ -358,6 +360,15 @@ def _convert_problem_representation(self) -> None: self._state.a4, self._state.b3 = self._get_a4_b3() def _get_q(self, variable_indices: List[int]) -> np.ndarray: + """Constructs a quadratic matrix for the variables with the specified indices + from the quadratic terms in the objective. + + Args: + variable_indices: variable indices to look for + + Returns: + A matrix as a numpy array of the shape(len(variable_indices), len(variable_indices)) + """ size = len(variable_indices) q = np.zeros(shape=(size, size)) # fill in the matrix @@ -373,14 +384,35 @@ def _get_q(self, variable_indices: List[int]) -> np.ndarray: return q * self._state.sense def _get_c(self, variable_indices: List[int]) -> np.ndarray: + """Constructs a vector for the variables with the specified indices from the linear terms + in the objective. + + Args: + variable_indices: variable indices to look for + + Returns: + A numpy array of the shape(len(variable_indices)) + """ c = np.array(self._state.op.objective.get_linear(variable_indices)) # flip the sign, according to the optimization sense, e.g. sense == 1 if minimize, # sense == -1 if maximize c *= self._state.sense return c - def _assign_row_values(self, matrix: List[List[float]], vector: List[float], constraint_index, - variable_indices): + def _assign_row_values(self, matrix: List[List[float]], vector: List[float], + constraint_index: int, variable_indices: List[int]): + """Appends a row to the specified matrix and vector based on the constraint specified by + the index using specified variables + + Args: + matrix: a matrix to extend + vector: a vector to expend + constraint_index: constraint index to look for + variable_indices: variables to look for + + Returns: + None + """ # assign matrix row row = [] for var_index in variable_indices: @@ -400,6 +432,16 @@ def _assign_row_values(self, matrix: List[List[float]], vector: List[float], con @staticmethod def _create_ndarrays(matrix: List[List[float]], vector: List[float], size: int) \ -> (np.ndarray, np.ndarray): + """Converts representation of a matrix and a vector in form of lists to numpy array. + + Args: + matrix: matrix to convert + vector: vector to convert + size: size to create matrix and vector + + Returns: + Converted matrix and vector as numpy arrays + """ # if we don't have such constraints, return just dummy arrays if len(matrix) != 0: return np.array(matrix), np.array(vector) @@ -407,6 +449,15 @@ def _create_ndarrays(matrix: List[List[float]], vector: List[float], size: int) return np.array([0] * size).reshape((1, -1)), np.zeros(shape=(1,)) def _get_a0_b0(self) -> (np.ndarray, np.ndarray): + """Constructs a matrix and a vector from the constraints in a form of Ax = b, where + x is a vector of binary variables. + + Returns: + Corresponding matrix and vector as numpy arrays. + + Raises: + ValueError: if the problem is not suitable for this optimizer + """ matrix = [] vector = [] @@ -430,7 +481,15 @@ def _get_a0_b0(self) -> (np.ndarray, np.ndarray): def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) \ -> (List[List[float]], List[float]): - # extracting matrix and vector from constraints like Ax <= b + """Constructs a matrix and a vector from the constraints in a form of Ax <= b, where + x is a vector of variables specified by the indices. + + Args: + variable_indices: variable indices to look for + + Returns: + A list based representation of the matrix and the vector. + """ matrix = [] vector = [] senses = self._state.op.linear_constraints.get_senses() @@ -448,14 +507,32 @@ def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) \ return matrix, vector def _get_a1_b1(self) -> (np.ndarray, np.ndarray): + """Constructs a matrix and a vector from the constraints in a form of Ax <= b, where + x is a vector of binary variables. + + Returns: + A numpy based representation of the matrix and the vector. + """ matrix, vector = self._get_inequality_matrix_and_vector(self._state.binary_indices) return self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) def _get_a4_b3(self) -> (np.ndarray, np.ndarray): + """Constructs a matrix and a vector from the constraints in a form of Au <= b, where + u is a vector of continuous variables. + + Returns: + A numpy based representation of the matrix and the vector. + """ matrix, vector = self._get_inequality_matrix_and_vector(self._state.continuous_indices) return self._create_ndarrays(matrix, vector, len(self._state.continuous_indices)) def _get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): + """Constructs matrices and a vector from the constraints in a form of A_2x + A_3u <= b, + where x is a vector of binary variables and u is a vector of continuous variables. + + Returns: + A numpy representation of two matrices and one vector + """ matrix = [] vector = [] senses = self._state.op.linear_constraints.get_senses() @@ -483,6 +560,11 @@ def _get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): return a2, a3, b2 def _create_step1_problem(self) -> OptimizationProblem: + """Creates a step 1 sub-problem + + Returns: + A newly created optimization problem + """ op1 = OptimizationProblem() binary_size = len(self._state.binary_indices) @@ -514,6 +596,11 @@ def _create_step1_problem(self) -> OptimizationProblem: return op1 def _create_step2_problem(self) -> OptimizationProblem: + """Creates a step 2 sub-problem + + Returns: + A newly created optimization problem + """ op2 = OptimizationProblem() continuous_size = len(self._state.continuous_indices) @@ -597,6 +684,11 @@ def _create_step2_problem(self) -> OptimizationProblem: return op2 def _create_step3_problem(self) -> OptimizationProblem: + """Creates a step 3 sub-problem + + Returns: + A newly created optimization problem + """ op3 = OptimizationProblem() # add y variables binary_size = len(self._state.binary_indices) @@ -620,27 +712,31 @@ def _create_step3_problem(self) -> OptimizationProblem: # when cplex.write() is called. # for debug only, list() should be used instead @staticmethod - def _to_list(values) -> List[Any]: - out_list = [] - for element in values: - out_list.append(float(element)) - return out_list + def _to_list(values: Iterable[Any]) -> List[Any]: + """Converts an iterable into a list of floats + + Args: + values: an iterable + + Returns: + List of floats + """ + return [float(element) for element in values] def _update_x0(self, op1: OptimizationProblem) -> np.ndarray: return np.asarray(self._qubo_optimizer.solve(op1).x) def _update_x1(self, op2: OptimizationProblem) -> (np.ndarray, np.ndarray): vars_op2 = self._continuous_optimizer.solve(op2).x - u = np.asarray(vars_op2[:len(self._state.continuous_indices)]) - z = np.asarray(vars_op2[len(self._state.continuous_indices):]) - return u, z + vars_u = np.asarray(vars_op2[:len(self._state.continuous_indices)]) + vars_z = np.asarray(vars_op2[len(self._state.continuous_indices):]) + return vars_u, vars_z def _update_y(self, op3: OptimizationProblem) -> np.ndarray: return np.asarray(self._continuous_optimizer.solve(op3).x) def _get_best_merit_solution(self) -> (List[np.ndarray], float): - """ - The ADMM solution is that for which the merit value is the best (least for min problems, + """The ADMM solution is that for which the merit value is the best (least for min problems, greatest for max problems) * sol: Iterate with the best merit value * sol_val: Value of sol, according to the original objective @@ -664,8 +760,7 @@ def _update_lambda_mult(self) -> np.ndarray: self._state.rho * (self._state.x0 - self._state.z + self._state.y) def _update_rho(self, primal_residual: float, dual_residual: float) -> None: - """ - Updating the rho parameter in ADMM + """Updating the rho parameter in ADMM. Args: primal_residual: primal residual @@ -683,8 +778,7 @@ def _update_rho(self, primal_residual: float, dual_residual: float) -> None: self._state.rho = self._tau_decr * self._state.rho def _get_constraint_residual(self) -> float: - """ - Compute violation of the constraints of the original problem, as: + """Compute violation of the constraints of the original problem, as: * norm 1 of the body-rhs of the constraints A0 x0 - b0 * -1 * min(body - rhs, 0) for geq constraints * max(body - rhs, 0) for leq constraints @@ -705,8 +799,7 @@ def _get_constraint_residual(self) -> float: return cr0 + cr1 + cr2 def _get_merit(self, cost_iterate: float, constraint_residual: float) -> float: - """ - Compute merit value associated with the current iterate + """Compute merit value associated with the current iterate Args: cost_iterate: cost at the certain iteration @@ -718,8 +811,7 @@ def _get_merit(self, cost_iterate: float, constraint_residual: float) -> float: return cost_iterate + self._mu * constraint_residual def _get_objective_value(self) -> float: - """ - Computes the value of the objective function. + """Computes the value of the objective function. Returns: Value of the objective function as a float @@ -734,8 +826,7 @@ def quadratic_form(matrix, x, c): return obj_val def _get_solution_residuals(self, iteration: int) -> (float, float): - """ - Compute primal and dual residual. + """Compute primal and dual residual. Args: iteration: iteration number @@ -744,15 +835,11 @@ def _get_solution_residuals(self, iteration: int) -> (float, float): r, s as primary and dual residuals """ elements = self._state.x0 - self._state.z - self._state.y - # debug - # elements = np.asarray([x0[i] - z[i] + y[i] for i in self.range_x0_vars]) - r = pow(sum(e ** 2 for e in elements), 0.5) + primal_residual = pow(sum(e ** 2 for e in elements), 0.5) if iteration > 0: elements_dual = self._state.z - self._state.z_saved[iteration - 1] else: elements_dual = self._state.z - self._state.z_init - # debug - # elements_dual = np.asarray([z[i] - z_old[i] for i in self.range_x0_vars]) - s = self._state.rho * pow(sum(e ** 2 for e in elements_dual), 0.5) + dual_residual = self._state.rho * pow(sum(e ** 2 for e in elements_dual), 0.5) - return r, s + return primal_residual, dual_residual From ee5b680bb021181510fc4e41099639623e0f7eab Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Tue, 24 Mar 2020 17:59:53 +0000 Subject: [PATCH 104/323] formatting, linting, typing --- qiskit/optimization/algorithms/admm_optimizer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 83b9adad96..34c1876002 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -802,11 +802,11 @@ def _get_merit(self, cost_iterate: float, constraint_residual: float) -> float: """Compute merit value associated with the current iterate Args: - cost_iterate: cost at the certain iteration - constraint_residual: value of violation of the constraints + cost_iterate: Cost at the certain iteration. + constraint_residual: Value of violation of the constraints. Returns: - merit value as a float + Merit value as a float """ return cost_iterate + self._mu * constraint_residual From a803b323b3c0d73b477afb72c0dd2579d7f16243 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Wed, 25 Mar 2020 10:44:54 +0000 Subject: [PATCH 105/323] formatting, linting, typing --- Julien_requests.docx | Bin 39065 -> 39357 bytes test/optimization/test_admm_miskp.py | 185 ++++++++++++++------------- 2 files changed, 99 insertions(+), 86 deletions(-) diff --git a/Julien_requests.docx b/Julien_requests.docx index 7960b727a7ea2dfbaa1d61654bcd4a6f032da4e6..7f005814d2d71aa550397926bcc62ca7451fc90e 100644 GIT binary patch delta 26492 zcmX_nRa9PE6C`fI-Q696LvRgzxVyW%9~=S%5AN<7+}$05ySoM_4ELWk^VWN<^VGYm zyQ+Ghv^t2+28h~87z8bE%MmS2Ffi4Nh9#JEDB$F^od3GS1DW4|!kZ8}loTBlq?8Vx z#vxPZyK!=dwvlP3O9@9Q@R4dhYw+x8DcAJ_U6RLczQGrwnR!d*ZQ6`kwipRFlRw@( zs?XT9_~f?mX(K{nsbE*`67Q`al(eK7{Pl**yqnUodQtV%C-dvJ+sFbeU-J$72oyKs z_EXMbIz58dig@iN1H*78(Hdgv35O4ApeA{Qg>f{w4B2&ZjypQ>HBv#&8@r^Jz}aOe zXFripQX<^cii+xp=wIL=hM)_cE^1CiH6Y1|ld}ywm_f*1IJgngOS4UCFJ(k6?Zza7 z!PTkz7sVgDrEE7kGAsXOQrl_a8;EH66eEJ7SYcL21c4MfI}W1%)Peg0?pyX$*~{wW zWlQTA$})=|->$D`PF*vV2c?#D3%EkHS942=W^{h0+!zn_-wS=nW+nAkzhSrRFTmlQ zjOrK-M+8(}*g5@V%j}^(v~T0f@ipvO%{9McmOd6^`$zKA?|Z6QdmF4N5$$#J`s?B2 z-d(qW_m*g%A_r-Am@dA@YiI)Or9sjHlPTsDvCSuf*)3X@xpQRAZLkqYo0u}xr1_|T zmnepm#xm!u*e@(+5k9Al33_e1vcPTGG`t3;@r9nesAi(ZOM`Y~A}Nv<#zxsl_MT=} zXd8yx5jX;*a16ANBXz1Nqs0o++1tO}K_rSLD-qh(-EQnclL(1}!d(<+aC;bys=k%& zn&{c<7)(06hV%5lBh6O&3rYGOo^kRCBH;rO%b%HKD?)ABsX;jn*Rh}#?bTXG zY11JHJ}(U19|g1Rr*kfj=0WNQDtFs@!_irUbs1_98m!osX%Ul8@rnndgA$%)%-;7+ zus>U0&9-eK4+8&Knggyl-7)z2WEO}ps8KI+Lmk-#orJz((Gklff6w?%!bPSwPrCsf+0L>0k!4DaKZj{EnO(cQ};3MLM074+>4SSgY5VI880J@0h@Dek6NT zBmEO>FDaya3VH(x|>A z*BPHK)luA>OMcA}r=zf%tG+a99EJ`Z`+XxP3`xb@&{ty0*aTt50CStcSKIGJW8rWsWDr^_Xq<;Kk&)|n`;vZ6Q4nxbM zS~Dr}#4=WnKzxcjF$2)qSn0zlD@nZHM( zqcKg|WJUz9w<2qgM8_r`motbk^n=O65_J1rT<;hSRrVx+hQK@IjgQ?=Q-OmX#ylQb z&J3P@A83x34DQvS7lv@CUfd3kWr&#D$p*$D>{Qi>Xn%!>~9TKnH~gc{m||@*vs<^EX(Sw z6Ef+Ua`ney*r;C2l2#*Gp{2v#+i^}^7}dgQA&ldYQzA9eg6KmQ;tr|H7^0GBGlr44 z1uKoT9$H!+EeUn`6X-V^jmbVFZp$jyfB0-)0qwsH(~*82;=E6&*T^z;db!KT{KY5j z)6i|=F5!pvz{G})z2D_*66bQ+#IrN+_mP}|A(Q&w;pUBsqt;ulrWdV-p>Wo#Xx*14 zZ>)#hfa$hD-8HQD)QBkhA!;rF+r~W%i*H8mwfv7a`;cP z3Fb1z`^-C-O1O%+54_#oV_%Mz@UPnsfsFLZ_VKZ;8_GpuM(VXD(~I}L;*zo}$GCKC z*?s||#@MJa9yND5aHz;-MFw|5Q$%Er@zKFAmnCzf;^zZM1p>4#@!BLGVvuC&rju%B z<64-;GQZqF7|@mGq8-F$Yj9G4t!PL3X4v^z=Jo@5=g+P&x3cbIN`XQ1FwVqUAZP|; zZZw!xBAr{K7Uh#A=-ro}2%8P^W*@Un(Vat5S#mpr$8NA=$ba_L1I>}tb97oypfim5 z(ff%#!nCbw(LQP|-A9|suu@cPBqgAI9jN-lmHJ|VAgIE7KHvM2?q|(BBfaF_TT zm*3V^u)lcK5A8dxAm6s9JKU+T%Kh+1g9MZ-jqafX-i5=cbH!j zfi315b{p#m=p7d$4}K4V9kI>{DHzL1eFMad-#t~Rf?vuUD}qM)sd;HnMR#O5(T$PW z41zomaMs2Ua|RC*CD1%3M)_>rI%ZB=a>zrGp?mXw_D2VK)oZH&gle-xR2s!+Aj#Ndt7k9OncNNA6} zry9bQei*HlDwWLNh}-GL$C~A5vshL{LgjPi@U*!VFX_nsQvYeG;*`6x(45h)^R11( zkD#8x*$a6c5Y)KIOyla%a|gvqADDm`sF?G!4Z0+P${uNVS1hGFkQ4ZszB6$L1+E@>m!D zPK^aAFAts};Q<6a&3B~Eko?95@`f*qmQuEiQ&QF6e}S!EZf+mcV1qWTFH`g;zj8SF z?-D*Tq;opj0&YX6CRJq|=t0S5DWvh&GCskr5RifS1UZ@aiCh}TOT{a_!|ryK~^uaGLqh?Oc4 z+Aas%kGwsxXgj?5l@W>DsKAEmw=L`nH%tKYL+q;)P=Paoqd`u}#g9xO?bYlo$c)A) zTe0@5S>(_rD?x{cqkzxbMHxG!*JykYE$+6(+q3Lu_(H;JM8hE=fAUH;ypEnh& zODUI3PcL~Y?{@lF6J@maMsphxkw9PTzigpH<{tcZU8;RRv3YeTn)2))gj42B$q+0h z@1Fq=G&s4LW>`U8@?AH|+4T%b6i}&vvY2b>C zyWBMNHblDAPJJS)(zGq~x)udvd@J_xl208LL1?~yXixs$L z(N5t!NSm46Q@P~#NRyY$yg#M`wSVs?A=kSUd6U$izPEHtXd*+|Z8Md9H@NwIz@%MY zG9RMuLwz~uh?-xW6VbXWUdp&x!4c1*VdzgFy|ny(t3cD|h_k6W_s4g@>@HBBuB zZZry~cki*M)coSv66X1t!|k$8qZl9}y#^BWYou}`y<8+qV+aaR10GFHg#2RBG_gaue6mM%Ud8d zfqumlYPRh2W(#R<&V|wbJW}?Gu=CL^Y({GWVPnK@H>F#`*{_LbQEziiy4q^pVyR}7 za%mv>=k(e!kRq$V_2adMhs2`;*gb#2msV)04k!zu&zR`e)D_Jkp-d5ug~u3Gii4U3 zl9?+O%T`I2n=Y9{krDenYklya$}AtuY||ArSK8PSCDrk1l0Hl1&TfL zBF2~9OMk@QTh`rmUq4wC9FRWbi;RIF06FM3M@uIik3L4@NuES$iJp2LqTsRlXY|m$ z;iBPv=Nm}-OR`OOt-ohZiD{oQZ7s%3h-u}BWlzgoSmeE~@Kp#CSjk}p40H5~2BHD_ z-~A(9+|gfD9@~s{2p?bw^t;7$tPWNYe%ZvWYwunQJa0}azycI8O?h@?Ry z<=R!{c>Kt4JOvxm($!;U(IOqpE9>%7IpGgO$cgv`@qWV5&#uGJ!ps{<+2>KprDR8} z8e`hT{X&kmlbH&2;;D=+isncYgl(&>uXT9RG}J4C8ZFL!9$Xq4}!*W_@jCnpJT3o zX_kBqYsq~4smKAwN;p5n1shMn0DBa|Nl}|d|N7a{t*di2H)Jq_2r;)0@^;8htwYi) zEp7eSxq4CnU7tw<0g1A&R&KsQ&P0a%CAiDBH7M|p3>3Rin{^JER4>-+BRqea(9$mH zdf;%3>&JKlFc)E%-<4%HzQ*syS!2)N)xRXXR?nVYQwK;9!)Djal0*M{;u$@sgpZE1 zZTwyZ{{t<-t~4ACT>!aQhV*#eb&(DA2zMxg+9Ht9(wUXEn?>ZA3#kEr_T)6T{A(Q2 zu+I0dr7?Lr=~1k+)7`sbsmD_aDcS}R;IZ3nB=b#Dj#_@Qb6E%bh@y@-g;!qfZIE!) zyfYVprdmLj8>;RB4{1x}>?7g$lKb>!YVN@K+orH%UN)4dV~MyJ+~w!h25z6W70M=m zIYnBeE}w*yVNyd~uj!_yXT z23a)3X1~kr^{x{dm4(a{57C?q3(>zJzy0H|ztw=26P~AJuy2E;RNQ66fdVE`3a$JPliq&imf8}vjm*pbHAFU zR~*36t1AFsY#s@F&)30) z-zaI0I||F~@IZoSM1`oKoNUpLgdmO@-0Y4H`FZ+&;zBZEG)w7lB|XBaxFM@S_R`=;9(3MCMk z-pQT}NnB)HMmsJ0j|-GhDkGt8w>X<35BjRApuYakPy}S0PCS2~<8(b4a?mq>zD79% zn4&@&8)490wJRGWzDv*zQ@-Te3OHzZ#EEM#^*1oWTXysPs3w*|*K^k$cKStQ=mF@W z8p=63Bph%5HbQ^*_Y07@M_RB#*X7V%RMXwkSItN`J*zM5>6O7{=fy^9oqoQ>tKF1I z=3-?!w`;@sS49ESGQI5;{ya*-=Zm+Q452Aw7+cAzj{YR4+nsZ0(*%!)v;@H$Ga$&F z_19#Bo%ST=f^X<{6L$R4rmY#bsTa_NIwVJSyk;8FJf9xw>8kRszJU_apRhVFuiziC zI?gtI2@c%QN|~RDp(x2?D6Swl#2}OiM&tkd0wga+ddFI#gn{IOpHVtbSwfbU;7~?= zSt8lk>U{m0c^vRFFjG@ci_BmweL<~|Vhulnf-f3X19e^iN2&6vWa!xJ9gtsUw6Lz% zXDUB$ng#!wXfrZuFaf0l+ zAY~2$DDZiy3lPa@`T-K*;z^|}$%&Nn@AlJlzeNN0S~lbt-BaXwsaWSN{Nh+B662Rr z>d$~nsYFIsl)y6kKsMD)>i_0MFkKC0zvj3cd0C_`K3u64NJjKCyBIkA3tfiH9H>Y= zVhD<%C=i{5Jd#yfD_V`WQJ2f+ii3fOh-yQ1+v#0-?G_HDRAMgSW5|pjDK73ugZ{`R1E_-5LemFq3<%3x@W=)A|)% zg}&6`9dmRi;7wF8-yVcG*|FJ#mLImo7@zCkzh*!^$8@8X5ov-{5_+voQ`d$NMTj0)X02ls^-k5S{t@=$;8ThuXsW~b z&8ub7T%#-685?`?olnPUPRTNlu^9Zl^a%gWU&=1zI#FgK|1LJ4>AiMbi)#KJglUzU z-3y=P8}K%~$;t(;qBI|Nt07u0#vqfl4_!cZ9)UPFO7x)(=~I^O2XZbd@~v2S6qtN|IY3{Nb()x4~0IxyhB?N{D(O`{(E_#v}L5laxx?$2Z~7CE96RYZ{0vGM_5sg z*|ij!<0f^|1Qxso7+ZUp1o4985UDQ>vabz5`c_}*9b@wi92ccvqW$!gxrycPYgd(w zx`kfSia-o)gXu`Z$#YLKF7pca5q!Y}NuwK5!9-9Z+n6rKXYh$T%UASfl4{fZRq_9o z7@?VdGqa^0=FJy$s!R(Ee3xms{IX_LdD7-{3Bo90Z8u<|5AO2NN;in!meUVN&W}>_ zJxGq<8tWh>`(lhHw^LDEe!q@1NzNDD9OicQ6{OBWg(I9j3BQQ7v?^TLAS7q)nfbJzd2h2Y?QkZyd0#OQ32DP6QA5_0>{*9lfZ&CdWt_U2FLaU zv9;3j?b1%BKYE@NK;o<~=%6jkFW z9wAIO_CAwJb}4Ynj6)bzbF)A1EuR*n?+Dz-3&!(@pbdya2vH^6-WDlEQ@%kX$tNom z{iQqInC!F!SEiX%cXV-zOo|nDkTKTtz;{N9_248-kGRtGZCtl}FC>pdm#7&*861u` zL{;ubSX?9sSg^um<2Kde?3K>_EZ}Fy>sLH|-k?=%wW7d5;}$72vQt++-6(PRD-i*bw>)V)F_qZds2l9M77*oE2Iu9P-T$M0uqY-Pr(s=M<5@ zX$H9UL6InW?8R5i6t!mTABu%GtgExFp(qm+cSO%TF)w*TJ*xjL=jBmdOY0djpV9c0orSA_TqJJzyFn- zD!1~f5XK7q&;TtK#KYb5c6(AiFG_g?lRZO;S;<=53x|wXBO9JltG~~k-ys<1n<=_N zeIp2XDaDmOMe6qWbp;eVYV%&zJQ4eF2lC)?@P2bP8j-xG-c5v;_=zy(w|n`noM383 zetI=b_aXDop${VYh@<^1a#O_YA8T|usCz$hHXpNbSXvkZ-G#fzI<^@iag72ncT1^Y zbmWX6w`0(tNz_l$ZOkd*C6L35Y*u1VFt-D;=JX7iQ4B>HCKDZb&3XxF>1gAcF$t?J zbz|TozLp}|r(>s(RctV?JoIQ7sAlz#gFGOnUTj`WTa;oaUfQfNEH9j>pw^GThzJrHhMpq<_alxuC?~|Xr9J9kB zR(q-#7bDKJrR`{`hJ#j>{)zbIhI7%dzWcVFz{&4df$31u+r_glw`}BG^xbYYA((f=`_xzy#$S;#nlB0ObqLNBPB|f_ zjYtbOZA-ZCs^V5njDALX;n`n>`bJf@>X=0GYb^wf>ta< zVyo#3oKUZu75*HpYi06Avd~N2NMK0KUcdPp;FJaCc#o;ZI>msU&qL#R~?cjdHJyw;-JJW90XR1o@@%~sr ziYgKJ(Sp=^LJFAO1n+@sMt?m&`AJhWNvwobd);PuW#7& z{2l9Auq}wShj)GlZ>9P*mcx8G{OnfntJAn8jQ5yjkwZ~#-pv(0RvuWV@cU!T`Htb! zvSyA2COyFl5nx^ZGtxLACo4P5D(;cmV~`f)ZRPG){xC7mgKy5!&Y#r=6c`h-*aW*n z1_*Y9zw43%ja*?3+EnS~CFS)ENxiHu#k&ujb|O^FWXEWX-4_Bv$ShszR83$oEZ>v< zO8*QKqVRa5$u~~+?gZpj(9(SRmqTXgeZcdXxvENJQp6Q&v#@~=02r4@`$rCvQH%*^ z99LN;eHgw^-f;Zb3z~7#8_pU39>+%SE-Z@3a#`FL443j-QzcSr?;gawtvz_@rVR|1mukB>*d)MZEBOnMHxs1 zzClg-K`gxnP%XfVVax9^=-pBflyJX^$~%N$G83l6aU1!`K5%RR5FGTES}UJ!uajWj zk!LYWMV6b7`u^T>136xOVaAYhkrraTVcdamZTRFvWds!%vzAT%PWG=)f>m!Jae;z3 z$iYStZ0&LYY(F}k^%b)VNGY0M?Z^|+PAs-he9^AmzZ$&jMC5J5bW|z%d#;s)~ zn;;^NvfIK9m;`ipg8R!f1Tm_C1N1pDZdR^;rRR>TP3iq`xP8x~+WI+O_OQ(-=Z^EM zVTxyM@{REQFx=7$XOX1Xssf{|Tvam|V7p`UR;}>g&uT`07?0~|Q5%%kudA8$v4oyS z>WXGQgqfoe4V|-p@1r$*9(Q$??8;HW5>l0*X$t}F>bJ|c5eZ{8_My^gfGx++7uh7t zZesDOR|N!*W(RS#5L@i_=}&5d%7{T2fZ-Lr91bcL+VdEl#20@)2K?~YbObrwB%=}TA!>?n6Q!Dx6A;&r0vF@rxpIo zDKM|LU5;kPyqov;vX2Bgz*|PTA$pTj3syR&qI~QY*-+1!ao*XY4BYiO$Iq-e^lSIk zmp@B!rG!#mQ{@G4y)-lwQ#*0nwl*)ffnW~&6YtZ8O9zMWs{2gr%(0y?_+yTk)9w zckvVF&VIF=q{SFnr6$>XC~Lc8d62$-@?2$Y{+PnwnJry%)lVYvRTCX@@o=p7%48oqac*rMHF1t7{#7tOHEF1jgSsewk&Ui% zywFvaPOVeFD-mcb^p%(*nKKK7+eYi(i;5zftfg8@Hk=KLBGW}AS`X^ZncYU(`&NvH zaYREM9mr?;tdN{9yrI9ZA(2kESeU(mX_9g^H(Hsar#<5r8((B z_d;n&@F2uR9PS!f7{_EHq1w=1_S#R}1l7r8Q^CA5RIwK)(lArCThRRQUiPB|o$>D@ zCE4|`2!IUoB_JX%-7*4q_0x)tARA14O)!Pf$@`>QUWyTUp@zLI^V`BK^vgUJgX3C) zS)FGI9&;?SH8tYB)1|V9SMjuDjC8~KzIVmZZ>yX#uc9m{Wv!|8(2>b^Pic)G3u3B6bdqmVaY5$k-AY9rTH1VIr1>;o2TzU|2 z__ol)gIpR|<{e{|>S;XGMJt0S@u_aZ{-9cu^5EscaL%Gy*Z$xG)g#pl;kKmzgVKj6 z2Zi9vg}-wsLNdzNCvJ;nNJ3$7;-vNOQZh-S8#LzqN|hl8`<%cCn1l`xg^sAzu(T|T2#3b|SkIkyTyg+W)x0;x zj`S=1=p})ga)c;Ws7lwr7sRi}<>C0iAWlpxt4dUaI`S|LJo2Shc%afO`d(W#Qttg#lZV4uqhXp zRQRod(@y)oqscaz>a;7&emMjrB2Yy`1oyTHf`DYCCGglH=7jnc%)LOx*KZvcW9`RH z`;Xtu$_O!ytFeDA-?_WNrZT84QRyWJM{Af=Pwe&R(zTuNRi9jDZ_MUMPAqO3RvTJH zq*l<)qPjGGW|JKG(|z@WtT4`7)&2J5hd9K^cokG<6Rl@*{sM2jR0WtnvPkoVe3PG@ z{BwYTY%wFcw%F1{@x5{jg`a22sQZ_W!1?FWa^C)dlX2s)RWGlxRbo6UTGAIxigr}B zjgYtmx#0HTx>VDT%lROoJVoq)2ew8Jd9f+>4foK^OKOk;?Q2+xb+M2p-#||slGk>r zqnq6uf|y!F>WWgX8x>$2#^aP7X#-Ex0NeZCiFr7T@?Q1(qUFKQS*W9ip>nhKJSKz@ zrQQ-^Cy*I08La=Xxxo6?MR1nV#3a3w498SlOf>uiDY$!(TW1^^97z&Z*RFnr)W=so zukw-r9c!P9X)g}ZsLhISD;_P1M#D_toA+1LW~-5p@bA37D=+}34~=Wz4}bn$5_kZC zhuorePvaa3+dH3cqG3X7*~ai)Zbe)*yRV+Zp%+8E3!|~O>ze@%n;&DF++WyqdX~I# zSA#~xajf{CZh838W4@c)1QP}uXT!HQWVz#p$ya%R84|9>qS?HvTR^}BTQ&K3`A&;5 z%Ja89a#pB7vQOt0DUgga9by)SAUg5vHKtHd00ol%w1a?)p ziK(+@M3x$9-aM7d24hti3nSJt__MEaEq|^yq5W+~xet0jEUwOyb+{8a42smdYGXj7 zr9fY|ajf0jL+0_#>e_>?czAzaz(@iV32H95;?W2O0UH}Ph7TvVPo3YQe}Gp&>qEi) z7dfFmbu8id5HJLEI(VUzxL8`${5Y*NEv@GR6iTbWl}f6vD5cEFJZpCKs66ZPr!HPq zJM9>vGGq;YooQKuHL!%Axisnd&F3G}V}PLr+V+HHW+Si8adKrv*m87_b-?2B%2Z2* z&QQ)(c|$UugpqH3#PT!2WSVDK6fGl6DXD(`qPYvFDan~+(Lt;A3NaU}g9O6OPMu{) zqoz0}?=F0&*R9KP^1oGsea4oLp?~S|z;pA*Q}f5mG_d7?ao%FFj;i_U@8u@Z0Ax+UvQN}x5pbZcp4~V|{zJGUhJ0^#w;lZLyW~iZHLrQcDYwsw zK%Ku077fn&M`#xoJclR_&nL*Xty;SnUWFqJslF_vg`WB+RcARz0v&#vgK_^6RV?ab zpvtA92p1U)Sp6?K$T&M3kJSL%Wgx*%s;)&da0RLXo>XVP)ufPgEsj@l3N{TndE1ED z2#-P!OImImBG1U?Qr*y#bNR3uyOh?EGFyxj?!P&I6 z3^TV(ANbl%vJj1cIZH6fb3O`zX^Dz(-`}H?e@EnMFxJ);wg#FFK5-o_p_WH&h*t4l z!pPcE^we4>gt}pJ$nkTF+_bx&?#ks=@kpW(Ah9VWkB2X@hDyxi!2&iv+^_sOQ(s{Q zzRcEz75VAr8SklcWU{NUVKnh|QA{Oz_EkrO{p(fxPWOU(xYUsYuufw5<8bT9HFt~p zDlnvX<(=iS`r;%FS*Fhg9A#iB_tU^Zx!l;JQoJ~c%o9@WP7DrEnc!Hw?G4zlQEpFc zNL~|6>7ft7xVO&PmI4X;d@<(g9pmVCHH`wCFO7cu#~R$u+TN9p0%s7DeX?w56tR~g zGH$$oV)kCsAFB zQq7Y>n&5b7|Ki0k=1GS!n=<6G`Elh8BRQyx9MmM4*=jog%LCjYSY)%e^?9Z-HaHni zDx6KwVr+209hNkQpT8IMG`#No{Ii{=jE-2#{xrz~$-vM>iDq!H+lBY!V%zU*N5-k3 z67+M7R1rckC`Ba*z~_Q$VKM6j2UBw9m2~YMnsLXV^FeM{NMiaTck)$bD6$NFrNJ*> zZ4Lu-+E8}?&L6lwbE_OwWx(+;Dc^SJjA`NWR_?rFe8+q(Fa zeYK?Ck_Bh>2S+z|g~~1SG9bRbFJ0 zm1EhH8zgdp1@+}F!^r>Zgah%cb^qHTS{2u4NOr*54R~cZU$)+Cl_7eQHnH#)yZ^rU ztpmfw;ejL{Js@-?hT+i_qj2BT?mzaHS4n_8BawRd?aX-;<<^2Wf*hI340F=TKWk!{ z$x2CFkbr$ElPYuFnt28c=ilQNb26{Nj!swYO^KV?o&g(^aV)2a0JbvQzIc2cqpzRC zp&P!8I?#kcyDz%k0zuYwn5dI#6&nm!*WFRLLbsZt=7+a^^Vt3Gz(B|Lygclis6ZEk zLsRCgopHsSJBNMc&&w=B|M)Nwn+=1lf1V3XkX09t6wN*Rgs`K(2E4*)09o5-4sm`Gk?_#UODHy+_#lJx=Y z-HUvTFWVPX^I#5JgIQdj?fdx2=Ru#HU}YQIgVnyXZ;iX)&oKt3em;tTQl^xDbufYZ zoPm4|w@Afk`Q+2|IHOd@bq8+>ZxrnLVop1K(fl zr~r%n;A)L0o0dUqUPq+ia@;hvlx33ISkW*Iw9t2<8*s!41y7f;nAN4;AYzg|c)nf- zgjqvm@dTnM?#+7cO+*cWFgl1|*yzyd-76g1p5MI089kr9w?WkOKQS*GitJdRN7dDa ze=+3k;0c`sEHEd7O|h z55W){Dx{WEGk1hhXj~Sh+-I6j5tkTOmhCNTZf@hl9i)zSf23x7{!u004)RTT#Nsml z^!SE$nHdX@KEkbFwX^AykqnhyTvmUivD;aHiXnz3*8E5Li`}L{A56D?#-sgruW{fU zM$5(ToR$zd_&jXKoLjod2<;qC3bcYW##2v_>gFE7a+A#lHs4$4P`ZL7icHp?XNqsi zeZDK-eh#DK#&)PrmkMId=p3_uda-dOCom1-yl)lVhgU~y9|**s{os#2VHqbyxLdn} z=oLiSUGW0f`vPVWWFN$V$V}d54G;K-{fkeVCF$q+I6=NQ)}VLyLX{(=jpL<_bax{u z4?O)sXQ9j_P8ia(bj&ZZpiSvdR=H z_sTKS7w=xLQEpnb3PFxyoY~zqO2J#(M$!)6#06y&e{$L6^8o{l<asB}_KmQ9TQiY2Cwn%OqaTaD#T#jGIv?=5p>@^)936`xz^Q~d? zM07j^dNB!6C4s)dtZv^ZI_pA<^i>Qk&hxI#S(gQ(-bfQ$DVJin)ZYp6HI->>}4>Ubs=BR_sKvmsZn?E}3;<)2az%nBHLRRb3HNSk)^tmuffYd$$p))C8`r$=+B7x2^8kOsxJKVjhVjDM|O5 z5PWqL)y4v){*Oj2wbG1%WVl#Df!bFggld>cut`s>Nw60D?bDc=JwVLSZ4jES>S{Ij zA!6Bl20g-8kKCX_n(!>Xk=Y-`KajF>zKo|SFU{WL{E6h`wNE6^TCJqm zB`)h$_Sk;HS@RP3WmVDVJgA|?z-Ydlt)POxf8P=`qWIySNS-D0w89)qd=L$b!!p#^61D|EUG`p2O z` z__^m|-%i1EZOMI#gJoPo%LI{Wb(H0HeT=EO4B07I*BUF9WOmnRAFBVAFp1dj9D$AK z*(ZQGU+n_&6?vm5*Q z-^~&K7M-irU-s82i1TisEPnKDmcZ_X_OHF|hzIAT5eOiu2~cFd4yFoUK|{?@FSNTz1`xhA^!fyjuD z?G=E@5@U5Q`T2}}Krg4U(IGKE*LcuXpb_A+$+ARnT6ANF9eJx*YXZ+WCka`~Ci50E zA)b|4DbFL#&SO1F>3`0g>LV?gxWdT~7Sx4(F2QU2-eOC6L-&&Wj@493k4c~IQ>8;G6Zy~I?(?T5)-fcIV=?o@YN zq+{;!qzZnC(FUuWUbX3RMe>18q~*Ul?DbV*y2-h90PcA)aNHvH5D_e$*=OVTC{Cil z?pUgT*gpF8cyJ@%)`1L7PW_NY$s5i;u&))gt&4Y{(4dLYB%e+!o%E5RQ0={8LP)!CO#7 z$oI_OCoI#3&u`kyzi#5nnaE#DpYlkn?_uL!)tBE~z}E$MaS}MEvjfNYNs5uLG(~T{ zJ5gI@3h&%(IQbIIihLP8n7qrc^WA-QE-YTY>g9Q)sbIN$N_L)0UNM11_TUT!D>9VV2Dj*ksQQd?+bB9p*qqD8){1eshYLs9mt0Vz?2x&|wJ zhQ@ZibFBr?A(s?I1Y@I5gNc}BzWjY^K%ddH?+w-){6DQq7Nms830z(e&pb6^@pi(` zMdSGgZC1nd(s%@KFa)Zwm&ZAq4EW73E+Y_KCH#Di-|LMUrhaNM6(rzO6} z-ZN!$dVB&i-sg!e|M{m+$&KOfZm!xg=#(kR0iYPYKudn6Cb-CpvP%lQ#%W2+K0M#E z&9v=%(M67&?gAEYiEL|;MZi) zb^G*PE(;+jJ5JkTqJWH^7_U z0fIMrL*ZPj#(J}FZ$akI&zbBSN4Px+1i^XD3YB5|F)=aP#~s#aD@hCA?+*uE1^=Iv z9ASwSIq&rF!0u(F>v|3SRwW_(6?N#XM*Kb@5h^)vnvM7&&n)SM7)HGYAIc`Z@M(Q~ z)mF0Ln2Y-R=i~Nli#fYRo^weA0G|U4X?5{m?m|_ck&qEGhs27rAj;g@;?uIRa*`)a zE2akNAB4I;c|+QDg#=w18(gOIs<^Su%|BP!G$)A{A1-R`5FOW!>tb4D)`u$QYHe^M z^-niru!vPTk@VCn(H*eMxVPpIC*$I zR#2WW3ZbN>Hjvbl^uPHM4?%xVM9SdSk$`9NY6z=^naWHTa30Es{|LXcs{M`P#mRj; zT43nU_GRkG_MBM*L%%iQ#!v&!*U-5xK^gZ zNDqHz&U);iRGcgGdmHM1Y=g2_B0@msC-|r5*#TTexjeux5(Lmu0Z(Vj!cbJYg&Zgyl2PNNKe2XWcvO zJ96!Kdm%w23)f(q-A!nun_k~E>sM^jAD``#j1=Bjr@EqD3KSQ@p0NAHAlYxlC#=b0 zTN9FU7EfAqgiILy6K4s_gIVf%uvvyH;T?=Iud=>Ye`%cc zYg3fdG)Fon*6Z3D!)jhvRIWADx$vOA4>5CxgYERtxt4xPI#Y;!pFs}c zuKx^%B;7egJ?@{879hgim2#$#s!`jZ&=8)Dvw-zQULnZ5#-_LlnZqbE`S^f-o`~*3 z+L3xwd>CFcgh9ltV=fN8;xbEim0VLG_+UDb?zG$bY8>3AE`k<=_Fw9zz%#Gyc*v(k z==*6Ak~LLzzYll6F9H8vyU^EfCh`0-rHnmHbXNaA^>{s4#F}g|SEyMTKCc=k{kyTg z*yQXT)|F$2_Jv^855~@nm(9wPJXRjI>uf2(`k@GJ3L2C-Z8|b;e^f(KrOfmgn5^>;`#wr^T}{x1 zehHdGiW*`J+n&~g&=2xHTTHuWuI?t2AGE?LO6dDc52B0=%uc^8Mkqo$QDac2udbfY zQ(B2EXuydXaE9OEwaqd0m?HjJDepI?V>HzDXmKe8oEjJty@S0GMz$<^|Zz2YeFlbH0Az*Bh0bZp&e@8>E+7lR z67MNUH9V^)`iGcx)|f+V9R;(%!0xcZSPA;1wwJjr_0c?^8ZLMj)8Hnp-+dE9x}gU| z0yq8-+M*@BwdK8Ql-Qf3IAhSBkjg8e?z}8G1OEIelPG6UGyME9XSD; zKq`;7by!xjCfq#;leTcd^xXEG(24EXdvM%l|Fqf*?V9HB*f&D^L|TMdfmfiJOZYc! zIn|M*&w(Vw7@f3x&UmARS6aw^pl-s=Os~o&t4fv1vXgZWv!Pmk$a&RVuj-@k*Sa@I zc(=w1|7ld)T968Se`G2vi36sXS3y3LyKA+?n$jHzX0vJ zS0t$(HQnb1$7;Z25!VBRTn&on@EGPKUksu?sQtja)*hDSJ?L}P_;GqQbTq4|yGe>A z04-#+cK0{@8$!&Q_KvEkGuN{gz@uHf$x;eM6=lZ*4dtJS&Q}+Y=`IlAArVAS8Ajab zUw7UBf}fjCuETYrG?r;?80k{cv+Y{E0*Crw&p11R0@$ZxZ#JgCFOnsh3PXawKy&j% zuQh9%?|0uC#k<)E4Q(2Rc4|=naWAF0G5Ocw+A5xQz3aU`dTCt4XBTK);RE72N!Uly zNbY!46-}}s9nUZB6=^|}3wV%rJ`)cIfD`${YCT2l$r}u6Rp=s5NElYqY@r|q4cRuo*2DBq5639rFk( zn$f#sd1yo;8IuNE&{0PUNT{w%Q=c~LYg0|oeJ!4QJ3KG&_lhf~Ym(wlS=iNka!hA7 zr{Zx%h^A3q6}9m&jN%O!`6^p61*MBI>qa~#v|ABr%(O$&ujY!X~4Es zDmBWs6u)L3;P#>+^aXkDr2NlV4|Ow}RgM82WkU1rb4Jm$n6wOMr2BA9#)CDuM5|R5 zXe|PbN2*o{7R6LDeQTFRt33RO$#|bG>v{hudg1u%=6?Q-sK{a%w~Y)ou@)q89p&Y5 z(;z~eU9`(7Q33QGyEs~nO*gwy>^&#M7&0c+zv>1~eUg9hO2YK&y%VdvF*A=9J2T27 z9m<$r-CfTN0}Y|2H#rrOuizQ~oM)Q^G!j@aa4x(QclDOm!GVW*@2+Y+ydSfnYA&=JEHiey(@e*{;CpXBc+^oj8M_l>;(4{x+B zm~okRcHh^c>fpGZRn7xzp>Fes+RUg8<2+~>bc@D*HI5r*N+dKX-tZHRtBToTBXec! zVWR()c}`3X@x#+h`t7n`SPsZ7`*IM*-v$Unx`L1Fu2L(jcr~2qBMYHGA-`lRj2b{y z3=-(Ahg|5Nl9LkHF=XTZ<*Wea^8eU>y>?VSQvcjQT?zVQzuI5h#}3a=F6})+Owrgp*aCPFf=X#?mh_~7#E!~c<$9XBbc3^AMSpmm%|;Nc zN1*UelH_1nma_o8Fd2CHgEAMsrj^|?%unaW0nKHH3Fbuja8ep*(*t7^JnrT$!OPm@ zZ^0C5*+%EaUnLfQA2WXQ^cCEib#pA3r?Rf}_LT%NhHt$CZVj+}5!*Vwgfp_8$>9Cj zrr2lwM=$BO^iY$aYhBn8InFB(C60B{KPJZso*mEnTLE;~aDbKd?5v#~v9xjtOTWfArItY2)~GL~|~wsf(hq^-FNJIlLi zD)5LT?TPrS&y<$>4Z z*C%myJmXfjpPq4JpY`3zHw26dg?wT#GMN#7z;PUGEoq2a7i=7$L&M?a=^xX zV5bBS7bZ)fvQd17q7KdeKoIsa;tst0WXB1V@mVK-EU{5I^iN_9nM^eu-Eu6o8K^d> zoHo>cuQQ*5AZVsHuE>F$w`o$>S%d)QG^pq@VAvyn5t5reYbrpZHIkk*}L(zPlQORvxU@N~D0<3aR94e_>QINyzR-^cHcrI0VG-h~&hHGy zanlZfU`i!Ll=mWXY8u;5S|Z5lh^jHtIy#pd!j?UaNl6`w5gA&1wUzrH`+H4cO@q)jZ^ZwAW!_p%ouRy+MTa__vZAdFJ~py` zQcGf(Gr%zUt$~@uP}ITF=A|rXuY@}*pUhBXrmTZBaR*TR4*+WR%JzvlLQk zo#teCbM=^^jol_yBxdii0e{1FUApS+#$N1oU6CJiEwONFov#3B{XTHqot=g28IY!%fz} znAvDl)~qo>tw*m}*1#2o?oooWxiJMm)&^+L(&j4)m5#mN z!ea#8&+Y>*0`T_ot(p8N*Na3X`yGc*yJC`KsYvW)zcLPmeBm&Q?~aJr3`zF_mwNuBz74qSKjd3>w|hGbhh?BBz#ZYWa{vv%*8OR-#}Z z>EM@TFxhZ{w&+poB}Qixy`u=!b=@{ng5%p=5IyG6mpewbP8cpWUUoSIF%!7HbVN9^ z)|DxL1R~uorIpia6eSl_2l^XbpC#%KEoto=^2}$GH_^tFe2H_NnqXMbm(Wj;ZIJuJKqp zRKJ4foySwRX)r3b7>8K>UL+(%4mz=`Z)9KP2Kdq6A3UsjH`!ZsT)!>0=YJh4_#e|j}KL1#Y^Q;OzfqA#@n^K-AO|6 z=oK`MMlO1+_{-m0Nb=jf1&O^H_mV{k8d*IZ#RaC&e;|TNWrGL%X{^W@V=g~G$}J;5 zAJsM(+*VecQ|RkPG7aWMcV_&)^72{GoKR72<54nY7TLfLhzF1eTuQaBVwcTc+z=t=Gy0GaH%3yfAXR<)nw4~#>k}g#(l$GDEyU5 z)wIi`%QsN&*F`6M=^YS(?Ta2{o7@s*I^(v{-p4v89O@j?=PU9nq|UEfN-#rd$T;^*$VS5}mo(XBn?t>Qc2oh2)O|O36^@TF zwU16&qu8l*hL0CCe-p$B0u4^eAXvie%OUd-?*$`E_=j+fx}Egxim2~e6l?RUXh|&I8M0OilHSZ9fL1-|IJFA8JM@L8-A|{! zJ(Mr!7oobvums7jZJrE-c{~F(zv<1pORx5g`SXNnE3p7pjUi$aAx5^`#PlyOmzzY& zHFy=H*y^0b^A?9Esa2LssY4W6bR53%#36?7m^9Csz2C`h^;M3*m-A`TG2&}5F5UHf zKeWA{vxAEjj^Uz@_?_&M%Kz!XTN!2jZ!^IbU$7x0g%Ev6g*dV9EB?2ynd=Y(1O7)~ z1!~>d35X`OAw z*TAjJdB2^_HKb=kI1S0Mb~hRFDAY6?5u7ja3@yBuG8bX=$%_t}Yf|lWgbFPYYrClg z7n2EYp_eEFq0b3avb4ClFV(bt;bjj~g5>1eB(X363!>1}6`39^{cBit%J(j*@_O4S z++Py&$Y3kfZFYMWuc*uTEj~^%tZ%|s!p9bga4dKP*(CVqdrLXY{jAkC-|Gwa;ZkI5 z%!L}nomQO@P$QYve=A1S`62)Djw6*c$+veh>rorOy;AT7Xu|-y7D{qt>CB6D+hC@cm}Gx1`eNt|!pk zD|L>d&#jI^rCE8D)U>ck0Ac0>zU<5vHdx^jbJHy4_q*7z@e?@u(KsbJ+UPi85%eJSruaFD&h=4 zD{AOisRt5p5|+9l&?he2{i zAE(S2xz&AThty%nEFwpe^Xv;*_^4KXo@Gq|--JSsY}ruUJL~Q2)6f_tN6)9HhhuWZaJ=~w~QTohM*RvA?_kA(|7tSw36NoI=lM>y+*W4RpQ`cp>{C})MQ{j8r zTACWtWpxScRw(xBDv-LPLd_Bl`gV1O5<8w3wmD>+aCRsb-#huxr4YKn|C~!HmX4z| zTiHrxx)(HrBo`s}2CR9XwwPaB%8AY{X6V-CbEha2I>U9T#i@Q0kCi<;1xpB;Ie*|i z)4P-<^_8WIndbNQps)l-)2{zB?J`DgNWGl zw;D#kAEFUV2#mY0y(fsg)8ae(I%jYF`D5QmrFWO(h;#SqhxN}q`qFeDa}^vC6N`Yn zU?{9)1#<8fv|6`6SN1m=KQZwA1?>Btm&;>i97J)yG}O$z1n-SRd1y6h;!oEXp4Wdj zkA6mJBoVU3e`M1N7V_VHI9O5xUe`FLDX+Hp_WTN=E2D#gY9C)r z-uYJPPJz-Ir2bA&_~4|8rpEddsu0v*X<^51dD*y#h>_^4{D`8l;laL~=4QjqBVY(- z+-{^9!WFJvs#s#4EOVdO-)>^(d)!1&(r)1_p9+}%lEMTL^jL_U-bx$Z!%2xH~LwzpA81} zO>%a@t#vua;s#6Mej0{>?`XJIX9Ff)t94&M7l(lDnOWzKRC+-poAu$gJF(ivPop2o z76d{02eMj2qU4PyKX+6RPnThi+tW(9~=}8loi+S(a9`_vy;(%& zx`%FGixe)9LWw!XCNyZpoP@S{@-BFHQqZ$8NMchE?J`x6SNFi8$&J1w*m@hu3Ukag zB)qJ#o&2AuY!{GGc?5fGlRw{Jw$o6W=#k*$q`OUg`eOk2hLmN?&1dsPsixtLvw{oe__z8z8HTUl)Vw2=2xes_0tsTieL2N~`~ z1~(rxk=XjOM_Iq<__F0N!Ieb5xbw)5MB?sz58_?~Ogzig7#Y5L0o5HT@>YfMP5jjY zjZB9j+NPx#c~AqsOUKaZ0Or70jrlKIbfvST-uZpm_n?9gw8`T|;KWMnT3Z+Lg zKI%A=KyTY`dhMw@t~h&Dt1Q@K=J3PS(CJpGWT;sItU1gbSacX!Z%QblWw$bKHkTS_ z`7l8%v{mvQwD{vFZG$KIy6M@fDWlma!>BU!KzxCtz68<7k?TI>)RIAy!X-L=)CcwBxK_GJmlvQOtHU=s?gKgI5~Yd_xJtCHawq~0T?fmN(bvzJ!5UiVm|cHy z$Q)q>p3O+#SB3RP>*t~F(`&m>EN;H@TNn`*d1w|P9(U9o_QdaiOJ4kqqo5nX{SLxg zE@ClAD`GZSEM_vmVCf52zJqqwt|9}nARgU%n%KNYZV2QklBeLg%h^-eQnA@>t;71a%K+e0mO38;vQ`PuZn; zPdm2tQK`to;TU>EhX2fXjlepulgKFiDI+5A-F7r!lVIsSQWfaeWL7AuiuvyDM(B?p zE5*JnXJ-A>?Gl&#cJ1f!@Lue;peQG=;Fcw9?-5!JhH>yf6m!Du6Jj>OPb3qut2ykR zJVjm_%mBH8APT2Mqv>SQ&m*F=n_}YEalB$qGTslHJmYx=qyuhxPx`yQ3Bx|?i!r+W zwcURJdxh%nZ4AC|_awT=Qv>Rz>>)?jS8>99`F`scM1-BpM8ciSy+ONjp2R1lugurq z6ApPNUvz!cLV`C`iUMwOfRrX7myM3!3$KNBqT7uN;t4e>o1TlVi05QJij9dg#iw5;u&U@%VV*KIlIF#+6I22|vxd^;} z%ik>UY5uJyx_>+6%26zZvRZ)^%FiNG5O2R13;Sl_CKqw>j|HX0|II|fwk?)|`$w0X z1?i5PrKBMKKcdCr$d@;p_r(Lgl*Ljpi5k@K|0>&m-ty@^fqj~KwUrNqGK!zyd?Y%R1gAms$!E$LoW20H z&A}(*r;M%z)$RvHOcvjB?-VjMQLkG@PJDS9r6gg|1W_;A^jr0r7%T3y%a zY6zl9lN>@jrXcfCAK`^<1p1XBZqXDlu!s=#XdM84fzDhXd#0D;G;8+?j1CBH-tlgC z#sQa11)>Xv$?aRU>it$}qc!5L`<)k}?7fIrxk9Z$g)%es+V!HZcslhdtrE?fsx6PF zsTe*u(2K;SbEk+LSM%lMFEes+~v(&pSZL2PCnqYMj@Hz3ls%8G(CCky? zh7WYt)!TQ=IeDdZ&tM!eHM@PZA=hE>#6SOb%d)0aF~$>Ltu|CC?mVXeo%iQ%q$*jC zqeiOohEOEo>|<-IQHMbMLrukx(}YQ3EdC#A)k_b8+dT7U!qCE#1KQ$$Y&OYmjC+4? z{CZpPp4{c(Q0eU9Gcl9ponC96-Y}uFNMr*zrE!c}*_Tj;ao6dtJX+c3o3<{6RyG;g z#!p^e+#lEjXWxE_G*o0snuuQ2wq+hI{XW!;ibnSCVjrj^NV?WBzCZC`cVD~-FzA| z1c%%eN^)&4U`RIMM?_9yNSnH&fe>;c~nc;t+K2EE}`+CR>E9-de6iX z9Ez(|^ZYgn;+b}^;6=eANj1Nv>LP73e?6vu=Sfrg&@458<VSPjXj6Iftssztl2vd)1=k~v(C5!1MHhIm#1wh9aR5_ z_EwrQD1DsMXa9{RjUcGt;5vhP9@<*q;n7l9_e|qq-j0$`^kkvz5@*}r#1^nyAoueb zIymzK!A&G z&;BQ6!O`qFd(}QRVu{?U$SQkxCPv488XiCEPrW=uf#Pd|p`bZNG2;8bk|c1{ooL8@ zwFoEIl;ID~;8&vQ`H*lM8{=)0nUjhq^=-;GGUfFZ7D`9vV<}S>r_O#asqBJzaJJdH=x34kcL}m>a@=bDGXlVH^M{CM_4N)KfZfnKf z2!5rYLul(Y=7)*T%bbT7N(rMZyi)^;WbmJt7Rah4>wN}(s=O8JJUEBlWnY`vvQZ~M z!QGqvKIB_$qF1uOF5d+L8$Tj`s+@hXQA%@ihrV1OT0co`k^$QnBJ!Y1bb`d*vmz}l zk#Yd3YaHX+f!8;=^tT_A7^ytPo`;Y{$PPzUkoIQMPL!;TJKE3(R#Z~^aK{u?s{6BW zU#LZNziJhmi|A@v+v`S!J;Zpw;TRVd$)!F%;VRn{Y5tkWf)ReYxF&8}#elegjCprQ z0{2y1BqN-sf;)-(Ib%S|-|@it9`R{|)6LOs<3X|*$;Yp`s`rm#u6cpxvRQ`3=1pdR_dHOk8LHg72At~u3 zFa&XshI9!S!~_U*JRSrg1B>N+qP{xnPieooJ?%n$(iT<);hns>Q}GcwQJwk zzN@;Y2`r-lto|1iTyU!mh@mD3$j;xUWvEODV2*NvKzf1iXHFfeKx~jGN(9va&98v( znZ;VYm-jE>_A{)EXb@<*!1t!(I$wTvsx5I?^6b_VZNWg@v@43wBi1y^1t^4>9O)(r zBNpBTS1*-!`{BYfWhbg9gwGwSNt^0{3{U)~t(d#}ea@mJ^qX_-nN{+t+R^h$?u>eR zV4L32o$M|zVdQ}J)WSAm2hX13 ztqf98p;%*cV&Warm+&2IUIzkW_^i4nK%N?#Ef3tnOY2+ z^`rI`S_pna%}H`ZLHWbHhWj+P6v_4hb{KV`?6`&mG9FA(I?(8$Eei-CQan=gyD<0I z&^v%WPZK0`66~H`Sx@9kq^H;jpkD6Q-B6{MR92ueBS?C6uM1qKsru;MB?p`Qp z576<%gOtSFQ_MFkUFyJkb^KhPgC8`X3ELIw;8HgMIbWUEM9a1oxFZqke7)?)0kr#}!XQ5QgpMdXy1k;p=^D>$4?ra!$Y2 z!PmftDkUj-?^GURYj|N`iB!Y?YWD{zY>gxDQ-A7YWdT+s+A}7M_@)Ou=E9D)74$Ww z+01&*hLdJ7inXybw7WJCA}6v{Pr=mTy>r-&l8SGiSQU%m`afkYrx%*uVFI8mzV-uUJiw3X_kaHWgh}uT#p3tTV!W`$9;C= zb&Z6LAB643;vCPU%qs01-QL7#JrHowr9^;oW#Tu~wPGH8 zP^F_-<7__(7p&7jdk3n=z92glO$qq)#+IkTk>WTp-^**PHCkl)joo6U1RiqN?jst0 z2&h8!mTD1M#+U<=9ye#@qH$JZn<}T*KkvemZ@%N&Yy+i(EayMEj?X zU^nxobAj3nJEgGKn10_MCatO< z03~`p8GnNIPb&^|6@Sd+7)p2p-=Jon2$npE+X7{^$0o2&)Yg_)l^>LP;hs$Lb82&- zb!O`srZi4S{soM1*z0=~J>0WHx9yQ@Z%Sw2pAy-&v9D%7bAyNR5zL7Nmf%hrUwP=B zBe|65o0Hz)Py!G8=U2vTE9Zv6t^-LTEG{DmNS*d8ky{$rVKB>9jqce}wMt(aUy$E- zh6t8Q$-4m8XfzYwPmgOFE+o~a__pl(r>L=I;{gN;!|YS9Nk9<7xgN%Hg30NPW?rU< zXE#2rt%wFEVZAw8jCEq3w9gk>UJ0#?jM6R3+~J3tQGtROO@W6HzQFDl^K(a#kAWNf z?m)Eu%|x(v_HWoe$l$#gwNf70NP0xM_%|KYX?Z}yZiv+0j}9lmXW;!Q|A1S5E8baD z^5kL=g(M5Bv5LlMr(ya&6lTc+MQ2A<%7zwxOgcVt7QGVP@+ z78QWUCWorR{i{|T=%9N^He}Q2P?6iCZfiR-`9eA*v_9$8rp%#d*k%?eApa3$2RW2o zJTXBLWp4?ZaAceuwxJ#FT@8(dd&IW`mn<{h$iGc+AGuF%&#EV`T_?DK}F8s)RrMo#z8_HB-+C z*OqMSq3@k=i;wO#ritAekXJfRJYU?->iBK7L^+ot&08$vs~PG?LvdBRCox+bNX%fB zYASn)rhpm>8fuhx_g2;I=wAccX9M(BOmuctzY{8|TnrdD+Q}{b6|(ovd_{t;uxenR zM%Q?2FOOC=OG6XR4@Yb<9upbe7H-Pc>)!UK&G|m8u)#bT%!*y{a3jQyVl1(wr{0<^ zL7J+jz09uMZ%s9Prra9ict1upTyHFP{`q)AM?5uNG4mO}kG}ZZf;+x8)kLthbV7&Mq%fe2~g5RE^R%6mFEw zbGbU?SLK8{=efzxN~dBJiP222m(J(H0d$p~gEenpX^HI`er6}eNlfn2-kg)SWEds1 zJAR3Wa@+f!ynfF(rG5d_76*rN=5|{Q;7Js;ROPAn^v5ra061)29 z(}&=()St|9jy%fovM8ar9ounPf6p(L2r5^5)+zx4|Ib|ZmG*nl_m(n zZQK`^e$|i+tjF4JSzHah(NKHw1j>K=em+nb!llkyo(I=P8_sW=7+lDR(W^#IdWdpI z=~?F3P*V*8CnSGtj0kLMG!8Z23uiv839R_+eW}=r#RS~(v{@)|a)+oOELQMuDMc?6~aqHSLK_3U>X7e;AJfbN}4N7@)0Ktq;Y$CfTAGT`(YO z)S~-9C%Lf1ygp{-PGH+Mftq5gN#q$^@`~I^p$+~Eg5q`>b!4qPkMP)e+D+N*vDI`Xuu_LOm?RoSL+X#)sZ=)@GIGo}&wey2yE@(K>AKkN0# zOv_@6&vEL?n_C`3ciZs&&mE+ldV=tuLv*ROnR{=Wiup=ZJ6a_6tXFpz&nhWR2~RP1 z2}Y(O3R*@cgG2h>(?r|kNO)8SqM$>SG`+b1pQ<#G2Oz@$r!i;=^?}9D12=FW_2nno z!R-A?gogeR+3bv26i^fBP~>$hoIrnVeCW}IS6j@t+uYI8i`dDMx2(H4D6;B7{r-SJ z0Zk1aU8E;`AYM_Brqt}NVd`FhcM%>l@-nVofflg7iQoOcSU92k>!Y#CD~xJJF?-Kn zq6sRt2AI+MFuMOL$`zo3XV$L&wH-%>c#RA8URj5b-P>up#oXUVgFU;xNC@%lw#>}1 zMc}#w`CPLHb8XXUW3|SLfG(zvQ)#UlU_h`J2>O|6j;bJ?bc;g9<3X<#O}uJ0+v+vzysLJt}Mi``Ir&Jw=0dAG0Gq_E4Qv& z&T-!wRcH90=4w(73X?(1+_oJ!BIwFwNcI?8e>MCFCK(I9UgG*bY`_VPy)$iT9#s~l zRqrqi9=I+6-Eawcra~m1a%p3q6+*6&oziw_BK0cY;Wxh@$E;h5*JSFkre@dU6Ao-f z0B3EitxG<_pOV+<%8J$0n%RwXOa+8PY2>Y@0YvqujTrn&yy{@riRA3wM~DKo>9)(t zD!%bHSwF=F4%M^jskXJT1cwy zg>uv8Gv}Jsu@HFcUn#*`N4{tOrC1O^-d+G_5*Vds^~9_z&}p$@^G89Y)*G`Zn&lFu zY#{4y8CNZ&w_tUXlKO^6DJnI4Uc|jt?Mh6ehd3<_cPcO+v71u;gf6$WTSBgtUD2(; zQAXa;e(GN?g`{$-JE`wyYr&4V$!U8++8F{ar z{dcgVl8IH)*?a7uzDCv0NkW($u}V%+u`TG(%km%Wn* zMt8jEFYB%2?jI5JAGHAALz#+La6n(PQDIqrM}AGuPlzT^45m<`-IYb zhNEutr(H%jGs7nnc`Pod0+|1Yt94r*6Rn+vT?&G zjJ&1C{(>!UUj4W0C<9betrR|rPJd{~R@@gC$H-QemKSvc?NsS?pD;F0ss~yt<`XdB z)A+~kpr>@#P)cs}XptN2;-c<32U{nMcTqPq&=Cjh{;fO2MrlSYgIsTrHTvRV<-iqN zyB*}P5u6b>`BOZz${+yNF1XE$Us9YM(rnm6X|@hGX&Vsm9h6Wy!)#LRI(2N`tc6$o z4SaoRv*dT6QF>(!qBv>2+bISIb)kW~>61a6){MBFalfLl2nMSFm|&-oK3e$xnZ6Dh z7Obs=o5y&-(H)M4(+wf6OW3vt9Nx&jyLc4tVDglNTj%Q(w>K~(E@p%QxAIMT`r~OW zvz$0>J=SK6=NNNGaUp%yNHowQzg+>367rjh=nI5mNFszh7<%sY$;>+M>V|!*U#n1e znT*YV^9A-2^3`4PcP%(bcd*R(XiQv09L+VQgk1RJoO;OZKfa6op)7BaJ934&@7eI= z9gX`)*%H`LbTokP2>5g_pCReL@xljfq0}{p|Z~5t^sefw%a|f zr7G5~g%xOCnwksUaeMcK@3?SfWr(o!po*arwf3YHE0q`C*7dBwZKmp-B#0|y4l7m@ zzE0?@cu4c7{~`|m$lzZVCFEn32EAmLN(fwZ(~xnNq5(^t`uyErnX&@63+EAq(`cU{ zzaR;tl#_u_%!0T;Z^T*X-xq;J+Lkp-ik!SJZ#g}cGoah>PUV*drXKlRQv~}0a#Lr@ zI#~OPgH;Ez1%cu?saD>pR`fFB!0$AMCqc<3S+e+-IrTRL9=O_-KE;)Z_YN%R9VCAD z;x~ibk0eP5^oI+2-M@b;s=VCRZwPy#)>2b$d}9ECTkdP$7_4vCV`DPr)G~%4rd8_J z^3u`qwqgR&@!L+!@?au)rB$(k++)m=>^$)M!b!m*FMhC-ZC2@v;R>42>N$b7+<#Rv zMcfcJ=k-D&{68^?2gN=7JO8|jB6vb9f26kx1WldlCSKB0Tc8AWk@)>5C&G@u0_~A0 zrhwDx;BDG5&;j-6-XpEvRv-5<2uky#C#WF!2MUxRK1w`MM=s?uSP0GK4xTnWC5LiO zWji8i>x!Ul{|^qHhMHQD-hH-pb+thze*Po#P0>QkhQt?VeCD+u{d~lfW;%!2Fh&^H zM7c>nvE!m>c;uW%PvRB4Pp-Kx$)&ieJ%C@4a!z{(RZA)+Nr##OBg-7{9AB?q5?E?a) z?jZl*plw|a^4)lXBsTiiF5<~J*)F$4m$Wwwn~f>C!PM2HU|>YfRuSvkZZMUW`i+Oc z1A~9%pOY{|^)SIqbZF(1F^auLuJ;ezGBt1?Nf1FJg~fQIcgx#-B(^Q#@Q%KH(dDKr zqs@ph%XTPxttxoz)cip#zC~SLiXJ-Ge2XgZlROv) z1wKpKYb)h7^PEYRhiVSnY|pE!Gnk^&8aLNWM)++}#`S?a@wjo1H@1o&vJVR%w-K+JcxnQL)nt@?9*1Zg$ha8VWzUZC7JDcc#DUV16MAE z;G9s-#%^YzzTa$AG!tTNZAfB|nML*f<4N&`-aNdbv6b0{Ny<7N_l=mxMxpSj1DzDHl!d-!?+W@xx7all=8p z5RbF-guR~7x|Xf82>FqX?E03Yyx<%KbUX*XPCo;Be@}z!5oTNonK@zNZ@K0fejYq) z<|`k88`x<6b#bq)K*d^Nu5yvt-tMxp%!LVUWo}Uq!Mid=hM?H%xFd(moGl8(cj6W< zKMI>vE=RSpCPu$v;)%J-N}ES@V0B=W``D#|?>n);q@PW?AewLZNzuP*EVw3~L*)c( zm+zIzWVA&1e=&Kr)DVd8E3D!*gdUM3l0491y-#UD2db8_i+S52L^DW&Ie22D;W&`9 znI!DetfgwTS-jSS@nA?8PeuQPLR(yYfng?!cVwSdJ4({VMTWbiIJDyk=h_da&J61? zAS6;F({JflW)(@dTDAMp@V08;#9!%-KDCe7#}yN_tq9D>Tc+%}^p#b66K2eIF+ZNV zMJrsv0j%tI-s_U#c>Yb|L9d7oKnBj)3#3LItjUf_O#>&Y?cU&+I%bT`_DoP+*{a{z zu^f@>>EGocQ)a4QWXEd;Y22${z2XyBHmwJpQEOo6Q&Fss-TIp%#@ciG`l@zCO^gk; z1X9M@Np3@p?99P9n6_uePBl*a@8yO=!;LT}fRS+r6&_XUzZxfFtc`mwuj!QAFDKj* z5pi%*FC=En!_-03=eyXoML2(%Kl%Qfr7V``0a@PV)UIeqH2ncyrP#y-9lO|HOQtz?{~yTZ zz=%!g)qGISfQ;Nf08DI7zpVp7bR8f9smwQanYnqFOCI+114+9YJ__nJg6)W)nOMOD zXkfMCJS(_+PEx7fvnnj_tDwjVzU3O}A~FBwhY-0$Hr0h1LmQgMN%c=1AeQL&%2Hz0 zq|gH$J}<@cQo_d#zRuykt_i%JYCPouY9AmB`J-8{DyzCydjDL#wNh#4xG9_8aTiV} zlFF4Hy>(f5+{3a=c5^A44@}^g)kcIVAIkr+YW9m*N7iIjwkurI<9YUrWv$c2Zf1;p zhKxX)X@ZUEuz;BRqYFbFulq^#D1zCVDJVBLLfh*+HSS;r{D_Z- zY=9}fR##Q`z6>)*B^=cq>U_$es!B_QCz(AAIgPZjqgXCwRalr4>xF7mM~&$?L%Ds@ zo1sFcZFU_Nuc;OwV8 zDHJ_B?rStdTp1;2#-sU`*tur~r1JOIT%WCir)~Rm|8O3RH?5{&tKEMYtOp&V>s(yk z{8WinzjO04#NEB#bR&qH-ef%-70C6Wm~0=rbD9yU+Q@lp*+C-6NsTx48rz~)E@fQd zXMlc_`(fJGnx>OA-D~}XsksdD#f;3MLO8@i0nh8^@QA7z*d7T^zz@R#*gw66^UXEK zRy)YXo~`~%;Xs`KOJPuyp=OQ@CaA#2sz98jhCc~zYFkf6d>E2}js+VUBw>zsDB;TJ zh=Dl&AeX|^zOr#YYYpT-^imJrnw6f~Uo}_;$P+4g-w^z z;%#6rQ7}-ito`VXYq(-1QLIsglScw)$X5CY3`M&{HF09i+EbCvkX%vbMEJp6LpdEO zHCt1!#Vf0_{1#j|o%mU~3h7Yv1CJvu%%7Kv=mh5mtYCp}8U?g?s-{TtEIkk8Qyp`> z3eutbmAqV!hKom}n16+DqX!U#Krw`+Aw_AC9Ir|Kz*IbkBhR6z6v);d=}fiTgs9O^ zuH3o1LC3=l-O8I8`sBF9!@Y5nV20VQ=bAAtIt~6tu8q}&tN;li5Tz{@7n2^LI%SVc z$8W68G^7BO{w1(4_wxrr9f62lRONiXG0R3#IEouDVXZ`A$kWfnY|Qu#EJ#O4SeP_N z5lq&FGbzy&cjTtUqO6jxEBJ2Bo!S4h6@EaKmCMyy_7iqY<-bldA!v(?{A9#k@JK^l zVa)(&R6f7Q@s)oNOA9I#*d}@Ab<;?z`ARTu3mO2z zs@yf%Pny2}a7#EFAv2q(gV~ha8~tV?q)-y_NYLrY|C>xBE9%43Uh)^k7nc#}luPEO zo5WQ?M7{*nD;*yV=ieI!( zJNM}=eU`INqm}a;bGH!0ibrN^PapXBhdk+7ZvPqEaEp&tD8em5%BOXr91>Gj>x-f! z-+JvbgQ-_WLL(;rv!fzpCK3s!7D(m$SXnA$#uC#1n2o8r5#wNypT8XfFowprMZA65 z#ww`jaS;>$)H8kKpM&Dlvl(djN}C%VW9@RV{lvr4(XFm|Zj7{Aug721Kw0#o$E?M> z1iiy!Duu{rHL&fbc;WJn9~*8$vyN$eEWF-4V;`s1;#>H%Fr;U^_VT~+k)?_dH&KW>+nQi)20mndrC zQ%mDF4A}+jl+QYC`Ull{Pog+xIp}I+YgoRAu@W)~RE~aH z(A1d=hq;u!>B6*@sN{X=L@vmL8^8Qb+6*9>6~t+l2Px2589fS!^lu4j=ebxo9n$oc z6O3TX6G#l*z4__qyDOC|mm|*%muRPL#9D$f$KG(u@A5f94>7(YO7)d3FYB$fqtl3+ z5Lu#9W^0d}h2-;mJt@C)bI1A<}7+hsyZiU-0 zF)^X@ny017V>DkP`AT6~Smg&vLi#!SZ~0vpj!rmx)PXM3fvYyf2zFAAKS1xNs?h?S z?ZijRF&dANJ9vlreQFEBEb_~9l>EF-arfF$i}#A>JN7`9ItU5N5DZ9V1t)E!jK8}# zUa836tPRW}_LGv;hl({e#XZaPx${fc)6bYlr|xRRHAIltYobS&i1)=coEhsB%gWr! z$r%->q2sK`_2p*Bl=hH(srvWUyLayHXPuN4Fr020s4FZtO`Ho*1*foGgJg6fJyZC&`0r@o=etkx zQzpn0=B$UG4{l=FS?mTEL^eR>;}+VQ;%yTu3^4i=Lf)ctKf&rhB_ww*qqo|9=|Vdp&SW;ybUe`> zPcFrrWjshE6?}k*bG?Zy6{Ov?CL+v7gHyR|A{Vnoh#6yHI2xhyKl^R020m-@nm{B2 z==sU#md~tt#mZzk;C)5m{6n9HnzXN9U?A;8-2RCOYolAOk4ifkgNg5|_zw6?`*$L) z8rf;b_UP#rvYtVtn7X($f9-0Q&xg{S)0A3Sz@1oN-<)cR!_w74-gbGEEkBU>KN8!g zjJZ?8$S!iCFRbu|oEdDjFGAhzzEJ)BD6~VNhCXeI#HMLDNGy zS+?mXy*emx+J99oS5zgHDCt3|deYUtV`A`!uyl^NjoBtu65EY~IpzmC;==KN>K$Xx z9chKA_r0j-e=LQtsIYKs0LUv~=w42;zNn-e)Y78+e>2tGN84@YVvjmzHcwMVlLVbI zC{1F!{m8C-E}-R|qkPXwgVcny*!JuuqyEhgyoAyHF(@p4eju^l1XS9){KzZGOq*8x zMk85+4Kubuvd6`yC4z*5`TJ6ams&M1QpP$&mMfMc{i+E4f}r5L-olhco4P9ehiq+q zwDcu43``#a;^<^2o2TP~H_zzVnZ=guT;AZLjt8L517i1|}r=X6~R*)M(u^}S)@2i%h`8iW!sf;_lR zNIxMgbQjW)Aw1WOE&-@6MlQvCGKT~wuXB#yN?}Ewt#p;B(^fQQhFz^f*VybBhioi) zFSyWKAs1YWj}@#rQ^r8O7=!;-j@2b_+01(NeTD0+ZAekiM8&%YjZ0vGg&r|!{z5@^ zs&*I2bnCQQPS$2DA$jR7IM8I9w9lvLlKI;Qjb@LLiq17DRf$cZk%~9ETP8%&_pUli zXJR?y{Qs>_^oFsDw-27N3Ldrd7ByPZe0uU|F?%B~Rf$JNvi-M`%H=5!6(B9SZu~{8 zlO6?>-`LaFiKQ5~)pQ+=UdNFoB&r}-_xgzquiL15jNN4SeK@2cK6H0mg67LtE=jZ- z)>71;?yhj>WUsRHL48VBmeMk}%sRJKnNbNshYD=Q6pd{+lIV5szODS^SyK>(!qd|P zyYTwy>%Hykvkur=y1wY>2kK-{KGp2j%#76243!6wBjLJ?pE((m(%z3sa~0(z`_?|t z*|}-aY}x<8hjt|5L+s&n>)O+6CZ87jI?SFZ&N3(xQOIi=-h*qsRz4w`3EyyhsaImr zr00f97phodmT`!G9^drA@#5xKKU@A71A4Vo0Z!RnCbF$I^^K|7?awa4Z3de^l+sGga2)p?|CJO8NSbLi3F4Ht{Zi zMj1kRpq1?KH#xzei^|I2_<)oHTeVeq&N(-Ks%;tFP2$ocp}q<;h38seC^6R%9DbCG45l3C2>O$?h%Ij*Y?GFvWJrNVuaYav68%<(g9; zkMRw-N3vEnc!J4(r;EhcDqY3TEHOh3_96I>^BtFpW#u~aC zoFP07VN7+Mf%^%=8hE^Hf3oZ!l9@MKXNPZujfIf5pG^W(*-OnWTK9ow2n`O|6|a^y zqR=$OL1EKa8!W^ekf%=f9?B`Wf!%z_f($^yFmm?~z&i$(V+a2ZYN3lJ#wE~##@UOO zb5wFFUCTYH`H6=!2Fk&#KFflLt~kAd zO&L*eEHwiB#uG(^peFy5PxWu`uLR4qiabL9yRwn@zxGxs_@q7$$~s^~#vRSqav|KX z@Q}=IXhf!-dm^EU7mGTBD*fM3R#u&9qj1V#0o8@pk!qW6iygpiGEF)m zF-SacsE%(hLg_etfW+p5DQ9+8l-J2w&M^n(IH-RhC{X%H?XsgpMeLCxlELMsX2STd z!Wxw!jVPCYncReO%fVY%s)X7zvQeRx$dI}^91Lpwm9&C9Awl98Be6z+MxW&V_^bB zLr}=>|J4SR_a@tRBu{#`gydhPILIMuS37o^ewvp9pt09w3H9$wpH=<>s53j zp@SKB)b%83o@$w!2xSN-dL(VUcW0?+ZaFyR*u zPY13{m}`x^u%q=&&hbo^6;ZnEIl2HqWS;cgDbl``G0%}Z7;CL|w1rUF_``o?zzONC zW@Fc=&00Lmy{XRVgmd-+uI3km&JR=eU2twunbP@#Yi>836sDh2Bd1LXbZKfm1hiv3 zo)^yLt$zDs%ymlm7>Gbv0ycHsBFM+#GRU+ z+;O@9%;i-HvylI>Ly6s3%T?0bj-S5KLztNdg{fD(1Ud||>qI>S4rd>sdQ#q91Qjm+ z!Z~6G2{|4b?{^2BcZS|o9Yz>SX5MYu?ByCo>KTTm4Sd%U1!u0(6D@b<{8EXVrdGo?ya0V~6_R=!-_VW;267z|Slu zTa73E%7^)oJI<nB|F6sA z6H6mh=d79G9nWzii07L)u~`xpJ0I^Y=L^{om@IIB=?F$QLvaQ?`nILUH=$11VYRLO zwjL=8NdEIG_`}OT(Dso?#np*XMemu}exei>WnSz}_mFoUrkmr>aY%7Hi!YOO9(w-f zxHvlVW}DTG$TmS;DBAn#3_mfMGpiK(t>y)*U;0*QrgiDZYpz}nRm7Ud^$c~V`SjZ0 z>kD(tnttG8sSSXSs=KYSs}qutxI|ZtH(GPou88^1w!-*I_=*`y$CL^;4?ykh zzxI+>D8~CsG!N8vc1D2-r8+#QOPp1U;jfGZa>m=qUD4ep$_zE*mi}4`;ZzzKCSUuv zrwjkv)5?Lwy0`LP0VE7+S#;2Samv{*VvB#iX8$fT09^L;R=u({zD6zbbLE45#_OE^ zv#!}EEl(>=sMG{Vkm9Kn?IHnC!fsRe_zf&v=sp}=J`FA${sCSA9gjuX*y&y&HM9X_ zu%H+;Drhjp!S>4Aaq-&P+8Hkg$Qn@v)|Jm4f@*duv4j;!ntTSo6EhaIYCWrZuy|#8Xq#i4}9Zg9AGbx&{A3E%>svu=}}IfW;S6En4s4;mccM9 z?c-a79|r49S`b<}^o@!3C(g7)Zj3efVT;x+ClXn6hky8ZIFL_JVqXf?mwo)1XEsh8 z{tPC+YGq>=TcF5Y{WzW}7ec>O+jA8$v!$e^tw#uPL+Fs_Cm?yX^lD)+ zpKa%nOwCVRRY#TpTVxF)ifX}wsMEqo9sA9uRbK+{d*MoB6Yp1RoeIFLU*3`9i0D_-0G}!&b zxI_w`8NbEMQIvqra?ursDWbrb?=F%^-Y;bTyEzv!J2FgZf?f80HtfPevAwY=evSXm z2x|h~<-1UAM>*j}@T*!Yjq=a{sPeLYTs~Yk0^G>U4#K$zW;=x!u4TUuj~78Jsp!YH z>^=7$3TL?Vu`!1`JV%;4{av;&+zw3UFyspBI>T^yD)F(Rl-F=JXJ)3O3|W zeIP8|%p}obMS#&r@tc^JrwO`>dGUxoJwbMsu${nuHt zEuuy?Yp%V3v(qDJ)eH}+=IQhFqN}Kt(9*7txWg{w1$KgrjNEjV^MR1Uwy*R~Ts1>G zmhPeuBw{Q1Yq@6;Jel8ybt%F@hg#mViyxs||Hf@W8DJSB2jxD^B!u>SEBdJ=*RAhr z4BFjeino37FPu;gX?jD6`v1iee|i^bv?k5-&HE3YU)UhseFAml-T17$;}rkL$i6vY zGakcw^jG7!GrRt@IX_uEl*dO{(^AKuc942Y8{{u0ct%j5tnn?4C>P_M;$s?`nVE0rS9-@9*Fbh|?k+0tkOSPDA(VJx zP9*l4)Q zwu>X_x+VFXB6dVl?qYsx4hgr{7j4JiImcmaj^#qf$*alSBZ{~Rzj@*;7+{NF9C~0w zR{Nn2FGPXOVwKOpY(iP#=t;V+Qos8GSD8NT&b9uy{M^QshjtX02FbeFHo0ax-Z!ZI2@l!iQ)KVj;ZhJ9LBocQO|gHx#a4g^i2vn z=2C6gI#lfCrKahLY8e7il*lIMwli?6#FYWV*~=+=-l*}2Lyh5JgnX|#3x(~G)n{S; z^T`W^NaSS_oQN{2lpq#A^KS$JB+jx>4~fw1RLTXCGBbP+ox^6s)wycwHT+UsOtziUTTcU zXzSV9mkSg!p+ z9U(+B&Tviu(jf#RH`myk553>yU)b<5I( z9a9%Nd|4Eerow070;>8}etgl;8pjdTz9aZ`7qGzn=7$@^kvOmC^9!aD!6;xBi;7lQ zwVB?@-73gFaZT1EeYC(nzqA;De!Maznp31v{H=~6IpP6C$nC|Qm*<~Xt+ZV`6fHP8 z9GK)}eOGLZaqeiBlp)xRNk#Z&pD4jDk=ATzh5K;9A+_WT#dy0f%52P|83_NIIT5t? z;Dvd|RPrvXclpOfc}X@rAmb@34esp;>Lg?t#Q zyd?_`zYUTANIt05RnMW`Xsj#KF*|>;If@Jw*A%KItSs?1dff$57c9<+GeVRu2Sk4i zJ&4s2^<)Sh?}Qo1O2uK zEweA0>PLh*UA((+g-H!W$Qz7Mo<4#Zb5}el9y9ko6vF!(s&S#&-)@6u zo)7#Y_llY3Q0DzXc2)*e)c4!qn4&C3c}>a^aQe3I!6T{{n9n@{Ni-B2!EKYaS=zNy zi`2m%VgkBq_p@^moZVVkUapm1k;iJ%hz*Jr5E5xooAqz8mc9N~nTZr6!M7U0;Ia~e z3C&~suOj`l`&W@TtXb5DTFh$y3fl#(g%GqB?ZfQXiy*BlZ2X&0N|F3TbY6jKn78rk zQ)(QCJRs<>L4bISJ;h|zrhj%^@^M)l8}L=wJ2??BU%%?LJEN!J`^&%s+PaNeOQ8vD zM!h7jKjjtQRx2Dn6Qpbx!hri4th`U<+Ezii*<~A(b>vcIqW3R>!tC&hb@L6p1l|%g{q!FaMPF|v&8AmMY@3wurTErs|P&)O4%kFlB zBCeg4Y8Isf!uL+8?X`N7&-K>>;npFbCs;LN5gy?-_EDHQl0y4dfIYtN7N*O=#$^G0#`13lXonW9Tk=S*>M4Q@%8+Avu5|!+|ME zMb+1!PETCmwKvjrKn3RZ%%?5mQI3Fp|ij*HTCJ% zAP~)SbbP{OK{EykSDs)Oj&22<%GAG9R+}l8!Sf0Km^0kyBcLJ}m%q6D6nY)4iy1$$ zi#Pv%gMRPM10&c$eEtJAJ5eMw8K3;J$9zEY*fYzPKbqj_2mjK*806lBj1Lxg;qP0}Z2jFP@&yUC0GYtrgx+6LOaCQ#zFKelx~RnYxE znMV_=o@Zpx1w(!YB3E!GP|80bF^cb-izcDc(6D3fmBhE?Lg4W~JAVADy zK9a=T8I!u7W{=`?vDqWZEEjq8ueWTqtbiI!|4Iq};gl%MMsRepypd86{#h@iIa)`Y zZ}N~8W+_F2bS%Pa=>K)Z-D8}#A6d*8NP{WDiLjJkNqV)@F@o;^|@pxy)aoA^A zewVW~DNKJz>0Z`BHh@fLY5;!Y6f_@Z#LYElR=_TZY@GSuM8ywYcb4Q5MHT$4mrW_e zk`_Tpe1bgyAZv9hL&PXB9#b(r!VMD&T7#f5mU+ArMm>W)rjaB2yL_I+E*j;}#kF28 zU}$PkU)MF;Wk4lk@ggxkoc)pbfpLi4#LTFaS-sW0N+WAIk%bwu=yLdHKY#V%_octh5x@yJRpAIL@rnH6G;8{@UhrBgW_$<55CU zToxI<>D9S_0RdCU$25x&_BCz|om(W!b+kCM&OEdUh-Hb45dVbR{3#*Up-;jh(=@uix`>HGg7^f_czG!Z zn8svWmnkM^FNQrB36|W!oKza9AzKsD)hhzwq=f4$@F2QUQW4Rd z5T=~k)*ubRx^49=;5ztqKWTDm>b2aSx>pA&sCHGwcvo~r(VznY6>0y z!87g$Hf}%Sh1OP?Fps#p=s1$9X}j*m-_0s+ZJQJ;VQC=Z zo0|kl^7${eyAJ=*nWm!{x6>XEb1IX2xJg|XMmK2<0uk1!b9vybiag3&)W%LeJ8Akb zDw_=S=Z$X&q=ZN+WfJrFw{@lOa}lF5NfMcn{ydPfOS+%@8HIaWYrvaxf95RtBt+y^ zDVLl->^f_~V{kSyvH|yS!^8hK4K;)K{JD!1o20Yt&!w}<*o#gW1YYny>f3uuako~J z@E z!Rd$xlv@n}aC`^ownJFuOA8O1&gdY*OPK1h$)$8sEoEVCKMRTAgFlVUs5X%{$5hFj zkNB7aVT|8e&$HPJ$kv3FQ1uth7QJgaemQb$q>$y4%?3uo$UvzTo8sk@{pTKFl6K1L zsn+F_{nx6x6VtB3P+`P9yk@v4%LfN_hm-tfM+OGlqe+-0=JpNmnPyZPsRWq7=_fEd z5tF1Hjd%ilQzW~!HRkw65iC}}$Ydd#L|k$70NYVC<$&5kigDfSP%RC;UCY^cDHsdV zc9dC%tC#a3y*M_-fU(KYE+NsV=)x)qjDl$W;+v4ximahwXUwf~h&YOc`}(0QN)89e zc;GcxMj+sXaBUBy^vayDWBbPOzZXOA-28#@hL0 z*&DbCs~G&6lf-DI4CDBJU3~>m9Z&Z4BWQ3B?i$<^EP+69_u%dt+y)Eo z4~Jln;4Z;k9~#^txV!tG-|p78U+q-gs;N6Y-Ca|6?woVF>&_JB?8CPotE;WrikE6W zg>M$CV@g#E)t%|XNu477eRT$gfOa<9pXU)_%ATv-k_Q8RX6^aK`^G{a>Q+ut-p0otA34@o zwl3Lr(ZPnctJQ!kRhA8134>T~H0yAFJ{T3fEV)k3)226h89Iy5{X0%)tTQ@CT}LC* zG`C@8W{x(95reSkz!G$RnjIPS2NtK&AbA%?_Da)=%i!-~lVbWr)*qIo@Sj zHPeuW4sHm;r34WvX^4n7Y}<=ouG7hw!gM0othhmMnvheDv3T0s35x2!Tv*Ck^RL2w==CF@Gu=^B5TDS4x8UQPJv8fX2LaB*lk z{#o8Cv|S;8_m=Onjl}PLJvu2=aqC)Cd3B`5=9I-4(KoWpFz|*Gl%ZvN>G@9l!bcQ! zUSQrq2?#AnueP&4LPyb5?uVSFp9^fh?pMjJSS&uW#z5%MD&Kn`i#etFZ|R|?i#*a+g+|&b?T>^ zC<}XNMeMd0HGyJCU<=;m!t z0?Z%>u}r0^n9oMll<{abxD9oeGev#!#o)s2VThc67o>uV$Q3@P{`o>+dwqM?Ic|J| zJE&>OpIl2HV@SxdcO5;LJiYh&YXF7vvipDISPbWxoS`->|Y*hZS$PpWL|FTC9q z$RK{bxq9#bBfW!05S2X)%L*_+`R)(TZV5tealPV~dKBrTDIbNA{laM%+5$xcnQn;P_ya^c6mEoY!E_s+cMwuM$FS5oh*Im z3|uQc)fW>t9IJPXk}aT4$e~~w{dIY~)|r5|9ZDAeQIwh=@;I}a1Q@f|2oCJsf`dkR zSs&H!d?rZ`xI%=#sCYIukYTo3b7GD4i{R&3I=J0W((e_Vn$sb)pONe53OonCQwU~= zM46ZLc<$|`-&^OEtjXd#IJqCLPH?o!^cEb!uTcqayM2SzNig`j3csC>72ju-o+buO zLQ7ZuE{3O~6lxzq04RgCF~}Z=sQA5~GI!J6gq|;FP5kMgRB5^T_*&7PIs_xCI=Foz z&O*`Pgrn6h-{s_m@`_WRpWOVW`Z%%$3rv1V13ksajFwuDL z6!^Um@^gxjdAILNnx(=gDi_zA>k46{vckB4@135)rP2Ept;eG#R(d)|ys<#%+=B4= zSGZ3}^piN+M1X(C$A=$_@GC!7medj9AXlvssDfxBj2|pd!M;nb3baGx;oh15& z3R+KOy!Kl}?-xIifLUQqKKz58PCANB8r!xr zxz|X~KO;0_&vtXJ9Z|!a?3S=J{&xJ&`+0(yO6up9APd_98I0p~M|6~3xBQJ#j18!o zlAbMJHlVZ^+bX_lSU^fdV{A7;w3c8syo!E-eINW)S)YJblIhR_7maYIE8@^DJTrvJ zIhk2l!GqN;I{$5Q1V!7B{Vmu=p572M=QQVTN{Y5p;LZqgBJ*u__BLkq#Jx-olRBTu zjuH8L0yD1!2k#u`T#~aw#kL(mG*FIXTjB&OG{Yr?aH<*N%H3gH<}oefZsTWm|5ZE1 z*32^tY1Nh2I_;PsSV@G!L_AqF@-D2EKiwLua2vMSY>t^^j#Qx1%a2^$?FehqsQ1oT zr6LCCT$qxRPGZAq|Hk*Wa(7$ZcUfBq#9Z9e6K^u?p^p*q9+pta%KE*l4!}Bx`3PIk zSW#IR`^8WEmDVtS+mbA)$|H~wqt!|HtQJr#_*Jvk{m~=Hmn7#zVmAcKNn$|jH&09c zU9X@VD>Z-fU(ruPo+K&mpbX^Re$BJY7s0agLbTZ8!L);v1+E74Y>JK~&*v|v{lsZX zJr9MI>1Oc(6BH)z0tm*rO#yw9J`3M|SwHU})^{Hfb>XS#Jy+#$61cRBM>vX`s@V+n zQZSm~T;Fy$gC=2QXKho^hx;DjeLE6$hx^(%j3S;M#UU28(o&bqvuB&qs>)m6Gq?@) z`4*6TxDlh*#16}}T7C%`KCRuJ(Pb>1>iubzp!8$0*w1;r^MGlE_r6MXh-%rS!EZK24n*{ay8 zrmr%KJj|Tk{0%jL4Ly)j_bgSl=VswvfUl{x5NrrI#W8FDcKmR&+e|C)Cni#Smvdkj zmzKkm+&6c!#?4W_V&3}GA8!r`AUeGq1Es!-<>titdjHkdDu zzL#bkwCl+jmI;63C`!#isWq&$SrLQTYHBX+-plkLE{X-Hs0)t76N-Bft(<>Fw>e;E zqiO#FRnr&Xr)jm{VYj2Ij1bw@g$DXZ{K~k-T-iIfA|p84JFEVkmQ_TMWK5U{vjKXO zh{|Mt;$jb6fhb~2J<83zW~6YC^3Y)6CTzM-L!VLC(e)7A34g-)2hqSHsdA!y&VDbk z;P<(yq^Oumgx*mxv98CB5N-iUeRPt2R6#*GjpJ@e9sxI>h4JO`8hEY%b1Z^tC_atE zTXeeoy;Y$V_d&{NSl8F;M|~D8N$~0A@a}yVg+Bw-<}!P5awYcSt{mynTJ&lNJ$l*@ zHAy{sjN+V+*J8e>2@)ozB--Y)0nU=&;L^^LyqExtziKv9u=%JONa?miUmEdeZQlZ+`+t$Sh~>fvd28&uAw>F%?dEXk-N?$_JYcOT=kEcd zrzr-^*7>OYTA$QEX2vwubVZIk#&XyWFC|$dv7h{SZbHhP+_&TtWty`L+JrG(66z{M zv$hW+v&nqsvPR17AWodBjU+Izk6*()2vJH1qa^aKV!Zgv)uSVbi%Y8chniGVezpB! zZEUsOP(OA{L@>t~6Rd~*Fz;9Upiu|F2tnz~^X3Ile`kso-G=L{GVmf?Qn=n!Q+{jn z-O@dI|4TPU@Do9>vsCK7sXo4Zqgr2j>M4xm>{1q1>bkgb{5sBR_KC9DTCl8KM3p21 zvvz92h(d}Y$CqPLfaTe*>+u4WmOMZpfu12ne!jUJvdI~GpJw{3#qo8YM6MfPHV9c< zjIT1!@OX${dK$i<*un0`_lk~ZI!hRWwfF6&*}q@}(|Gna{9J{*+osg7V6CFsi_g2L zE#`4~Ed;S=$%`1Pv0oiu4xg%30bxCujbq8(-=D4(uz_k``*9Q6BS{DsTxatahqL0b_!`unb%SOC#>kpo z>-=0__F2ck%$tq_HR%tFLKv*#&egYVX?z*KxRKps&%K>+^3qS5elY^?_Vd}a+FI_B z%_xt(H|ouGM90GMKA79PbCaJOyrgAFKsRBVgv_%{N(9j^a1!Vm2ima+eMET8Ws=WP z)^FC;zodn&-jd@gRHqxrUo1% z=B%-Nf)zU-r~L`{n7y;}EFl~j#>a&UPLc2L7(XI;Lis5{#UjHVKN`R;IvKmcGrn9X zN285nzRwzW2r+!}bZ90$xIyw?4L+3b$`~toR7rVRUI(hJTBiG(5i58k7^JkJMU`+O z@;8Z-;u$QgHBv-+c66KpLA;JO7pV8eGiYEr{VKI~xeNN--F(L^_3C5C#0uQ{A@187 zV(ZGgM)J7>Q70QU`fAOfyDN;0{TLf+gBpiKySeWpii0N^`t2CE8r#qeU5&x{3_)Gv zs{_;c9VMvEmB5&-=9BA}K*pOMXQ@l^MrrJ|l*D#DHm5V|yPdgphv?)u&@-8%N1E*O zYbh_=&9}I#A$Oa>3Lmk_@2#X7+&xoePkL~!BII1V87InsAh)#t{gSO6PLqxbgPmdHXd{qs^`8ix4MmRL zSs`OO+1RRR_n!NOY>j4R1;W+&Rhs5ST$Vm{qgDrWq86cy8=(J{mJH_h9;_RS^|nYi zq}2@MW_(NQc@iQL>n;?P;#Ib3slZ{97cUx?$V*qGStj}80T>m{+^FY*icfP6*In(( z$WkJ7P6u-jR#m+WA^h!&vjLpLSRnHDe8{5>$MOUB(DjVdND*rBABh2oMpN*Tx1Elc z;ky9z2LATA!lJ0d8+UHfar0~usY?S+N_@ZGkwz#4BS}IKxjl-`mD_6U6hf_hnK9-1 zhm?^vUU%)q-8@z52r-PX0=(^jCVK4AgzW+Ffy z?s?LzBErdbWU0Dl0Biua-sd|^Mpxc3K7wiX%T=g-$*F!eY8h`^NVWGs&`iMcVLIC8 z{f^$mOf#Ux_{mlCH*bRgEo{M!vkDAoc?i zU9VKSI}!Z&Chqxam0ZD^8n-~02^bI@lc$`w;mrI|5PQIo;mZ50Qm$#0G_A2GpTCAs zTGJp}ngEA}Z}#)$ z4l4UaBYu#YeNr^FLs)oPqd@t2C~1FavJuGaRJ}_P@snA20GnPW(gs#VLAi@On{;`6 zKqEl8s{3jL2B6)K&*m-c^r#1NvRlOHcQulTY*ZeRO3-J;x?`ZhQ>a2|BeQi{6=>E9 zwfCvzosmu#8985xu3!fMGaH#lab~mc>TrXOjpkd1lzPtoD!B&kJe2}%e{tE`+n}Nj+Y`uJWi53r)=K2r_d^W z$Po8nkL<2Y!PCmvalBPn%{z9x+G_t--pF?EKmd$aOL?MEK_F!h5C{VV0=d~cnXr9# z_OLPi&gyP!bNEy4`{D=e;1lh-7ynGc)udjBL;`1GquZ!)u|IgEEbg!t z9}eDuD8p27aET8cM-17*#H-2u+|S-vgi`@`SF~Y)S%O19WECWuPx>K#1v!yo_wM}n z&w#_#aW}&NO43*6B*ssqt>@mC2L3b`kkz-{>9PMvvnDBkW&pf zAtxtpT^DTfetReCC3z&5YS+rfPcb8rY^2-p_GJwFxAkHvi|=SEnPkASTu_lPa#aEK z`NI5fuA!*!d=fdx3qVN#`bu$jcm4a_-7;i;-;w~~7Bvxs3v+y$iO_b{uRu^QXrK30 zv$Tlrws|#~S9||zZ!MxB5Xi@9-^lF`O^g951AnGbtbh8fjxRPhF76A!jnl1K8Gm9rP{c`5GhzD^%D%kk3y++OiqE3~sqV~Ni3u2G9~6Ld&0qa65mdd@ zaAqwGToMttmwS<8?tPQ7Dy}yC7R<_Id8VASyXh#`l6~uzVoTmKRM70o{XeD5+Y2-S z*(&tR!<(nYQeSz6m%_c>wJjwb^!hmHjeK(UjDmRt`2hBmwIcsc)G<_4Gj}7!$;jd` z=BUD7uh#i$%f-)9({@Q;r~YlcEz4wLSm~5)ZToXaz|k>P(!J;HUr*T{L#ugfpE@-OPBx^ zcrgP8Oe+o%%SEQudfB>@n_OV0Z?$PNv7V`}UF+4~{-C=NGX>vN*1?pEC4uL&NoD>7 zw(oA0CV`DMwP&5&duK}5elL(DN|u<%!PP~Y-imRd>bJAH=TB4@bmlP;_MfLpIQK}! zL5nM zizrgRpq;oq^CvP#SXkT;R@mt=e!z0yXVktevM#vTl#EJel8bu>;y-@*n#$G9wB<) zgv}^vr|Yh)fG=j>IazT3f;6*2D>KrRm&v#ZLFWzho5NcgYf-{vsWg?$sx_?4$~C1d zHp65N!GIEr;2Ui^Dozg6Na?o#EF`|OqmY}OT^S~LRaGSrt-U56f!Nne4xakkn<)d; zr%2~}`g@~%1jxQQ!q@3B)X3s2LISl^xsqD3 zDcOvl2aw8N3rIZ3j$UG5nS5Jzlk=NOPBiZ7b^QUszC+B4SCgwjDmSeLJ|xBOV$XQS zv&W5S_beISaG>1j^kI#WTsia?w>VcD@&MoyBtPr&gjH3h!DaBssdl;1w#sYI*!9DRJuAXD3E>9T{XwW*ehZ@s&8P zUx?Z;mE7}dno6?v)#5eKZE}a|YdQ+9s z4*0?MqWr1_S>{V;Ps1D0{S5R+#!S2TZJ5R#o=trLG5KtP@h$e<`Iw6 zv(n&xyz54D%5%G0lM+e-_l^jYdHahSf0R<+a&&75F<zrOuS^6n#AOC8V;1m?!jNe{H9z8UuaAGfW=A1oSwPS=85^(| zAOQ|+5K-iXLQH?&_hPSC-Why=bh&3J8MeeIQTFy1#s>V`|9Xcg+`pO??<}kk9-FF4 ziV))=Q~mk+1F2SN#M2S162gJ_nYivAFS8x28o~~ICZ6_Y5O{NFh^UnxAOWgnnl>OQI|?2H^fwKr8K-mAYPekZM4C9f+@OrL(?efrLr z6)>~9d6e!AAs3T1rW~QQ>Ytr;DQ~<2U72ppMjVp^?NfWOS+6OA&Y+D9{xq;H?r++l zh(>aSL;%f6c1gi3b!YKKw)JJ1M1bXKw)G#LHl3BnoP27`C7$7_05{S#U5E<{39sWU zfi6lA%MuUg>>EQ^x2XQQj_%JGqv_)zLW_GAF68tS&My5#YjbkD`$>u=TMnpE;~Lz2 zITfB@=};ElwT{q4BJ4`#2aHj>faEFA1{;r9N-S4V~btP)!&_uzVOb3_aK>Oby_l zpfS|K`XeNLl(uydfCkvD-f(X;Mgr#y*+SFW@rZq8ik+rO(9%y1JB??>jJg3HKhpWz z6sdL#qGo+xaMcvF>pp+~D_84Dn}9xZ_4d%Z`p`_p_TkB1E%$AZ_>b>u>K0izTMUE3 zCoWed%onw@BLSosKX~x8Zg#iEe;Wd`M{1Tv!i9EjZ$Fbp0-go z>e5)Tf0}i%O*ZjP&5JbU{m_q@IUDkrnDXlz`NI*|h+^W+Y5TTm4wa{%54sUmi836MhE?7gT5t@;ex2Z;+p!$8Mo^qbT;fvME%?Er*d?|kqHNUTXl!rCx_O{INJ{2 zrmN|}ZIm!+?>j+!&pmmhZh7B&D<7Fnc2GYT%Uz1_j3YM7)7j+y z^Wj*4WfGa4chBC^FLdFR^P(J*J(VRct1#pft<^e6FHS38O|sH%Ka>5fS~KE?G^28H ze{-E~YhRssb^JU3ge>&tNXX-$Zr7|PGmGRTYmU1gJ*S%Wf?2~InFIHhiTBwd3 za-iJW6<*Dw3v-O)6byW?Oi+<=q`g$L7SpCRFH`o}FU5BVrVJ#>_eJPvgfq zMGfD$#!6y%#BtrKU+qyAoz=`{4Ca zUBe9X_S>!-<@+y)_CqYI308&O`JjjUp8RrF4jQF1ZNYL{7Ir{JF8E_)py4-$1J;_m z&pRFCggma>c5aM*`nKzv!afZ*eY0*69_OkD&mgs!U^XS8nA=*q1@498CPMUv9amgl z;$^i=M2I85h-21em;!;%s-+(>xS3HS5s z(|>t^1u4qF!hvPLpj=wxGFRN;6V zi61G=FE~?_sMX~S<3>o-h0yGvbV|_ZdE3P6Bt|6urN&%4NrA1kpp%B+JNI~*e4>iOcO?TvhcCKxR3+uYA(_9chkbf4wC2H z22RMm@=Pe&RzWt2aum1rqDz7MA&-T!_Gh67)E1XjlG}(;9;4y*??qPI&;*Jt629;z zMRoewN)njb{)xZF#b9R}s?r*;VxVx)`T!S**p+-7j5F~?#;qGjDRZhZ3Je*yGNX5M z72d#@E;|Wunlcim+&{g!#I*uu*`O6I33w|MvZQ QW*QGpOr=5yNc_k9f10Y!;Q#;t diff --git a/test/optimization/test_admm_miskp.py b/test/optimization/test_admm_miskp.py index b7b2788229..46570eac3a 100644 --- a/test/optimization/test_admm_miskp.py +++ b/test/optimization/test_admm_miskp.py @@ -1,7 +1,8 @@ -# -*-coding: utf-8 -*- +# -*- coding: utf-8 -*- + # This code is part of Qiskit. # -# (C) Copyright IBM 2000. +# (C) Copyright IBM 2020. # # 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 @@ -11,8 +12,10 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Tests of the ADMM algorithm. -""" +"""Tests of the ADMM algorithm.""" +from typing import Optional + +from test.optimization import QiskitOptimizationTestCase import numpy as np from cplex import SparsePair @@ -21,21 +24,16 @@ from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters from qiskit.optimization.problems import OptimizationProblem -from test.optimization import QiskitOptimizationTestCase - class TestADMMOptimizerMiskp(QiskitOptimizationTestCase): """ADMM Optimizer Tests based on Mixed-Integer Setup Knapsack Problem""" - def setUp(self): - super().setUp() - def test_admm_optimizer_miskp_eigen(self): - """ ADMM Optimizer Test based on Mixed-Integer Setup Knapsack Problem + """ADMM Optimizer Test based on Mixed-Integer Setup Knapsack Problem using NumPy eigen optimizer""" - K, T, P, S, D, C = self.get_problem_params() - miskp = Miskp(K, T, P, S, D, C) + miskp = Miskp(*self._get_problem_params()) op: OptimizationProblem = miskp.create_problem() + self.assertIsNotNone(op) # use numpy exact diagonalization qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) @@ -46,11 +44,7 @@ def test_admm_optimizer_miskp_eigen(self): solver = ADMMOptimizer(params=admm_params) solution = solver.solve(op) - - # debug - print("results") - print("x={}".format(solution.x)) - print("fval={}".format(solution.fval)) + self.assertIsNotNone(solution) correct_solution = [0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.006151, 0.006151, 0.006151, 0.006151, @@ -58,70 +52,83 @@ def test_admm_optimizer_miskp_eigen(self): 0., 0.] correct_objective = -1.2113693 + self.assertIsNotNone(solution.x) np.testing.assert_almost_equal(correct_solution, solution.x, 3) - np.testing.assert_almost_equal(solution.fval, correct_objective, 3) + self.assertIsNotNone(solution.fval) + np.testing.assert_almost_equal(correct_objective, solution.fval, 3) - def get_problem_params(self): + @staticmethod + def _get_problem_params() -> (int, int, float, np.ndarray, np.ndarray, np.ndarray): """Fills in parameters for a Mixed Integer Setup Knapsack Problem (MISKP) instance.""" - K = 2 - T = 10 - P = 45.10 - S = np.asarray([75.61, 75.54]) + family_count = 2 + items_per_family = 10 + knapsack_capacity = 45.10 + setup_costs = np.asarray([75.61, 75.54]) - D = np.asarray([9.78, 8.81, 9.08, 2.03, 8.9, 9, 4.12, 8.16, 6.55, 4.84, 3.78, 5.13, - 4.72, 7.6, 2.83, 1.44, 2.45, 2.24, 6.3, 5.02]).reshape((K, T)) + resource_values = np.asarray([9.78, 8.81, 9.08, 2.03, 8.9, 9, 4.12, 8.16, 6.55, 4.84, 3.78, + 5.13, 4.72, 7.6, 2.83, 1.44, 2.45, 2.24, 6.3, 5.02]) \ + .reshape((family_count, items_per_family)) - C = np.asarray([-11.78, -10.81, -11.08, -4.03, -10.90, -11.00, -6.12, -10.16, -8.55, -6.84, - -5.78, -7.13, -6.72, -9.60, -4.83, -3.44, -4.45, -4.24, -8.30, -7.02]) \ - .reshape((K, T)) + cost_values = np.asarray([-11.78, -10.81, -11.08, -4.03, -10.90, -11.00, -6.12, -10.16, + -8.55, -6.84, -5.78, -7.13, -6.72, -9.60, -4.83, -3.44, -4.45, + -4.24, -8.30, -7.02]) \ + .reshape((family_count, items_per_family)) - return K, T, P, S, D, C + return family_count, items_per_family, knapsack_capacity, setup_costs, \ + resource_values, cost_values class Miskp: - def __init__(self, K: int, T: int, P: float, S: np.ndarray, D: np.ndarray, C: np.ndarray, - pairwise_incomp: int = 0, multiple_choice: int = 0): - """ - Constructor method of the class. + """A Helper class to generate Mixed Integer Setup Knapsack problems""" + def __init__(self, family_count: int, items_per_family: int, knapsack_capacity: float, + setup_costs: np.ndarray, resource_values: np.ndarray, cost_values: np.ndarray, + pairwise_incomp: int = 0, multiple_choice: int = 0) -> None: + """Constructs an instance of this helper class to create suitable ADMM problems. Args: - K: number of families - T: number of items in each family - C: value of including item t in family k in the knapsack - D: resources consumed if item t in family k is included in the knapsack - S: setup cost to include family k in the knapsack - P: capacity of the knapsack + family_count: number of families + items_per_family: number of items in each family + knapsack_capacity: capacity of the knapsack + setup_costs: setup cost to include family k in the knapsack + resource_values: resources consumed if item t in family k is included in the knapsack + cost_values: value of including item t in family k in the knapsack + pairwise_incomp: + multiple_choice: """ self.multiple_choice = multiple_choice self.pairwise_incomp = pairwise_incomp - self.P = P - self.S = S - self.D = D - self.C = C - self.T = T - self.K = K + self.knapsack_capacity = knapsack_capacity + self.setup_costs = setup_costs + self.resource_values = resource_values + self.cost_values = cost_values + self.items_per_family = items_per_family + self.family_count = family_count # definitions of the internal variables self.op = None - self.range_K = None - self.range_T = None + self.range_family = None + self.range_items = None self.n_x_vars = None self.n_y_vars = None self.range_x_vars = None self.range_y_vars = None @staticmethod - def var_name(stem, index1, index2=None, index3=None): - """A method to return a string representing the name of a decision variable or + def _var_name(stem: str, index1: int, index2: Optional[int] = None, + index3: Optional[int] = None) -> str: + """A method to return a string representation of the name of a decision variable or a constraint, given its indices. - Args: - stem: Element name. - index1: Element indices - index2: Element indices - index3: Element indices + Args: + stem: Element name. + index1: Element indices + index2: Element indices + index3: Element indices + + Returns: + Textual representation of the variable name based on the parameters """ if index2 is None: return stem + "(" + str(index1) + ")" @@ -129,73 +136,79 @@ def var_name(stem, index1, index2=None, index3=None): return stem + "(" + str(index1) + "," + str(index2) + ")" return stem + "(" + str(index1) + "," + str(index2) + "," + str(index3) + ")" - def create_params(self): - self.range_K = range(self.K) - self.range_T = range(self.T) + def _create_params(self) -> None: + self.range_family = range(self.family_count) + self.range_items = range(self.items_per_family) # make sure instance params are floats + self.setup_costs = [float(val) for val in self.setup_costs] + self.cost_values = self.cost_values.astype(float) + self.resource_values = self.resource_values.astype(float) - self.S = [float(val) for val in self.S] - self.C = self.C.astype(float) - self.D = self.D.astype(float) + self.n_x_vars = self.family_count * self.items_per_family + self.n_y_vars = self.family_count - self.n_x_vars = self.K * self.T - self.n_y_vars = self.K + self.range_x_vars = [(k, t) for k in self.range_family for t in self.range_items] + self.range_y_vars = self.range_family - self.range_x_vars = [(k, t) for k in self.range_K for t in self.range_T] - self.range_y_vars = self.range_K - - def create_vars(self): + def _create_vars(self) -> None: self.op.variables.add( lb=[0.0] * self.n_x_vars, - names=[self.var_name("x", i, j) for i, j in self.range_x_vars]) + names=[self._var_name("x", i, j) for i, j in self.range_x_vars]) self.op.variables.add( # lb=[0.0] * self.n_y_vars, # ub=[1.0] * self.n_y_vars, types=["B"] * self.n_y_vars, - names=[self.var_name("y", i) for i in self.range_y_vars]) + names=[self._var_name("y", i) for i in self.range_y_vars]) - def create_constraint_capacity(self): + def _create_constraint_capacity(self) -> None: self.op.linear_constraints.add( lin_expr=[ SparsePair( - ind=[self.var_name("x", i, j) for i, j in self.range_x_vars] + ind=[self._var_name("x", i, j) for i, j in self.range_x_vars] , - val=[self.D[i, j] for i, j in self.range_x_vars]) + val=[self.resource_values[i, j] for i, j in self.range_x_vars]) ], senses="L", - rhs=[self.P], + rhs=[self.knapsack_capacity], names=["CAPACITY"]) - def create_constraint_allocation(self): + def _create_constraint_allocation(self) -> None: self.op.linear_constraints.add( lin_expr=[ SparsePair( - ind=[self.var_name("x", k, t)] + [self.var_name("y", k)], + ind=[self._var_name("x", k, t)] + [self._var_name("y", k)], val=[1.0, -1.0]) for k, t in self.range_x_vars ], senses="L" * self.n_x_vars, rhs=[0.0] * self.n_x_vars, - names=[self.var_name("ALLOCATION", k, t) for k, t in self.range_x_vars]) + names=[self._var_name("ALLOCATION", k, t) for k, t in self.range_x_vars]) - def create_constraints(self): - self.create_constraint_capacity() - self.create_constraint_allocation() + def _create_constraints(self) -> None: + self._create_constraint_capacity() + self._create_constraint_allocation() - def create_objective(self): - self.op.objective.set_linear([(self.var_name("y", k), self.S[k]) for k in self.range_K] + - [(self.var_name("x", k, t), self.C[k, t]) for k, t in + def _create_objective(self) -> None: + self.op.objective.set_linear([(self._var_name("y", k), self.setup_costs[k]) + for k in self.range_family] + + [(self._var_name("x", k, t), self.cost_values[k, t]) + for k, t in self.range_x_vars] ) - def create_problem(self): + def create_problem(self) -> OptimizationProblem: + """Creates an instance of optimization problem based on parameters specified. + + Returns: + an instance of optimization problem. + """ self.op = OptimizationProblem() - self.create_params() - self.create_vars() - self.create_objective() - self.create_constraints() + self._create_params() + self._create_vars() + self._create_objective() + self._create_constraints() return self.op From cda37513de79d90de248b6bb38a90d195b08bdc4 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Wed, 25 Mar 2020 10:54:11 +0000 Subject: [PATCH 106/323] formatting, linting, typing --- .../optimization/algorithms/admm_optimizer.py | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 34c1876002..8c6465767b 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -655,7 +655,7 @@ def _create_step2_problem(self) -> OptimizationProblem: # rhs="something from numpy" is ok # so, we convert every single value to python float, todo: consider removing this conversion lin_expr = [SparsePair(ind=list(range(continuous_size, continuous_size + binary_size)), - val=self._to_list(self._state.a1[i, :])) for i in + val=self._state.a1[i, :].tolist()) for i in range(constraint_count)] op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, rhs=list(self._state.b1)) @@ -664,22 +664,22 @@ def _create_step2_problem(self) -> OptimizationProblem: # A2 z + A3 u <= b2 constraint_count = self._state.a2.shape[0] lin_expr = [SparsePair(ind=list(range(continuous_size + binary_size)), - val=self._to_list(self._state.a3[i, :]) + self._to_list( - self._state.a2[i, :])) + val=self._state.a3[i, :].tolist() + + self._state.a2[i, :].tolist()) for i in range(constraint_count)] op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, - rhs=self._to_list(self._state.b2)) + rhs=self._state.b2.tolist()) if continuous_size: # A4 u <= b3 constraint_count = self._state.a4.shape[0] lin_expr = [SparsePair(ind=list(range(continuous_size)), - val=self._to_list(self._state.a4[i, :])) for i in + val=self._state.a4[i, :].tolist()) for i in range(constraint_count)] op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, - rhs=self._to_list(self._state.b3)) + rhs=self._state.b3.tolist()) return op2 @@ -708,21 +708,6 @@ def _create_step3_problem(self) -> OptimizationProblem: return op3 - # when a plain list() call is used a numpy type of values makes cplex to fail - # when cplex.write() is called. - # for debug only, list() should be used instead - @staticmethod - def _to_list(values: Iterable[Any]) -> List[Any]: - """Converts an iterable into a list of floats - - Args: - values: an iterable - - Returns: - List of floats - """ - return [float(element) for element in values] - def _update_x0(self, op1: OptimizationProblem) -> np.ndarray: return np.asarray(self._qubo_optimizer.solve(op1).x) From 0024e4a6118cba5d203b5fd6208d0aa6636c281b Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Wed, 25 Mar 2020 10:58:33 +0000 Subject: [PATCH 107/323] formatting, linting, typing --- qiskit/optimization/algorithms/admm_optimizer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 8c6465767b..f7ed99fe89 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -15,7 +15,7 @@ """An implementation of the ADMM algorithm.""" import logging import time -from typing import List, Optional, Any, Iterable +from typing import List, Optional import numpy as np from cplex import SparsePair @@ -665,7 +665,7 @@ def _create_step2_problem(self) -> OptimizationProblem: constraint_count = self._state.a2.shape[0] lin_expr = [SparsePair(ind=list(range(continuous_size + binary_size)), val=self._state.a3[i, :].tolist() + - self._state.a2[i, :].tolist()) + self._state.a2[i, :].tolist()) for i in range(constraint_count)] op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, From a5b0ffb46391a67594b53005823c6dd590ca0e81 Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Wed, 25 Mar 2020 14:45:26 +0000 Subject: [PATCH 108/323] Julien requests - Claudio's part --- Julien_requests.docx | Bin 39357 -> 0 bytes .../optimization/algorithms/admm_optimizer.py | 207 +++++++++++------- test/optimization/test_admm_miskp.py | 8 +- 3 files changed, 131 insertions(+), 84 deletions(-) delete mode 100644 Julien_requests.docx diff --git a/Julien_requests.docx b/Julien_requests.docx deleted file mode 100644 index 7f005814d2d71aa550397926bcc62ca7451fc90e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39357 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 Kh2|uDe)m6OaL2a* diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index f7ed99fe89..92139589b0 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -34,9 +34,9 @@ class ADMMParameters: """Defines a set of parameters for ADMM optimizer.""" def __init__(self, rho_initial: float = 10000, factor_c: float = 100000, beta: float = 1000, - max_iter: int = 10, tol: float = 1.e-4, max_time: float = 1800, + max_iter: int = 10, tol: float = 1.e-4, max_time: float = np.inf, three_block: bool = True, vary_rho: int = UPDATE_RHO_BY_TEN_PERCENT, - tau_incr: float = 2, tau_decr: float = 2, mu_res: float = 10, mu: float = 1000, + tau_incr: float = 2, tau_decr: float = 2, mu_res: float = 10, mu_merit: float = 1000, qubo_optimizer: Optional[OptimizationAlgorithm] = None, continuous_optimizer: Optional[OptimizationAlgorithm] = None) -> None: """Defines parameters for ADMM optimizer and their default values. @@ -52,17 +52,22 @@ def __init__(self, rho_initial: float = 10000, factor_c: float = 100000, beta: f vary_rho: Flag to select the rule to update rho. If set to 0, then rho increases by 10% at each iteration. If set to 1, then rho is modified according to primal and dual residuals. - tau_incr: Parameter used in the rho update. - tau_decr: Parameter used in the rho update. - mu_res: Parameter used in the rho update. - mu: Penalization for constraint residual. Used to compute the merit values. + tau_incr: Parameter used in the rho update (UPDATE_RHO_BY_RESIDUALS). + The update rule can be found in: + Boyd, S., Parikh, N., Chu, E., Peleato, B., & Eckstein, J. (2011). + Distributed optimization and statistical learning via the alternating + direction method of multipliers. + Foundations and Trends® in Machine learning, 3(1), 1-122. + tau_decr: Parameter used in the rho update (UPDATE_RHO_BY_RESIDUALS). + mu_res: Parameter used in the rho update (UPDATE_RHO_BY_RESIDUALS). + mu_merit: Penalization for constraint residual. Used to compute the merit values. qubo_optimizer: An instance of OptimizationAlgorithm that can effectively solve - QUBO problems + QUBO problems. continuous_optimizer: An instance of OptimizationAlgorithm that can solve - continuous problems + continuous problems. """ super().__init__() - self.mu = mu + self.mu_merit = mu_merit self.mu_res = mu_res self.tau_decr = tau_decr self.tau_incr = tau_incr @@ -84,7 +89,7 @@ class ADMMState: The state keeps track of various variables are stored that are being updated during problem solving. The values are relevant to the problem being solved. The state is recreated for each - optimization problem. State is returned as the third value + optimization problem. State is returned as the third value. """ def __init__(self, @@ -95,9 +100,10 @@ def __init__(self, """Constructs an internal computation state of the ADMM implementation. Args: - op: The optimization problem being solved - binary_indices: Indices of the binary decision variables of the original problem - continuous_indices: Indices of the continuous decision variables of the original problem + op: The optimization problem being solved. + binary_indices: Indices of the binary decision variables of the original problem. + continuous_indices: Indices of the continuous decision variables of the original + problem. rho_initial: Initial value of the rho parameter. """ super().__init__() @@ -157,7 +163,7 @@ def __init__(self, params: Optional[ADMMParameters] = None) -> None: """Constructs an instance of ADMMOptimizer. Args: - params: An instance of ADMMParameters + params: An instance of ADMMParameters. """ super().__init__() @@ -176,7 +182,7 @@ def __init__(self, params: Optional[ADMMParameters] = None) -> None: self._tau_incr = params.tau_incr self._vary_rho = params.vary_rho self._three_block = params.three_block - self._mu = params.mu + self._mu_merit = params.mu_merit self._rho_initial = params.rho_initial self._qubo_optimizer = params.qubo_optimizer @@ -200,7 +206,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: # 1. only binary and continuous variables are supported for var_type in problem.variables.get_types(): if var_type not in (CPX_BINARY, CPX_CONTINUOUS): - # variable is not binary and not continuous + # variable is not binary and not continuous. return "Only binary and continuous variables are supported" binary_indices = self._get_variable_indices(problem, CPX_BINARY) @@ -211,13 +217,13 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: for continuous_index in continuous_indices: coeff = problem.objective.get_quadratic_coefficients(binary_index, continuous_index) if coeff != 0: - # binary and continuous vars are mixed + # binary and continuous vars are mixed. return "Binary and continuous variables are not separable in the objective" - # 3. no quadratic constraints are supported + # 3. no quadratic constraints are supported. quad_constraints = problem.quadratic_constraints.get_num() if quad_constraints is not None and quad_constraints > 0: - # quadratic constraints are not supported + # quadratic constraints are not supported. return "Quadratic constraints are not supported" return None @@ -234,19 +240,19 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: Raises: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ - # parse problem and convert to an ADMM specific representation + # parse problem and convert to an ADMM specific representation. binary_indices = self._get_variable_indices(problem, CPX_BINARY) continuous_indices = self._get_variable_indices(problem, CPX_CONTINUOUS) - # create our computation state + # create our computation state. self._state = ADMMState(problem, binary_indices, continuous_indices, self._rho_initial) # convert optimization problem to a set of matrices and vector that are used - # at each iteration + # at each iteration. self._convert_problem_representation() start_time = time.time() - # we have not stated our computations yet, so elapsed time initialized as zero + # we have not stated our computations yet, so elapsed time initialized as zero. elapsed_time = 0 iteration = 0 residual = 1.e+2 @@ -280,7 +286,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: self._log.debug("cost_iterate=%s, cr=%s, merit=%s", cost_iterate, constraint_residual, merit) - # costs and merits are saved with their original sign + # costs and merits are saved with their original sign. self._state.cost_iterates.append(self._state.sense * cost_iterate) self._state.residuals.append(residual) self._state.dual_residuals.append(dual_residual) @@ -301,7 +307,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: solution, objective_value = self._get_best_merit_solution() solution = self._revert_solution_indexes(solution) - # third parameter is our internal state of computations + # third parameter is our internal state of computations. result = OptimizationResult(solution, objective_value, self._state) # debug self._log.debug("solution=%s, objective=%s at iteration=%s", @@ -310,14 +316,14 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: @staticmethod def _get_variable_indices(op: OptimizationProblem, var_type: str) -> List[int]: - """Returns a list of indices of the variables of the specified type + """Returns a list of indices of the variables of the specified type. Args: - op: Optimization problem - var_type: type of variables to look for + op: Optimization problem. + var_type: type of variables to look for. Returns: - List of indices + List of indices. """ indices = [] for i, variable_type in enumerate(op.variables.get_types()): @@ -335,7 +341,7 @@ def _revert_solution_indexes(self, internal_solution: List[np.ndarray]) \ for continuous variables. Returns: - a solution array + A solution array. """ binary_solutions, continuous_solutions = internal_solution solution = np.zeros(len(self._state.binary_indices) + len(self._state.continuous_indices)) @@ -347,7 +353,17 @@ def _revert_solution_indexes(self, internal_solution: List[np.ndarray]) \ return solution def _convert_problem_representation(self) -> None: - """Converts problem representation into set of matrices and vectors""" + """Converts problem representation into set of matrices and vectors. + Specifically, the optimization problem is represented as: + + min_{x0, u} x0^T q0 x0 + c0^T x0 + u^T q1 u + c1^T u + + s.t. a0 x0 = b0 + a1 x0 \leq b1 + a2 z + a3 u \leq b2 + a4 u <= b3 + + """ # objective self._state.q0 = self._get_q(self._state.binary_indices) self._state.c0 = self._get_c(self._state.binary_indices) @@ -364,10 +380,10 @@ def _get_q(self, variable_indices: List[int]) -> np.ndarray: from the quadratic terms in the objective. Args: - variable_indices: variable indices to look for + variable_indices: variable indices to look for. Returns: - A matrix as a numpy array of the shape(len(variable_indices), len(variable_indices)) + A matrix as a numpy array of the shape(len(variable_indices), len(variable_indices)). """ size = len(variable_indices) q = np.zeros(shape=(size, size)) @@ -380,7 +396,7 @@ def _get_q(self, variable_indices: List[int]) -> np.ndarray: var_index_j) # flip the sign, according to the optimization sense, e.g. sense == 1 if minimize, - # sense == -1 if maximize + # sense == -1 if maximize. return q * self._state.sense def _get_c(self, variable_indices: List[int]) -> np.ndarray: @@ -388,44 +404,44 @@ def _get_c(self, variable_indices: List[int]) -> np.ndarray: in the objective. Args: - variable_indices: variable indices to look for + variable_indices: variable indices to look for. Returns: - A numpy array of the shape(len(variable_indices)) + A numpy array of the shape(len(variable_indices)). """ c = np.array(self._state.op.objective.get_linear(variable_indices)) # flip the sign, according to the optimization sense, e.g. sense == 1 if minimize, - # sense == -1 if maximize + # sense == -1 if maximize. c *= self._state.sense return c def _assign_row_values(self, matrix: List[List[float]], vector: List[float], constraint_index: int, variable_indices: List[int]): """Appends a row to the specified matrix and vector based on the constraint specified by - the index using specified variables + the index using specified variables. Args: - matrix: a matrix to extend - vector: a vector to expend - constraint_index: constraint index to look for - variable_indices: variables to look for + matrix: a matrix to extend. + vector: a vector to expand. + constraint_index: constraint index to look for. + variable_indices: variables to look for. Returns: None """ - # assign matrix row + # assign matrix row. row = [] for var_index in variable_indices: row.append(self._state.op .linear_constraints.get_coefficients(constraint_index, var_index)) matrix.append(row) - # assign vector row + # assign vector row. vector.append(self._state.op.linear_constraints.get_rhs(constraint_index)) - # flip the sign if constraint is G, we want L constraints + # flip the sign if constraint is G, we want L constraints. if self._state.op.linear_constraints.get_senses(constraint_index) == "G": - # invert the sign to make constraint "L" + # invert the sign to make constraint "L". matrix[-1] = [-1 * el for el in matrix[-1]] vector[-1] = -1 * vector[-1] @@ -435,14 +451,14 @@ def _create_ndarrays(matrix: List[List[float]], vector: List[float], size: int) """Converts representation of a matrix and a vector in form of lists to numpy array. Args: - matrix: matrix to convert - vector: vector to convert - size: size to create matrix and vector + matrix: matrix to convert. + vector: vector to convert. + size: size to create matrix and vector. Returns: - Converted matrix and vector as numpy arrays + Converted matrix and vector as numpy arrays. """ - # if we don't have such constraints, return just dummy arrays + # if we don't have such constraints, return just dummy arrays. if len(matrix) != 0: return np.array(matrix), np.array(vector) else: @@ -456,7 +472,7 @@ def _get_a0_b0(self) -> (np.ndarray, np.ndarray): Corresponding matrix and vector as numpy arrays. Raises: - ValueError: if the problem is not suitable for this optimizer + ValueError: if the problem is not suitable for this optimizer. """ matrix = [] vector = [] @@ -464,7 +480,7 @@ def _get_a0_b0(self) -> (np.ndarray, np.ndarray): senses = self._state.op.linear_constraints.get_senses() index_set = set(self._state.binary_indices) for constraint_index, sense in enumerate(senses): - # we check only equality constraints here + # we check only equality constraints here. if sense != "E": continue row = self._state.op.linear_constraints.get_rows(constraint_index) @@ -485,7 +501,7 @@ def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) \ x is a vector of variables specified by the indices. Args: - variable_indices: variable indices to look for + variable_indices: variable indices to look for. Returns: A list based representation of the matrix and the vector. @@ -499,7 +515,7 @@ def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) \ if sense in ("E", "R"): # TODO: Ranged constraints should be supported continue - # sense either G or L + # sense either G or L. row = self._state.op.linear_constraints.get_rows(constraint_index) if set(row.ind).issubset(index_set): self._assign_row_values(matrix, vector, constraint_index, variable_indices) @@ -531,7 +547,7 @@ def _get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): where x is a vector of binary variables and u is a vector of continuous variables. Returns: - A numpy representation of two matrices and one vector + A numpy representation of two matrices and one vector. """ matrix = [] vector = [] @@ -544,11 +560,11 @@ def _get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): if sense in ("E", "R"): # TODO: Ranged constraints should be supported as well continue - # sense either G or L + # sense either G or L. row = self._state.op.linear_constraints.get_rows(constraint_index) row_indices = set(row.ind) # we must have a least one binary and one continuous variable, - # otherwise it is another type of constraints + # otherwise it is another type of constraints. if len(row_indices & binary_index_set) != 0 and len( row_indices & continuous_index_set) != 0: self._assign_row_values(matrix, vector, constraint_index, all_variables) @@ -560,15 +576,15 @@ def _get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): return a2, a3, b2 def _create_step1_problem(self) -> OptimizationProblem: - """Creates a step 1 sub-problem + """Creates a step 1 sub-problem. Returns: - A newly created optimization problem + A newly created optimization problem. """ op1 = OptimizationProblem() binary_size = len(self._state.binary_indices) - # create the same binary variables + # create the same binary variables. op1.variables.add(names=["x0_" + str(i + 1) for i in range(binary_size)], types=["I"] * binary_size, lb=[0.] * binary_size, @@ -586,7 +602,7 @@ def _create_step1_problem(self) -> OptimizationProblem: for j in range(i, binary_size): op1.objective.set_quadratic_coefficients(i, j, quadratic_objective[i, j]) - # prepare and set linear objective + # prepare and set linear objective. linear_objective = self._state.c0 - \ self._factor_c * np.dot(self._state.b0, self._state.a0) + \ self._state.rho * (self._state.y - self._state.z) @@ -596,10 +612,10 @@ def _create_step1_problem(self) -> OptimizationProblem: return op1 def _create_step2_problem(self) -> OptimizationProblem: - """Creates a step 2 sub-problem + """Creates a step 2 sub-problem. Returns: - A newly created optimization problem + A newly created optimization problem. """ op2 = OptimizationProblem() @@ -608,17 +624,17 @@ def _create_step2_problem(self) -> OptimizationProblem: lower_bounds = self._state.op.variables.get_lower_bounds(self._state.continuous_indices) upper_bounds = self._state.op.variables.get_upper_bounds(self._state.continuous_indices) if continuous_size: - # add u variables + # add u variables. op2.variables.add(names=["u0_" + str(i + 1) for i in range(continuous_size)], types=["C"] * continuous_size, lb=lower_bounds, ub=upper_bounds) - # add z variables + # add z variables. op2.variables.add(names=["z0_" + str(i + 1) for i in range(binary_size)], types=["C"] * binary_size, lb=[0.] * binary_size, ub=[1.] * binary_size) - # set quadratic objective coefficients for u variables + # set quadratic objective coefficients for u variables. if continuous_size: # NOTE: The multiplication by 2 is needed for the solvers to parse # the quadratic coefficients. @@ -636,23 +652,23 @@ def _create_step2_problem(self) -> OptimizationProblem: op2.objective.set_quadratic_coefficients(i + continuous_size, j + continuous_size, q_z[i, j]) - # set linear objective for u variables + # set linear objective for u variables. if continuous_size: linear_u = self._state.c1 for i in range(continuous_size): op2.objective.set_linear(i, linear_u[i]) - # set linear objective for z variables + # set linear objective for z variables. linear_z = -1 * self._state.lambda_mult - self._state.rho * (self._state.x0 + self._state.y) for i in range(binary_size): op2.objective.set_linear(i + continuous_size, linear_z[i]) - # constraints for z - # A1 z <= b1 + # constraints for z. + # A1 z <= b1. constraint_count = self._state.a1.shape[0] # in SparsePair val="something from numpy" causes an exception # when saving a model via cplex method. - # rhs="something from numpy" is ok + # rhs="something from numpy" is ok. # so, we convert every single value to python float, todo: consider removing this conversion lin_expr = [SparsePair(ind=list(range(continuous_size, continuous_size + binary_size)), val=self._state.a1[i, :].tolist()) for i in @@ -684,13 +700,13 @@ def _create_step2_problem(self) -> OptimizationProblem: return op2 def _create_step3_problem(self) -> OptimizationProblem: - """Creates a step 3 sub-problem + """Creates a step 3 sub-problem. Returns: - A newly created optimization problem + A newly created optimization problem. """ op3 = OptimizationProblem() - # add y variables + # add y variables. binary_size = len(self._state.binary_indices) op3.variables.add(names=["y_" + str(i + 1) for i in range(binary_size)], types=["C"] * binary_size) @@ -709,15 +725,43 @@ def _create_step3_problem(self) -> OptimizationProblem: return op3 def _update_x0(self, op1: OptimizationProblem) -> np.ndarray: + """Solves the Step1 OptimizationProblem via the qubo optimizer. + + Args: + op1: the Step1 OptimizationProblem. + + Returns: + A solution of the Step1, as a numpy array. + """ return np.asarray(self._qubo_optimizer.solve(op1).x) def _update_x1(self, op2: OptimizationProblem) -> (np.ndarray, np.ndarray): + """Solves the Step2 OptimizationProblem via the continuous optimizer. + + Args: + op2: the Step2 OptimizationProblem + + Returns: + A solution of the Step2, as a pair of numpy arrays. + First array contains the values of decision variables u, and + second array contains the values of decision variables z. + + """ vars_op2 = self._continuous_optimizer.solve(op2).x vars_u = np.asarray(vars_op2[:len(self._state.continuous_indices)]) vars_z = np.asarray(vars_op2[len(self._state.continuous_indices):]) return vars_u, vars_z def _update_y(self, op3: OptimizationProblem) -> np.ndarray: + """Solves the Step3 OptimizationProblem via the continuous optimizer. + + Args: + op3: the Step3 OptimizationProblem + + Returns: + A solution of the Step3, as a numpy array. + + """ return np.asarray(self._continuous_optimizer.solve(op3).x) def _get_best_merit_solution(self) -> (List[np.ndarray], float): @@ -741,6 +785,13 @@ def _get_best_merit_solution(self) -> (List[np.ndarray], float): return sol, sol_val def _update_lambda_mult(self) -> np.ndarray: + """ + Updates the values of lambda multiplier, given the updated iterates + x0, z, and y. + + Returns: The updated array of values of lambda multiplier. + + """ return self._state.lambda_mult + \ self._state.rho * (self._state.x0 - self._state.z + self._state.y) @@ -793,7 +844,7 @@ def _get_merit(self, cost_iterate: float, constraint_residual: float) -> float: Returns: Merit value as a float """ - return cost_iterate + self._mu * constraint_residual + return cost_iterate + self._mu_merit * constraint_residual def _get_objective_value(self) -> float: """Computes the value of the objective function. @@ -814,10 +865,10 @@ def _get_solution_residuals(self, iteration: int) -> (float, float): """Compute primal and dual residual. Args: - iteration: iteration number + iteration: Iteration number. Returns: - r, s as primary and dual residuals + r, s as primary and dual residuals. """ elements = self._state.x0 - self._state.z - self._state.y primal_residual = pow(sum(e ** 2 for e in elements), 0.5) diff --git a/test/optimization/test_admm_miskp.py b/test/optimization/test_admm_miskp.py index 46570eac3a..a1135e4855 100644 --- a/test/optimization/test_admm_miskp.py +++ b/test/optimization/test_admm_miskp.py @@ -82,8 +82,8 @@ def _get_problem_params() -> (int, int, float, np.ndarray, np.ndarray, np.ndarra class Miskp: """A Helper class to generate Mixed Integer Setup Knapsack problems""" def __init__(self, family_count: int, items_per_family: int, knapsack_capacity: float, - setup_costs: np.ndarray, resource_values: np.ndarray, cost_values: np.ndarray, - pairwise_incomp: int = 0, multiple_choice: int = 0) -> None: + setup_costs: np.ndarray, resource_values: np.ndarray, cost_values: np.ndarray)\ + -> None: """Constructs an instance of this helper class to create suitable ADMM problems. Args: @@ -93,12 +93,8 @@ def __init__(self, family_count: int, items_per_family: int, knapsack_capacity: setup_costs: setup cost to include family k in the knapsack resource_values: resources consumed if item t in family k is included in the knapsack cost_values: value of including item t in family k in the knapsack - pairwise_incomp: - multiple_choice: """ - self.multiple_choice = multiple_choice - self.pairwise_incomp = pairwise_incomp self.knapsack_capacity = knapsack_capacity self.setup_costs = setup_costs self.resource_values = resource_values From fe38ac95b57af1aca297faef628834b07397166a Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Wed, 25 Mar 2020 15:06:26 +0000 Subject: [PATCH 109/323] formatting, linting, typing --- qiskit/optimization/algorithms/admm_optimizer.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 92139589b0..f34d001e59 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -36,8 +36,8 @@ class ADMMParameters: def __init__(self, rho_initial: float = 10000, factor_c: float = 100000, beta: float = 1000, max_iter: int = 10, tol: float = 1.e-4, max_time: float = np.inf, three_block: bool = True, vary_rho: int = UPDATE_RHO_BY_TEN_PERCENT, - tau_incr: float = 2, tau_decr: float = 2, mu_res: float = 10, mu_merit: float = 1000, - qubo_optimizer: Optional[OptimizationAlgorithm] = None, + tau_incr: float = 2, tau_decr: float = 2, mu_res: float = 10, + mu_merit: float = 1000, qubo_optimizer: Optional[OptimizationAlgorithm] = None, continuous_optimizer: Optional[OptimizationAlgorithm] = None) -> None: """Defines parameters for ADMM optimizer and their default values. @@ -359,8 +359,8 @@ def _convert_problem_representation(self) -> None: min_{x0, u} x0^T q0 x0 + c0^T x0 + u^T q1 u + c1^T u s.t. a0 x0 = b0 - a1 x0 \leq b1 - a2 z + a3 u \leq b2 + a1 x0 <= b1 + a2 z + a3 u <= b2 a4 u <= b3 """ @@ -669,7 +669,7 @@ def _create_step2_problem(self) -> OptimizationProblem: # in SparsePair val="something from numpy" causes an exception # when saving a model via cplex method. # rhs="something from numpy" is ok. - # so, we convert every single value to python float, todo: consider removing this conversion + # so, we convert every single value to python float lin_expr = [SparsePair(ind=list(range(continuous_size, continuous_size + binary_size)), val=self._state.a1[i, :].tolist()) for i in range(constraint_count)] From 44b5e1800bcab0e85202f75a82c0883a9bd5e305 Mon Sep 17 00:00:00 2001 From: adekusar-drl <62334182+adekusar-drl@users.noreply.github.com> Date: Wed, 25 Mar 2020 15:44:53 +0000 Subject: [PATCH 110/323] Update qiskit/optimization/algorithms/admm_optimizer.py Co-Authored-By: Julien Gacon --- qiskit/optimization/algorithms/admm_optimizer.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index f34d001e59..c9b428f578 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -79,9 +79,8 @@ def __init__(self, rho_initial: float = 10000, factor_c: float = 100000, beta: f self.factor_c = factor_c self.beta = beta self.rho_initial = rho_initial - self.qubo_optimizer = qubo_optimizer if qubo_optimizer is not None else CplexOptimizer() - self.continuous_optimizer = continuous_optimizer if continuous_optimizer is not None \ - else CplexOptimizer() + self.qubo_optimizer = qubo_optimizer or CplexOptimizer() + self.continuous_optimizer = continuous_optimizer or CplexOptimizer() class ADMMState: From 18c40304f4b1b15fea661a76c7d6e4094146f90e Mon Sep 17 00:00:00 2001 From: adekusar-drl <62334182+adekusar-drl@users.noreply.github.com> Date: Wed, 25 Mar 2020 16:51:06 +0000 Subject: [PATCH 111/323] Update qiskit/optimization/algorithms/admm_optimizer.py Co-Authored-By: Julien Gacon --- qiskit/optimization/algorithms/admm_optimizer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index c9b428f578..cc70e68f3f 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -116,6 +116,7 @@ def __init__(self, # define heavily used matrix, they are used at each iteration, so let's cache them, # they are np.ndarrays + # pylint:disable=invalid-name # objective self.q0 = None self.c0 = None From 6a51541dc02bfaacff43c1c6ed78fcdc8a9385dd Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 25 Mar 2020 22:58:25 +0100 Subject: [PATCH 112/323] minor bug fixes + style updates --- .../algorithms/minimum_eigen_solvers/qaoa/qaoa.py | 3 ++- qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py | 2 +- qiskit/optimization/algorithms/__init__.py | 11 ++++++----- qiskit/optimization/algorithms/admm_optimizer.py | 4 ++-- qiskit/optimization/algorithms/cobyla_optimizer.py | 4 ++-- test/optimization/test_admm_miskp.py | 8 ++++---- test/optimization/test_qaoa.py | 2 -- test/optimization/test_quadratic_constraints.py | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/qaoa/qaoa.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/qaoa/qaoa.py index 2c8ae2cad5..1a589584e6 100755 --- a/qiskit/aqua/algorithms/minimum_eigen_solvers/qaoa/qaoa.py +++ b/qiskit/aqua/algorithms/minimum_eigen_solvers/qaoa/qaoa.py @@ -119,7 +119,8 @@ def __init__(self, operator: BaseOperator = None, optimizer: Optimizer = None, p # will cause the var form to be built super().__init__(operator, None, optimizer, initial_point=initial_point, max_evals_grouped=max_evals_grouped, aux_operators=aux_operators, - callback=callback, auto_conversion=auto_conversion) + callback=callback, auto_conversion=auto_conversion, + quantum_instance=quantum_instance) @VQE.operator.setter def operator(self, operator: BaseOperator) -> None: diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py index d897678b96..614cb0af16 100755 --- a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py @@ -164,7 +164,7 @@ def __init__(self, cost_fn=self._energy_evaluation, initial_point=initial_point) - self._quantum_instance = quantum_instance + self.quantum_instance = quantum_instance logger.info(self.print_settings()) self._var_form_params = None diff --git a/qiskit/optimization/algorithms/__init__.py b/qiskit/optimization/algorithms/__init__.py index 43365111ae..eac8aac70d 100644 --- a/qiskit/optimization/algorithms/__init__.py +++ b/qiskit/optimization/algorithms/__init__.py @@ -27,7 +27,7 @@ .. autosummary:: :toctree: ../stubs/ :nosignatures: - + OptimizationAlgorithm Algorithms @@ -36,16 +36,17 @@ .. autosummary:: :toctree: ../stubs/ :nosignatures: - + ADMMOptimizer CobylaOptimizer CplexOptimizer GroverMinimumFinder MinimumEigenOptimizer RecursiveMinimumEigenOptimizer - + """ +from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer from qiskit.optimization.algorithms.optimization_algorithm import OptimizationAlgorithm from qiskit.optimization.algorithms.cplex_optimizer import CplexOptimizer from qiskit.optimization.algorithms.cobyla_optimizer import CobylaOptimizer @@ -54,5 +55,5 @@ RecursiveMinimumEigenOptimizer from qiskit.optimization.algorithms.grover_minimum_finder import GroverMinimumFinder -__all__ = ["OptimizationAlgorithm", "CplexOptimizer", "CobylaOptimizer", "MinimumEigenOptimizer", - "RecursiveMinimumEigenOptimizer", "GroverMinimumFinder"] +__all__ = ["ADMMOptimizer", "OptimizationAlgorithm", "CplexOptimizer", "CobylaOptimizer", + "MinimumEigenOptimizer", "RecursiveMinimumEigenOptimizer", "GroverMinimumFinder"] diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index cc70e68f3f..c114f15f15 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -19,7 +19,7 @@ import numpy as np from cplex import SparsePair -from qiskit.optimization.algorithms import CplexOptimizer +from qiskit.optimization.algorithms.cplex_optimizer import CplexOptimizer from qiskit.optimization.algorithms.optimization_algorithm import OptimizationAlgorithm from qiskit.optimization.problems.optimization_problem import OptimizationProblem from qiskit.optimization.problems.variables import CPX_BINARY, CPX_CONTINUOUS @@ -116,7 +116,7 @@ def __init__(self, # define heavily used matrix, they are used at each iteration, so let's cache them, # they are np.ndarrays - # pylint:disable=invalid-name + # pylint:disable=invalid-name # objective self.q0 = None self.c0 = None diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index e2294eb676..542ba4bb8c 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -143,9 +143,9 @@ def objective(x): ubs = problem.variables.get_upper_bounds() for i in range(num_vars): if lbs[i] > -infinity: - constraints += [lambda x: x - lbs[i]] + constraints += [lambda x, lbs=lbs, i=i: x - lbs[i]] if ubs[i] < infinity: - constraints += [lambda x: ubs[i] - x] + constraints += [lambda x, lbs=lbs, i=i: ubs[i] - x] # add linear constraints for i in range(problem.linear_constraints.get_num()): diff --git a/test/optimization/test_admm_miskp.py b/test/optimization/test_admm_miskp.py index a1135e4855..d8ddbc7e02 100644 --- a/test/optimization/test_admm_miskp.py +++ b/test/optimization/test_admm_miskp.py @@ -75,12 +75,13 @@ def _get_problem_params() -> (int, int, float, np.ndarray, np.ndarray, np.ndarra -4.24, -8.30, -7.02]) \ .reshape((family_count, items_per_family)) - return family_count, items_per_family, knapsack_capacity, setup_costs, \ - resource_values, cost_values + return family_count, items_per_family, knapsack_capacity, setup_costs, resource_values,\ + cost_values class Miskp: """A Helper class to generate Mixed Integer Setup Knapsack problems""" + def __init__(self, family_count: int, items_per_family: int, knapsack_capacity: float, setup_costs: np.ndarray, resource_values: np.ndarray, cost_values: np.ndarray)\ -> None: @@ -162,8 +163,7 @@ def _create_constraint_capacity(self) -> None: self.op.linear_constraints.add( lin_expr=[ SparsePair( - ind=[self._var_name("x", i, j) for i, j in self.range_x_vars] - , + ind=[self._var_name("x", i, j) for i, j in self.range_x_vars], val=[self.resource_values[i, j] for i, j in self.range_x_vars]) ], senses="L", diff --git a/test/optimization/test_qaoa.py b/test/optimization/test_qaoa.py index 19882816c5..e9502af3b0 100755 --- a/test/optimization/test_qaoa.py +++ b/test/optimization/test_qaoa.py @@ -14,7 +14,6 @@ """ Test QAOA """ -import unittest from test.optimization import QiskitOptimizationTestCase import numpy as np @@ -84,4 +83,3 @@ def test_qaoa(self, w, prob, m, solutions): self.log.debug('solution: %s', graph_solution) self.log.debug('solution objective: %s', max_cut.max_cut_value(x, w)) self.assertIn(''.join([str(int(i)) for i in graph_solution]), solutions) - diff --git a/test/optimization/test_quadratic_constraints.py b/test/optimization/test_quadratic_constraints.py index ecce171472..c4184dcaff 100644 --- a/test/optimization/test_quadratic_constraints.py +++ b/test/optimization/test_quadratic_constraints.py @@ -14,11 +14,11 @@ """ Test QuadraticConstraintInterface """ +from test.optimization.common import QiskitOptimizationTestCase from cplex import SparsePair, SparseTriple from qiskit.optimization import QiskitOptimizationError from qiskit.optimization.problems import OptimizationProblem -from test.optimization.common import QiskitOptimizationTestCase class TestQuadraticConstraints(QiskitOptimizationTestCase): From 435dedf43dc57e89304fc80d32e4be33d9bd3307 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Thu, 26 Mar 2020 10:52:59 +0100 Subject: [PATCH 113/323] add contributors, minor fixes Co-authored-by: Constantin Gonciulea Co-authored-by: Austin Gilliam --- .../algorithms/grover_minimum_finder.py | 22 +++++++++------- ...zation_problem_to_negative_value_oracle.py | 7 ++--- .../test_grover_minimum_finder.py | 26 ++++++++----------- ...zation_problem_to_negative_value_oracle.py | 23 ++++++++-------- 4 files changed, 38 insertions(+), 40 deletions(-) diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index a338d3d238..def33849a5 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -32,6 +32,9 @@ from qiskit.providers import BaseBackend +logger = logging.getLogger(__name__) + + class GroverMinimumFinder(OptimizationAlgorithm): """Uses Grover Adaptive Search (GAS) to find the minimum of a QUBO function.""" @@ -48,7 +51,6 @@ def __init__(self, num_iterations: int = 3, backend = quantum_instance or Aer.get_backend('statevector_simulator') quantum_instance = QuantumInstance(backend) self._quantum_instance = quantum_instance - self._logger = logging.getLogger(__name__) def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: """Checks whether a given problem can be solved with this optimizer. @@ -135,7 +137,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: grover = Grover(oracle, init_state=a_operator, num_iterations=rotation_count) circuit = grover.construct_circuit( measurement=self._quantum_instance.is_statevector - ) + ) else: circuit = a_operator._circuit @@ -149,16 +151,16 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: int_v = self._bin_to_int(v, n_value) + threshold v = self._twos_complement(int_v, n_value) - self._logger.info('Iterations: %s', rotation_count) - self._logger.info('Outcome: %s', outcome) - self._logger.info('Value: %s = %s', v, int_v) + logger.info('Iterations: %s', rotation_count) + logger.info('Outcome: %s', outcome) + logger.info('Value: %s = %s', v, int_v) # If the value is an improvement, we update the iteration parameters (e.g. oracle). if int_v < optimum_value: optimum_key = k optimum_value = int_v - self._logger.info('Current Optimum Key: %s', optimum_key) - self._logger.info('Current Optimum Value: %s', optimum_value) + logger.info('Current Optimum Key: %s', optimum_key) + logger.info('Current Optimum Value: %s', optimum_value) if v.startswith('1'): improvement_found = True threshold = optimum_value @@ -171,7 +173,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: # Using Durr and Hoyer method, increase m. # TODO: Give option for a rotation schedule, or for different lambda's. m = int(np.ceil(min(m * 8/7, 2**(n_key / 2)))) - self._logger.info('No Improvement. M: %s', m) + logger.info('No Improvement. M: %s', m) # Check if we've already seen this value. if k not in keys_measured: @@ -186,7 +188,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: operations = circuit.count_ops() operation_count[iteration] = operations iteration += 1 - self._logger.info('Operation Count: %s\n', operations) + logger.info('Operation Count: %s\n', operations) # Get original key and value pairs. func_dict[-1] = orig_constant @@ -217,7 +219,7 @@ def _measure(self, circuit: QuantumCircuit, n_key: int, n_value: int) -> str: # Pick a random outcome. freq[len(freq)-1] = (freq[len(freq)-1][0], 1 - sum([x[1] for x in freq[0:len(freq)-1]])) idx = np.random.choice(len(freq), 1, p=[x[1] for x in freq])[0] - self._logger.info('Frequencies: %s', freq) + logger.info('Frequencies: %s', freq) return freq[idx][0] diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index 152e589773..e9c5d92b5c 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -24,6 +24,8 @@ from qiskit.aqua.components.iqfts import Standard as IQFT from qiskit.optimization.problems import OptimizationProblem +logger = logging.getLogger(__name__) + class OptimizationProblemToNegativeValueOracle: """Converts an optimization problem (QUBO) to a negative value oracle. @@ -42,11 +44,10 @@ def __init__(self, num_output_qubits: int, measurement: bool = False) -> None: self._num_key = 0 self._num_value = num_output_qubits self._measurement = measurement - self._logger = logging.getLogger(__name__) def encode(self, problem: OptimizationProblem) -> \ Tuple[Custom, CustomCircuitOracle, Dict[Union[int, Tuple[int, int]], int]]: - """ A helper function that converts a QUBO into an oracle that recognizes negative numbers. + """A helper function that converts a QUBO into an oracle that recognizes negative numbers. Args: problem: The problem to be solved. @@ -83,7 +84,7 @@ def encode(self, problem: OptimizationProblem) -> \ # Get the function dictionary. func = self._get_function(linear_coeff, quadratic_coeff, constant) - self._logger.info("Function: %s\n", func) + logger.info("Function: %s\n", func) # Define state preparation operator A from function. a_operator_circuit = self._build_operator(func) diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index 5aed7962ad..ac7f1281bd 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -12,19 +12,17 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" Test Grover Minimum Finder """ +"""Test Grover Minimum Finder.""" from test.optimization import QiskitOptimizationTestCase import numpy as np -from docplex.mp.model import Model -from qiskit.finance.applications.ising import portfolio from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.optimization.algorithms import GroverMinimumFinder, MinimumEigenOptimizer from qiskit.optimization.problems import OptimizationProblem class TestGroverMinimumFinder(QiskitOptimizationTestCase): - """GroverMinimumFinder Tests""" + """GroverMinimumFinder tests.""" def validate_results(self, problem, results, max_iterations): """Validate the results object returned by GroverMinimumFinder.""" @@ -35,8 +33,6 @@ def validate_results(self, problem, results, max_iterations): iterations = len(grover_results.operation_counts) rot = grover_results.rotation_count func = grover_results.func_dict - print("Function", func) - print("Optimum Key:", op_key, "Optimum Value:", op_value, "Rotations:", rot, "\n") # Get expected value. solver = MinimumEigenOptimizer(NumPyMinimumEigensolver()) @@ -49,12 +45,12 @@ def validate_results(self, problem, results, max_iterations): self.assertTrue(comp_result.fval == results.fval or max_hit) def test_qubo_gas_int_zero(self): - """ Test for when the answer is zero """ + """Test for when the answer is zero.""" # Input. op = OptimizationProblem() - op.variables.add(names=["x0", "x1"], types='BB') - linear = [("x0", 0), ("x1", 0)] + op.variables.add(names=['x0', 'x1'], types='BB') + linear = [('x0', 0), ('x1', 0)] op.objective.set_linear(linear) # Will not find a negative, should return 0. @@ -64,12 +60,12 @@ def test_qubo_gas_int_zero(self): self.assertEqual(results.fval, 0.0) def test_qubo_gas_int_simple(self): - """ Test for simple case, with 2 linear coeffs and no quadratic coeffs or constants """ + """Test for simple case, with 2 linear coeffs and no quadratic coeffs or constants.""" # Input. op = OptimizationProblem() - op.variables.add(names=["x0", "x1"], types='BB') - linear = [("x0", -1), ("x1", 2)] + op.variables.add(names=['x0', 'x1'], types='BB') + linear = [('x0', -1), ('x1', 2)] op.objective.set_linear(linear) # Get the optimum key and value. @@ -79,13 +75,13 @@ def test_qubo_gas_int_simple(self): self.validate_results(op, results, n_iter) def test_qubo_gas_int_paper_example(self): - """ Test the example from https://arxiv.org/abs/1912.04088 """ + """Test the example from https://arxiv.org/abs/1912.04088.""" # Input. op = OptimizationProblem() - op.variables.add(names=["x0", "x1", "x2"], types='BBB') + op.variables.add(names=['x0', 'x1', 'x2'], types='BBB') - linear = [("x0", -1), ("x1", 2), ("x2", -3)] + linear = [('x0', -1), ('x1', 2), ('x2', -3)] op.objective.set_linear(linear) op.objective.set_quadratic_coefficients('x0', 'x2', -2) op.objective.set_quadratic_coefficients('x1', 'x2', -1) diff --git a/test/optimization/test_optimization_problem_to_negative_value_oracle.py b/test/optimization/test_optimization_problem_to_negative_value_oracle.py index 472828d281..4440c4d666 100644 --- a/test/optimization/test_optimization_problem_to_negative_value_oracle.py +++ b/test/optimization/test_optimization_problem_to_negative_value_oracle.py @@ -12,7 +12,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" Test Optimization Problem to Negative Value Oracle """ +"""Test Optimization Problem to Negative Value Oracle.""" from test.optimization import QiskitOptimizationTestCase import numpy as np @@ -70,7 +70,7 @@ def _measure(qc, n_key, n_value): @staticmethod def _bin_to_int(v, num_value_bits): - if v.startswith("1"): + if v.startswith('1'): int_v = int(v, 2) - 2 ** num_value_bits else: int_v = int(v, 2) @@ -78,14 +78,14 @@ def _bin_to_int(v, num_value_bits): return int_v def test_optnvo_3_linear_2_quadratic_no_constant(self): - """ Test with 3 linear coefficients, 2 quadratic, and no constant """ + """Test with 3 linear coefficients, 2 quadratic, and no constant.""" # Circuit parameters. num_value = 4 # Input. problem = OptimizationProblem() - problem.variables.add(names=["x0", "x1", "x2"], types='BBB') - linear = [("x0", -1), ("x1", 2), ("x2", -3)] + problem.variables.add(names=['x0', 'x1', 'x2'], types='BBB') + linear = [('x0', -1), ('x1', 2), ('x2', -3)] problem.objective.set_linear(linear) problem.objective.set_quadratic_coefficients('x0', 'x2', -2) problem.objective.set_quadratic_coefficients('x1', 'x2', -1) @@ -98,14 +98,14 @@ def test_optnvo_3_linear_2_quadratic_no_constant(self): self._validate_operator(func_dict, len(linear), num_value, a_operator) def test_optnvo_4_key_all_negative(self): - """ Test with all negative values """ + """Test with all negative values.""" # Circuit parameters. num_value = 5 # Input. problem = OptimizationProblem() - problem.variables.add(names=["x0", "x1", "x2"], types='BBB') - linear = [("x0", -1), ("x1", -2), ("x2", -1)] + problem.variables.add(names=['x0', 'x1', 'x2'], types='BBB') + linear = [('x0', -1), ('x1', -2), ('x2', -1)] problem.objective.set_linear(linear) problem.objective.set_quadratic_coefficients('x0', 'x1', -1) problem.objective.set_quadratic_coefficients('x0', 'x2', -2) @@ -120,18 +120,17 @@ def test_optnvo_4_key_all_negative(self): self._validate_operator(func_dict, len(linear), num_value, a_operator) def test_optnvo_6_key(self): - """ Test with 6 linear coefficients, negative quadratics, no constant """ + """Test with 6 linear coefficients, negative quadratics, no constant.""" # Circuit parameters. num_value = 4 # Input. problem = OptimizationProblem() - problem.variables.add(names=["x0", "x1", "x2", "x3", "x4", "x5"], types='BBBBBB') - linear = [("x0", -1), ("x1", -2), ("x2", -1), ("x3", 0), ("x4", 1), ("x5", 2)] + problem.variables.add(names=['x0', 'x1', 'x2', 'x3', 'x4', 'x5'], types='BBBBBB') + linear = [('x0', -1), ('x1', -2), ('x2', -1), ('x3', 0), ('x4', 1), ('x5', 2)] problem.objective.set_linear(linear) problem.objective.set_quadratic_coefficients('x0', 'x3', -1) problem.objective.set_quadratic_coefficients('x1', 'x5', -2) - constant = 0 # Convert to dictionary format with operator/oracle. converter = OptimizationProblemToNegativeValueOracle(num_value) From aca8d6c8ea4a7e2e89993411f6c2dffca28f7356 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Thu, 26 Mar 2020 11:08:57 +0100 Subject: [PATCH 114/323] add contributors, minor lint fixes Co-authored-by: Jakub Marecek --- .../problems/optimization_problem.py | 77 ++++++++++--------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/qiskit/optimization/problems/optimization_problem.py b/qiskit/optimization/problems/optimization_problem.py index 44ee057877..91628cc6e3 100644 --- a/qiskit/optimization/problems/optimization_problem.py +++ b/qiskit/optimization/problems/optimization_problem.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2019, 2020. # # 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 @@ -12,6 +12,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""The optimization problem.""" + from docplex.mp.model import Model from cplex import Cplex, SparsePair from cplex.exceptions import CplexSolverError @@ -25,9 +27,8 @@ from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError -class OptimizationProblem(object): - """A class encapsulating an optimization problem, modelled after - Python CPLEX API. +class OptimizationProblem: + """A class encapsulating an optimization problem, modelled after Python CPLEX API. An instance of the OptimizationProblem class provides methods for creating, modifying, and querying an optimization problem, solving it, and @@ -61,27 +62,27 @@ def __init__(self, *args): self._disposed = False self._name = None + # see `qiskit.optimization.VariablesInterface()` self.variables = VariablesInterface() - """See `qiskit.optimization.VariablesInterface()` """ - self.linear_constraints = LinearConstraintInterface( - varindex=self.variables.get_indices) - """See `qiskit.optimization.LinearConstraintInterface()` """ + # see `qiskit.optimization.LinearConstraintInterface()` + self.linear_constraints = LinearConstraintInterface(varindex=self.variables.get_indices) + # see `qiskit.optimization.QuadraticConstraintInterface()` self.quadratic_constraints = QuadraticConstraintInterface( - varindex=self.variables.get_indices) - """See `qiskit.optimization.QuadraticConstraintInterface()` """ + varindex=self.variables.get_indices + ) + # see `qiskit.optimization.ObjectiveInterface()` # pylint: disable=unexpected-keyword-arg self.objective = ObjectiveInterface(varindex=self.variables.get_indices) - """See `qiskit.optimization.ObjectiveInterface()` """ + # see `qiskit.optimization.SolutionInterface()` self.solution = SolutionInterface() - """See `qiskit.optimization.SolutionInterface()` """ + # see `qiskit.optimization.ProblemType()` -- essentially conversions from integers to + # strings and back self.problem_type = ProblemType() - """See `qiskit.optimization.ProblemType()` -- - essentially conversions from integers to strings and back""" self.my_problem_type = 0 # read from file in case filename is given @@ -92,7 +93,6 @@ def __init__(self, *args): raise QiskitOptimizationError('Could not load file: %s' % args[0]) def from_cplex(self, op): - # make sure current problem is clean self._disposed = False try: @@ -141,8 +141,8 @@ def from_cplex(self, op): self.objective.set_linear(i, v) # set quadratic objective terms - for i, sp in enumerate(op.objective.get_quadratic()): - for j, v in zip(sp.ind, sp.val): + for i, sparse_pair in enumerate(op.objective.get_quadratic()): + for j, v in zip(sparse_pair.ind, sparse_pair.val): self.objective.set_quadratic_coefficients(i, j, v) # set objective offset @@ -169,7 +169,6 @@ def from_docplex(self, model: Model): self.from_cplex(cplex) def to_cplex(self): - # create empty CPLEX model op = Cplex() if self.get_problem_name() is not None: @@ -197,8 +196,8 @@ def to_cplex(self): op.objective.set_linear(i, v) # set quadratic objective terms - for i, vi in self.objective.get_quadratic().items(): - for j, v in vi.items(): + for i, v_i in self.objective.get_quadratic().items(): + for j, v in v_i.items(): op.objective.set_quadratic_coefficients(i, j, v) # set objective offset @@ -218,8 +217,7 @@ def to_cplex(self): return op def end(self): - """Releases the OptimizationProblem object. - """ + """Releases the OptimizationProblem object.""" if self._disposed: return self._disposed = True @@ -229,11 +227,11 @@ def __del__(self): self.end() def __enter__(self): - """ To implement a ContextManager, as in Cplex. """ + """To implement a ContextManager, as in Cplex.""" return self def __exit__(self, *exc): - """ To implement a ContextManager, as in Cplex. """ + """To implement a ContextManager, as in Cplex.""" return False def read(self, filename, filetype=""): @@ -355,17 +353,20 @@ def solve(self): OptimizationProblem.solution.get_status() to query the status of the current solution. """ - None # TODO: Implement me + pass def set_problem_name(self, name): + """Set the problem name.""" self._name = name def get_problem_name(self): + """Get the problem name.""" return self._name def substitute_variables(self, constants=None, variables=None): - """ + """Substitute variables of the problem. + constants: SparsePair (replace variable by constant) variables: SparseTriple (replace variables by weighted other variable need to copy everything using name reference to make sure that indices are matched correctly @@ -407,19 +408,19 @@ def substitute_variables(self, constants=None, variables=None): # copy variables that are not replaced # TODO: what about columns? - for name, var_type, lb, ub in zip( + for name, var_type, lower_bound, upper_bound in zip( self.variables.get_names(), self.variables.get_types(), self.variables.get_lower_bounds(), self.variables.get_upper_bounds(), ): if name not in vars_to_be_replaced: - op.variables.add(lb=[lb], ub=[ub], types=[var_type], names=[name]) + op.variables.add(lb=[lower_bound], ub=[upper_bound], types=[var_type], names=[name]) else: # check that replacement satisfies bounds repl = vars_to_be_replaced[name] if len(repl) == 1: - if not (lb <= repl[0] <= ub): + if not lower_bound <= repl[0] <= upper_bound: raise QiskitOptimizationError('Infeasible substitution for variable') # initialize offset @@ -442,8 +443,8 @@ def substitute_variables(self, constants=None, variables=None): op.objective.set_linear(i_name, w_i) # construct quadratic part of objective - for i, vi in self.objective.get_quadratic().items(): - for j, v in vi.items(): + for i, v_i in self.objective.get_quadratic().items(): + for j, v in v_i.items(): i = self.variables.get_indices(i) j = self.variables.get_indices(j) i_name = self.variables.get_names(i) @@ -508,22 +509,22 @@ def substitute_variables(self, constants=None, variables=None): if k_name in vars_to_be_replaced.keys(): raise QiskitOptimizationError( 'Cannot substitute by variable that gets substituted itself.') - l = self.variables.get_indices(j_repl[0]) - l_name = self.variables.get_names(l) - if l_name in vars_to_be_replaced.keys(): + m = self.variables.get_indices(j_repl[0]) + m_name = self.variables.get_names(m) + if m_name in vars_to_be_replaced.keys(): raise QiskitOptimizationError( 'Cannot substitute by variable that gets substituted itself.') - w_kl = op.objective.get_quadratic_coefficients(k_name, l_name) + w_kl = op.objective.get_quadratic_coefficients(k_name, m_name) w_kl += w_ij * i_repl[1] * j_repl[1] - op.objective.set_quadratic_coefficients(k_name, l_name, w_kl) + op.objective.set_quadratic_coefficients(k_name, m_name, w_kl) else: # nothing to be replaced, just copy coefficients if i == j: w_ij = sum([self.objective.get_quadratic_coefficients(i_name, j_name), - op.objective.get_quadratic_coefficients(i_name, j_name)]) + op.objective.get_quadratic_coefficients(i_name, j_name)]) else: w_ij = sum([self.objective.get_quadratic_coefficients(i_name, j_name) / 2, - op.objective.get_quadratic_coefficients(i_name, j_name)]) + op.objective.get_quadratic_coefficients(i_name, j_name)]) op.objective.set_quadratic_coefficients(i_name, j_name, w_ij) # set offset From 67e359ef5d098c8c2df4f88f9d22958e17d2fc2f Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Thu, 26 Mar 2020 12:04:39 +0000 Subject: [PATCH 115/323] fix max --- 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 92139589b0..34befdcb8a 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -777,7 +777,7 @@ def _get_best_merit_solution(self) -> (List[np.ndarray], float): """ it_best_merits = self._state.merits.index( - min(list(map(lambda x: self._state.sense * x, self._state.merits)))) + self._state.sense * min(list(map(lambda x: self._state.sense * x, self._state.merits)))) x0 = self._state.x0_saved[it_best_merits] u = self._state.u_saved[it_best_merits] sol = [x0, u] From 10ce0e64b5eec1b0f0605f9c42809271aad5cd0a Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Thu, 26 Mar 2020 14:47:49 +0000 Subject: [PATCH 116/323] added admm ref; linalg --- qiskit/optimization/algorithms/admm_optimizer.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 66c27bb559..4fb6d8d754 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -157,7 +157,11 @@ def __init__(self, class ADMMOptimizer(OptimizationAlgorithm): - """An implementation of the ADMM algorithm.""" + """An implementation of the ADMM-based heuristic introduced here: + Gambella, C., & Simonetto, A. (2020). + Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical and Quantum Computers. + arXiv preprint arXiv:2001.02069. + """ def __init__(self, params: Optional[ADMMParameters] = None) -> None: """Constructs an instance of ADMMOptimizer. @@ -871,11 +875,11 @@ def _get_solution_residuals(self, iteration: int) -> (float, float): r, s as primary and dual residuals. """ elements = self._state.x0 - self._state.z - self._state.y - primal_residual = pow(sum(e ** 2 for e in elements), 0.5) + primal_residual = np.linalg.norm(elements) if iteration > 0: elements_dual = self._state.z - self._state.z_saved[iteration - 1] else: elements_dual = self._state.z - self._state.z_init - dual_residual = self._state.rho * pow(sum(e ** 2 for e in elements_dual), 0.5) + dual_residual = self._state.rho * np.linalg.norm(elements_dual) return primal_residual, dual_residual From d9bc36df8eabe505820da8d077483a9d8d8a19ba Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Thu, 26 Mar 2020 12:04:39 +0000 Subject: [PATCH 117/323] fix max --- 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 c114f15f15..cbb7b614ca 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -777,7 +777,7 @@ def _get_best_merit_solution(self) -> (List[np.ndarray], float): """ it_best_merits = self._state.merits.index( - min(list(map(lambda x: self._state.sense * x, self._state.merits)))) + self._state.sense * min(list(map(lambda x: self._state.sense * x, self._state.merits)))) x0 = self._state.x0_saved[it_best_merits] u = self._state.u_saved[it_best_merits] sol = [x0, u] From c6a7930a45bd639d447678a44f6e5febaa5cc4c3 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Fri, 27 Mar 2020 13:34:57 +0000 Subject: [PATCH 118/323] formatting, linting, typing --- .../{test_admm_miskp.py => test_admm.py} | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) rename test/optimization/{test_admm_miskp.py => test_admm.py} (87%) diff --git a/test/optimization/test_admm_miskp.py b/test/optimization/test_admm.py similarity index 87% rename from test/optimization/test_admm_miskp.py rename to test/optimization/test_admm.py index d8ddbc7e02..c9f5d4470f 100644 --- a/test/optimization/test_admm_miskp.py +++ b/test/optimization/test_admm.py @@ -15,6 +15,8 @@ """Tests of the ADMM algorithm.""" from typing import Optional +from docplex.mp.model import Model + from test.optimization import QiskitOptimizationTestCase import numpy as np @@ -25,13 +27,13 @@ from qiskit.optimization.problems import OptimizationProblem -class TestADMMOptimizerMiskp(QiskitOptimizationTestCase): +class TestADMMOptimizer(QiskitOptimizationTestCase): """ADMM Optimizer Tests based on Mixed-Integer Setup Knapsack Problem""" - def test_admm_optimizer_miskp_eigen(self): + def test_admm_miskp_eigen(self): """ADMM Optimizer Test based on Mixed-Integer Setup Knapsack Problem using NumPy eigen optimizer""" - miskp = Miskp(*self._get_problem_params()) + miskp = Miskp(*self._get_miskp_problem_params()) op: OptimizationProblem = miskp.create_problem() self.assertIsNotNone(op) @@ -58,7 +60,7 @@ def test_admm_optimizer_miskp_eigen(self): np.testing.assert_almost_equal(correct_objective, solution.fval, 3) @staticmethod - def _get_problem_params() -> (int, int, float, np.ndarray, np.ndarray, np.ndarray): + def _get_miskp_problem_params() -> (int, int, float, np.ndarray, np.ndarray, np.ndarray): """Fills in parameters for a Mixed Integer Setup Knapsack Problem (MISKP) instance.""" family_count = 2 @@ -78,6 +80,29 @@ def _get_problem_params() -> (int, int, float, np.ndarray, np.ndarray, np.ndarra return family_count, items_per_family, knapsack_capacity, setup_costs, resource_values,\ cost_values + def test_admm_maximization(self): + mdl = Model('test') + c = mdl.continuous_var(lb=0, ub=10, name='c') + x = mdl.binary_var(name='x') + mdl.maximize(c + x * x) + op = OptimizationProblem() + op.from_docplex(mdl) + self.assertIsNotNone(op) + + qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) + continuous_optimizer = CplexOptimizer() + + admm_params = ADMMParameters(qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer) + solver = ADMMOptimizer(params=admm_params) + solution = solver.solve(op) + self.assertIsNotNone(solution) + + self.assertIsNotNone(solution.x) + np.testing.assert_almost_equal([10, 0], solution.x, 3) + self.assertIsNotNone(solution.fval) + np.testing.assert_almost_equal(10, solution.fval, 3) + class Miskp: """A Helper class to generate Mixed Integer Setup Knapsack problems""" From 3be0330e6ebfe1eba97a09c817bd3b20855292ad Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Fri, 27 Mar 2020 15:54:01 +0000 Subject: [PATCH 119/323] changes as discussed --- .../optimization/algorithms/admm_optimizer.py | 91 ++++++++++--------- test/optimization/test_admm.py | 22 +++-- 2 files changed, 61 insertions(+), 52 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 558a2906e9..89a213bccf 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -15,7 +15,7 @@ """An implementation of the ADMM algorithm.""" import logging import time -from typing import List, Optional +from typing import List, Optional, Any import numpy as np from cplex import SparsePair @@ -37,8 +37,7 @@ def __init__(self, rho_initial: float = 10000, factor_c: float = 100000, beta: f max_iter: int = 10, tol: float = 1.e-4, max_time: float = np.inf, three_block: bool = True, vary_rho: int = UPDATE_RHO_BY_TEN_PERCENT, tau_incr: float = 2, tau_decr: float = 2, mu_res: float = 10, - mu_merit: float = 1000, qubo_optimizer: Optional[OptimizationAlgorithm] = None, - continuous_optimizer: Optional[OptimizationAlgorithm] = None) -> None: + mu_merit: float = 1000) -> None: """Defines parameters for ADMM optimizer and their default values. Args: @@ -61,10 +60,6 @@ def __init__(self, rho_initial: float = 10000, factor_c: float = 100000, beta: f tau_decr: Parameter used in the rho update (UPDATE_RHO_BY_RESIDUALS). mu_res: Parameter used in the rho update (UPDATE_RHO_BY_RESIDUALS). mu_merit: Penalization for constraint residual. Used to compute the merit values. - qubo_optimizer: An instance of OptimizationAlgorithm that can effectively solve - QUBO problems. - continuous_optimizer: An instance of OptimizationAlgorithm that can solve - continuous problems. """ super().__init__() self.mu_merit = mu_merit @@ -79,8 +74,6 @@ def __init__(self, rho_initial: float = 10000, factor_c: float = 100000, beta: f self.factor_c = factor_c self.beta = beta self.rho_initial = rho_initial - self.qubo_optimizer = qubo_optimizer or CplexOptimizer() - self.continuous_optimizer = continuous_optimizer or CplexOptimizer() class ADMMState: @@ -156,6 +149,20 @@ def __init__(self, self.rho = rho_initial +class ADMMOptimizerResult(OptimizationResult): + """ ADMMOptimizer Result.""" + + def __init__(self, x: Optional[Any] = None, fval: Optional[Any] = None, + state: Optional[ADMMState] = None) -> None: + super().__init__(x, fval, state) + self._state = state + + @property + def state(self) -> Optional[ADMMState]: + """ returns samples """ + return self._state + + class ADMMOptimizer(OptimizationAlgorithm): """An implementation of the ADMM-based heuristic introduced here: Gambella, C., & Simonetto, A. (2020). @@ -163,10 +170,16 @@ class ADMMOptimizer(OptimizationAlgorithm): arXiv preprint arXiv:2001.02069. """ - def __init__(self, params: Optional[ADMMParameters] = None) -> None: + def __init__(self, qubo_optimizer: Optional[OptimizationAlgorithm] = None, + continuous_optimizer: Optional[OptimizationAlgorithm] = None, + params: Optional[ADMMParameters] = None) -> None: """Constructs an instance of ADMMOptimizer. Args: + qubo_optimizer: An instance of OptimizationAlgorithm that can effectively solve + QUBO problems. + continuous_optimizer: An instance of OptimizationAlgorithm that can solve + continuous problems. params: An instance of ADMMParameters. """ @@ -174,23 +187,11 @@ def __init__(self, params: Optional[ADMMParameters] = None) -> None: self._log = logging.getLogger(__name__) # create default params if not present - params = params or ADMMParameters() - self._three_block = params.three_block - self._max_time = params.max_time - self._tol = params.tol - self._max_iter = params.max_iter - self._factor_c = params.factor_c - self._beta = params.beta - self._mu_res = params.mu_res - self._tau_decr = params.tau_decr - self._tau_incr = params.tau_incr - self._vary_rho = params.vary_rho - self._three_block = params.three_block - self._mu_merit = params.mu_merit - self._rho_initial = params.rho_initial - - self._qubo_optimizer = params.qubo_optimizer - self._continuous_optimizer = params.continuous_optimizer + self._params = params or ADMMParameters() + + # create optimizers if not specified + self._qubo_optimizer = qubo_optimizer or CplexOptimizer() + self._continuous_optimizer = continuous_optimizer or CplexOptimizer() # internal state where we'll keep intermediate solution # here, we just declare the class variable, the variable is initialized in kept in @@ -232,7 +233,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: return None - def solve(self, problem: OptimizationProblem) -> OptimizationResult: + def solve(self, problem: OptimizationProblem) -> ADMMOptimizerResult: """Tries to solves the given problem using ADMM algorithm. Args: @@ -249,7 +250,8 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: continuous_indices = self._get_variable_indices(problem, CPX_CONTINUOUS) # create our computation state. - self._state = ADMMState(problem, binary_indices, continuous_indices, self._rho_initial) + self._state = ADMMState(problem, binary_indices, + continuous_indices, self._params.rho_initial) # convert optimization problem to a set of matrices and vector that are used # at each iteration. @@ -261,8 +263,8 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: iteration = 0 residual = 1.e+2 - while (iteration < self._max_iter and residual > self._tol) \ - and (elapsed_time < self._max_time): + while (iteration < self._params.max_iter and residual > self._params.tol) \ + and (elapsed_time < self._params.max_time): op1 = self._create_step1_problem() self._state.x0 = self._update_x0(op1) # debug @@ -274,7 +276,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: self._log.debug("u=%s", self._state.u) self._log.debug("z=%s", self._state.z) - if self._three_block: + if self._params.three_block: op3 = self._create_step3_problem() self._state.y = self._update_y(op3) # debug @@ -312,7 +314,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: solution = self._revert_solution_indexes(solution) # third parameter is our internal state of computations. - result = OptimizationResult(solution, objective_value, self._state) + result = ADMMOptimizerResult(solution, objective_value, self._state) # debug self._log.debug("solution=%s, objective=%s at iteration=%s", solution, objective_value, iteration) @@ -573,6 +575,7 @@ def _get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): row_indices & continuous_index_set) != 0: self._assign_row_values(matrix, vector, constraint_index, all_variables) + # pylint:disable=invalid-name matrix, b2 = self._create_ndarrays(matrix, vector, len(all_variables)) # a2 a2 = matrix[:, 0:len(self._state.binary_indices)] @@ -599,7 +602,7 @@ def _create_step1_problem(self) -> OptimizationProblem: # the quadratic coefficients. quadratic_objective = 2 * ( self._state.q0 + - self._factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) + + self._params.factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) + self._state.rho / 2 * np.eye(binary_size) ) for i in range(binary_size): @@ -608,7 +611,7 @@ def _create_step1_problem(self) -> OptimizationProblem: # prepare and set linear objective. linear_objective = self._state.c0 - \ - self._factor_c * np.dot(self._state.b0, self._state.a0) + \ + self._params.factor_c * np.dot(self._state.b0, self._state.a0) + \ self._state.rho * (self._state.y - self._state.z) for i in range(binary_size): @@ -717,7 +720,8 @@ def _create_step3_problem(self) -> OptimizationProblem: # set quadratic objective. # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coeff-s. - q_y = 2 * (self._beta / 2 * np.eye(binary_size) + self._state.rho / 2 * np.eye(binary_size)) + q_y = 2 * (self._params.beta / 2 * np.eye(binary_size) + + self._state.rho / 2 * np.eye(binary_size)) for i in range(binary_size): for j in range(i, binary_size): op3.objective.set_quadratic_coefficients(i, j, q_y[i, j]) @@ -780,6 +784,7 @@ def _get_best_merit_solution(self) -> (List[np.ndarray], float): * sol_val: Value of the objective function """ + # pylint:disable=invalid-name it_best_merits = self._state.merits.index( self._state.sense * min(list(map(lambda x: self._state.sense * x, self._state.merits)))) x0 = self._state.x0_saved[it_best_merits] @@ -807,15 +812,15 @@ def _update_rho(self, primal_residual: float, dual_residual: float) -> None: dual_residual: dual residual """ - if self._vary_rho == UPDATE_RHO_BY_TEN_PERCENT: + if self._params.vary_rho == UPDATE_RHO_BY_TEN_PERCENT: # Increase rho, to aid convergence. if self._state.rho < 1.e+10: self._state.rho *= 1.1 - elif self._vary_rho == UPDATE_RHO_BY_RESIDUALS: - if primal_residual > self._mu_res * dual_residual: - self._state.rho = self._tau_incr * self._state.rho - elif dual_residual > self._mu_res * primal_residual: - self._state.rho = self._tau_decr * self._state.rho + elif self._params.vary_rho == UPDATE_RHO_BY_RESIDUALS: + if primal_residual > self._params.mu_res * dual_residual: + self._state.rho = self._params.tau_incr * self._state.rho + elif dual_residual > self._params.mu_res * primal_residual: + self._state.rho = self._params.tau_decr * self._state.rho def _get_constraint_residual(self) -> float: """Compute violation of the constraints of the original problem, as: @@ -848,7 +853,7 @@ def _get_merit(self, cost_iterate: float, constraint_residual: float) -> float: Returns: Merit value as a float """ - return cost_iterate + self._mu_merit * constraint_residual + return cost_iterate + self._params.mu_merit * constraint_residual def _get_objective_value(self) -> float: """Computes the value of the objective function. diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index c9f5d4470f..ad759f5e08 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -15,10 +15,10 @@ """Tests of the ADMM algorithm.""" from typing import Optional -from docplex.mp.model import Model - from test.optimization import QiskitOptimizationTestCase +from docplex.mp.model import Model + import numpy as np from cplex import SparsePair from qiskit.aqua.algorithms import NumPyMinimumEigensolver @@ -37,14 +37,15 @@ def test_admm_miskp_eigen(self): op: OptimizationProblem = miskp.create_problem() self.assertIsNotNone(op) + admm_params = ADMMParameters() + # use numpy exact diagonalization qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) continuous_optimizer = CplexOptimizer() - admm_params = ADMMParameters(qubo_optimizer=qubo_optimizer, - continuous_optimizer=continuous_optimizer) - - solver = ADMMOptimizer(params=admm_params) + solver = ADMMOptimizer(qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer, + params=admm_params) solution = solver.solve(op) self.assertIsNotNone(solution) @@ -81,6 +82,7 @@ def _get_miskp_problem_params() -> (int, int, float, np.ndarray, np.ndarray, np. cost_values def test_admm_maximization(self): + """Tests a simple maximization problem using ADMM optimizer""" mdl = Model('test') c = mdl.continuous_var(lb=0, ub=10, name='c') x = mdl.binary_var(name='x') @@ -89,12 +91,14 @@ def test_admm_maximization(self): op.from_docplex(mdl) self.assertIsNotNone(op) + admm_params = ADMMParameters() + qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) continuous_optimizer = CplexOptimizer() - admm_params = ADMMParameters(qubo_optimizer=qubo_optimizer, - continuous_optimizer=continuous_optimizer) - solver = ADMMOptimizer(params=admm_params) + solver = ADMMOptimizer(qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer, + params=admm_params) solution = solver.solve(op) self.assertIsNotNone(solution) From 4dc9ea5d87d3f2a15910da318df22f370bd4e342 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Fri, 27 Mar 2020 16:10:29 +0000 Subject: [PATCH 120/323] asserts in the admm tests to support ADMMOptimizationResult and ADMMState --- qiskit/optimization/algorithms/admm_optimizer.py | 2 +- test/optimization/test_admm.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 89a213bccf..1c97a86bb9 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -159,7 +159,7 @@ def __init__(self, x: Optional[Any] = None, fval: Optional[Any] = None, @property def state(self) -> Optional[ADMMState]: - """ returns samples """ + """ returns state """ return self._state diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index ad759f5e08..caf11e70b3 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -23,7 +23,8 @@ from cplex import SparsePair from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.optimization.algorithms import CplexOptimizer, MinimumEigenOptimizer -from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters +from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters, \ + ADMMOptimizerResult, ADMMState from qiskit.optimization.problems import OptimizationProblem @@ -46,8 +47,9 @@ def test_admm_miskp_eigen(self): solver = ADMMOptimizer(qubo_optimizer=qubo_optimizer, continuous_optimizer=continuous_optimizer, params=admm_params) - solution = solver.solve(op) + solution: ADMMOptimizerResult = solver.solve(op) self.assertIsNotNone(solution) + self.assertIsInstance(solution, ADMMOptimizerResult) correct_solution = [0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.006151, 0.006151, 0.006151, 0.006151, @@ -59,6 +61,8 @@ def test_admm_miskp_eigen(self): np.testing.assert_almost_equal(correct_solution, solution.x, 3) self.assertIsNotNone(solution.fval) np.testing.assert_almost_equal(correct_objective, solution.fval, 3) + self.assertIsNotNone(solution.state) + self.assertIsInstance(solution.state, ADMMState) @staticmethod def _get_miskp_problem_params() -> (int, int, float, np.ndarray, np.ndarray, np.ndarray): @@ -99,13 +103,16 @@ def test_admm_maximization(self): solver = ADMMOptimizer(qubo_optimizer=qubo_optimizer, continuous_optimizer=continuous_optimizer, params=admm_params) - solution = solver.solve(op) + solution: ADMMOptimizerResult = solver.solve(op) self.assertIsNotNone(solution) + self.assertIsInstance(solution, ADMMOptimizerResult) self.assertIsNotNone(solution.x) np.testing.assert_almost_equal([10, 0], solution.x, 3) self.assertIsNotNone(solution.fval) np.testing.assert_almost_equal(10, solution.fval, 3) + self.assertIsNotNone(solution.state) + self.assertIsInstance(solution.state, ADMMState) class Miskp: From 601cf80291bcfd5df7f2bb8fba56b5ed628a2938 Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Fri, 27 Mar 2020 18:14:44 +0000 Subject: [PATCH 121/323] handling case no bin vars, updated get_obj_val --- .../optimization/algorithms/admm_optimizer.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 4fb6d8d754..6fe91e0786 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -263,8 +263,12 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: while (iteration < self._max_iter and residual > self._tol) \ and (elapsed_time < self._max_time): - op1 = self._create_step1_problem() - self._state.x0 = self._update_x0(op1) + if binary_indices: + op1 = self._create_step1_problem() + self._state.x0 = self._update_x0(op1) + # else, no binary variables exist, + # and no update to be done in this case. + # debug self._log.debug("x0=%s", self._state.x0) @@ -275,8 +279,9 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: self._log.debug("z=%s", self._state.z) if self._three_block: - op3 = self._create_step3_problem() - self._state.y = self._update_y(op3) + if binary_indices: + op3 = self._create_step3_problem() + self._state.y = self._update_y(op3) # debug self._log.debug("y=%s", self._state.y) @@ -858,10 +863,12 @@ def _get_objective_value(self) -> float: """ def quadratic_form(matrix, x, c): - return np.dot(x.T, np.dot(matrix, x)) + np.dot(c.T, x) + return np.dot(x.T, np.dot(matrix / 2, x)) + np.dot(c.T, x) obj_val = quadratic_form(self._state.q0, self._state.x0, self._state.c0) obj_val += quadratic_form(self._state.q1, self._state.u, self._state.c1) + + obj_val += self._state.op.objective.get_offset() return obj_val From 6165ba2f9e8535d77bef028a4017108d1371c41f Mon Sep 17 00:00:00 2001 From: andrea-simonetto <55157704+andrea-simonetto@users.noreply.github.com> Date: Fri, 27 Mar 2020 20:06:42 +0000 Subject: [PATCH 122/323] minor --- test/optimization/test_admm.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index caf11e70b3..4aaddb8ee3 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -27,7 +27,6 @@ ADMMOptimizerResult, ADMMState from qiskit.optimization.problems import OptimizationProblem - class TestADMMOptimizer(QiskitOptimizationTestCase): """ADMM Optimizer Tests based on Mixed-Integer Setup Knapsack Problem""" From 469001c7fd65d10fcb08b6aa4a9d0d3f7e8e9975 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sat, 28 Mar 2020 13:15:53 +0100 Subject: [PATCH 123/323] use q_instance as in master --- .../algorithms/minimum_eigen_solvers/qpe.py | 11 ++++--- .../algorithms/minimum_eigen_solvers/vqe.py | 29 +++++++++---------- qiskit/aqua/algorithms/quantum_algorithm.py | 7 +++-- qiskit/aqua/algorithms/vq_algorithm.py | 12 +++++--- 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/qpe.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/qpe.py index 8ba2280f28..842cc23869 100644 --- a/qiskit/aqua/algorithms/minimum_eigen_solvers/qpe.py +++ b/qiskit/aqua/algorithms/minimum_eigen_solvers/qpe.py @@ -16,13 +16,14 @@ """ import logging -from typing import Optional, List, Dict +from typing import Optional, List, Dict, Union import warnings import numpy as np from qiskit import QuantumCircuit from qiskit.quantum_info import Pauli - +from qiskit.providers import BaseBackend +from qiskit.aqua import QuantumInstance from qiskit.aqua.operators import op_converter from qiskit.aqua.utils import get_subsystem_density_matrix from qiskit.aqua.algorithms import QuantumAlgorithm @@ -64,7 +65,8 @@ def __init__(self, num_ancillae: int = 1, expansion_mode: str = 'trotter', expansion_order: int = 1, - shallow_circuit_concat: bool = False) -> None: + shallow_circuit_concat: bool = False, + quantum_instance: Optional[Union[QuantumInstance, BaseBackend]] = None) -> None: """ Args: @@ -80,12 +82,13 @@ def __init__(self, shallow_circuit_concat: Set True to use shallow (cheap) mode for circuit concatenation of evolution slices. By default this is False. See :meth:`qiskit.aqua.operators.common.evolution_instruction` for more information. + quantum_instance: Quantum Instance or Backend """ validate_min('num_time_slices', num_time_slices, 1) validate_min('num_ancillae', num_ancillae, 1) validate_in_set('expansion_mode', expansion_mode, {'trotter', 'suzuki'}) validate_min('expansion_order', expansion_order, 1) - super().__init__() + super().__init__(quantum_instance) self._state_in = state_in self._iqft = iqft self._num_time_slices = num_time_slices diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py index 614cb0af16..f0f95ee4fc 100755 --- a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py @@ -18,7 +18,7 @@ See https://arxiv.org/abs/1304.3061 """ -from typing import Optional, List, Callable +from typing import Optional, List, Callable, Union import logging import functools import warnings @@ -27,13 +27,12 @@ import numpy as np from qiskit import ClassicalRegister, QuantumCircuit from qiskit.circuit import ParameterVector - -from qiskit.aqua import AquaError +from qiskit.providers import BaseBackend +from qiskit.aqua import QuantumInstance, AquaError from qiskit.aqua.operators import (TPBGroupedWeightedPauliOperator, WeightedPauliOperator, MatrixOperator, op_converter) from qiskit.aqua.utils.backend_utils import (is_statevector_backend, is_aer_provider) -from qiskit.aqua import QuantumInstance from qiskit.aqua.operators import BaseOperator from qiskit.aqua.components.optimizers import Optimizer, SLSQP from qiskit.aqua.components.variational_forms import VariationalForm, RY @@ -91,8 +90,7 @@ def __init__(self, aux_operators: Optional[List[BaseOperator]] = None, callback: Optional[Callable[[int, np.ndarray, float, float], None]] = None, auto_conversion: bool = True, - quantum_instance: Optional[QuantumInstance] = None - ) -> None: + quantum_instance: Optional[Union[QuantumInstance, BaseBackend]] = None) -> None: """ Args: @@ -127,8 +125,8 @@ def __init__(self, :class:`~qiskit.aqua.operators.WeightedPauliOperator` - for *qasm simulator or real backend:* :class:`~qiskit.aqua.operators.TPBGroupedWeightedPauliOperator` - quantum_instance: Quantum instance to be used, needs to be set here or when the - algorithm is executed. + quantum_instance: Quantum instance or Backend to be used, needs to be set here or when + the algorithm is executed. """ validate_min('max_evals_grouped', max_evals_grouped, 1) @@ -147,6 +145,13 @@ def __init__(self, initial_point = var_form.preferred_init_points self._max_evals_grouped = max_evals_grouped + + super().__init__(var_form=var_form, + optimizer=optimizer, + cost_fn=self._energy_evaluation, + initial_point=initial_point, + quantum_instance=quantum_instance) + self._in_operator = None self._operator = None self._in_aux_operators = None @@ -159,13 +164,6 @@ def __init__(self, self._eval_time = None self._eval_count = 0 - super().__init__(var_form=var_form, - optimizer=optimizer, - cost_fn=self._energy_evaluation, - initial_point=initial_point) - - self.quantum_instance = quantum_instance - logger.info(self.print_settings()) self._var_form_params = None if self.var_form is not None: @@ -205,7 +203,6 @@ def aux_operators(self, aux_operators: List[BaseOperator]) -> None: @VQAlgorithm.var_form.setter def var_form(self, var_form: VariationalForm): """ Sets variational form """ - VQAlgorithm.var_form.fset(self, var_form) if var_form: self._var_form_params = ParameterVector('θ', var_form.num_parameters) diff --git a/qiskit/aqua/algorithms/quantum_algorithm.py b/qiskit/aqua/algorithms/quantum_algorithm.py index 048120bf25..7e3e0d4d21 100644 --- a/qiskit/aqua/algorithms/quantum_algorithm.py +++ b/qiskit/aqua/algorithms/quantum_algorithm.py @@ -34,8 +34,11 @@ class QuantumAlgorithm(ABC): use an exception if a component of the module is available. """ @abstractmethod - def __init__(self) -> None: + def __init__(self, + quantum_instance: Optional[Union[QuantumInstance, BaseBackend]]) -> None: self._quantum_instance = None + if quantum_instance: + self.quantum_instance = quantum_instance @property def random(self): @@ -77,7 +80,7 @@ def quantum_instance(self) -> Union[None, QuantumInstance]: @quantum_instance.setter def quantum_instance(self, quantum_instance: Union[QuantumInstance, BaseBackend]) -> None: - """Set quantum instance.""" + """Set quantum instance.""" if isinstance(quantum_instance, BaseBackend): quantum_instance = QuantumInstance(quantum_instance) self._quantum_instance = quantum_instance diff --git a/qiskit/aqua/algorithms/vq_algorithm.py b/qiskit/aqua/algorithms/vq_algorithm.py index a57645ef85..0623d1870d 100644 --- a/qiskit/aqua/algorithms/vq_algorithm.py +++ b/qiskit/aqua/algorithms/vq_algorithm.py @@ -22,7 +22,7 @@ overridden to opt-out of this infrastructure but still meet the interface requirements. """ -from typing import Optional, Callable +from typing import Optional, Callable, Union import time import logging import warnings @@ -30,6 +30,8 @@ import numpy as np from qiskit.circuit import ParameterVector +from qiskit.providers import BaseBackend +from qiskit.aqua import QuantumInstance from qiskit.aqua.algorithms import AlgorithmResult, QuantumAlgorithm from qiskit.aqua.components.optimizers import Optimizer from qiskit.aqua.components.variational_forms import VariationalForm @@ -48,7 +50,8 @@ def __init__(self, var_form: Optional[VariationalForm] = None, optimizer: Optional[Optimizer] = None, cost_fn: Optional[Callable] = None, - initial_point: Optional[np.ndarray] = None) -> None: + initial_point: Optional[np.ndarray] = None, + quantum_instance: Optional[Union[QuantumInstance, BaseBackend]] = None) -> None: """ Args: var_form: An optional parameterized variational form (ansatz). @@ -57,18 +60,19 @@ def __init__(self, supplied on :meth:`find_minimum`. initial_point: An optional initial point (i.e. initial parameter values) for the optimizer. + quantum_instance: The Quantum Instance or Backend to run the circuits. Raises: ValueError: for invalid input """ - super().__init__() + super().__init__(quantum_instance) - self._initial_point = initial_point self.var_form = var_form if optimizer is None: raise ValueError('Missing optimizer.') self._optimizer = optimizer self._cost_fn = cost_fn + self._initial_point = initial_point self._parameterized_circuits = None From 895eefb080174f4d66dcc9589da6e5489026f2b8 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Sat, 28 Mar 2020 23:00:52 +0100 Subject: [PATCH 124/323] add correlations to min eigen optimizer results --- .../algorithms/minimum_eigen_optimizer.py | 19 +++++++++++++++++++ .../recursive_minimum_eigen_optimizer.py | 18 +----------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 98eb53cbe2..80b8b9913d 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -25,6 +25,7 @@ """ from typing import Optional, Any +import numpy as np from qiskit.aqua.algorithms import MinimumEigensolver @@ -54,6 +55,24 @@ def samples(self, samples: Any) -> None: """ set samples """ self._samples = samples + def get_correlations(self): + """ get correlation matrix from samples """ + + states = [v[0] for v in self.samples] + probs = [v[2] for v in self.samples] + + n = len(states[0]) + correlations = np.zeros((n, n)) + for k, prob in enumerate(probs): + b = states[k] + for i in range(n): + for j in range(i): + if b[i] == b[j]: + correlations[i, j] += prob + else: + correlations[i, j] -= prob + return correlations + class MinimumEigenOptimizer(OptimizationAlgorithm): """A wrapper for minimum eigen solvers from Qiskit Aqua to be used within Qiskit Optimization. diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index b33297d24e..4ddad65f66 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -120,12 +120,9 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: # solve current problem with optimizer result = self._min_eigen_optimizer.solve(problem_) - samples = result.samples # analyze results to get strongest correlation - states = [v[0] for v in samples] - probs = [v[2] for v in samples] - correlations = self._construct_correlations(states, probs) + correlations = result.get_correlations() i, j = self._find_strongest_correlation(correlations) x_i = problem_.variables.get_names(i) @@ -192,19 +189,6 @@ def find_value(x, replacements, var_values): results = qubo_converter.decode(results) return results - def _construct_correlations(self, states, probs): - n = len(states[0]) - correlations = np.zeros((n, n)) - for k, prob in enumerate(probs): - b = states[k] - for i in range(n): - for j in range(i): - if b[i] == b[j]: - correlations[i, j] += prob - else: - correlations[i, j] -= prob - return correlations - def _find_strongest_correlation(self, correlations): m_max = np.argmax(np.abs(correlations.flatten())) i = int(m_max // len(correlations)) From 82b643281b940887245ae32475e7bb1ec8e14827 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sun, 29 Mar 2020 18:57:36 +0200 Subject: [PATCH 125/323] fix test and some minor lints --- .../algorithms/minimum_eigen_solvers/vqe.py | 12 +-- qiskit/aqua/algorithms/vq_algorithm.py | 9 +- qiskit/optimization/__init__.py | 4 +- qiskit/optimization/infinity.py | 18 ++++ qiskit/optimization/results/__init__.py | 8 +- .../results/grover_optimization_results.py | 2 +- .../results/optimization_result.py | 6 +- .../optimization/results/solution_status.py | 101 +++++++----------- 8 files changed, 78 insertions(+), 82 deletions(-) create mode 100644 qiskit/optimization/infinity.py diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py index f0f95ee4fc..fc09d8ab25 100755 --- a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py @@ -146,12 +146,6 @@ def __init__(self, self._max_evals_grouped = max_evals_grouped - super().__init__(var_form=var_form, - optimizer=optimizer, - cost_fn=self._energy_evaluation, - initial_point=initial_point, - quantum_instance=quantum_instance) - self._in_operator = None self._operator = None self._in_aux_operators = None @@ -164,6 +158,12 @@ def __init__(self, self._eval_time = None self._eval_count = 0 + super().__init__(var_form=var_form, + optimizer=optimizer, + cost_fn=self._energy_evaluation, + initial_point=initial_point, + quantum_instance=quantum_instance) + logger.info(self.print_settings()) self._var_form_params = None if self.var_form is not None: diff --git a/qiskit/aqua/algorithms/vq_algorithm.py b/qiskit/aqua/algorithms/vq_algorithm.py index 0623d1870d..ce4c1f7334 100644 --- a/qiskit/aqua/algorithms/vq_algorithm.py +++ b/qiskit/aqua/algorithms/vq_algorithm.py @@ -33,7 +33,7 @@ from qiskit.providers import BaseBackend from qiskit.aqua import QuantumInstance from qiskit.aqua.algorithms import AlgorithmResult, QuantumAlgorithm -from qiskit.aqua.components.optimizers import Optimizer +from qiskit.aqua.components.optimizers import Optimizer, SLSQP from qiskit.aqua.components.variational_forms import VariationalForm logger = logging.getLogger(__name__) @@ -66,13 +66,14 @@ def __init__(self, """ super().__init__(quantum_instance) - self.var_form = var_form - if optimizer is None: - raise ValueError('Missing optimizer.') + logger.info('No optimizer provided, setting it to SLSPQ.') + optimizer = SLSQP() + self._optimizer = optimizer self._cost_fn = cost_fn self._initial_point = initial_point + self.var_form = var_form self._parameterized_circuits = None diff --git a/qiskit/optimization/__init__.py b/qiskit/optimization/__init__.py index 7e73e54191..f5b88e22dd 100755 --- a/qiskit/optimization/__init__.py +++ b/qiskit/optimization/__init__.py @@ -29,9 +29,7 @@ """ -CPX_INFBOUND = 1.0E+20 -infinity = CPX_INFBOUND - +from qiskit.optimization.infinity import infinity # must be at the top of the file from qiskit.optimization.utils import QiskitOptimizationError from qiskit.optimization.problems.linear_constraint import LinearConstraintInterface from qiskit.optimization.problems.objective import ObjSense, ObjectiveInterface diff --git a/qiskit/optimization/infinity.py b/qiskit/optimization/infinity.py new file mode 100644 index 0000000000..4dc2d09868 --- /dev/null +++ b/qiskit/optimization/infinity.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +"""Infinity constant from CPLEX optimization.""" + +CPX_INFBOUND = 1.0E+20 +infinity = CPX_INFBOUND # pylint: disable=invalid-name diff --git a/qiskit/optimization/results/__init__.py b/qiskit/optimization/results/__init__.py index cbb700a70d..a56ccaf39b 100644 --- a/qiskit/optimization/results/__init__.py +++ b/qiskit/optimization/results/__init__.py @@ -44,11 +44,11 @@ """ +from .quality_metrics import QualityMetrics +from .solution import SolutionInterface +from .solution_status import SolutionStatus +from .optimization_result import OptimizationResult from .grover_optimization_results import GroverOptimizationResults -from qiskit.optimization.results.quality_metrics import QualityMetrics -from qiskit.optimization.results.solution import SolutionInterface -from qiskit.optimization.results.solution_status import SolutionStatus -from qiskit.optimization.results.optimization_result import OptimizationResult __all__ = ["SolutionStatus", "QualityMetrics", "SolutionStatus", "OptimizationResult", "GroverOptimizationResults"] diff --git a/qiskit/optimization/results/grover_optimization_results.py b/qiskit/optimization/results/grover_optimization_results.py index a97801ad81..804c56d2e3 100644 --- a/qiskit/optimization/results/grover_optimization_results.py +++ b/qiskit/optimization/results/grover_optimization_results.py @@ -41,7 +41,7 @@ def __init__(self, operation_counts: Dict[int, Dict[str, int]], rotations: int, @property def operation_counts(self) -> Dict[int, Dict[str, int]]: """Get the operation counts. - + Returns: The counts of each operation performed per iteration. """ diff --git a/qiskit/optimization/results/optimization_result.py b/qiskit/optimization/results/optimization_result.py index 3b0404d447..df163c2ee5 100644 --- a/qiskit/optimization/results/optimization_result.py +++ b/qiskit/optimization/results/optimization_result.py @@ -33,7 +33,7 @@ class OptimizationResult: def __init__(self, x: Optional[Any] = None, fval: Optional[Any] = None, results: Optional[Any] = None) -> None: """Initialize the optimization result.""" - self._x = x + self._val = x self._fval = fval self._results = results @@ -47,7 +47,7 @@ def x(self) -> Any: Returns: The optimal value found in the optimization. """ - return self._x + return self._val @property def fval(self) -> Any: @@ -76,7 +76,7 @@ def x(self, x: Any) -> None: Args: x: The new optimal value. """ - self._x = x + self._val = x @fval.setter def fval(self, fval: Any) -> None: diff --git a/qiskit/optimization/results/solution_status.py b/qiskit/optimization/results/solution_status.py index 088fe03132..a24346c825 100755 --- a/qiskit/optimization/results/solution_status.py +++ b/qiskit/optimization/results/solution_status.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2020. # # 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 @@ -12,8 +12,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""Solution status codes.""" -from qiskit.optimization.utils.base import BaseInterface from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError CPX_STAT_ABORT_DETTIME_LIM = 25 @@ -23,7 +23,7 @@ CPX_STAT_ABORT_USER = 13 CPX_STAT_FEASIBLE = 23 CPX_STAT_INFEASIBLE = 3 -CPX_STAT_INForUNBD = 4 +CPX_STAT_INF_OR_UNBD = 4 CPX_STAT_OPTIMAL = 1 CPX_STAT_UNBOUNDED = 2 @@ -38,16 +38,16 @@ CPXMIP_ABORT_FEAS = 113 CPXMIP_ABORT_INFEAS = 114 CPXMIP_UNBOUNDED = 118 -CPXMIP_INForUNBD = 119 +CPXMIP_INF_OR_UNBD = 119 CPXMIP_FEASIBLE = 127 CPXMIP_DETTIME_LIM_FEAS = 131 CPXMIP_DETTIME_LIM_INFEAS = 132 -class SolutionStatus(object): +class SolutionStatus: """Solution status codes. - For documentation of each status code, see the reference manual + For documentation of each status code, see the reference manual of the CPLEX Callable Library, especially the group optim.cplex.callable.solutionstatus. """ @@ -56,7 +56,7 @@ class SolutionStatus(object): unbounded = CPX_STAT_UNBOUNDED infeasible = CPX_STAT_INFEASIBLE feasible = CPX_STAT_FEASIBLE - infeasible_or_unbounded = CPX_STAT_INForUNBD + infeasible_or_unbounded = CPX_STAT_INF_OR_UNBD abort_obj_limit = CPX_STAT_ABORT_OBJ_LIM abort_iteration_limit = CPX_STAT_ABORT_IT_LIM abort_time_limit = CPX_STAT_ABORT_TIME_LIM @@ -76,7 +76,7 @@ class SolutionStatus(object): MIP_abort_feasible = CPXMIP_ABORT_FEAS MIP_abort_infeasible = CPXMIP_ABORT_INFEAS MIP_unbounded = CPXMIP_UNBOUNDED - MIP_infeasible_or_unbounded = CPXMIP_INForUNBD + MIP_infeasible_or_unbounded = CPXMIP_INF_OR_UNBD MIP_feasible = CPXMIP_FEASIBLE def __getitem__(self, item): @@ -91,56 +91,35 @@ def __getitem__(self, item): >>> c.solution.status[1] 'optimal' """ - if item == 0: - return 'unknown' - if item == CPX_STAT_OPTIMAL: - return 'optimal' - if item == CPX_STAT_UNBOUNDED: - return 'unbounded' - if item == CPX_STAT_INFEASIBLE: - return 'infeasible' - if item == CPX_STAT_FEASIBLE: - return 'feasible' - if item == CPX_STAT_INForUNBD: - return 'infeasible_or_unbounded' - if item == CPX_STAT_ABORT_OBJ_LIM: - return 'abort_obj_limit' - if item == CPX_STAT_ABORT_IT_LIM: - return 'abort_iteration_limit' - if item == CPX_STAT_ABORT_TIME_LIM: - return 'abort_time_limit' - if item == CPX_STAT_ABORT_DETTIME_LIM: - return 'abort_dettime_limit' - if item == CPX_STAT_ABORT_USER: - return 'abort_user' - if item == CPXMIP_FAIL_FEAS: - return 'fail_feasible' - if item == CPXMIP_FAIL_INFEAS: - return 'fail_infeasible' - if item == CPXMIP_MEM_LIM_FEAS: - return 'mem_limit_feasible' - if item == CPXMIP_MEM_LIM_INFEAS: - return 'mem_limit_infeasible' - if item == CPXMIP_OPTIMAL: - return 'MIP_optimal' - if item == CPXMIP_INFEASIBLE: - return 'MIP_infeasible' - if item == CPXMIP_TIME_LIM_FEAS: - return 'MIP_time_limit_feasible' - if item == CPXMIP_TIME_LIM_INFEAS: - return 'MIP_time_limit_infeasible' - if item == CPXMIP_DETTIME_LIM_FEAS: - return 'MIP_dettime_limit_feasible' - if item == CPXMIP_DETTIME_LIM_INFEAS: - return 'MIP_dettime_limit_infeasible' - if item == CPXMIP_ABORT_FEAS: - return 'MIP_abort_feasible' - if item == CPXMIP_ABORT_INFEAS: - return 'MIP_abort_infeasible' - if item == CPXMIP_UNBOUNDED: - return 'MIP_unbounded' - if item == CPXMIP_INForUNBD: - return 'MIP_infeasible_or_unbounded' - if item == CPXMIP_FEASIBLE: - return 'MIP_feasible' - raise QiskitOptimizationError("Unexpected solution status code!") + solution_status = { + 0: 'unknown', + CPX_STAT_OPTIMAL: 'optimal', + CPX_STAT_UNBOUNDED: 'unbounded', + CPX_STAT_INFEASIBLE: 'infeasible', + CPX_STAT_FEASIBLE: 'feasible', + CPX_STAT_INF_OR_UNBD: 'infeasible_or_unbounded', + CPX_STAT_ABORT_OBJ_LIM: 'abort_obj_limit', + CPX_STAT_ABORT_IT_LIM: 'abort_iteration_limit', + CPX_STAT_ABORT_TIME_LIM: 'abort_time_limit', + CPX_STAT_ABORT_DETTIME_LIM: 'abort_dettime_limit', + CPX_STAT_ABORT_USER: 'abort_user', + CPXMIP_FAIL_FEAS: 'fail_feasible', + CPXMIP_FAIL_INFEAS: 'fail_infeasible', + CPXMIP_MEM_LIM_FEAS: 'mem_limit_feasible', + CPXMIP_MEM_LIM_INFEAS: 'mem_limit_infeasible', + CPXMIP_OPTIMAL: 'MIP_optimal', + CPXMIP_INFEASIBLE: 'MIP_infeasible', + CPXMIP_TIME_LIM_FEAS: 'MIP_time_limit_feasible', + CPXMIP_TIME_LIM_INFEAS: 'MIP_time_limit_infeasible', + CPXMIP_DETTIME_LIM_FEAS: 'MIP_dettime_limit_feasible', + CPXMIP_DETTIME_LIM_INFEAS: 'MIP_dettime_limit_infeasible', + CPXMIP_ABORT_FEAS: 'MIP_abort_feasible', + CPXMIP_ABORT_INFEAS: 'MIP_abort_infeasible', + CPXMIP_UNBOUNDED: 'MIP_unbounded', + CPXMIP_INF_OR_UNBD: 'MIP_infeasible_or_unbounded', + CPXMIP_FEASIBLE: 'MIP_feasible' + } + try: + return solution_status[item] + except KeyError: + raise QiskitOptimizationError('Unexpected solution status code!') From 34bc19cf7866d0679e880c8daee39dc47e992a3f Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Mon, 30 Mar 2020 11:28:43 +0100 Subject: [PATCH 126/323] some fixes --- .../optimization/algorithms/admm_optimizer.py | 23 ++++++++++--------- test/optimization/test_admm.py | 8 +++---- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index fd58bc62e3..2638452adf 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -286,7 +286,7 @@ def solve(self, problem: OptimizationProblem) -> ADMMOptimizerResult: # debug self._log.debug("y=%s", self._state.y) - lambda_mult = self._update_lambda_mult() + self._state.lambda_mult = self._update_lambda_mult() cost_iterate = self._get_objective_value() constraint_residual = self._get_constraint_residual() @@ -302,7 +302,7 @@ def solve(self, problem: OptimizationProblem) -> ADMMOptimizerResult: self._state.dual_residuals.append(dual_residual) self._state.cons_r.append(constraint_residual) self._state.merits.append(merit) - self._state.lambdas.append(np.linalg.norm(lambda_mult)) + self._state.lambdas.append(np.linalg.norm(self._state.lambda_mult)) self._state.x0_saved.append(self._state.x0) self._state.u_saved.append(self._state.u) @@ -604,8 +604,8 @@ def _create_step1_problem(self) -> OptimizationProblem: # prepare and set quadratic objective. # NOTE: The multiplication by 2 is needed for the solvers to parse # the quadratic coefficients. - quadratic_objective = 2 * ( - self._state.q0 + + quadratic_objective = self._state.q0 +\ + 2 * ( self._params.factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) + self._state.rho / 2 * np.eye(binary_size) ) @@ -616,7 +616,8 @@ def _create_step1_problem(self) -> OptimizationProblem: # prepare and set linear objective. linear_objective = self._state.c0 - \ self._params.factor_c * np.dot(self._state.b0, self._state.a0) + \ - self._state.rho * (self._state.y - self._state.z) + self._state.rho * (- self._state.y - self._state.z) + \ + self._state.lambda_mult for i in range(binary_size): op1.objective.set_linear(i, linear_objective[i]) @@ -647,9 +648,7 @@ def _create_step2_problem(self) -> OptimizationProblem: # set quadratic objective coefficients for u variables. if continuous_size: - # NOTE: The multiplication by 2 is needed for the solvers to parse - # the quadratic coefficients. - q_u = 2 * self._state.q1 + q_u = self._state.q1 for i in range(continuous_size): for j in range(i, continuous_size): op2.objective.set_quadratic_coefficients(i, j, q_u[i, j]) @@ -670,7 +669,7 @@ def _create_step2_problem(self) -> OptimizationProblem: op2.objective.set_linear(i, linear_u[i]) # set linear objective for z variables. - linear_z = -1 * self._state.lambda_mult - self._state.rho * (self._state.x0 + self._state.y) + linear_z = -1 * self._state.lambda_mult - self._state.rho * (self._state.x0 - self._state.y) for i in range(binary_size): op2.objective.set_linear(i + continuous_size, linear_z[i]) @@ -730,7 +729,8 @@ def _create_step3_problem(self) -> OptimizationProblem: for j in range(i, binary_size): op3.objective.set_quadratic_coefficients(i, j, q_y[i, j]) - linear_y = self._state.lambda_mult + self._state.rho * (self._state.x0 - self._state.z) + linear_y = - self._state.lambda_mult - self._state.rho * ( + self._state.x0 - self._state.z) for i in range(binary_size): op3.objective.set_linear(i, linear_y[i]) @@ -806,7 +806,7 @@ def _update_lambda_mult(self) -> np.ndarray: """ return self._state.lambda_mult + \ - self._state.rho * (self._state.x0 - self._state.z + self._state.y) + self._state.rho * (self._state.x0 - self._state.z - self._state.y) def _update_rho(self, primal_residual: float, dual_residual: float) -> None: """Updating the rho parameter in ADMM. @@ -825,6 +825,7 @@ def _update_rho(self, primal_residual: float, dual_residual: float) -> None: self._state.rho = self._params.tau_incr * self._state.rho elif dual_residual > self._params.mu_res * primal_residual: self._state.rho = self._params.tau_decr * self._state.rho + def _get_constraint_residual(self) -> float: """Compute violation of the constraints of the original problem, as: diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index caf11e70b3..0576bee731 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -51,11 +51,11 @@ def test_admm_miskp_eigen(self): self.assertIsNotNone(solution) self.assertIsInstance(solution, ADMMOptimizerResult) - correct_solution = [0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, - 0.009127, 0.009127, 0.009127, 0.006151, 0.006151, 0.006151, 0.006151, - 0.006151, 0.006151, 0.006151, 0.006151, 0.006151, 0.006151, + correct_solution = [0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0.] - correct_objective = -1.2113693 + correct_objective = 0.0 self.assertIsNotNone(solution.x) np.testing.assert_almost_equal(correct_solution, solution.x, 3) From f9b1ae1ac45622ad5a7f198613db2aa358145e89 Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Mon, 30 Mar 2020 13:33:34 +0100 Subject: [PATCH 127/323] prints --- .../optimization/algorithms/admm_optimizer.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 2638452adf..62df0c4388 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -272,12 +272,21 @@ def solve(self, problem: OptimizationProblem) -> ADMMOptimizerResult: # and no update to be done in this case. # debug self._log.debug("x0=%s", self._state.x0) + # Claudio + op1.write('op1.lp') + print(op1.write_as_string()) + print("x0=%s", self._state.x0) op2 = self._create_step2_problem() self._state.u, self._state.z = self._update_x1(op2) # debug self._log.debug("u=%s", self._state.u) self._log.debug("z=%s", self._state.z) + # Claudio + op2.write('op2.lp') + print(op2.write_as_string()) + print("u=%s", self._state.u) + print("z=%s", self._state.z) if self._params.three_block: if binary_indices: @@ -285,6 +294,10 @@ def solve(self, problem: OptimizationProblem) -> ADMMOptimizerResult: self._state.y = self._update_y(op3) # debug self._log.debug("y=%s", self._state.y) + # Claudio + op3.write('op3.lp') + print(op3.write_as_string()) + print("y=%s", self._state.y) self._state.lambda_mult = self._update_lambda_mult() @@ -616,7 +629,7 @@ def _create_step1_problem(self) -> OptimizationProblem: # prepare and set linear objective. linear_objective = self._state.c0 - \ self._params.factor_c * np.dot(self._state.b0, self._state.a0) + \ - self._state.rho * (- self._state.y - self._state.z) + \ + self._state.rho * (- self._state.y - self._state.z) +\ self._state.lambda_mult for i in range(binary_size): @@ -729,8 +742,9 @@ def _create_step3_problem(self) -> OptimizationProblem: for j in range(i, binary_size): op3.objective.set_quadratic_coefficients(i, j, q_y[i, j]) + # linear_y = self._state.lambda_mult + self._state.rho * (self._state.x0 - self._state.z) #Claudio linear_y = - self._state.lambda_mult - self._state.rho * ( - self._state.x0 - self._state.z) + self._state.x0 - self._state.z) # Claudio for i in range(binary_size): op3.objective.set_linear(i, linear_y[i]) From 31c099d266a35f875a2e5b963044a00b3ee4dd87 Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Mon, 30 Mar 2020 13:36:37 +0100 Subject: [PATCH 128/323] run e5 --- test/run_ex5.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 test/run_ex5.py diff --git a/test/run_ex5.py b/test/run_ex5.py new file mode 100644 index 0000000000..a45e741a3b --- /dev/null +++ b/test/run_ex5.py @@ -0,0 +1,48 @@ +""" +Created: 2020-03-27 +@author Claudio Gambella [claudio.gambella1@ie.ibm.com] +""" + +from docplex.mp.model import Model + +from qiskit.aqua.algorithms import NumPyMinimumEigensolver +from qiskit.optimization import OptimizationProblem +from qiskit.optimization.algorithms import MinimumEigenOptimizer, CplexOptimizer +from qiskit.optimization.algorithms.admm_optimizer import ADMMParameters, ADMMOptimizer + + +mdl = Model('ex5') + +v = mdl.binary_var(name='v') +w = mdl.binary_var(name='w') +t = mdl.binary_var(name='t') + +mdl.minimize(v + w + t) +mdl.add_constraint(2 * v + 2 * w + t <= 3, "cons1") +mdl.add_constraint(v + w + t >= 1, "cons2") +mdl.add_constraint(v + w == 1, "cons3") + + +op = OptimizationProblem() +op.from_docplex(mdl) + +# qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) +qubo_optimizer = CplexOptimizer() + +continuous_optimizer = CplexOptimizer() + +admm_params = ADMMParameters( + rho_initial=1001, beta=1000, factor_c=900, + max_iter=100, three_block=True, + ) + +solver = ADMMOptimizer(params=admm_params, qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer,) +solution = solver.solve(op) + +print("results") +print("x={}".format(solution.x)) +print("fval={}".format(solution.fval)) +print("merits={}".format(solution.results.merits)) +print("residuals={}".format(solution.results.residuals)) +print("number of iters={}".format(len(solution.results.merits))) From 25711d99dd0c7d60e7ed4cd8953da746816074d7 Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Mon, 30 Mar 2020 13:40:49 +0100 Subject: [PATCH 129/323] run e5 --- test/{ => optimization}/run_ex5.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{ => optimization}/run_ex5.py (100%) diff --git a/test/run_ex5.py b/test/optimization/run_ex5.py similarity index 100% rename from test/run_ex5.py rename to test/optimization/run_ex5.py From 6e072b9af72e05d6f0ee15e08e179485d3ecaa86 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Mon, 30 Mar 2020 15:14:53 +0100 Subject: [PATCH 130/323] added ex6 as a unit test --- test/optimization/test_admm.py | 41 +++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index bad641657e..db83c05078 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -28,8 +28,9 @@ from qiskit.optimization.problems import OptimizationProblem class TestADMMOptimizer(QiskitOptimizationTestCase): - """ADMM Optimizer Tests based on Mixed-Integer Setup Knapsack Problem""" + """ADMM Optimizer Tests""" + # todo: reconsider the test def test_admm_miskp_eigen(self): """ADMM Optimizer Test based on Mixed-Integer Setup Knapsack Problem using NumPy eigen optimizer""" @@ -113,6 +114,44 @@ def test_admm_maximization(self): self.assertIsNotNone(solution.state) self.assertIsInstance(solution.state, ADMMState) + def test_admm_ex6(self): + """Example 6 as a unit test""" + mdl = Model('ex6') + + v = mdl.binary_var(name='v') + w = mdl.binary_var(name='w') + t = mdl.binary_var(name='t') + u = mdl.continuous_var(name='u') + + mdl.minimize(v + w + t + 5 * (u - 2) ** 2) + mdl.add_constraint(v + 2 * w + t + u <= 3, "cons1") + mdl.add_constraint(v + w + t >= 1, "cons2") + mdl.add_constraint(v + w == 1, "cons3") + + op = OptimizationProblem() + op.from_docplex(mdl) + + qubo_optimizer = CplexOptimizer() + continuous_optimizer = CplexOptimizer() + + admm_params = ADMMParameters( + rho_initial=1001, beta=1000, factor_c=900, + max_iter=100, three_block=True, + ) + + solver = ADMMOptimizer(params=admm_params, qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer) + solution = solver.solve(op) + self.assertIsNotNone(solution) + self.assertIsInstance(solution, ADMMOptimizerResult) + + self.assertIsNotNone(solution.x) + np.testing.assert_almost_equal([1., 0., 1., 1.016], solution.x, 3) + self.assertIsNotNone(solution.fval) + np.testing.assert_almost_equal(6.832, solution.fval, 3) + self.assertIsNotNone(solution.state) + self.assertIsInstance(solution.state, ADMMState) + class Miskp: """A Helper class to generate Mixed Integer Setup Knapsack problems""" From b82f3e6e790de00e609b7b30f776ff23e9c8be9a Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Mon, 30 Mar 2020 15:17:49 +0100 Subject: [PATCH 131/323] removed miskp test --- test/optimization/test_admm.py | 187 --------------------------------- 1 file changed, 187 deletions(-) diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index db83c05078..fe3b723ab8 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -20,7 +20,6 @@ from docplex.mp.model import Model import numpy as np -from cplex import SparsePair from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.optimization.algorithms import CplexOptimizer, MinimumEigenOptimizer from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters, \ @@ -30,61 +29,6 @@ class TestADMMOptimizer(QiskitOptimizationTestCase): """ADMM Optimizer Tests""" - # todo: reconsider the test - def test_admm_miskp_eigen(self): - """ADMM Optimizer Test based on Mixed-Integer Setup Knapsack Problem - using NumPy eigen optimizer""" - miskp = Miskp(*self._get_miskp_problem_params()) - op: OptimizationProblem = miskp.create_problem() - self.assertIsNotNone(op) - - admm_params = ADMMParameters() - - # use numpy exact diagonalization - qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) - continuous_optimizer = CplexOptimizer() - - solver = ADMMOptimizer(qubo_optimizer=qubo_optimizer, - continuous_optimizer=continuous_optimizer, - params=admm_params) - solution: ADMMOptimizerResult = solver.solve(op) - self.assertIsNotNone(solution) - self.assertIsInstance(solution, ADMMOptimizerResult) - - correct_solution = [0., 0., 0., 0., 0., 0., 0., - 0., 0., 0., 0., 0., 0., 0., - 0., 0., 0., 0., 0., 0., - 0., 0.] - correct_objective = 0.0 - - self.assertIsNotNone(solution.x) - np.testing.assert_almost_equal(correct_solution, solution.x, 3) - self.assertIsNotNone(solution.fval) - np.testing.assert_almost_equal(correct_objective, solution.fval, 3) - self.assertIsNotNone(solution.state) - self.assertIsInstance(solution.state, ADMMState) - - @staticmethod - def _get_miskp_problem_params() -> (int, int, float, np.ndarray, np.ndarray, np.ndarray): - """Fills in parameters for a Mixed Integer Setup Knapsack Problem (MISKP) instance.""" - - family_count = 2 - items_per_family = 10 - knapsack_capacity = 45.10 - setup_costs = np.asarray([75.61, 75.54]) - - resource_values = np.asarray([9.78, 8.81, 9.08, 2.03, 8.9, 9, 4.12, 8.16, 6.55, 4.84, 3.78, - 5.13, 4.72, 7.6, 2.83, 1.44, 2.45, 2.24, 6.3, 5.02]) \ - .reshape((family_count, items_per_family)) - - cost_values = np.asarray([-11.78, -10.81, -11.08, -4.03, -10.90, -11.00, -6.12, -10.16, - -8.55, -6.84, -5.78, -7.13, -6.72, -9.60, -4.83, -3.44, -4.45, - -4.24, -8.30, -7.02]) \ - .reshape((family_count, items_per_family)) - - return family_count, items_per_family, knapsack_capacity, setup_costs, resource_values,\ - cost_values - def test_admm_maximization(self): """Tests a simple maximization problem using ADMM optimizer""" mdl = Model('test') @@ -151,134 +95,3 @@ def test_admm_ex6(self): np.testing.assert_almost_equal(6.832, solution.fval, 3) self.assertIsNotNone(solution.state) self.assertIsInstance(solution.state, ADMMState) - - -class Miskp: - """A Helper class to generate Mixed Integer Setup Knapsack problems""" - - def __init__(self, family_count: int, items_per_family: int, knapsack_capacity: float, - setup_costs: np.ndarray, resource_values: np.ndarray, cost_values: np.ndarray)\ - -> None: - """Constructs an instance of this helper class to create suitable ADMM problems. - - Args: - family_count: number of families - items_per_family: number of items in each family - knapsack_capacity: capacity of the knapsack - setup_costs: setup cost to include family k in the knapsack - resource_values: resources consumed if item t in family k is included in the knapsack - cost_values: value of including item t in family k in the knapsack - """ - - self.knapsack_capacity = knapsack_capacity - self.setup_costs = setup_costs - self.resource_values = resource_values - self.cost_values = cost_values - self.items_per_family = items_per_family - self.family_count = family_count - - # definitions of the internal variables - self.op = None - self.range_family = None - self.range_items = None - self.n_x_vars = None - self.n_y_vars = None - self.range_x_vars = None - self.range_y_vars = None - - @staticmethod - def _var_name(stem: str, index1: int, index2: Optional[int] = None, - index3: Optional[int] = None) -> str: - """A method to return a string representation of the name of a decision variable or - a constraint, given its indices. - - Args: - stem: Element name. - index1: Element indices - index2: Element indices - index3: Element indices - - Returns: - Textual representation of the variable name based on the parameters - """ - if index2 is None: - return stem + "(" + str(index1) + ")" - if index3 is None: - return stem + "(" + str(index1) + "," + str(index2) + ")" - return stem + "(" + str(index1) + "," + str(index2) + "," + str(index3) + ")" - - def _create_params(self) -> None: - self.range_family = range(self.family_count) - self.range_items = range(self.items_per_family) - - # make sure instance params are floats - self.setup_costs = [float(val) for val in self.setup_costs] - self.cost_values = self.cost_values.astype(float) - self.resource_values = self.resource_values.astype(float) - - self.n_x_vars = self.family_count * self.items_per_family - self.n_y_vars = self.family_count - - self.range_x_vars = [(k, t) for k in self.range_family for t in self.range_items] - self.range_y_vars = self.range_family - - def _create_vars(self) -> None: - self.op.variables.add( - lb=[0.0] * self.n_x_vars, - names=[self._var_name("x", i, j) for i, j in self.range_x_vars]) - - self.op.variables.add( - # lb=[0.0] * self.n_y_vars, - # ub=[1.0] * self.n_y_vars, - types=["B"] * self.n_y_vars, - names=[self._var_name("y", i) for i in self.range_y_vars]) - - def _create_constraint_capacity(self) -> None: - self.op.linear_constraints.add( - lin_expr=[ - SparsePair( - ind=[self._var_name("x", i, j) for i, j in self.range_x_vars], - val=[self.resource_values[i, j] for i, j in self.range_x_vars]) - ], - senses="L", - rhs=[self.knapsack_capacity], - names=["CAPACITY"]) - - def _create_constraint_allocation(self) -> None: - self.op.linear_constraints.add( - lin_expr=[ - SparsePair( - ind=[self._var_name("x", k, t)] + [self._var_name("y", k)], - val=[1.0, -1.0]) - for k, t in self.range_x_vars - ], - senses="L" * self.n_x_vars, - rhs=[0.0] * self.n_x_vars, - names=[self._var_name("ALLOCATION", k, t) for k, t in self.range_x_vars]) - - def _create_constraints(self) -> None: - self._create_constraint_capacity() - self._create_constraint_allocation() - - def _create_objective(self) -> None: - self.op.objective.set_linear([(self._var_name("y", k), self.setup_costs[k]) - for k in self.range_family] + - [(self._var_name("x", k, t), self.cost_values[k, t]) - for k, t in - self.range_x_vars] - ) - - def create_problem(self) -> OptimizationProblem: - """Creates an instance of optimization problem based on parameters specified. - - Returns: - an instance of optimization problem. - """ - self.op = OptimizationProblem() - - self._create_params() - self._create_vars() - self._create_objective() - self._create_constraints() - - return self.op From 9bd4232e91ce94f148624a6957d4bef061b1a772 Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Mon, 30 Mar 2020 16:25:37 +0100 Subject: [PATCH 132/323] more unit tests, and variable bound fix in step3 --- .../optimization/algorithms/admm_optimizer.py | 19 +------ requirements.txt | 6 +- test/optimization/run_ex5.py | 48 ---------------- test/optimization/test_admm.py | 56 +++++++++++++++++-- 4 files changed, 57 insertions(+), 72 deletions(-) delete mode 100644 test/optimization/run_ex5.py diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 62df0c4388..ac4f0a1819 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -272,21 +272,12 @@ def solve(self, problem: OptimizationProblem) -> ADMMOptimizerResult: # and no update to be done in this case. # debug self._log.debug("x0=%s", self._state.x0) - # Claudio - op1.write('op1.lp') - print(op1.write_as_string()) - print("x0=%s", self._state.x0) op2 = self._create_step2_problem() self._state.u, self._state.z = self._update_x1(op2) # debug self._log.debug("u=%s", self._state.u) self._log.debug("z=%s", self._state.z) - # Claudio - op2.write('op2.lp') - print(op2.write_as_string()) - print("u=%s", self._state.u) - print("z=%s", self._state.z) if self._params.three_block: if binary_indices: @@ -294,10 +285,6 @@ def solve(self, problem: OptimizationProblem) -> ADMMOptimizerResult: self._state.y = self._update_y(op3) # debug self._log.debug("y=%s", self._state.y) - # Claudio - op3.write('op3.lp') - print(op3.write_as_string()) - print("y=%s", self._state.y) self._state.lambda_mult = self._update_lambda_mult() @@ -732,7 +719,8 @@ def _create_step3_problem(self) -> OptimizationProblem: # add y variables. binary_size = len(self._state.binary_indices) op3.variables.add(names=["y_" + str(i + 1) for i in range(binary_size)], - types=["C"] * binary_size) + types=["C"] * binary_size, lb=[-np.inf]*binary_size, + ub=[np.inf]*binary_size) # set quadratic objective. # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coeff-s. @@ -742,9 +730,8 @@ def _create_step3_problem(self) -> OptimizationProblem: for j in range(i, binary_size): op3.objective.set_quadratic_coefficients(i, j, q_y[i, j]) - # linear_y = self._state.lambda_mult + self._state.rho * (self._state.x0 - self._state.z) #Claudio linear_y = - self._state.lambda_mult - self._state.rho * ( - self._state.x0 - self._state.z) # Claudio + self._state.x0 - self._state.z) for i in range(binary_size): op3.objective.set_linear(i, linear_y[i]) diff --git a/requirements.txt b/requirements.txt index 9d2dd7eb30..996551619f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,12 @@ qiskit-terra>=0.11.0 qiskit-ignis>=0.2.0 -scipy>=1.0 +scipy>=1.4.1 sympy>=1.3 -numpy>=1.13 +numpy>=1.18 psutil>=5 scikit-learn>=0.20.0 dlx -docplex +docplex>=2.12.182 fastdtw quandl setuptools>=40.1.0 diff --git a/test/optimization/run_ex5.py b/test/optimization/run_ex5.py deleted file mode 100644 index a45e741a3b..0000000000 --- a/test/optimization/run_ex5.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -Created: 2020-03-27 -@author Claudio Gambella [claudio.gambella1@ie.ibm.com] -""" - -from docplex.mp.model import Model - -from qiskit.aqua.algorithms import NumPyMinimumEigensolver -from qiskit.optimization import OptimizationProblem -from qiskit.optimization.algorithms import MinimumEigenOptimizer, CplexOptimizer -from qiskit.optimization.algorithms.admm_optimizer import ADMMParameters, ADMMOptimizer - - -mdl = Model('ex5') - -v = mdl.binary_var(name='v') -w = mdl.binary_var(name='w') -t = mdl.binary_var(name='t') - -mdl.minimize(v + w + t) -mdl.add_constraint(2 * v + 2 * w + t <= 3, "cons1") -mdl.add_constraint(v + w + t >= 1, "cons2") -mdl.add_constraint(v + w == 1, "cons3") - - -op = OptimizationProblem() -op.from_docplex(mdl) - -# qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) -qubo_optimizer = CplexOptimizer() - -continuous_optimizer = CplexOptimizer() - -admm_params = ADMMParameters( - rho_initial=1001, beta=1000, factor_c=900, - max_iter=100, three_block=True, - ) - -solver = ADMMOptimizer(params=admm_params, qubo_optimizer=qubo_optimizer, - continuous_optimizer=continuous_optimizer,) -solution = solver.solve(op) - -print("results") -print("x={}".format(solution.x)) -print("fval={}".format(solution.fval)) -print("merits={}".format(solution.results.merits)) -print("residuals={}".format(solution.results.residuals)) -print("number of iters={}".format(len(solution.results.merits))) diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index fe3b723ab8..dc7bd0800a 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -13,7 +13,6 @@ # that they have been altered from the originals. """Tests of the ADMM algorithm.""" -from typing import Optional from test.optimization import QiskitOptimizationTestCase @@ -59,7 +58,11 @@ def test_admm_maximization(self): self.assertIsInstance(solution.state, ADMMState) def test_admm_ex6(self): - """Example 6 as a unit test""" + """Example 6 as a unit test. Example 6 is reported in: + Gambella, C., & Simonetto, A. (2020). + Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical + and Quantum Computers. + arXiv preprint arXiv:2001.02069.""" mdl = Model('ex6') v = mdl.binary_var(name='v') @@ -80,7 +83,7 @@ def test_admm_ex6(self): admm_params = ADMMParameters( rho_initial=1001, beta=1000, factor_c=900, - max_iter=100, three_block=True, + max_iter=100, three_block=True, tol=1.e-6 ) solver = ADMMOptimizer(params=admm_params, qubo_optimizer=qubo_optimizer, @@ -90,8 +93,51 @@ def test_admm_ex6(self): self.assertIsInstance(solution, ADMMOptimizerResult) self.assertIsNotNone(solution.x) - np.testing.assert_almost_equal([1., 0., 1., 1.016], solution.x, 3) + np.testing.assert_almost_equal([1., 0., 0., 2.], solution.x, 3) self.assertIsNotNone(solution.fval) - np.testing.assert_almost_equal(6.832, solution.fval, 3) + np.testing.assert_almost_equal(1., solution.fval, 3) + self.assertIsNotNone(solution.state) + self.assertIsInstance(solution.state, ADMMState) + + def test_admm_ex5(self): + """Example 5 as a unit test. Example 5 is reported in: + Gambella, C., & Simonetto, A. (2020). + Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical + and Quantum Computers. + arXiv preprint arXiv:2001.02069.""" + mdl = Model('ex5') + + v = mdl.binary_var(name='v') + w = mdl.binary_var(name='w') + t = mdl.binary_var(name='t') + + mdl.minimize(v + w + t) + mdl.add_constraint(2 * v + 2 * w + t <= 3, "cons1") + mdl.add_constraint(v + w + t >= 1, "cons2") + mdl.add_constraint(v + w == 1, "cons3") + + op = OptimizationProblem() + op.from_docplex(mdl) + + qubo_optimizer = CplexOptimizer() + + continuous_optimizer = CplexOptimizer() + + admm_params = ADMMParameters( + rho_initial=1001, beta=1000, factor_c=900, + max_iter=100, three_block=False + ) + + solver = ADMMOptimizer(params=admm_params, qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer, ) + solution = solver.solve(op) + + self.assertIsNotNone(solution) + self.assertIsInstance(solution, ADMMOptimizerResult) + + self.assertIsNotNone(solution.x) + np.testing.assert_almost_equal([1., 0., 1.], solution.x, 3) + self.assertIsNotNone(solution.fval) + np.testing.assert_almost_equal(2., solution.fval, 3) self.assertIsNotNone(solution.state) self.assertIsInstance(solution.state, ADMMState) From cf98ebb5885747af71a39cb04509dfa65584689d Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Mon, 30 Mar 2020 14:07:20 -0400 Subject: [PATCH 133/323] Changes: - GroverMinimumFinder refactored to GroverOptimizer - Uses Grover's Incremental Approach - Fix evaluate_classically() --- qiskit/optimization/algorithms/__init__.py | 4 +- ..._minimum_finder.py => grover_optimizer.py} | 124 +++++++----------- ...zation_problem_to_negative_value_oracle.py | 10 +- .../results/grover_optimization_results.py | 13 +- .../test_grover_minimum_finder.py | 37 +++--- 5 files changed, 66 insertions(+), 122 deletions(-) rename qiskit/optimization/algorithms/{grover_minimum_finder.py => grover_optimizer.py} (68%) diff --git a/qiskit/optimization/algorithms/__init__.py b/qiskit/optimization/algorithms/__init__.py index 43365111ae..ac413c5bba 100644 --- a/qiskit/optimization/algorithms/__init__.py +++ b/qiskit/optimization/algorithms/__init__.py @@ -52,7 +52,7 @@ from qiskit.optimization.algorithms.minimum_eigen_optimizer import MinimumEigenOptimizer from qiskit.optimization.algorithms.recursive_minimum_eigen_optimizer import\ RecursiveMinimumEigenOptimizer -from qiskit.optimization.algorithms.grover_minimum_finder import GroverMinimumFinder +from qiskit.optimization.algorithms.grover_optimizer import GroverOptimizer __all__ = ["OptimizationAlgorithm", "CplexOptimizer", "CobylaOptimizer", "MinimumEigenOptimizer", - "RecursiveMinimumEigenOptimizer", "GroverMinimumFinder"] + "RecursiveMinimumEigenOptimizer", "GroverOptimizer"] diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_optimizer.py similarity index 68% rename from qiskit/optimization/algorithms/grover_minimum_finder.py rename to qiskit/optimization/algorithms/grover_optimizer.py index a338d3d238..08d382ce82 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_optimizer.py @@ -12,11 +12,10 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""GroverMinimumFinder module""" +"""GroverOptimizer module""" import logging from typing import Optional, Dict, Union -import random import math import numpy as np from qiskit.aqua import QuantumInstance @@ -32,10 +31,10 @@ from qiskit.providers import BaseBackend -class GroverMinimumFinder(OptimizationAlgorithm): +class GroverOptimizer(OptimizationAlgorithm): """Uses Grover Adaptive Search (GAS) to find the minimum of a QUBO function.""" - def __init__(self, num_iterations: int = 3, + def __init__(self, num_value_qubits: int, num_iterations: int = 3, quantum_instance: Optional[Union[BaseBackend, QuantumInstance]] = None) -> None: """ Args: @@ -43,6 +42,7 @@ def __init__(self, num_iterations: int = 3, no improvement. quantum_instance: Instance of selected backend, defaults to Aer's statevector simulator. """ + self._num_value_qubits = num_value_qubits self._n_iterations = num_iterations if quantum_instance is None or isinstance(quantum_instance, BaseBackend): backend = quantum_instance or Aer.get_backend('statevector_simulator') @@ -84,16 +84,13 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: qubo_converter = OptimizationProblemToQubo() problem_ = qubo_converter.encode(problem) - # TODO: How to get from Optimization Problem? - num_output_qubits = 6 - # Variables for tracking the optimum. optimum_found = False optimum_key = math.inf optimum_value = math.inf threshold = 0 n_key = problem_.variables.get_num() - n_value = num_output_qubits + n_value = self._num_value_qubits # Variables for tracking the solutions encountered. num_solutions = 2**n_key @@ -104,89 +101,57 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: operation_count = {} iteration = 0 - # Variables for stopping if we've hit the rotation max. - rotations = 0 - max_rotations = int(np.ceil(100*np.pi/4)) - # Initialize oracle helper object. orig_constant = problem_.objective.get_offset() measurement = not self._quantum_instance.is_statevector opt_prob_converter = OptimizationProblemToNegativeValueOracle(n_value, measurement) + loops_with_no_improvement = 0 while not optimum_found: - m = 1 - improvement_found = False - # Get oracle O and the state preparation operator A for the current threshold. problem_.objective.set_offset(orig_constant - threshold) a_operator, oracle, func_dict = opt_prob_converter.encode(problem_) - # Iterate until we measure a negative. - loops_with_no_improvement = 0 - while not improvement_found: - # Determine the number of rotations. - loops_with_no_improvement += 1 - rotation_count = int(np.ceil(random.uniform(0, m-1))) - rotations += rotation_count - - # Apply Grover's Algorithm to find values below the threshold. - if rotation_count > 0: - grover = Grover(oracle, init_state=a_operator, num_iterations=rotation_count) - circuit = grover.construct_circuit( - measurement=self._quantum_instance.is_statevector - ) - - else: - circuit = a_operator._circuit - - # Get the next outcome. - outcome = self._measure(circuit, n_key, n_value) - k = int(outcome[0:n_key], 2) - v = outcome[n_key:n_key + n_value] - - # Convert the binary string to integer. - int_v = self._bin_to_int(v, n_value) + threshold - v = self._twos_complement(int_v, n_value) - - self._logger.info('Iterations: %s', rotation_count) - self._logger.info('Outcome: %s', outcome) - self._logger.info('Value: %s = %s', v, int_v) - - # If the value is an improvement, we update the iteration parameters (e.g. oracle). - if int_v < optimum_value: - optimum_key = k - optimum_value = int_v - self._logger.info('Current Optimum Key: %s', optimum_key) - self._logger.info('Current Optimum Value: %s', optimum_value) - if v.startswith('1'): - improvement_found = True - threshold = optimum_value - else: - # No better number after the max number of iterations, so we assume the optimal. - if loops_with_no_improvement >= self._n_iterations: - improvement_found = True - optimum_found = True - - # Using Durr and Hoyer method, increase m. - # TODO: Give option for a rotation schedule, or for different lambda's. - m = int(np.ceil(min(m * 8/7, 2**(n_key / 2)))) - self._logger.info('No Improvement. M: %s', m) - - # Check if we've already seen this value. - if k not in keys_measured: - keys_measured.append(k) - - # Stop if we've seen all the keys or hit the rotation max. - if len(keys_measured) == num_solutions or rotations >= max_rotations: - improvement_found = True + # Apply Grover's Algorithm to find values below the threshold. + grover = Grover(oracle, init_state=a_operator, incremental=True) + circuit = grover.construct_circuit(measurement=self._quantum_instance.is_statevector) + + # Get the next outcome. + outcome = self._measure(circuit, n_key, n_value) + k = int(outcome[0:n_key], 2) + v = outcome[n_key:n_key + n_value] + int_v = self._bin_to_int(v, n_value) + threshold + v = self._twos_complement(int_v, n_value) + self._logger.info('Outcome: %s', outcome) + self._logger.info('Value: %s = %s', v, int_v) + + # If the value is an improvement, we update the iteration parameters (e.g. oracle). + if int_v < optimum_value: + optimum_key = k + optimum_value = int_v + self._logger.info('Current Optimum Key: %s', optimum_key) + self._logger.info('Current Optimum Value: %s', optimum_value) + if v.startswith('1'): + threshold = optimum_value + else: + # No better number after the max number of iterations, so we assume the optimal. + if loops_with_no_improvement >= self._n_iterations: optimum_found = True - # Track the operation count. - operations = circuit.count_ops() - operation_count[iteration] = operations - iteration += 1 - self._logger.info('Operation Count: %s\n', operations) + # Check if we've already seen this value. + if k not in keys_measured: + keys_measured.append(k) + + # Stop if we've seen all the keys. + if len(keys_measured) == num_solutions: + optimum_found = True + + # Track the operation count. + operations = circuit.count_ops() + operation_count[iteration] = operations + iteration += 1 + self._logger.info('Operation Count: %s\n', operations) # Get original key and value pairs. func_dict[-1] = orig_constant @@ -198,8 +163,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: opt_x = [1 if s == '1' else 0 for s in ('{0:%sb}' % n_key).format(optimum_key)] # Build the results object. - grover_results = GroverOptimizationResults(operation_count, rotations, n_key, n_value, - func_dict) + grover_results = GroverOptimizationResults(operation_count, n_key, n_value, func_dict) result = OptimizationResult(x=opt_x, fval=solutions[optimum_key], results={"grover_results": grover_results, "qubo_converter": qubo_converter}) diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index 152e589773..4752ed76ca 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -122,14 +122,12 @@ def _get_function(linear: np.array, quadratic: np.array, constant: int) -> \ return func def _evaluate_classically(self, measurement): - # TODO: Typing for this method? Still not sure what it's used for. Required by Grover. """ evaluate classical """ - assignment = [(var + 1) * (int(tf) * 2 - 1) for tf, var in zip(measurement[::-1], + value = measurement[self._num_key:self._num_key + self._num_value] + assignment = [(var + 1) * (int(tf) * 2 - 1) for tf, var in zip(measurement, range(len(measurement)))] - assignment_dict = dict() - for v in assignment: - assignment_dict[v] = bool(v < 0) - return assignment_dict, assignment + evaluation = value[0] == '1' + return evaluation, assignment def _build_operator(self, func_dict: Dict[Union[int, Tuple[int, int]], int]) -> QuantumCircuit: """Creates a circuit for the state preparation operator. diff --git a/qiskit/optimization/results/grover_optimization_results.py b/qiskit/optimization/results/grover_optimization_results.py index a97801ad81..06716842f2 100644 --- a/qiskit/optimization/results/grover_optimization_results.py +++ b/qiskit/optimization/results/grover_optimization_results.py @@ -20,20 +20,18 @@ class GroverOptimizationResults: """A results object for Grover Optimization methods.""" - def __init__(self, operation_counts: Dict[int, Dict[str, int]], rotations: int, + def __init__(self, operation_counts: Dict[int, Dict[str, int]], n_input_qubits: int, n_output_qubits: int, func_dict: Dict[Union[int, Tuple[int, int]], int]) -> None: """ Args: operation_counts: The counts of each operation performed per iteration. - rotations: The total number of Grover rotations performed. n_input_qubits: The number of qubits used to represent the input. n_output_qubits: The number of qubits used to represent the output. func_dict: A dictionary representation of the function, where the keys correspond to a variable, and the values are the corresponding coefficients. """ self._operation_counts = operation_counts - self._rotations = rotations self._n_input_qubits = n_input_qubits self._n_output_qubits = n_output_qubits self._func_dict = func_dict @@ -47,15 +45,6 @@ def operation_counts(self) -> Dict[int, Dict[str, int]]: """ return self._operation_counts - @property - def rotation_count(self) -> int: - """Getter of rotation_count - - Returns: - The total number of Grover rotations. - """ - return self._rotations - @property def n_input_qubits(self) -> int: """Getter of n_input_qubits diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index 5aed7962ad..5a8dcef147 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -12,41 +12,34 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" Test Grover Minimum Finder """ +""" Test Grover Optimizer""" from test.optimization import QiskitOptimizationTestCase -import numpy as np -from docplex.mp.model import Model -from qiskit.finance.applications.ising import portfolio from qiskit.aqua.algorithms import NumPyMinimumEigensolver -from qiskit.optimization.algorithms import GroverMinimumFinder, MinimumEigenOptimizer +from qiskit.optimization.algorithms import GroverOptimizer, MinimumEigenOptimizer from qiskit.optimization.problems import OptimizationProblem -class TestGroverMinimumFinder(QiskitOptimizationTestCase): - """GroverMinimumFinder Tests""" +class TestGroverOptimizer(QiskitOptimizationTestCase): + """GroverOptimizer Tests""" - def validate_results(self, problem, results, max_iterations): + def validate_results(self, problem, results): """Validate the results object returned by GroverMinimumFinder.""" # Get measured values. grover_results = results.results['grover_results'] - op_key = results.x - op_value = results.fval - iterations = len(grover_results.operation_counts) - rot = grover_results.rotation_count + op_key = int(''.join(str(x) for x in results.x), 2) + op_value = int(results.fval) func = grover_results.func_dict print("Function", func) - print("Optimum Key:", op_key, "Optimum Value:", op_value, "Rotations:", rot, "\n") + print("Optimum Key:", op_key, "Optimum Value:", op_value, "\n") # Get expected value. solver = MinimumEigenOptimizer(NumPyMinimumEigensolver()) comp_result = solver.solve(problem) - max_rotations = int(np.ceil(100*np.pi/4)) # Validate results. - max_hit = max_rotations <= rot or max_iterations <= iterations - self.assertTrue(comp_result.x == results.x or max_hit) - self.assertTrue(comp_result.fval == results.fval or max_hit) + self.assertTrue(comp_result.x == results.x) + self.assertTrue(comp_result.fval == results.fval) def test_qubo_gas_int_zero(self): """ Test for when the answer is zero """ @@ -58,7 +51,7 @@ def test_qubo_gas_int_zero(self): op.objective.set_linear(linear) # Will not find a negative, should return 0. - gmf = GroverMinimumFinder(num_iterations=1) + gmf = GroverOptimizer(1, num_iterations=1) results = gmf.solve(op) self.assertEqual(results.x, [0, 0]) self.assertEqual(results.fval, 0.0) @@ -74,9 +67,9 @@ def test_qubo_gas_int_simple(self): # Get the optimum key and value. n_iter = 8 - gmf = GroverMinimumFinder(num_iterations=n_iter) + gmf = GroverOptimizer(4, num_iterations=n_iter) results = gmf.solve(op) - self.validate_results(op, results, n_iter) + self.validate_results(op, results) def test_qubo_gas_int_paper_example(self): """ Test the example from https://arxiv.org/abs/1912.04088 """ @@ -92,6 +85,6 @@ def test_qubo_gas_int_paper_example(self): # Get the optimum key and value. n_iter = 10 - gmf = GroverMinimumFinder(num_iterations=n_iter) + gmf = GroverOptimizer(6, num_iterations=n_iter) results = gmf.solve(op) - self.validate_results(op, results, 10) + self.validate_results(op, results) From 3e9d763c5e0de6b8fb5e16f9931e3e342225861a Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Mon, 30 Mar 2020 22:51:38 +0100 Subject: [PATCH 134/323] linter, rolling back requirements.txt --- qiskit/optimization/algorithms/admm_optimizer.py | 11 +++++------ requirements.txt | 6 +++--- test/optimization/test_admm.py | 8 +++++--- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index ac4f0a1819..a717e93218 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -606,9 +606,9 @@ def _create_step1_problem(self) -> OptimizationProblem: # the quadratic coefficients. quadratic_objective = self._state.q0 +\ 2 * ( - self._params.factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) + - self._state.rho / 2 * np.eye(binary_size) - ) + self._params.factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) + + self._state.rho / 2 * np.eye(binary_size) + ) for i in range(binary_size): for j in range(i, binary_size): op1.objective.set_quadratic_coefficients(i, j, quadratic_objective[i, j]) @@ -731,7 +731,7 @@ def _create_step3_problem(self) -> OptimizationProblem: op3.objective.set_quadratic_coefficients(i, j, q_y[i, j]) linear_y = - self._state.lambda_mult - self._state.rho * ( - self._state.x0 - self._state.z) + self._state.x0 - self._state.z) for i in range(binary_size): op3.objective.set_linear(i, linear_y[i]) @@ -826,7 +826,6 @@ def _update_rho(self, primal_residual: float, dual_residual: float) -> None: self._state.rho = self._params.tau_incr * self._state.rho elif dual_residual > self._params.mu_res * primal_residual: self._state.rho = self._params.tau_decr * self._state.rho - def _get_constraint_residual(self) -> float: """Compute violation of the constraints of the original problem, as: @@ -873,7 +872,7 @@ def quadratic_form(matrix, x, c): obj_val = quadratic_form(self._state.q0, self._state.x0, self._state.c0) obj_val += quadratic_form(self._state.q1, self._state.u, self._state.c1) - + obj_val += self._state.op.objective.get_offset() return obj_val diff --git a/requirements.txt b/requirements.txt index 996551619f..9d2dd7eb30 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,12 @@ qiskit-terra>=0.11.0 qiskit-ignis>=0.2.0 -scipy>=1.4.1 +scipy>=1.0 sympy>=1.3 -numpy>=1.18 +numpy>=1.13 psutil>=5 scikit-learn>=0.20.0 dlx -docplex>=2.12.182 +docplex fastdtw quandl setuptools>=40.1.0 diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index dc7bd0800a..79cf2f0226 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -60,11 +60,12 @@ def test_admm_maximization(self): def test_admm_ex6(self): """Example 6 as a unit test. Example 6 is reported in: Gambella, C., & Simonetto, A. (2020). - Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical + Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical and Quantum Computers. arXiv preprint arXiv:2001.02069.""" mdl = Model('ex6') + # pylint:disable=invalid-name v = mdl.binary_var(name='v') w = mdl.binary_var(name='w') t = mdl.binary_var(name='t') @@ -98,15 +99,16 @@ def test_admm_ex6(self): np.testing.assert_almost_equal(1., solution.fval, 3) self.assertIsNotNone(solution.state) self.assertIsInstance(solution.state, ADMMState) - + def test_admm_ex5(self): """Example 5 as a unit test. Example 5 is reported in: Gambella, C., & Simonetto, A. (2020). - Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical + Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical and Quantum Computers. arXiv preprint arXiv:2001.02069.""" mdl = Model('ex5') + # pylint:disable=invalid-name v = mdl.binary_var(name='v') w = mdl.binary_var(name='w') t = mdl.binary_var(name='t') From 4068a88642be270d4e1a9f796fd7daacbba5ef41 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Tue, 31 Mar 2020 13:27:25 +0100 Subject: [PATCH 135/323] ADMMResult fixes. --- qiskit/optimization/algorithms/admm_optimizer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index a717e93218..bc3d59e77b 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -153,8 +153,8 @@ class ADMMOptimizerResult(OptimizationResult): """ ADMMOptimizer Result.""" def __init__(self, x: Optional[Any] = None, fval: Optional[Any] = None, - state: Optional[ADMMState] = None) -> None: - super().__init__(x, fval, state) + state: Optional[ADMMState] = None, results: Optional[Any] = None) -> None: + super().__init__(x, fval, results) self._state = state @property From ccf6bb0aaa408c73b0dc2d5a41f5c8b44d47a0e8 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Wed, 1 Apr 2020 14:11:36 -0400 Subject: [PATCH 136/323] merge master, fix conflicts, fix style and some spelling --- .pylintdict | 5 +++ .../qiskit.finance.applications.ising.rst | 6 ++++ docs/apidocs/qiskit.finance.applications.rst | 6 ++++ docs/apidocs/qiskit.finance.ising.rst | 6 ---- qiskit/finance/__init__.py | 4 +-- qiskit/finance/applications/__init__.py | 31 +++++++++++++++++++ qiskit/finance/applications/ising/__init__.py | 6 ++-- .../optimization/algorithms/admm_optimizer.py | 19 ++++++------ .../optimization/applications/ising/clique.py | 2 +- .../optimization/applications/ising/common.py | 2 +- .../applications/ising/docplex.py | 2 +- .../applications/ising/exact_cover.py | 2 +- .../applications/ising/graph_partition.py | 2 +- .../{ => applications}/ising/knapsack.py | 0 .../applications/ising/max_cut.py | 2 +- .../applications/ising/partition.py | 2 +- .../applications/ising/set_packing.py | 2 +- .../applications/ising/stable_set.py | 2 +- qiskit/optimization/applications/ising/tsp.py | 2 +- .../applications/ising/vehicle_routing.py | 2 +- .../applications/ising/vertex_cover.py | 2 +- qiskit/optimization/problems/__init__.py | 8 +++-- .../problems/linear_constraint.py | 7 ++--- qiskit/optimization/problems/objective.py | 7 ++--- .../problems/optimization_problem.py | 10 +++--- qiskit/optimization/problems/problem_type.py | 2 +- .../problems/quadratic_constraint.py | 9 +++--- qiskit/optimization/problems/variables.py | 2 +- .../optimization/results/quality_metrics.py | 2 +- qiskit/optimization/results/solution.py | 4 +-- qiskit/optimization/utils/__init__.py | 2 +- qiskit/optimization/utils/base.py | 2 +- .../utils/eigenvector_to_solutions.py | 2 +- qiskit/optimization/utils/helpers.py | 2 +- .../utils/qiskit_optimization_error.py | 2 +- test/aqua/test_compute_min_eigenvalue.py | 2 +- test/optimization/common.py | 2 +- test/optimization/test_cobyla_optimizer.py | 2 +- test/optimization/test_cplex_optimizer.py | 2 +- test/optimization/test_knapsack.py | 4 +-- .../test_quadratic_constraints.py | 16 +++++----- 41 files changed, 119 insertions(+), 77 deletions(-) create mode 100644 docs/apidocs/qiskit.finance.applications.ising.rst create mode 100644 docs/apidocs/qiskit.finance.applications.rst delete mode 100644 docs/apidocs/qiskit.finance.ising.rst create mode 100644 qiskit/finance/applications/__init__.py rename qiskit/optimization/{ => applications}/ising/knapsack.py (100%) diff --git a/.pylintdict b/.pylintdict index 94b204cd3e..06fbf455c3 100644 --- a/.pylintdict +++ b/.pylintdict @@ -22,6 +22,7 @@ api appfactory arcsin args +Armijo asmatrix ast atol @@ -111,6 +112,7 @@ deriv deutsch devs devsglobals +diagonalization diagonalize diagonalizing dict @@ -267,6 +269,7 @@ kumar kwargs labelled ldots +lefthand len leq lhs @@ -454,6 +457,7 @@ rhf rhobeg rhs rightarrow +righthand rohf rosen rsgtu @@ -499,6 +503,7 @@ subclasses subdirectories subgraph subpattern +subscriptable subspaces succ sudo diff --git a/docs/apidocs/qiskit.finance.applications.ising.rst b/docs/apidocs/qiskit.finance.applications.ising.rst new file mode 100644 index 0000000000..6a2bf3699a --- /dev/null +++ b/docs/apidocs/qiskit.finance.applications.ising.rst @@ -0,0 +1,6 @@ +.. _qiskit-finance-applications-ising: + +.. automodule:: qiskit.finance.applications.ising + :no-members: + :no-inherited-members: + :no-special-members: diff --git a/docs/apidocs/qiskit.finance.applications.rst b/docs/apidocs/qiskit.finance.applications.rst new file mode 100644 index 0000000000..f00c75d1f9 --- /dev/null +++ b/docs/apidocs/qiskit.finance.applications.rst @@ -0,0 +1,6 @@ +.. _qiskit-finance-applications: + +.. automodule:: qiskit.finance.applications + :no-members: + :no-inherited-members: + :no-special-members: diff --git a/docs/apidocs/qiskit.finance.ising.rst b/docs/apidocs/qiskit.finance.ising.rst deleted file mode 100644 index e57ff05a7e..0000000000 --- a/docs/apidocs/qiskit.finance.ising.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _qiskit-finance-ising: - -.. automodule:: qiskit.finance.ising - :no-members: - :no-inherited-members: - :no-special-members: diff --git a/qiskit/finance/__init__.py b/qiskit/finance/__init__.py index 27fef7aef8..dbcc811de7 100644 --- a/qiskit/finance/__init__.py +++ b/qiskit/finance/__init__.py @@ -13,7 +13,7 @@ # that they have been altered from the originals. """ ========================================================== -Finance application stack for Aqua (:mod:`qiskit.finance`) +Finance stack for Aqua (:mod:`qiskit.finance`) ========================================================== This is the finance domain logic.... @@ -25,9 +25,9 @@ .. autosummary:: :toctree: + applications components data_providers - ising """ diff --git a/qiskit/finance/applications/__init__.py b/qiskit/finance/applications/__init__.py new file mode 100644 index 0000000000..3786d60a5f --- /dev/null +++ b/qiskit/finance/applications/__init__.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" +======================================================================= +Finance application stack for Aqua (:mod:`qiskit.finance.applications`) +======================================================================= +This is the finance applications domain logic.... + +.. currentmodule:: qiskit.finance.applications + +Submodules +========== + +.. autosummary:: + :toctree: + + ising + +""" diff --git a/qiskit/finance/applications/ising/__init__.py b/qiskit/finance/applications/ising/__init__.py index 299ddff9b9..f5e4085191 100644 --- a/qiskit/finance/applications/ising/__init__.py +++ b/qiskit/finance/applications/ising/__init__.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2019, 2020. # # 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 @@ -13,11 +13,11 @@ # that they have been altered from the originals. """ -Ising Models (:mod:`qiskit.finance.ising`) +Ising Models (:mod:`qiskit.finance.applications.ising`) ========================================== Ising models for finance problems -.. currentmodule:: qiskit.finance.ising +.. currentmodule:: qiskit.finance.applications.ising Ising Models ============ diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index c114f15f15..5d82ff38e4 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -25,7 +25,6 @@ from qiskit.optimization.problems.variables import CPX_BINARY, CPX_CONTINUOUS from qiskit.optimization.results.optimization_result import OptimizationResult - UPDATE_RHO_BY_TEN_PERCENT = 0 UPDATE_RHO_BY_RESIDUALS = 1 @@ -490,8 +489,8 @@ def _get_a0_b0(self) -> (np.ndarray, np.ndarray): else: raise ValueError( "Linear constraint with the 'E' sense must contain only binary variables, " - "row indices: {}, binary variable indices: {}" - .format(row, self._state.binary_indices)) + "row indices: {}, binary variable indices: {}".format( + row, self._state.binary_indices)) return self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) @@ -594,9 +593,9 @@ def _create_step1_problem(self) -> OptimizationProblem: # NOTE: The multiplication by 2 is needed for the solvers to parse # the quadratic coefficients. quadratic_objective = 2 * ( - self._state.q0 + - self._factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) + - self._state.rho / 2 * np.eye(binary_size) + self._state.q0 + + self._factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) + + self._state.rho / 2 * np.eye(binary_size) ) for i in range(binary_size): for j in range(i, binary_size): @@ -604,8 +603,8 @@ def _create_step1_problem(self) -> OptimizationProblem: # prepare and set linear objective. linear_objective = self._state.c0 - \ - self._factor_c * np.dot(self._state.b0, self._state.a0) + \ - self._state.rho * (self._state.y - self._state.z) + self._factor_c * np.dot(self._state.b0, self._state.a0) + \ + self._state.rho * (self._state.y - self._state.z) for i in range(binary_size): op1.objective.set_linear(i, linear_objective[i]) @@ -681,7 +680,7 @@ def _create_step2_problem(self) -> OptimizationProblem: constraint_count = self._state.a2.shape[0] lin_expr = [SparsePair(ind=list(range(continuous_size + binary_size)), val=self._state.a3[i, :].tolist() + - self._state.a2[i, :].tolist()) + self._state.a2[i, :].tolist()) for i in range(constraint_count)] op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, @@ -793,7 +792,7 @@ def _update_lambda_mult(self) -> np.ndarray: """ return self._state.lambda_mult + \ - self._state.rho * (self._state.x0 - self._state.z + self._state.y) + self._state.rho * (self._state.x0 - self._state.z + self._state.y) def _update_rho(self, primal_residual: float, dual_residual: float) -> None: """Updating the rho parameter in ADMM. diff --git a/qiskit/optimization/applications/ising/clique.py b/qiskit/optimization/applications/ising/clique.py index c5bd89d6d6..d3d48a4162 100644 --- a/qiskit/optimization/applications/ising/clique.py +++ b/qiskit/optimization/applications/ising/clique.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2018, 2020. # # 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 diff --git a/qiskit/optimization/applications/ising/common.py b/qiskit/optimization/applications/ising/common.py index 12a35fb14d..8ad0b1a2b5 100644 --- a/qiskit/optimization/applications/ising/common.py +++ b/qiskit/optimization/applications/ising/common.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2018, 2020. # # 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 diff --git a/qiskit/optimization/applications/ising/docplex.py b/qiskit/optimization/applications/ising/docplex.py index 83ad04afcc..0275ec6a72 100644 --- a/qiskit/optimization/applications/ising/docplex.py +++ b/qiskit/optimization/applications/ising/docplex.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2019, 2020. # # 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 diff --git a/qiskit/optimization/applications/ising/exact_cover.py b/qiskit/optimization/applications/ising/exact_cover.py index 21fd743ed3..e69794c538 100644 --- a/qiskit/optimization/applications/ising/exact_cover.py +++ b/qiskit/optimization/applications/ising/exact_cover.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2018, 2020. # # 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 diff --git a/qiskit/optimization/applications/ising/graph_partition.py b/qiskit/optimization/applications/ising/graph_partition.py index 0e404bed33..33bd62e4d0 100644 --- a/qiskit/optimization/applications/ising/graph_partition.py +++ b/qiskit/optimization/applications/ising/graph_partition.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2018, 2020. # # 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 diff --git a/qiskit/optimization/ising/knapsack.py b/qiskit/optimization/applications/ising/knapsack.py similarity index 100% rename from qiskit/optimization/ising/knapsack.py rename to qiskit/optimization/applications/ising/knapsack.py diff --git a/qiskit/optimization/applications/ising/max_cut.py b/qiskit/optimization/applications/ising/max_cut.py index 9212d8e175..bf276dfa45 100644 --- a/qiskit/optimization/applications/ising/max_cut.py +++ b/qiskit/optimization/applications/ising/max_cut.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2018, 2020. # # 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 diff --git a/qiskit/optimization/applications/ising/partition.py b/qiskit/optimization/applications/ising/partition.py index 77c73f803e..cca588dde4 100644 --- a/qiskit/optimization/applications/ising/partition.py +++ b/qiskit/optimization/applications/ising/partition.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2018, 2020. # # 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 diff --git a/qiskit/optimization/applications/ising/set_packing.py b/qiskit/optimization/applications/ising/set_packing.py index 35e0a1b575..f12b96c43e 100644 --- a/qiskit/optimization/applications/ising/set_packing.py +++ b/qiskit/optimization/applications/ising/set_packing.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2018, 2020. # # 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 diff --git a/qiskit/optimization/applications/ising/stable_set.py b/qiskit/optimization/applications/ising/stable_set.py index 370a6c0d02..be06b4b846 100644 --- a/qiskit/optimization/applications/ising/stable_set.py +++ b/qiskit/optimization/applications/ising/stable_set.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2018, 2020. # # 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 diff --git a/qiskit/optimization/applications/ising/tsp.py b/qiskit/optimization/applications/ising/tsp.py index 0bcc7e2dac..b740b83de5 100644 --- a/qiskit/optimization/applications/ising/tsp.py +++ b/qiskit/optimization/applications/ising/tsp.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2018, 2020. # # 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 diff --git a/qiskit/optimization/applications/ising/vehicle_routing.py b/qiskit/optimization/applications/ising/vehicle_routing.py index 197a77d4d5..d360459e7a 100644 --- a/qiskit/optimization/applications/ising/vehicle_routing.py +++ b/qiskit/optimization/applications/ising/vehicle_routing.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2019, 2020. # # 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 diff --git a/qiskit/optimization/applications/ising/vertex_cover.py b/qiskit/optimization/applications/ising/vertex_cover.py index 179c068747..0f61aedc97 100644 --- a/qiskit/optimization/applications/ising/vertex_cover.py +++ b/qiskit/optimization/applications/ising/vertex_cover.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2018, 2020. # # 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 diff --git a/qiskit/optimization/problems/__init__.py b/qiskit/optimization/problems/__init__.py index d6ba245f9d..fa26930d24 100644 --- a/qiskit/optimization/problems/__init__.py +++ b/qiskit/optimization/problems/__init__.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2019, 2020. # # 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 @@ -31,8 +31,10 @@ LinearConstraintInterface QuadraticConstraintInterface -N.B. Additional classes LinearConstraintInterface, QuadraticConstraintInterface, ObjectiveInterface, and VariablesInterface -are not to be instantiated directly. Objects of those types are available within an instantiated OptimizationProblem. +N.B. Additional classes LinearConstraintInterface, QuadraticConstraintInterface, +ObjectiveInterface, and VariablesInterface +are not to be instantiated directly. Objects of those types are available within +an instantiated OptimizationProblem. """ diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index 80f42407e9..f42669e0d4 100644 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2019, 2020. # # 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 @@ -15,8 +15,6 @@ import copy from collections.abc import Sequence -from cplex import SparsePair - from qiskit.optimization.utils.base import BaseInterface from qiskit.optimization.utils.helpers import init_list_args, NameIndex from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError @@ -122,7 +120,7 @@ def add(self, lin_expr=None, senses="", rhs=None, range_values=None, >>> op.linear_constraints.get_rhs() [0.0, 1.0, -1.0, 2.0] """ - + from cplex import SparsePair arg_list = init_list_args(lin_expr, senses, rhs, range_values, names) arg_lengths = [len(x) for x in arg_list] if len(arg_lengths) == 0: @@ -388,6 +386,7 @@ def set_linear_components(self, *args): """ def _set(i, v): + from cplex import SparsePair if isinstance(v, SparsePair): zip_iter = zip(v.ind, v.val) elif isinstance(v, Sequence) and len(v) == 2: diff --git a/qiskit/optimization/problems/objective.py b/qiskit/optimization/problems/objective.py index 84a6fbd123..d34cab2e75 100644 --- a/qiskit/optimization/problems/objective.py +++ b/qiskit/optimization/problems/objective.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2019, 2020. # # 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 @@ -18,8 +18,6 @@ from logging import getLogger from typing import Callable, List -from cplex import SparsePair - from qiskit.optimization.utils import BaseInterface, QiskitOptimizationError CPX_MAX = -1 @@ -142,7 +140,7 @@ def set_quadratic(self, args: List): [SparsePair(ind = [0], val = [1.0]), SparsePair(ind = [1], val = [2.0]), SparsePair(ind = [2], val = [3.0])] """ - + from cplex import SparsePair # clear data self._quadratic = {} @@ -374,6 +372,7 @@ def get_quadratic(self, *args): """ def _get(i): + from cplex import SparsePair qi = self._quadratic.get(i, {}) return SparsePair(list(qi.keys()), list(qi.values())) diff --git a/qiskit/optimization/problems/optimization_problem.py b/qiskit/optimization/problems/optimization_problem.py index 91628cc6e3..4f07295f85 100644 --- a/qiskit/optimization/problems/optimization_problem.py +++ b/qiskit/optimization/problems/optimization_problem.py @@ -15,8 +15,6 @@ """The optimization problem.""" from docplex.mp.model import Model -from cplex import Cplex, SparsePair -from cplex.exceptions import CplexSolverError from qiskit.optimization.problems.linear_constraint import LinearConstraintInterface from qiskit.optimization.problems.objective import ObjectiveInterface @@ -56,7 +54,7 @@ def __init__(self, *args): When the with block is finished, the end() method will be called automatically. """ - + from cplex.exceptions import CplexSolverError if len(args) > 1: raise QiskitOptimizationError("Too many arguments to OptimizationProblem()") self._disposed = False @@ -94,6 +92,7 @@ def __init__(self, *args): def from_cplex(self, op): # make sure current problem is clean + from cplex.exceptions import CplexSolverError self._disposed = False try: self._name = op.get_problem_name() @@ -160,6 +159,7 @@ def from_cplex(self, op): # TODO: add quadratic constraints def from_docplex(self, model: Model): + from cplex.exceptions import CplexSolverError cplex = model.get_cplex() try: cplex.set_problem_name(model.get_name()) @@ -169,6 +169,7 @@ def from_docplex(self, model: Model): self.from_cplex(cplex) def to_cplex(self): + from cplex import Cplex # create empty CPLEX model op = Cplex() if self.get_problem_name() is not None: @@ -243,6 +244,7 @@ def read(self, filename, filetype=""): >>> op = qiskit.optimization.OptimizationProblem() >>> op.read("lpex.mps") """ + from cplex import Cplex cplex = Cplex() cplex.read(filename, filetype) self.from_cplex(cplex) @@ -371,7 +373,7 @@ def substitute_variables(self, constants=None, variables=None): variables: SparseTriple (replace variables by weighted other variable need to copy everything using name reference to make sure that indices are matched correctly """ - + from cplex import SparsePair # guarantee that there is no overlap between variables to be replaced and combine input vars_to_be_replaced = {} if constants is not None: diff --git a/qiskit/optimization/problems/problem_type.py b/qiskit/optimization/problems/problem_type.py index 25c14d8136..0bf8257047 100644 --- a/qiskit/optimization/problems/problem_type.py +++ b/qiskit/optimization/problems/problem_type.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2019, 2020. # # 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 diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index ecf4000faf..d210ebd355 100644 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2019, 2020. # # 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 @@ -17,8 +17,6 @@ from logging import getLogger from typing import List, Dict, Tuple, Callable -from cplex import SparsePair, SparseTriple - from qiskit.optimization.utils.base import BaseInterface from qiskit.optimization.utils.helpers import NameIndex from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError @@ -33,8 +31,8 @@ def __init__(self, varindex: Callable): """Creates a new QuadraticConstraintInterface. The quadratic constraints interface is exposed by the top-level - `OptimizationProblem` class as `OptimizationProblem.quadratic_constraints`. This constructor - is not meant to be used externally. + `OptimizationProblem` class as `OptimizationProblem.quadratic_constraints`. + This constructor is not meant to be used externally. """ super(QuadraticConstraintInterface, self).__init__() self._rhs = [] @@ -111,6 +109,7 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): ... sense = "G") 0 """ + from cplex import SparsePair, SparseTriple # We only ever create one quadratic constraint at a time. # check constraint name diff --git a/qiskit/optimization/problems/variables.py b/qiskit/optimization/problems/variables.py index cc4726be6a..31e26fcf54 100644 --- a/qiskit/optimization/problems/variables.py +++ b/qiskit/optimization/problems/variables.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2019, 2020. # # 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 diff --git a/qiskit/optimization/results/quality_metrics.py b/qiskit/optimization/results/quality_metrics.py index 7bdf2807f2..b82169d4ca 100755 --- a/qiskit/optimization/results/quality_metrics.py +++ b/qiskit/optimization/results/quality_metrics.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2019, 2020. # # 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 diff --git a/qiskit/optimization/results/solution.py b/qiskit/optimization/results/solution.py index 6bf4b47f54..607500195b 100755 --- a/qiskit/optimization/results/solution.py +++ b/qiskit/optimization/results/solution.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2019, 2020. # # 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 @@ -49,7 +49,7 @@ def get_status(self): """Returns the status of the solution. Returns an attribute of Cplex.solution.status. - For interpretations of the status codes, see the + For interpretations of the status codes, see the reference manual of the CPLEX Callable Library, especially the group optim.cplex.callable.solutionstatus diff --git a/qiskit/optimization/utils/__init__.py b/qiskit/optimization/utils/__init__.py index d983674705..d9ac389fdb 100644 --- a/qiskit/optimization/utils/__init__.py +++ b/qiskit/optimization/utils/__init__.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2019, 2020. # # 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 diff --git a/qiskit/optimization/utils/base.py b/qiskit/optimization/utils/base.py index f52e3c5342..7ffed22127 100644 --- a/qiskit/optimization/utils/base.py +++ b/qiskit/optimization/utils/base.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2019, 2020. # # 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 diff --git a/qiskit/optimization/utils/eigenvector_to_solutions.py b/qiskit/optimization/utils/eigenvector_to_solutions.py index 65163366ba..d44efe7efc 100644 --- a/qiskit/optimization/utils/eigenvector_to_solutions.py +++ b/qiskit/optimization/utils/eigenvector_to_solutions.py @@ -3,7 +3,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2018, 2020. # # 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 diff --git a/qiskit/optimization/utils/helpers.py b/qiskit/optimization/utils/helpers.py index e564b01b99..33bb7898d0 100644 --- a/qiskit/optimization/utils/helpers.py +++ b/qiskit/optimization/utils/helpers.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2019, 2020. # # 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 diff --git a/qiskit/optimization/utils/qiskit_optimization_error.py b/qiskit/optimization/utils/qiskit_optimization_error.py index cca56e9150..5a50d4bfd9 100644 --- a/qiskit/optimization/utils/qiskit_optimization_error.py +++ b/qiskit/optimization/utils/qiskit_optimization_error.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2019, 2020. # # 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 diff --git a/test/aqua/test_compute_min_eigenvalue.py b/test/aqua/test_compute_min_eigenvalue.py index 0d67358dca..15fa3c9594 100755 --- a/test/aqua/test_compute_min_eigenvalue.py +++ b/test/aqua/test_compute_min_eigenvalue.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2018, 2020. # # 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 diff --git a/test/optimization/common.py b/test/optimization/common.py index 6c488f6603..f581c4ef88 100755 --- a/test/optimization/common.py +++ b/test/optimization/common.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2018, 2020. # # 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 diff --git a/test/optimization/test_cobyla_optimizer.py b/test/optimization/test_cobyla_optimizer.py index 9b7e990499..45208ff60e 100644 --- a/test/optimization/test_cobyla_optimizer.py +++ b/test/optimization/test_cobyla_optimizer.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2018, 2020. # # 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 diff --git a/test/optimization/test_cplex_optimizer.py b/test/optimization/test_cplex_optimizer.py index 373bfeb799..bf469a3559 100644 --- a/test/optimization/test_cplex_optimizer.py +++ b/test/optimization/test_cplex_optimizer.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2018, 2020. # # 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 diff --git a/test/optimization/test_knapsack.py b/test/optimization/test_knapsack.py index ad0379e7fa..2fb4770808 100644 --- a/test/optimization/test_knapsack.py +++ b/test/optimization/test_knapsack.py @@ -17,8 +17,8 @@ from test.optimization import QiskitOptimizationTestCase import numpy as np -from qiskit.optimization.ising import knapsack -from qiskit.optimization.ising.common import sample_most_likely +from qiskit.optimization.applications.ising import knapsack +from qiskit.optimization.applications.ising.common import sample_most_likely from qiskit.aqua.algorithms import NumPyMinimumEigensolver diff --git a/test/optimization/test_quadratic_constraints.py b/test/optimization/test_quadratic_constraints.py index c4184dcaff..d1754b6a7d 100644 --- a/test/optimization/test_quadratic_constraints.py +++ b/test/optimization/test_quadratic_constraints.py @@ -53,10 +53,10 @@ def test_initial2(self): self.assertListEqual(quad.get_senses(), ['E']) self.assertListEqual(quad.get_linear_num_nonzeros(), [2]) self.assertListEqual(quad.get_quad_num_nonzeros(), [2]) - l = quad.get_linear_components() - self.assertEqual(len(l), 1) - self.assertListEqual(l[0].ind, [0, 2]) - self.assertListEqual(l[0].val, [1.0, -1.0]) + la = quad.get_linear_components() + self.assertEqual(len(la), 1) + self.assertListEqual(la[0].ind, [0, 2]) + self.assertListEqual(la[0].val, [1.0, -1.0]) q = quad.get_quadratic_components() self.assertEqual(len(q), 1) self.assertListEqual(q[0].ind1, [1, 2]) @@ -66,20 +66,20 @@ def test_initial2(self): def test_get_num(self): op = OptimizationProblem() op.variables.add(names=['x', 'y']) - l = SparsePair(ind=['x'], val=[1.0]) + la = SparsePair(ind=['x'], val=[1.0]) q = SparseTriple(ind1=['x'], ind2=['y'], val=[1.0]) n = 10 for i in range(n): - self.assertEqual(op.quadratic_constraints.add(name=str(i), lin_expr=l, quad_expr=q), i) + self.assertEqual(op.quadratic_constraints.add(name=str(i), lin_expr=la, quad_expr=q), i) self.assertEqual(op.quadratic_constraints.get_num(), n) def test_add(self): op = OptimizationProblem() op.variables.add(names=['x', 'y']) - l = SparsePair(ind=['x'], val=[1.0]) + la = SparsePair(ind=['x'], val=[1.0]) q = SparseTriple(ind1=['x'], ind2=['y'], val=[1.0]) self.assertEqual(op.quadratic_constraints.add( - name='my quad', lin_expr=l, quad_expr=q, rhs=1.0, sense='G'), 0) + name='my quad', lin_expr=la, quad_expr=q, rhs=1.0, sense='G'), 0) def test_delete(self): op = OptimizationProblem() From ddb2b26a6681bd750cfbfa5a095894ea4b289e69 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Wed, 1 Apr 2020 15:32:01 -0400 Subject: [PATCH 137/323] fix more lint --- .../eigen_solvers/numpy_eigen_solver.py | 4 +- .../minimum_eigen_solvers/min_eigen_solver.py | 1 + .../optimization/algorithms/admm_optimizer.py | 8 +- .../recursive_minimum_eigen_optimizer.py | 3 +- .../problems/linear_constraint.py | 2 + .../problems/optimization_problem.py | 3 + qiskit/optimization/problems/variables.py | 2 + .../optimization/results/quality_metrics.py | 4 - qiskit/optimization/results/solution.py | 1 - .../utils/eigenvector_to_solutions.py | 1 - qiskit/optimization/utils/helpers.py | 2 +- requirements.txt | 1 + setup.py | 1 + test/aqua/test_compute_min_eigenvalue.py | 3 +- test/finance/test_data_providers.py | 5 + .../test_european_call_expected_value.py | 5 + test/finance/test_portfolio.py | 5 + .../finance/test_portfolio_diversification.py | 5 + test/optimization/__init__.py | 2 +- test/optimization/common.py | 108 ------------------ test/optimization/optimization_test_case.py | 25 ++++ test/optimization/test_admm_miskp.py | 6 + test/optimization/test_classical_cplex.py | 5 + test/optimization/test_clique.py | 5 + test/optimization/test_cobyla_optimizer.py | 12 +- test/optimization/test_converters.py | 14 ++- test/optimization/test_cplex_optimizer.py | 4 +- test/optimization/test_docplex.py | 5 + test/optimization/test_exact_cover.py | 5 + test/optimization/test_graph_partition.py | 5 + .../test_grover_minimum_finder.py | 5 + test/optimization/test_helpers.py | 11 +- test/optimization/test_knapsack.py | 5 + test/optimization/test_linear_constraints.py | 20 +++- test/optimization/test_min_eigen_optimizer.py | 7 +- test/optimization/test_objective.py | 22 +++- .../optimization/test_optimization_problem.py | 21 +++- ...zation_problem_to_negative_value_oracle.py | 5 + test/optimization/test_partition.py | 5 + test/optimization/test_qaoa.py | 5 + .../test_quadratic_constraints.py | 21 +++- .../test_recursive_optimization.py | 7 +- test/optimization/test_set_packing.py | 5 + test/optimization/test_stable_set.py | 5 + test/optimization/test_tsp.py | 5 + test/optimization/test_variables.py | 34 +++++- test/optimization/test_vehicle_routing.py | 5 + test/optimization/test_vertex_cover.py | 5 + 48 files changed, 301 insertions(+), 144 deletions(-) delete mode 100755 test/optimization/common.py create mode 100755 test/optimization/optimization_test_case.py diff --git a/qiskit/aqua/algorithms/eigen_solvers/numpy_eigen_solver.py b/qiskit/aqua/algorithms/eigen_solvers/numpy_eigen_solver.py index 8174094656..0a0e5db69d 100755 --- a/qiskit/aqua/algorithms/eigen_solvers/numpy_eigen_solver.py +++ b/qiskit/aqua/algorithms/eigen_solvers/numpy_eigen_solver.py @@ -14,7 +14,7 @@ """The Eigensolver algorithm.""" -from typing import List, Optional, Tuple +from typing import List, Optional import logging import pprint import warnings @@ -191,7 +191,7 @@ def _run(self): Returns: dict: Dictionary of results Raises: - ValueError: if no operator has been provided + AquaError: if no operator has been provided """ if self._operator is None: raise AquaError("Operator was never provided") diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/min_eigen_solver.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/min_eigen_solver.py index fdb29498cc..e246278559 100644 --- a/qiskit/aqua/algorithms/minimum_eigen_solvers/min_eigen_solver.py +++ b/qiskit/aqua/algorithms/minimum_eigen_solvers/min_eigen_solver.py @@ -26,6 +26,7 @@ def __init__(self, operator=None): @abstractmethod def compute_min_eigenvalue(self, operator=None): + """ computes minimum eigen value """ raise NotImplementedError() # Cannot implement this, since ExactEigenSolver and VQE both inherit from diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 5d82ff38e4..cc359e7169 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -593,9 +593,9 @@ def _create_step1_problem(self) -> OptimizationProblem: # NOTE: The multiplication by 2 is needed for the solvers to parse # the quadratic coefficients. quadratic_objective = 2 * ( - self._state.q0 + - self._factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) + - self._state.rho / 2 * np.eye(binary_size) + self._state.q0 + + self._factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) + + self._state.rho / 2 * np.eye(binary_size) ) for i in range(binary_size): for j in range(i, binary_size): @@ -680,7 +680,7 @@ def _create_step2_problem(self) -> OptimizationProblem: constraint_count = self._state.a2.shape[0] lin_expr = [SparsePair(ind=list(range(continuous_size + binary_size)), val=self._state.a3[i, :].tolist() + - self._state.a2[i, :].tolist()) + self._state.a2[i, :].tolist()) for i in range(constraint_count)] op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 4ddad65f66..0d98cf1c92 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -27,7 +27,6 @@ from copy import deepcopy from typing import Optional import numpy as np -from cplex import SparseTriple from qiskit.aqua.algorithms import NumPyMinimumEigensolver @@ -108,7 +107,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: The result of the optimizer applied to the problem. """ - + from cplex import SparseTriple # convert problem to QUBO qubo_converter = OptimizationProblemToQubo() problem_ = qubo_converter.encode(problem) diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index f42669e0d4..24ed22b622 100644 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -737,6 +737,7 @@ def get_rows(self, *args): """ def _get(i): + from cplex import SparsePair keys = list(self._lin_expr[i].keys()) keys.sort() return SparsePair(keys, [self._lin_expr[i][k] for k in keys]) @@ -805,4 +806,5 @@ def _get(i): return self._getter(_get, keys) def get_histogram(self): + """ get histogram """ raise NotImplementedError("histrogram is not implemented") diff --git a/qiskit/optimization/problems/optimization_problem.py b/qiskit/optimization/problems/optimization_problem.py index 4f07295f85..d3f27ccfb1 100644 --- a/qiskit/optimization/problems/optimization_problem.py +++ b/qiskit/optimization/problems/optimization_problem.py @@ -91,6 +91,7 @@ def __init__(self, *args): raise QiskitOptimizationError('Could not load file: %s' % args[0]) def from_cplex(self, op): + """ from cplex """ # make sure current problem is clean from cplex.exceptions import CplexSolverError self._disposed = False @@ -159,6 +160,7 @@ def from_cplex(self, op): # TODO: add quadratic constraints def from_docplex(self, model: Model): + """ from docplex """ from cplex.exceptions import CplexSolverError cplex = model.get_cplex() try: @@ -169,6 +171,7 @@ def from_docplex(self, model: Model): self.from_cplex(cplex) def to_cplex(self): + """ to cplex """ from cplex import Cplex # create empty CPLEX model op = Cplex() diff --git a/qiskit/optimization/problems/variables.py b/qiskit/optimization/problems/variables.py index 31e26fcf54..ad2b50750b 100644 --- a/qiskit/optimization/problems/variables.py +++ b/qiskit/optimization/problems/variables.py @@ -677,7 +677,9 @@ def _get(i): return self._getter(_get, keys) def get_cols(self, *args): + """ get cols """ raise NotImplementedError("Please use LinearConstraintInterface instead.") def get_obj(self, *args): + """ get obj """ raise NotImplementedError("Please use ObjectiveInterface instead.") diff --git a/qiskit/optimization/results/quality_metrics.py b/qiskit/optimization/results/quality_metrics.py index b82169d4ca..4a33b52c90 100755 --- a/qiskit/optimization/results/quality_metrics.py +++ b/qiskit/optimization/results/quality_metrics.py @@ -13,10 +13,6 @@ # that they have been altered from the originals. -from qiskit.optimization.utils.base import BaseInterface -from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError - - class QualityMetrics(object): """A class containing measures of the quality of a solution. diff --git a/qiskit/optimization/results/solution.py b/qiskit/optimization/results/solution.py index 607500195b..56ad576a63 100755 --- a/qiskit/optimization/results/solution.py +++ b/qiskit/optimization/results/solution.py @@ -17,7 +17,6 @@ from qiskit.optimization.results.solution_status import SolutionStatus from qiskit.optimization.utils.base import BaseInterface -from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError class SolutionInterface(BaseInterface): diff --git a/qiskit/optimization/utils/eigenvector_to_solutions.py b/qiskit/optimization/utils/eigenvector_to_solutions.py index d44efe7efc..d7b487080c 100644 --- a/qiskit/optimization/utils/eigenvector_to_solutions.py +++ b/qiskit/optimization/utils/eigenvector_to_solutions.py @@ -18,7 +18,6 @@ from qiskit.aqua.operators import MatrixOperator from typing import Union, List, Tuple from qiskit import QuantumCircuit, BasicAer, execute -from qiskit.aqua import QuantumInstance from qiskit.aqua.operators import WeightedPauliOperator, MatrixOperator import numpy diff --git a/qiskit/optimization/utils/helpers.py b/qiskit/optimization/utils/helpers.py index 33bb7898d0..ea55e9fd5f 100644 --- a/qiskit/optimization/utils/helpers.py +++ b/qiskit/optimization/utils/helpers.py @@ -13,7 +13,7 @@ # that they have been altered from the originals. -from typing import Union, List, Dict, Sequence +from typing import Union, List, Sequence from qiskit.optimization.utils import QiskitOptimizationError diff --git a/requirements.txt b/requirements.txt index 9d2dd7eb30..3002ab07b0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,3 +13,4 @@ setuptools>=40.1.0 h5py networkx>=2.2 pyscf; sys_platform == 'linux' or (python_version < '3.8' and sys_platform != 'win32') +cplex; python_version < '3.8' diff --git a/setup.py b/setup.py index 4cbf228553..6a357679d0 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ "h5py", "networkx>=2.2", "pyscf; sys_platform == 'linux' or (python_version < '3.8' and sys_platform != 'win32')", + "cplex; python_version < '3.8'", ] if not hasattr(setuptools, 'find_namespace_packages') or not inspect.ismethod(setuptools.find_namespace_packages): diff --git a/test/aqua/test_compute_min_eigenvalue.py b/test/aqua/test_compute_min_eigenvalue.py index 15fa3c9594..898277f893 100755 --- a/test/aqua/test_compute_min_eigenvalue.py +++ b/test/aqua/test_compute_min_eigenvalue.py @@ -21,8 +21,7 @@ from qiskit.aqua import QuantumInstance, aqua_globals from qiskit.aqua.operators import WeightedPauliOperator, MatrixOperator from qiskit.aqua.components.variational_forms import RY, RYRZ -from qiskit.aqua.components.optimizers import L_BFGS_B, SPSA, SLSQP -from qiskit.aqua.components.initial_states import Zero +from qiskit.aqua.components.optimizers import L_BFGS_B, SPSA from qiskit.aqua.algorithms import VQE, NumPyMinimumEigensolver diff --git a/test/finance/test_data_providers.py b/test/finance/test_data_providers.py index 2097f91f8d..a09be19c76 100644 --- a/test/finance/test_data_providers.py +++ b/test/finance/test_data_providers.py @@ -14,6 +14,7 @@ """ Test Data Providers """ +import unittest import datetime from test.finance import QiskitFinanceTestCase import warnings @@ -132,3 +133,7 @@ def test_exchangedata(self): self.fail("Test of DataOnDemandProvider should have failed due to the lack of a token.") except QiskitFinanceError: self.skipTest("Test of DataOnDemandProvider skipped due to the lack of a token.") + + +if __name__ == '__main__': + unittest.main() diff --git a/test/finance/test_european_call_expected_value.py b/test/finance/test_european_call_expected_value.py index 76312738ca..51f52deb27 100644 --- a/test/finance/test_european_call_expected_value.py +++ b/test/finance/test_european_call_expected_value.py @@ -14,6 +14,7 @@ """ Test European Call Expected Value uncertainty problem """ +import unittest from test.finance import QiskitFinanceTestCase import numpy as np @@ -71,3 +72,7 @@ def test_ecev(self): result = algo.run(quantum_instance=BasicAer.get_backend('statevector_simulator')) self.assertAlmostEqual(result['estimation'], 1.2580, places=4) self.assertAlmostEqual(result['max_probability'], 0.8785, places=4) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/finance/test_portfolio.py b/test/finance/test_portfolio.py index 1f6462dc8a..2aa5b72893 100644 --- a/test/finance/test_portfolio.py +++ b/test/finance/test_portfolio.py @@ -14,6 +14,7 @@ """ Test Portfolio """ +import unittest from test.finance import QiskitFinanceTestCase import datetime @@ -77,3 +78,7 @@ def test_portfolio_qaoa(self): selection, self.muu, self.sigma, self.risk, self.budget, self.penalty) np.testing.assert_array_equal(selection, [1, 0, 0, 1]) self.assertAlmostEqual(value, -0.0055989) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/finance/test_portfolio_diversification.py b/test/finance/test_portfolio_diversification.py index 58fac37783..dfd5d4c694 100644 --- a/test/finance/test_portfolio_diversification.py +++ b/test/finance/test_portfolio_diversification.py @@ -14,6 +14,7 @@ """ Test Portfolio Diversification Optimization """ +import unittest import math from test.finance import QiskitFinanceTestCase @@ -243,3 +244,7 @@ def test_portfolio_diversification(self): if x is not None: np.testing.assert_approx_equal(ground_level, classical_cost) np.testing.assert_array_almost_equal(quantum_solution, x, 5) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/__init__.py b/test/optimization/__init__.py index 8b498a9dc7..53208963d7 100755 --- a/test/optimization/__init__.py +++ b/test/optimization/__init__.py @@ -14,6 +14,6 @@ """ Optimization test packages """ -from .common import QiskitOptimizationTestCase +from .optimization_test_case import QiskitOptimizationTestCase __all__ = ['QiskitOptimizationTestCase'] diff --git a/test/optimization/common.py b/test/optimization/common.py deleted file mode 100755 index f581c4ef88..0000000000 --- a/test/optimization/common.py +++ /dev/null @@ -1,108 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Shared functionality and helpers for the unit tests.""" - -from enum import Enum -import inspect -import logging -import os -import unittest -import time - -from qiskit.optimization import __path__ as qiskit_optimization_path - - -class Path(Enum): - """Helper with paths commonly used during the tests.""" - # Main SDK path: qiskit/optimization/ - SDK = qiskit_optimization_path[0] - # test.python path: test/optimization - TEST = os.path.dirname(__file__) - - -class QiskitOptimizationTestCase(unittest.TestCase): - """Helper class that contains common functionality.""" - - def setUp(self): - self._started_at = time.time() - - def tearDown(self): - elapsed = time.time() - self._started_at - if elapsed > 5.0: - print('({:.2f}s)'.format(round(elapsed, 2)), flush=True) - - @classmethod - def setUpClass(cls): - cls.moduleName = os.path.splitext(inspect.getfile(cls))[0] - cls.log = logging.getLogger(cls.__name__) - - # Set logging to file and stdout if the LOG_LEVEL environment variable - # is set. - if os.getenv('LOG_LEVEL'): - # Set up formatter. - log_fmt = ('{}.%(funcName)s:%(levelname)s:%(asctime)s:' - ' %(message)s'.format(cls.__name__)) - formatter = logging.Formatter(log_fmt) - - # Set up the file handler. - log_file_name = '%s.log' % cls.moduleName - file_handler = logging.FileHandler(log_file_name) - file_handler.setFormatter(formatter) - cls.log.addHandler(file_handler) - - # Set the logging level from the environment variable, defaulting - # to INFO if it is not a valid level. - level = logging._nameToLevel.get(os.getenv('LOG_LEVEL'), - logging.INFO) - cls.log.setLevel(level) - - @staticmethod - def get_resource_path(filename, path=Path.TEST): - """ Get the absolute path to a resource. - Args: - filename (string): filename or relative path to the resource. - path (Path): path used as relative to the filename. - Returns: - str: the absolute path to the resource. - """ - return os.path.normpath(os.path.join(path.value, filename)) - - def assertNoLogs(self, logger=None, level=None): - """The opposite to assertLogs. - """ - # pylint: disable=invalid-name - return _AssertNoLogsContext(self, logger, level) - - -class _AssertNoLogsContext(unittest.case._AssertLogsContext): - """A context manager used to implement TestCase.assertNoLogs().""" - - LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s" - - # pylint: disable=inconsistent-return-statements - def __exit__(self, exc_type, exc_value, tb): - """ - This is a modified version of unittest.case._AssertLogsContext.__exit__(...) - """ - self.logger.handlers = self.old_handlers - self.logger.propagate = self.old_propagate - self.logger.setLevel(self.old_level) - if exc_type is not None: - # let unexpected exceptions pass through - return False - for record in self.watcher.records: - self._raiseFailure( - "Something was logged in the logger %s by %s:%i" % - (record.name, record.pathname, record.lineno)) diff --git a/test/optimization/optimization_test_case.py b/test/optimization/optimization_test_case.py new file mode 100755 index 0000000000..f147c88ed2 --- /dev/null +++ b/test/optimization/optimization_test_case.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2020. +# +# 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. + +"""Optimization Test Case""" + +from test import QiskitBaseTestCase + + +class QiskitOptimizationTestCase(QiskitBaseTestCase): + """Optimization Test Case""" + + def setUp(self) -> None: + super().setUp() + self._class_location = __file__ diff --git a/test/optimization/test_admm_miskp.py b/test/optimization/test_admm_miskp.py index d8ddbc7e02..47abb30b03 100644 --- a/test/optimization/test_admm_miskp.py +++ b/test/optimization/test_admm_miskp.py @@ -13,6 +13,8 @@ # that they have been altered from the originals. """Tests of the ADMM algorithm.""" + +import unittest from typing import Optional from test.optimization import QiskitOptimizationTestCase @@ -208,3 +210,7 @@ def create_problem(self) -> OptimizationProblem: self._create_constraints() return self.op + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_classical_cplex.py b/test/optimization/test_classical_cplex.py index 9126dfe6de..da75a59d2f 100755 --- a/test/optimization/test_classical_cplex.py +++ b/test/optimization/test_classical_cplex.py @@ -14,6 +14,7 @@ """ Test Classical Cplex """ +import unittest from test.optimization import QiskitOptimizationTestCase import numpy as np @@ -45,3 +46,7 @@ def test_cplex_ising(self): self.assertEqual(max_cut.max_cut_value(x, self.w), 24) except NameError as ex: self.skipTest(str(ex)) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_clique.py b/test/optimization/test_clique.py index c0302250de..db99cabd34 100755 --- a/test/optimization/test_clique.py +++ b/test/optimization/test_clique.py @@ -14,6 +14,7 @@ """ Test Clique """ +import unittest from test.optimization import QiskitOptimizationTestCase import numpy as np from qiskit import BasicAer @@ -80,3 +81,7 @@ def test_clique_vqe(self): np.testing.assert_array_equal(ising_sol, [1, 1, 1, 1, 1]) oracle = self._brute_force() self.assertEqual(clique.satisfy_or_not(ising_sol, self.w, self.k), oracle) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_cobyla_optimizer.py b/test/optimization/test_cobyla_optimizer.py index 45208ff60e..a1e607bb0a 100644 --- a/test/optimization/test_cobyla_optimizer.py +++ b/test/optimization/test_cobyla_optimizer.py @@ -14,16 +14,14 @@ """ Test Cobyla Optimizer """ - -from test.optimization.common import QiskitOptimizationTestCase +import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase from ddt import ddt, data from qiskit.optimization.algorithms import CobylaOptimizer from qiskit.optimization.problems import OptimizationProblem -from cplex import SparsePair, SparseTriple - @ddt class TestCobylaOptimizer(QiskitOptimizationTestCase): @@ -56,7 +54,7 @@ def test_cobyla_optimizer(self, config): def test_cobyla_optimizer_with_quadratic_constraint(self): """ Cobyla Optimizer Test """ - + from cplex import SparsePair, SparseTriple # load optimization problem problem = OptimizationProblem() problem.variables.add(lb=[0, 0], ub=[1, 1], types='CC') @@ -72,3 +70,7 @@ def test_cobyla_optimizer_with_quadratic_constraint(self): # analyze results self.assertAlmostEqual(result.fval, 1.0, places=2) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index 2619e90f75..68aa6e641b 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -14,12 +14,13 @@ """ Test Converters """ +import unittest from cplex import SparsePair from qiskit.optimization import OptimizationProblem, QiskitOptimizationError from qiskit.optimization.converters import InequalityToEqualityConverter, \ OptimizationProblemToOperator, IntegerToBinaryConverter, PenalizeLinearEqualityConstraints -from test.optimization.common import QiskitOptimizationTestCase +from test.optimization.optimization_test_case import QiskitOptimizationTestCase class TestConverters(QiskitOptimizationTestCase): @@ -29,6 +30,7 @@ def setUp(self): super().setUp() def test_empty_problem(self): + """ test empty problem """ op = OptimizationProblem() conv = InequalityToEqualityConverter() op = conv.encode(op) @@ -41,6 +43,7 @@ def test_empty_problem(self): self.assertEqual(shift, 0.0) def test_inequality_binary(self): + """ test inequality binary """ op = OptimizationProblem() op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( @@ -61,6 +64,7 @@ def test_inequality_binary(self): self.assertListEqual(cst.get_rhs(), [1, 2, 3]) def test_inequality_integer(self): + """ test inequality integer """ op = OptimizationProblem() op.variables.add(names=['x', 'y', 'z'], types='I' * 3, lb=[-3] * 3, ub=[3] * 3) op.linear_constraints.add( @@ -81,6 +85,7 @@ def test_inequality_integer(self): self.assertListEqual(cst.get_rhs(), [1, 2, 3]) def test_penalize_sense(self): + """ test penalize sense """ op = OptimizationProblem() op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( @@ -96,6 +101,7 @@ def test_penalize_sense(self): self.assertRaises(QiskitOptimizationError, lambda: conv.encode(op)) def test_penalize_binary(self): + """ test penalize binary """ op = OptimizationProblem() op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( @@ -111,6 +117,7 @@ def test_penalize_binary(self): self.assertEqual(op2.linear_constraints.get_num(), 0) def test_penalize_integer(self): + """ test penalize integer """ op = OptimizationProblem() op.variables.add(names=['x', 'y', 'z'], types='I' * 3, lb=[-3] * 3, ub=[3] * 3) op.linear_constraints.add( @@ -126,6 +133,7 @@ def test_penalize_integer(self): self.assertEqual(op2.linear_constraints.get_num(), 0) def test_integer_to_binary(self): + """ test integer to binary """ op = OptimizationProblem() op.variables.add(names=['x', 'y', 'z'], types='BIC', lb=[0, 0, 0], ub=[1, 5, 10]) self.assertEqual(op.variables.get_num(), 3) @@ -141,3 +149,7 @@ def test_integer_to_binary(self): self.assertEqual(vars.get_upper_bounds('x'), 1.0) self.assertEqual(vars.get_upper_bounds('z'), 10.0) self.assertListEqual(vars.get_types(['x', 'z']), ['B', 'C']) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_cplex_optimizer.py b/test/optimization/test_cplex_optimizer.py index bf469a3559..2cb9445b5d 100644 --- a/test/optimization/test_cplex_optimizer.py +++ b/test/optimization/test_cplex_optimizer.py @@ -14,8 +14,8 @@ """ Test Cplex Optimizer """ -from test.optimization.common import QiskitOptimizationTestCase -import numpy as np +import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase from qiskit.optimization.algorithms import CplexOptimizer from qiskit.optimization.problems import OptimizationProblem diff --git a/test/optimization/test_docplex.py b/test/optimization/test_docplex.py index 725dab6434..8525658079 100755 --- a/test/optimization/test_docplex.py +++ b/test/optimization/test_docplex.py @@ -14,6 +14,7 @@ """ Test Docplex """ +import unittest from math import fsum, isclose from test.optimization import QiskitOptimizationTestCase @@ -320,3 +321,7 @@ def test_docplex_constant_and_quadratic_terms_in_object_function(self): # Compare objective self.assertEqual(result.eigenvalue.real + offset, expected_result) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_exact_cover.py b/test/optimization/test_exact_cover.py index 30aab55944..dddfd858b3 100755 --- a/test/optimization/test_exact_cover.py +++ b/test/optimization/test_exact_cover.py @@ -14,6 +14,7 @@ """ Test Exact Cover """ +import unittest import json from test.optimization import QiskitOptimizationTestCase import numpy as np @@ -81,3 +82,7 @@ def test_exact_cover_vqe(self): oracle = self._brute_force() self.assertEqual(exact_cover.check_solution_satisfiability(ising_sol, self.list_of_subsets), oracle) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_graph_partition.py b/test/optimization/test_graph_partition.py index d32b5ee92d..16a6eb1b55 100755 --- a/test/optimization/test_graph_partition.py +++ b/test/optimization/test_graph_partition.py @@ -14,6 +14,7 @@ """ Test Graph Partition """ +import unittest from test.optimization import QiskitOptimizationTestCase import numpy as np from qiskit import BasicAer @@ -84,3 +85,7 @@ def test_graph_partition_vqe(self): np.testing.assert_array_equal(ising_sol, [0, 1, 0, 1]) oracle = self._brute_force() self.assertEqual(graph_partition.objective_value(x, self.w), oracle) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index ac7f1281bd..e775f9527d 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -14,6 +14,7 @@ """Test Grover Minimum Finder.""" +import unittest from test.optimization import QiskitOptimizationTestCase import numpy as np from qiskit.aqua.algorithms import NumPyMinimumEigensolver @@ -91,3 +92,7 @@ def test_qubo_gas_int_paper_example(self): gmf = GroverMinimumFinder(num_iterations=n_iter) results = gmf.solve(op) self.validate_results(op, results, 10) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_helpers.py b/test/optimization/test_helpers.py index 2079674de0..aa3fc83a0f 100644 --- a/test/optimization/test_helpers.py +++ b/test/optimization/test_helpers.py @@ -14,9 +14,10 @@ """ Test helpers """ +import unittest from qiskit.optimization.utils.helpers import NameIndex, init_list_args from qiskit.optimization import QiskitOptimizationError -from test.optimization.common import QiskitOptimizationTestCase +from test.optimization.optimization_test_case import QiskitOptimizationTestCase class TestHelpers(QiskitOptimizationTestCase): @@ -26,10 +27,12 @@ def setUp(self): super().setUp() def test_init_list_args(self): + """ test init list args """ a = init_list_args(1, [2], None) self.assertTupleEqual(a, (1, [2], [])) def test_name_index1(self): + """ test name index 1 """ a = NameIndex() self.assertEqual(a.convert('1'), 0) self.assertListEqual(a.convert(['2', '3']), [1, 2]) @@ -39,6 +42,7 @@ def test_name_index1(self): self.assertListEqual(a.convert('1', '2'), [0, 1]) def test_name_index2(self): + """ test name index 2 """ a = NameIndex() a.build(['1', '2', '3']) self.assertEqual(a.convert('1'), 0) @@ -47,8 +51,13 @@ def test_name_index2(self): self.assertListEqual(a.convert('1', '2'), [0, 1]) def test_name_index3(self): + """ test name index 3 """ a = NameIndex() with self.assertRaises(QiskitOptimizationError): a.convert({}) with self.assertRaises(QiskitOptimizationError): a.convert(1, 2, 3) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_knapsack.py b/test/optimization/test_knapsack.py index 2fb4770808..036c668dcc 100644 --- a/test/optimization/test_knapsack.py +++ b/test/optimization/test_knapsack.py @@ -14,6 +14,7 @@ """ Test Knapsack Problem """ +import unittest from test.optimization import QiskitOptimizationTestCase import numpy as np @@ -73,3 +74,7 @@ def test_knapsack_large_max_weight(self): np.testing.assert_array_equal(solution, [1, 1, 1, 1]) np.testing.assert_equal(value, 175) np.testing.assert_equal(weight, 30) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_linear_constraints.py b/test/optimization/test_linear_constraints.py index 02fac9a575..1262633794 100644 --- a/test/optimization/test_linear_constraints.py +++ b/test/optimization/test_linear_constraints.py @@ -14,10 +14,11 @@ """ Test LinearConstraintInterface """ +import unittest from cplex import SparsePair from qiskit.optimization import OptimizationProblem -from test.optimization.common import QiskitOptimizationTestCase +from test.optimization.optimization_test_case import QiskitOptimizationTestCase class TestLinearConstraints(QiskitOptimizationTestCase): @@ -27,11 +28,13 @@ def setUp(self): super().setUp() def test_get_num(self): + """ test get num """ op = OptimizationProblem() op.linear_constraints.add(names=["c1", "c2", "c3"]) self.assertEqual(op.linear_constraints.get_num(), 3) def test_add(self): + """ test add """ op = OptimizationProblem() op.variables.add(names=["x1", "x2", "x3"]) op.linear_constraints.add( @@ -46,6 +49,7 @@ def test_add(self): self.assertListEqual(op.linear_constraints.get_rhs(), [0.0, 1.0, -1.0, 2.0]) def test_delete(self): + """ test delete """ op = OptimizationProblem() op.linear_constraints.add(names=[str(i) for i in range(10)]) self.assertEqual(op.linear_constraints.get_num(), 10) @@ -60,6 +64,7 @@ def test_delete(self): self.assertListEqual(op.linear_constraints.get_names(), []) def test_rhs(self): + """ test rhs """ op = OptimizationProblem() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) self.assertListEqual(op.linear_constraints.get_rhs(), [0.0, 0.0, 0.0, 0.0]) @@ -69,6 +74,7 @@ def test_rhs(self): self.assertListEqual(op.linear_constraints.get_rhs(), [0.0, 1.0, -1.0, 2.0]) def test_set_names(self): + """ test set names """ op = OptimizationProblem() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) op.linear_constraints.set_names("c1", "second") @@ -77,6 +83,7 @@ def test_set_names(self): self.assertListEqual(op.linear_constraints.get_names(), ['c0', 'second', 'middle', 'last']) def test_set_senses(self): + """ test set senses """ op = OptimizationProblem() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) self.assertListEqual(op.linear_constraints.get_senses(), ['E', 'E', 'E', 'E']) @@ -86,6 +93,7 @@ def test_set_senses(self): self.assertListEqual(op.linear_constraints.get_senses(), ['E', 'G', 'R', 'L']) def test_set_linear_components(self): + """ test set lieear components """ op = OptimizationProblem() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) op.variables.add(names=["x0", "x1"]) @@ -105,6 +113,7 @@ def test_set_linear_components(self): self.assertListEqual(sp.val, [-2.0, 3.0]) def test_set_range_values(self): + """ test set range values """ op = OptimizationProblem() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) op.linear_constraints.set_range_values("c1", 1.0) @@ -113,6 +122,7 @@ def test_set_range_values(self): self.assertListEqual(op.linear_constraints.get_range_values(), [0.0, 1.0, -1.0, 2.0]) def test_set_coeffients(self): + """ test set coefficients """ op = OptimizationProblem() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) op.variables.add(names=["x0", "x1"]) @@ -127,6 +137,7 @@ def test_set_coeffients(self): self.assertListEqual(sp.val, [2.0, -1.0]) def test_get_rhs(self): + """ test get rhs """ op = OptimizationProblem() op.linear_constraints.add(rhs=[1.5 * i for i in range(10)], names=[str(i) for i in range(10)]) @@ -137,6 +148,7 @@ def test_get_rhs(self): [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5]) def test_get_senses(self): + """ test get senses """ op = OptimizationProblem() op.linear_constraints.add( senses=["E", "G", "L", "R"], @@ -148,6 +160,7 @@ def test_get_senses(self): self.assertListEqual(op.linear_constraints.get_senses(), ['E', 'G', 'L', 'R']) def test_get_range_values(self): + """ test get range values """ op = OptimizationProblem() op.linear_constraints.add( range_values=[1.5 * i for i in range(10)], @@ -161,6 +174,7 @@ def test_get_range_values(self): [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5]) def test_get_coefficients(self): + """ test get coefficients """ op = OptimizationProblem() op.variables.add(names=["x0", "x1"]) op.linear_constraints.add( @@ -171,6 +185,7 @@ def test_get_coefficients(self): op.linear_constraints.get_coefficients([("c1", "x0"), ("c1", "x1")]), [2.0, -1.0]) def test_get_rows(self): + """ test get rows """ op = OptimizationProblem() op.variables.add(names=["x1", "x2", "x3"]) op.linear_constraints.add( @@ -208,6 +223,7 @@ def test_get_rows(self): self.assertListEqual(sp[3].val, [10.0, -2.0]) def test_get_num_nonzeros(self): + """ test get num non zeros """ op = OptimizationProblem() op.variables.add(names=["x1", "x2", "x3"]) op.linear_constraints.add( @@ -221,6 +237,7 @@ def test_get_num_nonzeros(self): self.assertEqual(op.linear_constraints.get_num_nonzeros(), 8) def test_get_names(self): + """ test get names """ op = OptimizationProblem() op.linear_constraints.add(names=["c" + str(i) for i in range(10)]) self.assertEqual(op.linear_constraints.get_num(), 10) @@ -230,5 +247,6 @@ def test_get_names(self): ['c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9']) def test_get_histogram(self): + """ test get histogram """ op = OptimizationProblem() self.assertRaises(NotImplementedError, lambda: op.linear_constraints.get_histogram()) diff --git a/test/optimization/test_min_eigen_optimizer.py b/test/optimization/test_min_eigen_optimizer.py index ef411df21a..d2d74ed31e 100644 --- a/test/optimization/test_min_eigen_optimizer.py +++ b/test/optimization/test_min_eigen_optimizer.py @@ -14,7 +14,8 @@ """ Test Min Eigen Optimizer """ -from test.optimization.common import QiskitOptimizationTestCase +import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase from ddt import ddt, data from qiskit import BasicAer @@ -78,3 +79,7 @@ def test_min_eigen_optimizer(self, config): # analyze results self.assertAlmostEqual(cplex_result.fval, result.fval) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_objective.py b/test/optimization/test_objective.py index 2a0f469ff9..815fc132c6 100644 --- a/test/optimization/test_objective.py +++ b/test/optimization/test_objective.py @@ -14,10 +14,11 @@ """ Test ObjectiveInterface """ +import unittest from cplex import SparsePair from qiskit.optimization import OptimizationProblem -from test.optimization.common import QiskitOptimizationTestCase +from test.optimization.optimization_test_case import QiskitOptimizationTestCase class TestObjective(QiskitOptimizationTestCase): @@ -27,6 +28,7 @@ def setUp(self): super().setUp() def test_obj_sense(self): + """ test obj sense """ op = OptimizationProblem() self.assertEqual(op.objective.sense.minimize, 1) self.assertEqual(op.objective.sense.maximize, -1) @@ -48,6 +50,7 @@ def test_set_linear0(self): pass def test_set_linear(self): + """ test set linear """ op = OptimizationProblem() n = 4 op.variables.add(names=[str(i) for i in range(n)]) @@ -62,11 +65,13 @@ def test_set_linear(self): self.assertListEqual(op.objective.get_linear(range(n)), [1.0, 0.5, 2.0, -1.0]) def test_set_empty_quadratic(self): + """ test set empty quadratic """ op = OptimizationProblem() op.objective.set_quadratic([]) self.assertRaises(TypeError, lambda: op.objective.set_quadratic()) def test_set_quadratic(self): + """ test set quadratic """ op = OptimizationProblem() n = 3 op.variables.add(names=[str(i) for i in range(n)]) @@ -92,6 +97,7 @@ def test_set_quadratic(self): self.assertListEqual(lst[2].val, [3.0]) def test_set_quadratic_coefficients(self): + """ test set quadratic coefficients """ op = OptimizationProblem() n = 3 op.variables.add(names=[str(i) for i in range(n)]) @@ -124,6 +130,7 @@ def test_set_quadratic_coefficients(self): self.assertListEqual(lst[2].val, [3.0]) def test_set_senses(self): + """ test set senses """ op = OptimizationProblem() self.assertEqual(op.objective.sense[op.objective.get_sense()], 'minimize') op.objective.set_sense(op.objective.sense.maximize) @@ -132,11 +139,13 @@ def test_set_senses(self): self.assertEqual(op.objective.sense[op.objective.get_sense()], 'minimize') def test_set_name(self): + """ test set name """ op = OptimizationProblem() op.objective.set_name('cost') self.assertEqual(op.objective.get_name(), 'cost') def test_get_linear(self): + """ test get linear """ op = OptimizationProblem() n = 10 op.variables.add(names=[str(i) for i in range(n)]) @@ -150,6 +159,7 @@ def test_get_linear(self): [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5]) def test_get_quadratic(self): + """ test get quadratic """ op = OptimizationProblem() n = 10 op.variables.add(names=[str(i) for i in range(n)]) @@ -181,6 +191,7 @@ def test_get_quadratic(self): self.assertListEqual(sp[i].val, [1.5 * i]) def test_get_quadratic(self): + """ test get quadratic """ op = OptimizationProblem() n = 3 op.variables.add(names=[str(i) for i in range(n)]) @@ -192,6 +203,7 @@ def test_get_quadratic(self): [5.0, 2.0, 3.0]) def test_get_sense(self): + """ test get sense """ op = OptimizationProblem() self.assertEqual(op.objective.sense[op.objective.get_sense()], 'minimize') op.objective.set_sense(op.objective.sense.maximize) @@ -200,11 +212,13 @@ def test_get_sense(self): self.assertEqual(op.objective.sense[op.objective.get_sense()], 'minimize') def test_get_name(self): + """ test get name """ op = OptimizationProblem() op.objective.set_name('cost') self.assertEqual(op.objective.get_name(), 'cost') def test_get_num_quadratic_variables(self): + """ test get num quadratic vraiables """ op = OptimizationProblem() n = 3 op.variables.add(names=[str(i) for i in range(n)]) @@ -217,6 +231,7 @@ def test_get_num_quadratic_variables(self): self.assertEqual(obj.get_num_quadratic_variables(), 3) def test_get_num_quadratic_nonzeros(self): + """ test get num quadratic non zeros """ op = OptimizationProblem() n = 3 op.variables.add(names=[str(i) for i in range(n)]) @@ -229,7 +244,12 @@ def test_get_num_quadratic_nonzeros(self): self.assertEqual(obj.get_num_quadratic_nonzeros(), 3) def test_offset(self): + """ test offset """ op = OptimizationProblem() self.assertEqual(op.objective.get_offset(), 0.0) op.objective.set_offset(3.14) self.assertEqual(op.objective.get_offset(), 3.14) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_optimization_problem.py b/test/optimization/test_optimization_problem.py index 2b87165986..66a8cb4658 100644 --- a/test/optimization/test_optimization_problem.py +++ b/test/optimization/test_optimization_problem.py @@ -14,9 +14,10 @@ """ Test OptimizationProblem """ +import unittest import os.path import tempfile -from test.optimization.common import QiskitOptimizationTestCase +from test.optimization.optimization_test_case import QiskitOptimizationTestCase import qiskit.optimization.problems.optimization_problem from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError @@ -30,21 +31,25 @@ def setUp(self): self.resource_file = './test/optimization/resources/op_ip2.lp' def test_constructor1(self): + """ test constructor """ op = qiskit.optimization.OptimizationProblem() op.variables.add(names=['x1', 'x2', 'x3']) self.assertEqual(op.variables.get_num(), 3) def test_constructor2(self): + """ test constructor 2 """ with self.assertRaises(QiskitOptimizationError): op = qiskit.optimization.OptimizationProblem("unknown") # If filename does not exist, an exception is raised. def test_constructor3(self): + """ test constructor 3 """ # we can pass at most one argument with self.assertRaises(QiskitOptimizationError): op = qiskit.optimization.OptimizationProblem("test", "west") def test_constructor_context(self): + """ test constructor context """ with qiskit.optimization.OptimizationProblem() as op: op.variables.add(names=['x1', 'x2', 'x3']) self.assertEqual(op.variables.get_num(), 3) @@ -57,11 +62,13 @@ def test_constructor_context(self): # op.variables.add(names=['x1', 'x2', 'x3']) def test_read1(self): + """ test read 2""" op = qiskit.optimization.OptimizationProblem() op.read(self.resource_file) self.assertEqual(op.variables.get_num(), 3) def test_write1(self): + """ test write 1 """ op = qiskit.optimization.OptimizationProblem() op.variables.add(names=['x1', 'x2', 'x3']) f, fn = tempfile.mkstemp(suffix='.lp') @@ -70,6 +77,7 @@ def test_write1(self): assert os.path.exists(fn) == 1 def test_write2(self): + """ test write 2 """ op1 = qiskit.optimization.OptimizationProblem() op1.variables.add(names=['x1', 'x2', 'x3']) f, fn = tempfile.mkstemp(suffix='.lp') @@ -80,6 +88,7 @@ def test_write2(self): self.assertEqual(op2.variables.get_num(), 3) def test_write3(self): + """ test write 3 """ op = qiskit.optimization.OptimizationProblem() op.variables.add(names=['x1', 'x2', 'x3']) @@ -88,10 +97,12 @@ def __init__(self): self.was_called = False def write(self, bytes): + """ write """ self.was_called = True pass def flush(self): + """ flush """ pass stream = NoOpStream() op.write_to_stream(stream) @@ -100,6 +111,7 @@ def flush(self): op.write_to_stream("this-is-no-stream") def test_write4(self): + """ test write 4 """ # Writes a problem as a string in the given file format. op = qiskit.optimization.OptimizationProblem() op.variables.add(names=['x1', 'x2', 'x3']) @@ -107,6 +119,7 @@ def test_write4(self): self.assertGreater(len(lp_str), 0) def test_problem_type1(self): + """ test problem type 1 """ op = qiskit.optimization.OptimizationProblem() op.read(self.resource_file) self.assertEqual(op.get_problem_type(), @@ -114,6 +127,7 @@ def test_problem_type1(self): self.assertEqual(op.problem_type[op.get_problem_type()], 'QP') def test_problem_type2(self): + """ test problemm type 2""" op = qiskit.optimization.OptimizationProblem() op.set_problem_type(op.problem_type.LP) self.assertEqual(op.get_problem_type(), @@ -121,7 +135,12 @@ def test_problem_type2(self): self.assertEqual(op.problem_type[op.get_problem_type()], 'LP') def test_problem_name(self): + """ test problem name """ op = qiskit.optimization.OptimizationProblem() op.set_problem_name("test") # test self.assertEqual(op.get_problem_name(), "test") + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_optimization_problem_to_negative_value_oracle.py b/test/optimization/test_optimization_problem_to_negative_value_oracle.py index 4440c4d666..1a0a09b7df 100644 --- a/test/optimization/test_optimization_problem_to_negative_value_oracle.py +++ b/test/optimization/test_optimization_problem_to_negative_value_oracle.py @@ -14,6 +14,7 @@ """Test Optimization Problem to Negative Value Oracle.""" +import unittest from test.optimization import QiskitOptimizationTestCase import numpy as np from qiskit.optimization.converters import OptimizationProblemToNegativeValueOracle @@ -138,3 +139,7 @@ def test_optnvo_6_key(self): self._validate_function(func_dict, problem) self._validate_operator(func_dict, len(linear), num_value, a_operator) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_partition.py b/test/optimization/test_partition.py index 4e5fec9669..abc24262d8 100755 --- a/test/optimization/test_partition.py +++ b/test/optimization/test_partition.py @@ -14,6 +14,7 @@ """ Test Partition """ +import unittest from test.optimization import QiskitOptimizationTestCase import numpy as np from qiskit import BasicAer @@ -54,3 +55,7 @@ def test_partition_vqe(self): x = sample_most_likely(result['eigvecs'][0]) self.assertNotEqual(x[0], x[1]) self.assertNotEqual(x[2], x[1]) # hardcoded oracle + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_qaoa.py b/test/optimization/test_qaoa.py index e9502af3b0..1cdf36fc1c 100755 --- a/test/optimization/test_qaoa.py +++ b/test/optimization/test_qaoa.py @@ -14,6 +14,7 @@ """ Test QAOA """ +import unittest from test.optimization import QiskitOptimizationTestCase import numpy as np @@ -83,3 +84,7 @@ def test_qaoa(self, w, prob, m, solutions): self.log.debug('solution: %s', graph_solution) self.log.debug('solution objective: %s', max_cut.max_cut_value(x, w)) self.assertIn(''.join([str(int(i)) for i in graph_solution]), solutions) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_quadratic_constraints.py b/test/optimization/test_quadratic_constraints.py index d1754b6a7d..77d65b4a88 100644 --- a/test/optimization/test_quadratic_constraints.py +++ b/test/optimization/test_quadratic_constraints.py @@ -14,7 +14,8 @@ """ Test QuadraticConstraintInterface """ -from test.optimization.common import QiskitOptimizationTestCase +import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase from cplex import SparsePair, SparseTriple from qiskit.optimization import QiskitOptimizationError @@ -28,6 +29,7 @@ def setUp(self): super().setUp() def test_initial1(self): + """ test initial 1""" op = OptimizationProblem() c1 = op.quadratic_constraints.add(name='c1') c2 = op.quadratic_constraints.add(name='c2') @@ -38,6 +40,7 @@ def test_initial1(self): self.assertRaises(QiskitOptimizationError, lambda: op.quadratic_constraints.add(name='c1')) def test_initial2(self): + """ test initial 2""" op = OptimizationProblem() op.variables.add(names=['x1', 'x2', 'x3'], types='B' * 3) c = op.quadratic_constraints.add( @@ -64,6 +67,7 @@ def test_initial2(self): self.assertListEqual(q[0].val, [1.0, -1.0]) def test_get_num(self): + """ test get num """ op = OptimizationProblem() op.variables.add(names=['x', 'y']) la = SparsePair(ind=['x'], val=[1.0]) @@ -74,6 +78,7 @@ def test_get_num(self): self.assertEqual(op.quadratic_constraints.get_num(), n) def test_add(self): + """ test add """ op = OptimizationProblem() op.variables.add(names=['x', 'y']) la = SparsePair(ind=['x'], val=[1.0]) @@ -82,6 +87,7 @@ def test_add(self): name='my quad', lin_expr=la, quad_expr=q, rhs=1.0, sense='G'), 0) def test_delete(self): + """ test delete """ op = OptimizationProblem() q0 = [op.quadratic_constraints.add(name=str(i)) for i in range(10)] self.assertListEqual(q0, list(range(10))) @@ -97,6 +103,7 @@ def test_delete(self): self.assertListEqual(q.get_names(), []) def test_get_rhs(self): + """ test get rhs """ op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(10)]) q0 = [op.quadratic_constraints.add(rhs=1.5 * i, name=str(i)) for i in range(10)] @@ -109,6 +116,7 @@ def test_get_rhs(self): self.assertListEqual(q.get_rhs(), [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5]) def test_get_senses(self): + """ test get senses """ op = OptimizationProblem() op.variables.add(names=["x0"]) q = op.quadratic_constraints @@ -120,6 +128,7 @@ def test_get_senses(self): self.assertListEqual(q.get_senses(), ['G', 'G', 'L', 'L']) def test_get_linear_num_nonzeros(self): + """ test get linear num non zeros """ op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(11)], types="B" * 11) q = op.quadratic_constraints @@ -134,6 +143,7 @@ def test_get_linear_num_nonzeros(self): self.assertListEqual(q.get_linear_num_nonzeros(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) def test_get_linear_components(self): + """ test get linear components """ op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(4)], types="B" * 4) q = op.quadratic_constraints @@ -167,6 +177,7 @@ def test_get_linear_components(self): self.assertListEqual(sp[2].val, [1.0, 2.0]) def test_get_linear_components2(self): + """ test get linear components 2 """ op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(11)], types="B" * 11) q = op.quadratic_constraints @@ -208,6 +219,7 @@ def test_get_linear_components2(self): self.assertListEqual(s[3].val, [1.0, 2.0, 3.0]) def test_quad_num_nonzeros(self): + """ test quad num non zeros """ op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(11)]) q = op.quadratic_constraints @@ -221,6 +233,7 @@ def test_quad_num_nonzeros(self): self.assertListEqual(q.get_quad_num_nonzeros(), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) def test_get_quadratic_components(self): + """ test get quadratic components """ op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(4)]) q = op.quadratic_constraints @@ -260,6 +273,7 @@ def test_get_quadratic_components(self): self.assertListEqual(st[1].val, [1.0, 2.0]) def test_get_quadratic_components2(self): + """ test get quadratic components 2 """ op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(11)]) q = op.quadratic_constraints @@ -315,6 +329,7 @@ def test_get_quadratic_components2(self): self.assertListEqual(s[3].val, [1.0, 2.0, 3.0, 4.0]) def test_get_names(self): + """ tet get names """ op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(11)]) q = op.quadratic_constraints @@ -327,3 +342,7 @@ def test_get_names(self): self.assertListEqual(q.get_names([2, 0, 5]), ['q3', 'q1', 'q6']) self.assertListEqual(q.get_names(), ['q1', 'q2', 'q3', 'q4', 'q5', 'q6', 'q7', 'q8', 'q9', 'q10']) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_recursive_optimization.py b/test/optimization/test_recursive_optimization.py index 70472190e3..87a931e880 100755 --- a/test/optimization/test_recursive_optimization.py +++ b/test/optimization/test_recursive_optimization.py @@ -14,7 +14,8 @@ """ Test Recursive Min Eigen Optimizer """ -from test.optimization.common import QiskitOptimizationTestCase +import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase from ddt import ddt, data from qiskit import BasicAer @@ -82,3 +83,7 @@ def test_recursive_min_eigen_optimizer(self, config): # analyze results self.assertAlmostEqual(cplex_result.fval, result.fval) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_set_packing.py b/test/optimization/test_set_packing.py index 54e2632dd9..0ec5dbbee7 100755 --- a/test/optimization/test_set_packing.py +++ b/test/optimization/test_set_packing.py @@ -14,6 +14,7 @@ """ Test Set Packing """ +import unittest import json from test.optimization import QiskitOptimizationTestCase import numpy as np @@ -84,3 +85,7 @@ def test_set_packing_vqe(self): ising_sol = set_packing.get_solution(x) oracle = self._brute_force() self.assertEqual(np.count_nonzero(ising_sol), oracle) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_stable_set.py b/test/optimization/test_stable_set.py index 65dc0d7752..6379cd1e4f 100644 --- a/test/optimization/test_stable_set.py +++ b/test/optimization/test_stable_set.py @@ -14,6 +14,7 @@ """ Test Stable Set """ +import unittest from test.optimization import QiskitOptimizationTestCase import numpy as np from qiskit import BasicAer @@ -62,3 +63,7 @@ def test_stable_set_vqe(self): ising_sol = stable_set.get_graph_solution(x) np.testing.assert_array_equal(ising_sol, [0, 0, 1, 1, 1]) self.assertEqual(stable_set.stable_set_value(x, self.w), (3.0, False)) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_tsp.py b/test/optimization/test_tsp.py index 58c43ab147..7d47084379 100644 --- a/test/optimization/test_tsp.py +++ b/test/optimization/test_tsp.py @@ -14,6 +14,7 @@ """ Test TSP (Traveling Salesman Problem) """ +import unittest from test.optimization import QiskitOptimizationTestCase import numpy as np @@ -41,3 +42,7 @@ def test_tsp(self): x = sample_most_likely(result.eigenstate) order = tsp.get_tsp_solution(x) np.testing.assert_array_equal(order, [1, 2, 0]) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_variables.py b/test/optimization/test_variables.py index 18f1cbd93b..2046425a1c 100644 --- a/test/optimization/test_variables.py +++ b/test/optimization/test_variables.py @@ -14,10 +14,9 @@ """ Test VariablesInterface """ -from cplex import infinity - +import unittest from qiskit.optimization.problems import OptimizationProblem -from test.optimization.common import QiskitOptimizationTestCase +from test.optimization.optimization_test_case import QiskitOptimizationTestCase class TestVariables(QiskitOptimizationTestCase): @@ -27,11 +26,13 @@ def setUp(self): super().setUp() def test_type(self): + """ test type """ op = OptimizationProblem() self.assertEqual(op.variables.type.binary, 'B') self.assertEqual(op.variables.type['B'], 'binary') def test_initial(self): + """ test initial """ op = OptimizationProblem() op.variables.add(names=["x0", "x1", "x2"]) self.assertListEqual(op.variables.get_lower_bounds(), [0.0, 0.0, 0.0]) @@ -44,42 +45,50 @@ def test_initial(self): self.assertEqual(op.variables.get_num_binary(), 1) def test_get_num(self): + """ test get num """ op = OptimizationProblem() t = op.variables.type op.variables.add(types=[t.continuous, t.binary, t.integer]) self.assertEqual(op.variables.get_num(), 3) def test_get_num_continuous(self): + """ test get num continuous """ op = OptimizationProblem() t = op.variables.type op.variables.add(types=[t.continuous, t.binary, t.integer]) self.assertEqual(op.variables.get_num_continuous(), 1) def test_get_num_integer(self): + """ test get num integer """ op = OptimizationProblem() t = op.variables.type op.variables.add(types=[t.continuous, t.binary, t.integer]) self.assertEqual(op.variables.get_num_integer(), 1) def test_get_num_binary(self): + """ test get num binary """ op = OptimizationProblem() t = op.variables.type op.variables.add(types=[t.semi_continuous, t.binary, t.integer]) self.assertEqual(op.variables.get_num_binary(), 1) def test_get_num_semicontinuous(self): + """ test get num semi continuous """ op = OptimizationProblem() t = op.variables.type op.variables.add(types=[t.semi_continuous, t.semi_integer, t.semi_integer]) self.assertEqual(op.variables.get_num_semicontinuous(), 1) def test_get_num_semiinteger(self): + """ test get num semi integer """ op = OptimizationProblem() t = op.variables.type op.variables.add(types=[t.semi_continuous, t.semi_integer, t.semi_integer]) self.assertEqual(op.variables.get_num_semiinteger(), 2) def test_add(self): + """ add test """ + from cplex import infinity op = OptimizationProblem() op.linear_constraints.add(names=["c0", "c1", "c2"]) op.variables.add(types=[op.variables.type.integer] * 3) @@ -97,6 +106,7 @@ def test_add(self): [infinity, infinity, infinity, 100.0, infinity, infinity]) def test_delete(self): + """ test delete """ op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(10)]) self.assertEqual(op.variables.get_num(), 10) @@ -113,6 +123,7 @@ def test_delete(self): self.assertListEqual(op.variables.get_names(), []) def test_set_lower_bounds(self): + """ test set lower bounds """ op = OptimizationProblem() op.variables.add(names=["x0", "x1", "x2"]) op.variables.set_lower_bounds(0, 1.0) @@ -121,6 +132,7 @@ def test_set_lower_bounds(self): self.assertListEqual(op.variables.get_lower_bounds(), [1.0, -1.0, 3.0]) def test_set_upper_bounds(self): + """ test set upper bounds """ op = OptimizationProblem() op.variables.add(names=["x0", "x1", "x2"]) op.variables.set_upper_bounds(0, 1.0) @@ -128,6 +140,7 @@ def test_set_upper_bounds(self): self.assertListEqual(op.variables.get_upper_bounds(), [1.0, 10.0, 3.0]) def test_set_names(self): + """ test set names """ op = OptimizationProblem() t = op.variables.type op.variables.add(types=[t.continuous, t.binary, t.integer]) @@ -136,6 +149,7 @@ def test_set_names(self): self.assertListEqual(op.variables.get_names(), ['first', 'second', 'third']) def test_set_types(self): + """ test set types """ op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(5)]) op.variables.set_types(0, op.variables.type.continuous) @@ -147,6 +161,7 @@ def test_set_types(self): self.assertEqual(op.variables.type[op.variables.get_types(0)], 'continuous') def test_get_lower_bounds(self): + """ test get lower bounds """ op = OptimizationProblem() op.variables.add(lb=[1.5 * i for i in range(10)], names=[str(i) for i in range(10)]) @@ -158,6 +173,7 @@ def test_get_lower_bounds(self): [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5]) def test_get_upper_bounds(self): + """ test get upper bounds """ op = OptimizationProblem() op.variables.add(ub=[(1.5 * i) + 1.0 for i in range(10)], names=[str(i) for i in range(10)]) @@ -169,6 +185,7 @@ def test_get_upper_bounds(self): [1.0, 2.5, 4.0, 5.5, 7.0, 8.5, 10.0, 11.5, 13.0, 14.5]) def test_get_names(self): + """ test get names """ op = OptimizationProblem() op.variables.add(names=['x' + str(i) for i in range(10)]) self.assertEqual(op.variables.get_num(), 10) @@ -179,6 +196,7 @@ def test_get_names(self): ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9']) def test_set_types(self): + """ test set types """ op = OptimizationProblem() t = op.variables.type op.variables.add(names=[str(i) for i in range(5)], @@ -197,22 +215,26 @@ def test_set_types(self): self.assertEqual(types, ['C', 'I', 'B', 'S', 'N']) def test_get_cols(self): + """ test get cols """ op = OptimizationProblem() with self.assertRaises(NotImplementedError): op.variables.get_cols() def test_get_obj(self): + """ test get obj """ op = OptimizationProblem() with self.assertRaises(NotImplementedError): op.variables.get_obj() def test_get_indices(self): + """ test get indices """ op = OptimizationProblem() op.variables.add(names=['a', 'b']) self.assertEqual(op.variables.get_indices('a'), 0) self.assertListEqual(op.variables.get_indices(['a', 'b']), [0, 1]) def test_add2(self): + """ test add2 """ op = OptimizationProblem() op.variables.add(names=['x']) self.assertEqual(op.variables.get_indices('x'), 0) @@ -223,9 +245,15 @@ def test_add2(self): self.assertListEqual(op.variables.get_indices(), [0, 1]) def test_default_bounds(self): + """ test default bounds """ + from cplex import infinity op = OptimizationProblem() types = ['B', 'I', 'C', 'S', 'N'] op.variables.add(names=types, types=types) self.assertListEqual(op.variables.get_lower_bounds(), [0.0] * 5) # the upper bound of binary variable is 1. self.assertListEqual(op.variables.get_upper_bounds(), [1.0] + [infinity] * 4) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_vehicle_routing.py b/test/optimization/test_vehicle_routing.py index 15e41d276a..3486e37b78 100755 --- a/test/optimization/test_vehicle_routing.py +++ b/test/optimization/test_vehicle_routing.py @@ -14,6 +14,7 @@ """ Test Vehicle Routing """ +import unittest from test.optimization import QiskitOptimizationTestCase import numpy as np @@ -64,3 +65,7 @@ def test_simple2(self): result = NumPyMinimumEigensolver(self.qubit_op).run() arr = np.array([0., 0., 0., 1.]) np.testing.assert_array_almost_equal(arr, result.eigenstate, 4) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_vertex_cover.py b/test/optimization/test_vertex_cover.py index a3e93ca3d1..0cd9f391c4 100755 --- a/test/optimization/test_vertex_cover.py +++ b/test/optimization/test_vertex_cover.py @@ -14,6 +14,7 @@ """ Test Vertex Cover """ +import unittest from test.optimization import QiskitOptimizationTestCase import numpy as np from qiskit import BasicAer @@ -83,3 +84,7 @@ def test_vertex_cover_vqe(self): sol = vertex_cover.get_graph_solution(x) oracle = self._brute_force() self.assertEqual(np.count_nonzero(sol), oracle) + + +if __name__ == '__main__': + unittest.main() From 59be3624f2e90c2e4e865ebf13d0e1c359452df1 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Wed, 1 Apr 2020 15:59:25 -0400 Subject: [PATCH 138/323] fix spelling --- .pylintdict | 86 +++++++++++++++---- .../algorithms/cobyla_optimizer.py | 2 +- .../algorithms/cplex_optimizer.py | 2 +- .../algorithms/grover_minimum_finder.py | 2 +- .../algorithms/minimum_eigen_optimizer.py | 8 +- .../algorithms/optimization_algorithm.py | 2 +- .../recursive_minimum_eigen_optimizer.py | 4 +- .../inequality_to_equality_converter.py | 2 +- .../converters/integer_to_binary_converter.py | 2 +- ...zation_problem_to_negative_value_oracle.py | 2 +- .../penalize_linear_equality_constraints.py | 4 +- .../problems/optimization_problem.py | 2 +- qiskit/optimization/utils/base.py | 2 +- .../utils/eigenvector_to_solutions.py | 2 +- test/optimization/test_linear_constraints.py | 2 +- test/optimization/test_objective.py | 2 +- .../optimization/test_optimization_problem.py | 2 +- .../test_quadratic_constraints.py | 2 +- 18 files changed, 90 insertions(+), 40 deletions(-) diff --git a/.pylintdict b/.pylintdict index 06fbf455c3..6b80c5a63d 100644 --- a/.pylintdict +++ b/.pylintdict @@ -1,3 +1,33 @@ + +AOs +Aer's +Armijo +Chu +CircuitCache +Eckstein +Eq +FermionicOperator +GGLL +LinearConstraintInterface +MIP +Nakanishi +ObjectiveInterface +OptimizationProblem +Parikh +Peleato +ProblemType +QCP +QFactory +QiskitOptimizationError +QuadraticConstraintInterface +RunConfig +SolutionInterface +Todo +UCCS +VariablesInterface +ZZ +Zj +abstractmethod adag adam ae @@ -16,18 +46,17 @@ ansatz ansatzes anticommute ao -AOs ap api appfactory arcsin args -Armijo asmatrix +assertRaises ast atol -autosummary autorecovery +autosummary babbush backend backends @@ -53,6 +82,7 @@ brassard bravyi broyden cargs +catol ccphase cct ccx @@ -61,7 +91,6 @@ cdot ceil chernoff circ -CircuitCache clas clbits clifford @@ -78,11 +107,14 @@ coeffs commutativities comparator comparators +comptype conda conf config +conv coord coords +correlators cov coveney cplex @@ -91,6 +123,7 @@ crs crx csr ctls +cts currentmodule cvs cx @@ -115,6 +148,7 @@ devsglobals diagonalization diagonalize diagonalizing +dic dict dicts diederik @@ -134,6 +168,7 @@ dj dnf docplex dp +dtype durr ecc ee @@ -166,10 +201,10 @@ factr fcidump fcompiler fermionic -FermionicOperator fermions fileio filepath +filetype fm fock formatter @@ -180,6 +215,7 @@ ftol fujii fullname func +fval gambetta gauopen gaussian @@ -189,18 +225,21 @@ gcd gde geq getattr +getfunc getter +gfortran github +globals gogolin goldfarb -gfortran -globals graycode gridpoints grover gset +gsls gto gtol +gz hadamard halfangle hamiltonian @@ -249,6 +288,7 @@ iso isub isym iteratively +ith izaac jac jacobian @@ -282,6 +322,8 @@ loglikelihood logn lognormal lor +lp +lpex lr lst majorana @@ -322,13 +364,13 @@ momentums monomial monomials mprev +mps msb msg msq multiclass multinomial multiprocess -Nakanishi namelist nan narray @@ -337,6 +379,7 @@ ncx nd ndarray ndarray's +ndarrays negoro nelder nelec @@ -347,11 +390,12 @@ nfev nft nk nlopt -nmo nlopts +nmo nn noancilla noint +nonzeros noqa norb norbs @@ -411,13 +455,13 @@ pyscfd pytorch qae qancilla +qaoa qargs qasm qbit qc qcmatrixio qeom -QFactory qft qfts qgan @@ -437,6 +481,7 @@ quantumcircuit quantumregister qubit qubits +qubo quine qutip randgiven @@ -450,38 +495,44 @@ refactor renormalize renyi reparameterizing +repl reqd rescaling retval +rew rhf rhobeg +rhoend rhs rightarrow righthand +rlp rohf rosen rsgtu rtype runarsson -RunConfig ry rz sanjiv sashank satyen +sav savefile sca scf -scikit schemas schuld +scikit scipy sd sdg sdk seealso seeley +setfunc setia +setsenses sgn shanno shor @@ -490,6 +541,7 @@ sigmoid sj sklearn slsqp +solutionstatus spsa sqrt srange @@ -531,7 +583,6 @@ timelimit timestamp tnc toctree -Todo toffoli tol tomo @@ -544,6 +595,8 @@ transpiler tranter trunc ub +ucc +uccd uccsd uhf ulimit @@ -570,6 +623,7 @@ variational vazirani vdag vertices +vir visualisation vqc vqe @@ -581,6 +635,7 @@ wigner wih wikipedia workq +wrt xatol xc xixj @@ -595,11 +650,6 @@ yy zi zmatrix zv -ZZ zzz äguivalenzverbot über -ucc -uccd -UCCS -vir diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index 542ba4bb8c..52e5eb5f59 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -75,7 +75,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: continuous variables, and otherwise, returns a message explaining the incompatibility. Args: - problem: The optization problem to check compatibility. + problem: The optimization problem to check compatibility. Returns: Returns ``None`` if the problem is compatible and else a string with the error message. diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index 752b12519c..e84e59c4aa 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -77,7 +77,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: to be non-convex. This case could be addressed by setting CPLEX parameters accordingly. Args: - problem: The optization problem to check compatibility. + problem: The optimization problem to check compatibility. Returns: Returns ``None`` if the problem is compatible and else a string with the error message. diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index def33849a5..bb36f43682 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -59,7 +59,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: to a QUBO, and otherwise, returns a message explaining the incompatibility. Args: - problem: The optization problem to check compatibility. + problem: The optimization problem to check compatibility. Returns: Returns ``None`` if the problem is compatible and else a string with the error message. diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 80b8b9913d..344777ac70 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -84,7 +84,7 @@ class MinimumEigenOptimizer(OptimizationAlgorithm): the linear equality constraints as weighted penalty terms to the objective function. The resulting QUBO is then translated into an Ising Hamiltonian whose minimal eigen vector and corresponding eigenstate correspond to the optimal solution of the original optimization - problem. The provided minimum eigen solver is then used to approximate the groundstate of the + problem. The provided minimum eigen solver is then used to approximate the ground state of the Hamiltonian to find a good solution for the optimization problem. """ @@ -92,13 +92,13 @@ def __init__(self, min_eigen_solver: MinimumEigensolver, penalty: Optional[float ) -> None: """Initializes the minimum eigen optimizer. - This initializer takes the minimum eigen solver to be used to approximate the groundstate + This initializer takes the minimum eigen solver to be used to approximate the ground state of the resulting Hamiltonian as well as a optional penalty factor to scale penalty terms representing linear equality constraints. If no penalty factor is provided, a default is computed during the algorithm (TODO). Args: - min_eigen_solver: The eigen solver to find the groundstate of the Hamiltonian. + min_eigen_solver: The eigen solver to find the ground state of the Hamiltonian. penalty: The penalty factor to be used, or ``None`` for applying a default logic. """ self._min_eigen_solver = min_eigen_solver @@ -111,7 +111,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: to a QUBO, and otherwise, returns a message explaining the incompatibility. Args: - problem: The optization problem to check compatibility. + problem: The optimization problem to check compatibility. Returns: Returns ``None`` if the problem is compatible and else a string with the error message. diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index 4814e5852a..de43f92a7c 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -31,7 +31,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: """Checks whether a given problem can be solved with the optimizer implementing this method. Args: - problem: The optization problem to check compatibility. + problem: The optimization problem to check compatibility. Returns: Returns ``None`` if the problem is compatible and else a string with the error message. diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 0d98cf1c92..4ce878f861 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -46,7 +46,7 @@ class RecursiveMinimumEigenOptimizer(OptimizationAlgorithm): def __init__(self, min_eigen_optimizer: MinimumEigenOptimizer, min_num_vars: int = 1, min_num_vars_optimizer: Optional[OptimizationAlgorithm] = None, penalty: Optional[float] = None) -> None: - """ Initializes the recusrive miniimum eigen optimizer. + """ Initializes the recursive minimum eigen optimizer. This initializer takes a ``MinimumEigenOptimizer``, the parameters to specify until when to to apply the iterative scheme, and the optimizer to be applied once the threshold number of @@ -88,7 +88,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: to a QUBO, and otherwise, returns a message explaining the incompatibility. Args: - problem: The optization problem to check compatibility. + problem: The optimization problem to check compatibility. Returns: Returns ``None`` if the problem is compatible and else a string with the error message. diff --git a/qiskit/optimization/converters/inequality_to_equality_converter.py b/qiskit/optimization/converters/inequality_to_equality_converter.py index 8870e49f96..12a06ca018 100644 --- a/qiskit/optimization/converters/inequality_to_equality_converter.py +++ b/qiskit/optimization/converters/inequality_to_equality_converter.py @@ -38,7 +38,7 @@ class InequalityToEqualityConverter: _delimiter = '@' # users are supposed not to use this character in variable names def __init__(self) -> None: - """Initialize the interal variables.""" + """Initialize the integral variables.""" self._src = None self._dst = None diff --git a/qiskit/optimization/converters/integer_to_binary_converter.py b/qiskit/optimization/converters/integer_to_binary_converter.py index ab51bdf794..65c969ccd2 100644 --- a/qiskit/optimization/converters/integer_to_binary_converter.py +++ b/qiskit/optimization/converters/integer_to_binary_converter.py @@ -86,7 +86,7 @@ def encode(self, op: OptimizationProblem, name: Optional[str] = None) -> Optimiz # replace integer variables with binary variables in the constrains # self.linear_constraints.subs(self._conv) # self.quadratic_constraints.subs(self._conv) - # note: `subs` substibutes variables with sets of auxiliary variables + # note: `subs` substitutes variables with sets of auxiliary variables self._substitute_int_var() diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index e9c5d92b5c..72fc91880a 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -31,7 +31,7 @@ class OptimizationProblemToNegativeValueOracle: """Converts an optimization problem (QUBO) to a negative value oracle. In addition, a state preparation operator is generated from the coefficients and constant of a - QUBO, which can be used to encode the function into a quantum state. In conjuction, this oracle + QUBO, which can be used to encode the function into a quantum state. In conjunction, this oracle and operator can be used to flag the negative values of a QUBO encoded in a quantum state. """ diff --git a/qiskit/optimization/converters/penalize_linear_equality_constraints.py b/qiskit/optimization/converters/penalize_linear_equality_constraints.py index f5b43428f7..0224d5b357 100644 --- a/qiskit/optimization/converters/penalize_linear_equality_constraints.py +++ b/qiskit/optimization/converters/penalize_linear_equality_constraints.py @@ -106,14 +106,14 @@ def encode(self, op: OptimizationProblem, penalty_factor: float = 1e5, # linear parts of penalty*(Constant-func)**2: penalty*(-2*Constant*func) for var_ind, coef in zip(row.ind, row.val): - # if var_ind already exisits in the linear terms dic, add a penalty term + # if var_ind already exists in the linear terms dic, add a penalty term # into existing value else create new key and value in the linear_term dict linear_terms[var_ind] += penalty_factor * -2 * coef * constant # quadratic parts of penalty*(Constant-func)**2: penalty*(func**2) for var_ind_1, coef_1 in zip(row.ind, row.val): for var_ind_2, coef_2 in zip(row.ind, row.val): - # if var_ind_1 and var_ind_2 already exisit in the quadratic terms dic, + # if var_ind_1 and var_ind_2 already exist in the quadratic terms dic, # add a penalty term into existing value # else create new key and value in the quadratic term dict diff --git a/qiskit/optimization/problems/optimization_problem.py b/qiskit/optimization/problems/optimization_problem.py index d3f27ccfb1..c8fbed084b 100644 --- a/qiskit/optimization/problems/optimization_problem.py +++ b/qiskit/optimization/problems/optimization_problem.py @@ -26,7 +26,7 @@ class OptimizationProblem: - """A class encapsulating an optimization problem, modelled after Python CPLEX API. + """A class encapsulating an optimization problem, modeled after Python CPLEX API. An instance of the OptimizationProblem class provides methods for creating, modifying, and querying an optimization problem, solving it, and diff --git a/qiskit/optimization/utils/base.py b/qiskit/optimization/utils/base.py index 7ffed22127..64b3298baa 100644 --- a/qiskit/optimization/utils/base.py +++ b/qiskit/optimization/utils/base.py @@ -93,7 +93,7 @@ def _getter(getfunc: Callable[[int], Any], *args) -> Any: `index` should be already converted by `NameIndex.convert`. *args: A single index or a list of indices. `getfunc` is invoked with args. - Returns: if `args` is a single index, this returns a single value genereted by `getfunc`. + Returns: if `args` is a single index, this returns a single value generated by `getfunc`. If `args` is a list of indices, this returns a list of values. """ if len(args) == 0: diff --git a/qiskit/optimization/utils/eigenvector_to_solutions.py b/qiskit/optimization/utils/eigenvector_to_solutions.py index d7b487080c..e25adc76b5 100644 --- a/qiskit/optimization/utils/eigenvector_to_solutions.py +++ b/qiskit/optimization/utils/eigenvector_to_solutions.py @@ -13,7 +13,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Auxilliary methods to translate eigenvectors into optimization results.""" +"""Auxiliary methods to translate eigenvectors into optimization results.""" from qiskit.aqua.operators import MatrixOperator from typing import Union, List, Tuple diff --git a/test/optimization/test_linear_constraints.py b/test/optimization/test_linear_constraints.py index 1262633794..6a3e6cfc32 100644 --- a/test/optimization/test_linear_constraints.py +++ b/test/optimization/test_linear_constraints.py @@ -93,7 +93,7 @@ def test_set_senses(self): self.assertListEqual(op.linear_constraints.get_senses(), ['E', 'G', 'R', 'L']) def test_set_linear_components(self): - """ test set lieear components """ + """ test set linear components """ op = OptimizationProblem() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) op.variables.add(names=["x0", "x1"]) diff --git a/test/optimization/test_objective.py b/test/optimization/test_objective.py index 815fc132c6..ba8b44a6b4 100644 --- a/test/optimization/test_objective.py +++ b/test/optimization/test_objective.py @@ -218,7 +218,7 @@ def test_get_name(self): self.assertEqual(op.objective.get_name(), 'cost') def test_get_num_quadratic_variables(self): - """ test get num quadratic vraiables """ + """ test get num quadratic variables """ op = OptimizationProblem() n = 3 op.variables.add(names=[str(i) for i in range(n)]) diff --git a/test/optimization/test_optimization_problem.py b/test/optimization/test_optimization_problem.py index 66a8cb4658..31a07e979c 100644 --- a/test/optimization/test_optimization_problem.py +++ b/test/optimization/test_optimization_problem.py @@ -127,7 +127,7 @@ def test_problem_type1(self): self.assertEqual(op.problem_type[op.get_problem_type()], 'QP') def test_problem_type2(self): - """ test problemm type 2""" + """ test problem type 2""" op = qiskit.optimization.OptimizationProblem() op.set_problem_type(op.problem_type.LP) self.assertEqual(op.get_problem_type(), diff --git a/test/optimization/test_quadratic_constraints.py b/test/optimization/test_quadratic_constraints.py index 77d65b4a88..1b9420cf9e 100644 --- a/test/optimization/test_quadratic_constraints.py +++ b/test/optimization/test_quadratic_constraints.py @@ -329,7 +329,7 @@ def test_get_quadratic_components2(self): self.assertListEqual(s[3].val, [1.0, 2.0, 3.0, 4.0]) def test_get_names(self): - """ tet get names """ + """ test get names """ op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(11)]) q = op.quadratic_constraints From 41d7f3bd6ec3e9f9c95ce52ac15ee60a735ebc09 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Wed, 1 Apr 2020 16:58:42 -0400 Subject: [PATCH 139/323] more lint fixes --- .../optimization/algorithms/admm_optimizer.py | 14 +- .../problems/linear_constraint.py | 14 +- qiskit/optimization/problems/objective.py | 14 +- qiskit/optimization/problems/problem_type.py | 2 + .../problems/quadratic_constraint.py | 21 +- qiskit/optimization/problems/variables.py | 2 + qiskit/optimization/utils/helpers.py | 10 +- test/aqua/test_compute_min_eigenvalue.py | 4 +- test/optimization/test_converters.py | 19 +- test/optimization/test_cplex_optimizer.py | 10 +- .../test_grover_minimum_finder.py | 4 +- test/optimization/test_helpers.py | 41 ++- test/optimization/test_linear_constraints.py | 94 +++--- test/optimization/test_objective.py | 56 ++-- .../optimization/test_optimization_problem.py | 25 +- .../test_quadratic_constraints.py | 296 +++++++++--------- test/optimization/test_variables.py | 43 ++- 17 files changed, 329 insertions(+), 340 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index cc359e7169..9e70254944 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -568,11 +568,11 @@ def _get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): row_indices & continuous_index_set) != 0: self._assign_row_values(matrix, vector, constraint_index, all_variables) - matrix, b2 = self._create_ndarrays(matrix, vector, len(all_variables)) + matrix, b_2 = self._create_ndarrays(matrix, vector, len(all_variables)) # a2 - a2 = matrix[:, 0:len(self._state.binary_indices)] - a3 = matrix[:, len(self._state.binary_indices):] - return a2, a3, b2 + a_2 = matrix[:, 0:len(self._state.binary_indices)] + a_3 = matrix[:, len(self._state.binary_indices):] + return a_2, a_3, b_2 def _create_step1_problem(self) -> OptimizationProblem: """Creates a step 1 sub-problem. @@ -777,9 +777,9 @@ def _get_best_merit_solution(self) -> (List[np.ndarray], float): it_best_merits = self._state.merits.index( min(list(map(lambda x: self._state.sense * x, self._state.merits)))) - x0 = self._state.x0_saved[it_best_merits] - u = self._state.u_saved[it_best_merits] - sol = [x0, u] + x_0 = self._state.x0_saved[it_best_merits] + u_s = self._state.u_saved[it_best_merits] + sol = [x_0, u_s] sol_val = self._state.cost_iterates[it_best_merits] return sol, sol_val diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index 24ed22b622..3e24101578 100644 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -153,12 +153,12 @@ def add(self, lin_expr=None, senses="", rhs=None, range_values=None, if not lin_expr: lin_expr = [SparsePair()] * max_length - for sp in lin_expr: + for s_p in lin_expr: lin_expr_dict = {} - if isinstance(sp, SparsePair): - zip_iter = zip(sp.ind, sp.val) - elif isinstance(sp, Sequence) and len(sp) == 2: - zip_iter = zip(sp[0], sp[1]) + if isinstance(s_p, SparsePair): + zip_iter = zip(s_p.ind, s_p.val) + elif isinstance(s_p, Sequence) and len(s_p) == 2: + zip_iter = zip(s_p[0], s_p[1]) else: raise QiskitOptimizationError('Invalid lin_expr: {}'.format(lin_expr)) for i, val in zip_iter: @@ -763,8 +763,8 @@ def get_num_nonzeros(self): """ nnz = 0 for c in self._lin_expr: - for e in c.values(): - if e != 0.0: + for e_e in c.values(): + if e_e != 0.0: nnz += 1 return nnz diff --git a/qiskit/optimization/problems/objective.py b/qiskit/optimization/problems/objective.py index d34cab2e75..1c881a2eee 100644 --- a/qiskit/optimization/problems/objective.py +++ b/qiskit/optimization/problems/objective.py @@ -161,12 +161,12 @@ def _set(i, j, val): for i, val in enumerate(args): _set(i, i, val) else: - for i, sp in enumerate(args): - if isinstance(sp, SparsePair): - for j, val in zip(sp.ind, sp.val): + for i, s_p in enumerate(args): + if isinstance(s_p, SparsePair): + for j, val in zip(s_p.ind, s_p.val): _set(i, j, val) - elif isinstance(sp, Sequence) and len(sp) == 2: - for j, val in zip(sp[0], sp[1]): + elif isinstance(s_p, Sequence) and len(s_p) == 2: + for j, val in zip(s_p[0], s_p[1]): _set(i, j, val) else: raise QiskitOptimizationError( @@ -373,8 +373,8 @@ def get_quadratic(self, *args): def _get(i): from cplex import SparsePair - qi = self._quadratic.get(i, {}) - return SparsePair(list(qi.keys()), list(qi.values())) + q_i = self._quadratic.get(i, {}) + return SparsePair(list(q_i.keys()), list(q_i.values())) if len(args) == 0: return copy.deepcopy(self._quadratic) diff --git a/qiskit/optimization/problems/problem_type.py b/qiskit/optimization/problems/problem_type.py index 0bf8257047..81ad8296e7 100644 --- a/qiskit/optimization/problems/problem_type.py +++ b/qiskit/optimization/problems/problem_type.py @@ -12,6 +12,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +# pylint: disable=invalid-name + CPXPROB_LP = 0 CPXPROB_MILP = 1 CPXPROB_FIXEDMILP = 3 diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index d210ebd355..527bc84869 100644 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -16,7 +16,7 @@ from collections.abc import Sequence from logging import getLogger from typing import List, Dict, Tuple, Callable - +from cplex import SparsePair, SparseTriple from qiskit.optimization.utils.base import BaseInterface from qiskit.optimization.utils.helpers import NameIndex from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError @@ -109,7 +109,6 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): ... sense = "G") 0 """ - from cplex import SparsePair, SparseTriple # We only ever create one quadratic constraint at a time. # check constraint name @@ -132,10 +131,10 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): else: raise QiskitOptimizationError('Invalid lin_expr: {}'.format(lin_expr)) for i, val in zip(ind, val): - i2 = self._varindex(i) - if i2 in lin_expr_dict: + i_2 = self._varindex(i) + if i_2 in lin_expr_dict: logger.warning('lin_expr contains duplicate index: {}'.format(i)) - lin_expr_dict[i2] = val + lin_expr_dict[i_2] = val self._lin_expr.append(lin_expr_dict) # quadratic terms @@ -152,13 +151,13 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): else: raise QiskitOptimizationError('Invalid quad_expr: {}'.format(quad_expr)) for i, j, val in zip(ind1, ind2, val): - i2 = self._varindex(i) - j2 = self._varindex(j) - if i2 < j2: - i2, j2 = j2, i2 - if (i2, j2) in quad_expr_dict: + i_2 = self._varindex(i) + j_2 = self._varindex(j) + if i_2 < j_2: + i_2, j_2 = j_2, i_2 + if (i_2, j_2) in quad_expr_dict: logger.warning('quad_expr contains duplicate index: {} {}'.format(i, j)) - quad_expr_dict[i2, j2] = val + quad_expr_dict[i_2, j_2] = val self._quad_expr.append(quad_expr_dict) if sense not in ['L', 'G', 'E']: diff --git a/qiskit/optimization/problems/variables.py b/qiskit/optimization/problems/variables.py index ad2b50750b..92123e01c0 100644 --- a/qiskit/optimization/problems/variables.py +++ b/qiskit/optimization/problems/variables.py @@ -19,6 +19,8 @@ from qiskit.optimization.utils.helpers import init_list_args, NameIndex from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError +# pylint: disable=invalid-name + CPX_CONTINUOUS = 'C' CPX_BINARY = 'B' CPX_INTEGER = 'I' diff --git a/qiskit/optimization/utils/helpers.py b/qiskit/optimization/utils/helpers.py index ea55e9fd5f..1d3c2045cb 100644 --- a/qiskit/optimization/utils/helpers.py +++ b/qiskit/optimization/utils/helpers.py @@ -74,11 +74,11 @@ def convert(self, *args) -> Union[int, List[int]]: if len(args) == 0: return list(self._dict.values()) elif len(args) == 1: - a0 = args[0] - if isinstance(a0, (int, str)): - return self._convert_one(a0) - elif isinstance(a0, Sequence): - return [self._convert_one(e) for e in a0] + a_0 = args[0] + if isinstance(a_0, (int, str)): + return self._convert_one(a_0) + elif isinstance(a_0, Sequence): + return [self._convert_one(e) for e in a_0] else: raise QiskitOptimizationError('Invalid argument: {}'.format(args)) elif len(args) == 2: diff --git a/test/aqua/test_compute_min_eigenvalue.py b/test/aqua/test_compute_min_eigenvalue.py index 898277f893..b147d5f234 100755 --- a/test/aqua/test_compute_min_eigenvalue.py +++ b/test/aqua/test_compute_min_eigenvalue.py @@ -76,8 +76,8 @@ def test_vqe_qasm(self): def test_ee(self): """ EE test """ dummy_operator = MatrixOperator([[1]]) - ee = NumPyMinimumEigensolver() - output = ee.compute_minimum_eigenvalue(self.qubit_op) + nee = NumPyMinimumEigensolver() + output = nee.compute_minimum_eigenvalue(self.qubit_op) self.assertAlmostEqual(output.eigenvalue, -1.85727503) diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index 68aa6e641b..97b1b9ae67 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -15,20 +15,17 @@ """ Test Converters """ import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase from cplex import SparsePair from qiskit.optimization import OptimizationProblem, QiskitOptimizationError from qiskit.optimization.converters import InequalityToEqualityConverter, \ OptimizationProblemToOperator, IntegerToBinaryConverter, PenalizeLinearEqualityConstraints -from test.optimization.optimization_test_case import QiskitOptimizationTestCase class TestConverters(QiskitOptimizationTestCase): """Test Converters""" - def setUp(self): - super().setUp() - def test_empty_problem(self): """ test empty problem """ op = OptimizationProblem() @@ -39,7 +36,7 @@ def test_empty_problem(self): conv = PenalizeLinearEqualityConstraints() op = conv.encode(op) conv = OptimizationProblemToOperator() - qubitop, shift = conv.encode(op) + _, shift = conv.encode(op) self.assertEqual(shift, 0.0) def test_inequality_binary(self): @@ -143,12 +140,12 @@ def test_integer_to_binary(self): names = op2.variables.get_names() self.assertIn('x', names) self.assertIn('z', names) - vars = op2.variables - self.assertEqual(vars.get_lower_bounds('x'), 0.0) - self.assertEqual(vars.get_lower_bounds('z'), 0.0) - self.assertEqual(vars.get_upper_bounds('x'), 1.0) - self.assertEqual(vars.get_upper_bounds('z'), 10.0) - self.assertListEqual(vars.get_types(['x', 'z']), ['B', 'C']) + variables = op2.variables + self.assertEqual(variables.get_lower_bounds('x'), 0.0) + self.assertEqual(variables.get_lower_bounds('z'), 0.0) + self.assertEqual(variables.get_upper_bounds('x'), 1.0) + self.assertEqual(variables.get_upper_bounds('z'), 10.0) + self.assertListEqual(variables.get_types(['x', 'z']), ['B', 'C']) if __name__ == '__main__': diff --git a/test/optimization/test_cplex_optimizer.py b/test/optimization/test_cplex_optimizer.py index 2cb9445b5d..3444b8b170 100644 --- a/test/optimization/test_cplex_optimizer.py +++ b/test/optimization/test_cplex_optimizer.py @@ -16,12 +16,10 @@ import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase - +from ddt import ddt, data from qiskit.optimization.algorithms import CplexOptimizer from qiskit.optimization.problems import OptimizationProblem -from ddt import ddt, data - @ddt class TestCplexOptimizer(QiskitOptimizationTestCase): @@ -34,7 +32,7 @@ def setUp(self): self.cplex_optimizer = CplexOptimizer() @data( - ('op_ip1.lp', [0, 2], 6), + ('op_ip1.lp', [0, 2], 6), ('op_mip1.lp', [1, 1, 0], 6), ('op_lp1.lp', [0.25, 1.75], 5.8750) ) @@ -54,3 +52,7 @@ def test_cplex_optimizer(self, config): # analyze results self.assertAlmostEqual(result.fval, fval) self.assertAlmostEqual(result.x, x) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index e775f9527d..e434db4fbb 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -29,11 +29,9 @@ def validate_results(self, problem, results, max_iterations): """Validate the results object returned by GroverMinimumFinder.""" # Get measured values. grover_results = results.results['grover_results'] - op_key = results.x - op_value = results.fval + iterations = len(grover_results.operation_counts) rot = grover_results.rotation_count - func = grover_results.func_dict # Get expected value. solver = MinimumEigenOptimizer(NumPyMinimumEigensolver()) diff --git a/test/optimization/test_helpers.py b/test/optimization/test_helpers.py index aa3fc83a0f..1854eb5ff9 100644 --- a/test/optimization/test_helpers.py +++ b/test/optimization/test_helpers.py @@ -15,48 +15,45 @@ """ Test helpers """ import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase from qiskit.optimization.utils.helpers import NameIndex, init_list_args from qiskit.optimization import QiskitOptimizationError -from test.optimization.optimization_test_case import QiskitOptimizationTestCase class TestHelpers(QiskitOptimizationTestCase): """Test helpers.""" - def setUp(self): - super().setUp() - def test_init_list_args(self): """ test init list args """ - a = init_list_args(1, [2], None) - self.assertTupleEqual(a, (1, [2], [])) + args = init_list_args(1, [2], None) + self.assertTupleEqual(args, (1, [2], [])) def test_name_index1(self): """ test name index 1 """ - a = NameIndex() - self.assertEqual(a.convert('1'), 0) - self.assertListEqual(a.convert(['2', '3']), [1, 2]) - self.assertEqual(a.convert('1'), 0) - self.assertListEqual(a.convert(), [0, 1, 2]) - self.assertListEqual(a.convert('1', '3'), [0, 1, 2]) - self.assertListEqual(a.convert('1', '2'), [0, 1]) + n_i = NameIndex() + self.assertEqual(n_i.convert('1'), 0) + self.assertListEqual(n_i.convert(['2', '3']), [1, 2]) + self.assertEqual(n_i.convert('1'), 0) + self.assertListEqual(n_i.convert(), [0, 1, 2]) + self.assertListEqual(n_i.convert('1', '3'), [0, 1, 2]) + self.assertListEqual(n_i.convert('1', '2'), [0, 1]) def test_name_index2(self): """ test name index 2 """ - a = NameIndex() - a.build(['1', '2', '3']) - self.assertEqual(a.convert('1'), 0) - self.assertListEqual(a.convert(), [0, 1, 2]) - self.assertListEqual(a.convert('1', '3'), [0, 1, 2]) - self.assertListEqual(a.convert('1', '2'), [0, 1]) + n_i = NameIndex() + n_i.build(['1', '2', '3']) + self.assertEqual(n_i.convert('1'), 0) + self.assertListEqual(n_i.convert(), [0, 1, 2]) + self.assertListEqual(n_i.convert('1', '3'), [0, 1, 2]) + self.assertListEqual(n_i.convert('1', '2'), [0, 1]) def test_name_index3(self): """ test name index 3 """ - a = NameIndex() + n_i = NameIndex() with self.assertRaises(QiskitOptimizationError): - a.convert({}) + n_i.convert({}) with self.assertRaises(QiskitOptimizationError): - a.convert(1, 2, 3) + n_i.convert(1, 2, 3) if __name__ == '__main__': diff --git a/test/optimization/test_linear_constraints.py b/test/optimization/test_linear_constraints.py index 6a3e6cfc32..735f029c49 100644 --- a/test/optimization/test_linear_constraints.py +++ b/test/optimization/test_linear_constraints.py @@ -15,18 +15,14 @@ """ Test LinearConstraintInterface """ import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase from cplex import SparsePair - from qiskit.optimization import OptimizationProblem -from test.optimization.optimization_test_case import QiskitOptimizationTestCase class TestLinearConstraints(QiskitOptimizationTestCase): """Test LinearConstraintInterface.""" - def setUp(self): - super().setUp() - def test_get_num(self): """ test get num """ op = OptimizationProblem() @@ -98,19 +94,19 @@ def test_set_linear_components(self): op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) op.variables.add(names=["x0", "x1"]) op.linear_constraints.set_linear_components("c0", [["x0"], [1.0]]) - sp = op.linear_constraints.get_rows("c0") - self.assertListEqual(sp.ind, [0]) - self.assertListEqual(sp.val, [1.0]) + s_p = op.linear_constraints.get_rows("c0") + self.assertListEqual(s_p.ind, [0]) + self.assertListEqual(s_p.val, [1.0]) op.linear_constraints.set_linear_components( [("c3", SparsePair(ind=["x1"], val=[-1.0])), (2, [[0, 1], [-2.0, 3.0]])] ) - sp = op.linear_constraints.get_rows("c3") - self.assertListEqual(sp.ind, [1]) - self.assertListEqual(sp.val, [-1.0]) - sp = op.linear_constraints.get_rows(2) - self.assertListEqual(sp.ind, [0, 1]) - self.assertListEqual(sp.val, [-2.0, 3.0]) + s_p = op.linear_constraints.get_rows("c3") + self.assertListEqual(s_p.ind, [1]) + self.assertListEqual(s_p.val, [-1.0]) + s_p = op.linear_constraints.get_rows(2) + self.assertListEqual(s_p.ind, [0, 1]) + self.assertListEqual(s_p.val, [-2.0, 3.0]) def test_set_range_values(self): """ test set range values """ @@ -127,14 +123,14 @@ def test_set_coeffients(self): op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) op.variables.add(names=["x0", "x1"]) op.linear_constraints.set_coefficients("c0", "x1", 1.0) - sp = op.linear_constraints.get_rows(0) - self.assertListEqual(sp.ind, [1]) - self.assertListEqual(sp.val, [1.0]) + s_p = op.linear_constraints.get_rows(0) + self.assertListEqual(s_p.ind, [1]) + self.assertListEqual(s_p.val, [1.0]) op.linear_constraints.set_coefficients([("c2", "x0", 2.0), ("c2", "x1", -1.0)]) - sp = op.linear_constraints.get_rows("c2") - self.assertListEqual(sp.ind, [0, 1]) - self.assertListEqual(sp.val, [2.0, -1.0]) + s_p = op.linear_constraints.get_rows("c2") + self.assertListEqual(s_p.ind, [0, 1]) + self.assertListEqual(s_p.val, [2.0, -1.0]) def test_get_rhs(self): """ test get rhs """ @@ -194,33 +190,33 @@ def test_get_rows(self): SparsePair(ind=["x1", "x2"], val=[1.0, 1.0]), SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])]) - sp = op.linear_constraints.get_rows(0) - self.assertListEqual(sp.ind, [0, 2]) - self.assertListEqual(sp.val, [1.0, -1.0]) - - sp = op.linear_constraints.get_rows(1, 3) - self.assertListEqual(sp[0].ind, [0, 1]) - self.assertListEqual(sp[0].val, [1.0, 1.0]) - self.assertListEqual(sp[1].ind, [0, 1, 2]) - self.assertListEqual(sp[1].val, [-1.0, -1.0, -1.0]) - self.assertListEqual(sp[2].ind, [1, 2]) - self.assertListEqual(sp[2].val, [10.0, -2.0]) - - sp = op.linear_constraints.get_rows(['c2', 0]) - self.assertListEqual(sp[0].ind, [0, 1, 2]) - self.assertListEqual(sp[0].val, [-1.0, -1.0, -1.0]) - self.assertListEqual(sp[1].ind, [0, 2]) - self.assertListEqual(sp[1].val, [1.0, -1.0]) - - sp = op.linear_constraints.get_rows() - self.assertListEqual(sp[0].ind, [0, 2]) - self.assertListEqual(sp[0].val, [1.0, -1.0]) - self.assertListEqual(sp[1].ind, [0, 1]) - self.assertListEqual(sp[1].val, [1.0, 1.0]) - self.assertListEqual(sp[2].ind, [0, 1, 2]) - self.assertListEqual(sp[2].val, [-1.0, -1.0, -1.0]) - self.assertListEqual(sp[3].ind, [1, 2]) - self.assertListEqual(sp[3].val, [10.0, -2.0]) + s_p = op.linear_constraints.get_rows(0) + self.assertListEqual(s_p.ind, [0, 2]) + self.assertListEqual(s_p.val, [1.0, -1.0]) + + s_p = op.linear_constraints.get_rows(1, 3) + self.assertListEqual(s_p[0].ind, [0, 1]) + self.assertListEqual(s_p[0].val, [1.0, 1.0]) + self.assertListEqual(s_p[1].ind, [0, 1, 2]) + self.assertListEqual(s_p[1].val, [-1.0, -1.0, -1.0]) + self.assertListEqual(s_p[2].ind, [1, 2]) + self.assertListEqual(s_p[2].val, [10.0, -2.0]) + + s_p = op.linear_constraints.get_rows(['c2', 0]) + self.assertListEqual(s_p[0].ind, [0, 1, 2]) + self.assertListEqual(s_p[0].val, [-1.0, -1.0, -1.0]) + self.assertListEqual(s_p[1].ind, [0, 2]) + self.assertListEqual(s_p[1].val, [1.0, -1.0]) + + s_p = op.linear_constraints.get_rows() + self.assertListEqual(s_p[0].ind, [0, 2]) + self.assertListEqual(s_p[0].val, [1.0, -1.0]) + self.assertListEqual(s_p[1].ind, [0, 1]) + self.assertListEqual(s_p[1].val, [1.0, 1.0]) + self.assertListEqual(s_p[2].ind, [0, 1, 2]) + self.assertListEqual(s_p[2].val, [-1.0, -1.0, -1.0]) + self.assertListEqual(s_p[3].ind, [1, 2]) + self.assertListEqual(s_p[3].val, [10.0, -2.0]) def test_get_num_nonzeros(self): """ test get num non zeros """ @@ -250,3 +246,7 @@ def test_get_histogram(self): """ test get histogram """ op = OptimizationProblem() self.assertRaises(NotImplementedError, lambda: op.linear_constraints.get_histogram()) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_objective.py b/test/optimization/test_objective.py index ba8b44a6b4..3f8e6adf3d 100644 --- a/test/optimization/test_objective.py +++ b/test/optimization/test_objective.py @@ -15,18 +15,14 @@ """ Test ObjectiveInterface """ import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase from cplex import SparsePair - from qiskit.optimization import OptimizationProblem -from test.optimization.optimization_test_case import QiskitOptimizationTestCase class TestObjective(QiskitOptimizationTestCase): """Test ObjectiveInterface""" - def setUp(self): - super().setUp() - def test_obj_sense(self): """ test obj sense """ op = OptimizationProblem() @@ -165,33 +161,33 @@ def test_get_quadratic(self): op.variables.add(names=[str(i) for i in range(n)]) obj = op.objective obj.set_quadratic([1.5 * i for i in range(n)]) - sp = obj.get_quadratic(8) - self.assertListEqual(sp.ind, [8]) - self.assertListEqual(sp.val, [12.0]) - - sp = obj.get_quadratic('1', 3) - self.assertListEqual(sp[0].ind, [1]) - self.assertListEqual(sp[0].val, [1.5]) - self.assertListEqual(sp[1].ind, [2]) - self.assertListEqual(sp[1].val, [3.0]) - self.assertListEqual(sp[2].ind, [3]) - self.assertListEqual(sp[2].val, [4.5]) - - sp = obj.get_quadratic([3, '1', 5]) - self.assertListEqual(sp[0].ind, [3]) - self.assertListEqual(sp[0].val, [4.5]) - self.assertListEqual(sp[1].ind, [1]) - self.assertListEqual(sp[1].val, [1.5]) - self.assertListEqual(sp[2].ind, [5]) - self.assertListEqual(sp[2].val, [7.5]) - - sp = obj.get_quadratic(range(n)) + s_p = obj.get_quadratic(8) + self.assertListEqual(s_p.ind, [8]) + self.assertListEqual(s_p.val, [12.0]) + + s_p = obj.get_quadratic('1', 3) + self.assertListEqual(s_p[0].ind, [1]) + self.assertListEqual(s_p[0].val, [1.5]) + self.assertListEqual(s_p[1].ind, [2]) + self.assertListEqual(s_p[1].val, [3.0]) + self.assertListEqual(s_p[2].ind, [3]) + self.assertListEqual(s_p[2].val, [4.5]) + + s_p = obj.get_quadratic([3, '1', 5]) + self.assertListEqual(s_p[0].ind, [3]) + self.assertListEqual(s_p[0].val, [4.5]) + self.assertListEqual(s_p[1].ind, [1]) + self.assertListEqual(s_p[1].val, [1.5]) + self.assertListEqual(s_p[2].ind, [5]) + self.assertListEqual(s_p[2].val, [7.5]) + + s_p = obj.get_quadratic(range(n)) for i in range(n): - self.assertListEqual(sp[i].ind, [i]) - self.assertListEqual(sp[i].val, [1.5 * i]) + self.assertListEqual(s_p[i].ind, [i]) + self.assertListEqual(s_p[i].val, [1.5 * i]) - def test_get_quadratic(self): - """ test get quadratic """ + def test_get_quadratic2(self): + """ test get quadratic 2 """ op = OptimizationProblem() n = 3 op.variables.add(names=[str(i) for i in range(n)]) diff --git a/test/optimization/test_optimization_problem.py b/test/optimization/test_optimization_problem.py index 31a07e979c..f5ebb29d20 100644 --- a/test/optimization/test_optimization_problem.py +++ b/test/optimization/test_optimization_problem.py @@ -39,14 +39,14 @@ def test_constructor1(self): def test_constructor2(self): """ test constructor 2 """ with self.assertRaises(QiskitOptimizationError): - op = qiskit.optimization.OptimizationProblem("unknown") + _ = qiskit.optimization.OptimizationProblem("unknown") # If filename does not exist, an exception is raised. def test_constructor3(self): """ test constructor 3 """ # we can pass at most one argument with self.assertRaises(QiskitOptimizationError): - op = qiskit.optimization.OptimizationProblem("test", "west") + _ = qiskit.optimization.OptimizationProblem("test", "west") def test_constructor_context(self): """ test constructor context """ @@ -71,20 +71,20 @@ def test_write1(self): """ test write 1 """ op = qiskit.optimization.OptimizationProblem() op.variables.add(names=['x1', 'x2', 'x3']) - f, fn = tempfile.mkstemp(suffix='.lp') - os.close(f) - op.write(fn) - assert os.path.exists(fn) == 1 + file, filename = tempfile.mkstemp(suffix='.lp') + os.close(file) + op.write(filename) + assert os.path.exists(filename) == 1 def test_write2(self): """ test write 2 """ op1 = qiskit.optimization.OptimizationProblem() op1.variables.add(names=['x1', 'x2', 'x3']) - f, fn = tempfile.mkstemp(suffix='.lp') - os.close(f) - op1.write(fn) + file, filename = tempfile.mkstemp(suffix='.lp') + os.close(file) + op1.write(filename) op2 = qiskit.optimization.OptimizationProblem() - op2.read(fn) + op2.read(filename) self.assertEqual(op2.variables.get_num(), 3) def test_write3(self): @@ -92,11 +92,12 @@ def test_write3(self): op = qiskit.optimization.OptimizationProblem() op.variables.add(names=['x1', 'x2', 'x3']) - class NoOpStream(object): + class NoOpStream: + """ stream """ def __init__(self): self.was_called = False - def write(self, bytes): + def write(self, byt): """ write """ self.was_called = True pass diff --git a/test/optimization/test_quadratic_constraints.py b/test/optimization/test_quadratic_constraints.py index 1b9420cf9e..0f0cd72e77 100644 --- a/test/optimization/test_quadratic_constraints.py +++ b/test/optimization/test_quadratic_constraints.py @@ -25,25 +25,22 @@ class TestQuadraticConstraints(QiskitOptimizationTestCase): """Test QuadraticConstraintInterface.""" - def setUp(self): - super().setUp() - def test_initial1(self): """ test initial 1""" op = OptimizationProblem() - c1 = op.quadratic_constraints.add(name='c1') - c2 = op.quadratic_constraints.add(name='c2') - c3 = op.quadratic_constraints.add(name='c3') + c_1 = op.quadratic_constraints.add(name='c1') + c_2 = op.quadratic_constraints.add(name='c2') + c_3 = op.quadratic_constraints.add(name='c3') self.assertEqual(op.quadratic_constraints.get_num(), 3) self.assertListEqual(op.quadratic_constraints.get_names(), ['c1', 'c2', 'c3']) - self.assertListEqual([c1, c2, c3], [0, 1, 2]) + self.assertListEqual([c_1, c_2, c_3], [0, 1, 2]) self.assertRaises(QiskitOptimizationError, lambda: op.quadratic_constraints.add(name='c1')) def test_initial2(self): """ test initial 2""" op = OptimizationProblem() op.variables.add(names=['x1', 'x2', 'x3'], types='B' * 3) - c = op.quadratic_constraints.add( + _ = op.quadratic_constraints.add( lin_expr=SparsePair(ind=['x1', 'x3'], val=[1.0, -1.0]), quad_expr=SparseTriple(ind1=['x1', 'x2'], ind2=['x2', 'x3'], val=[1.0, -1.0]), sense='E', @@ -56,10 +53,10 @@ def test_initial2(self): self.assertListEqual(quad.get_senses(), ['E']) self.assertListEqual(quad.get_linear_num_nonzeros(), [2]) self.assertListEqual(quad.get_quad_num_nonzeros(), [2]) - la = quad.get_linear_components() - self.assertEqual(len(la), 1) - self.assertListEqual(la[0].ind, [0, 2]) - self.assertListEqual(la[0].val, [1.0, -1.0]) + l_a = quad.get_linear_components() + self.assertEqual(len(l_a), 1) + self.assertListEqual(l_a[0].ind, [0, 2]) + self.assertListEqual(l_a[0].val, [1.0, -1.0]) q = quad.get_quadratic_components() self.assertEqual(len(q), 1) self.assertListEqual(q[0].ind1, [1, 2]) @@ -70,27 +67,28 @@ def test_get_num(self): """ test get num """ op = OptimizationProblem() op.variables.add(names=['x', 'y']) - la = SparsePair(ind=['x'], val=[1.0]) + l_a = SparsePair(ind=['x'], val=[1.0]) q = SparseTriple(ind1=['x'], ind2=['y'], val=[1.0]) n = 10 for i in range(n): - self.assertEqual(op.quadratic_constraints.add(name=str(i), lin_expr=la, quad_expr=q), i) + self.assertEqual(op.quadratic_constraints.add(name=str(i), + lin_expr=l_a, quad_expr=q), i) self.assertEqual(op.quadratic_constraints.get_num(), n) def test_add(self): """ test add """ op = OptimizationProblem() op.variables.add(names=['x', 'y']) - la = SparsePair(ind=['x'], val=[1.0]) + l_a = SparsePair(ind=['x'], val=[1.0]) q = SparseTriple(ind1=['x'], ind2=['y'], val=[1.0]) self.assertEqual(op.quadratic_constraints.add( - name='my quad', lin_expr=la, quad_expr=q, rhs=1.0, sense='G'), 0) + name='my quad', lin_expr=l_a, quad_expr=q, rhs=1.0, sense='G'), 0) def test_delete(self): """ test delete """ op = OptimizationProblem() - q0 = [op.quadratic_constraints.add(name=str(i)) for i in range(10)] - self.assertListEqual(q0, list(range(10))) + q_0 = [op.quadratic_constraints.add(name=str(i)) for i in range(10)] + self.assertListEqual(q_0, list(range(10))) q = op.quadratic_constraints self.assertListEqual(q.get_names(), ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']) q.delete(8) @@ -106,8 +104,8 @@ def test_get_rhs(self): """ test get rhs """ op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(10)]) - q0 = [op.quadratic_constraints.add(rhs=1.5 * i, name=str(i)) for i in range(10)] - self.assertListEqual(q0, list(range(10))) + q_0 = [op.quadratic_constraints.add(rhs=1.5 * i, name=str(i)) for i in range(10)] + self.assertListEqual(q_0, list(range(10))) q = op.quadratic_constraints self.assertEqual(q.get_num(), 10) self.assertEqual(q.get_rhs(8), 12.0) @@ -120,8 +118,8 @@ def test_get_senses(self): op = OptimizationProblem() op.variables.add(names=["x0"]) q = op.quadratic_constraints - q0 = [q.add(name=str(i), sense=j) for i, j in enumerate('GGLL')] - self.assertListEqual(q0, [0, 1, 2, 3]) + q_0 = [q.add(name=str(i), sense=j) for i, j in enumerate('GGLL')] + self.assertListEqual(q_0, [0, 1, 2, 3]) self.assertEqual(q.get_senses(1), 'G') self.assertListEqual(q.get_senses('1', 3), ['G', 'L', 'L']) self.assertListEqual(q.get_senses([2, '0', 1]), ['L', 'G', 'G']) @@ -152,29 +150,29 @@ def test_get_linear_components(self): self.assertListEqual(z, [0, 1, 2]) self.assertEqual(q.get_num(), 3) - sp = q.get_linear_components(2) - self.assertListEqual(sp.ind, [0, 1]) - self.assertListEqual(sp.val, [1.0, 2.0]) - - sp = q.get_linear_components('0', 1) - self.assertListEqual(sp[0].ind, []) - self.assertListEqual(sp[0].val, []) - self.assertListEqual(sp[1].ind, [0]) - self.assertListEqual(sp[1].val, [1.0]) - - sp = q.get_linear_components([1, '0']) - self.assertListEqual(sp[0].ind, [0]) - self.assertListEqual(sp[0].val, [1.0]) - self.assertListEqual(sp[1].ind, []) - self.assertListEqual(sp[1].val, []) - - sp = q.get_linear_components() - self.assertListEqual(sp[0].ind, []) - self.assertListEqual(sp[0].val, []) - self.assertListEqual(sp[1].ind, [0]) - self.assertListEqual(sp[1].val, [1.0]) - self.assertListEqual(sp[2].ind, [0, 1]) - self.assertListEqual(sp[2].val, [1.0, 2.0]) + s_p = q.get_linear_components(2) + self.assertListEqual(s_p.ind, [0, 1]) + self.assertListEqual(s_p.val, [1.0, 2.0]) + + s_p = q.get_linear_components('0', 1) + self.assertListEqual(s_p[0].ind, []) + self.assertListEqual(s_p[0].val, []) + self.assertListEqual(s_p[1].ind, [0]) + self.assertListEqual(s_p[1].val, [1.0]) + + s_p = q.get_linear_components([1, '0']) + self.assertListEqual(s_p[0].ind, [0]) + self.assertListEqual(s_p[0].val, [1.0]) + self.assertListEqual(s_p[1].ind, []) + self.assertListEqual(s_p[1].val, []) + + s_p = q.get_linear_components() + self.assertListEqual(s_p[0].ind, []) + self.assertListEqual(s_p[0].val, []) + self.assertListEqual(s_p[1].ind, [0]) + self.assertListEqual(s_p[1].val, [1.0]) + self.assertListEqual(s_p[2].ind, [0, 1]) + self.assertListEqual(s_p[2].val, [1.0, 2.0]) def test_get_linear_components2(self): """ test get linear components 2 """ @@ -184,39 +182,39 @@ def test_get_linear_components2(self): [q.add(name=str(i), lin_expr=[range(i), [1.0 * (j + 1.0) for j in range(i)]]) for i in range(10)] - s = q.get_linear_components(8) - self.assertListEqual(s.ind, [0, 1, 2, 3, 4, 5, 6, 7]) - self.assertListEqual(s.val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]) - - s = q.get_linear_components('1', 3) - self.assertEqual(len(s), 3) - self.assertListEqual(s[0].ind, [0]) - self.assertListEqual(s[0].val, [1.0]) - self.assertListEqual(s[1].ind, [0, 1]) - self.assertListEqual(s[1].val, [1.0, 2.0]) - self.assertListEqual(s[2].ind, [0, 1, 2]) - self.assertListEqual(s[2].val, [1.0, 2.0, 3.0]) - - s = q.get_linear_components([2, '0', 5]) - self.assertEqual(len(s), 3) - self.assertListEqual(s[0].ind, [0, 1]) - self.assertListEqual(s[0].val, [1.0, 2.0]) - self.assertListEqual(s[1].ind, []) - self.assertListEqual(s[1].val, []) - self.assertListEqual(s[2].ind, [0, 1, 2, 3, 4]) - self.assertListEqual(s[2].val, [1.0, 2.0, 3.0, 4.0, 5.0]) + s_c = q.get_linear_components(8) + self.assertListEqual(s_c.ind, [0, 1, 2, 3, 4, 5, 6, 7]) + self.assertListEqual(s_c.val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]) + + s_c = q.get_linear_components('1', 3) + self.assertEqual(len(s_c), 3) + self.assertListEqual(s_c[0].ind, [0]) + self.assertListEqual(s_c[0].val, [1.0]) + self.assertListEqual(s_c[1].ind, [0, 1]) + self.assertListEqual(s_c[1].val, [1.0, 2.0]) + self.assertListEqual(s_c[2].ind, [0, 1, 2]) + self.assertListEqual(s_c[2].val, [1.0, 2.0, 3.0]) + + s_c = q.get_linear_components([2, '0', 5]) + self.assertEqual(len(s_c), 3) + self.assertListEqual(s_c[0].ind, [0, 1]) + self.assertListEqual(s_c[0].val, [1.0, 2.0]) + self.assertListEqual(s_c[1].ind, []) + self.assertListEqual(s_c[1].val, []) + self.assertListEqual(s_c[2].ind, [0, 1, 2, 3, 4]) + self.assertListEqual(s_c[2].val, [1.0, 2.0, 3.0, 4.0, 5.0]) q.delete(4, 9) - s = q.get_linear_components() - self.assertEqual(len(s), 4) - self.assertListEqual(s[0].ind, []) - self.assertListEqual(s[0].val, []) - self.assertListEqual(s[1].ind, [0]) - self.assertListEqual(s[1].val, [1.0]) - self.assertListEqual(s[2].ind, [0, 1]) - self.assertListEqual(s[2].val, [1.0, 2.0]) - self.assertListEqual(s[3].ind, [0, 1, 2]) - self.assertListEqual(s[3].val, [1.0, 2.0, 3.0]) + s_c = q.get_linear_components() + self.assertEqual(len(s_c), 4) + self.assertListEqual(s_c[0].ind, []) + self.assertListEqual(s_c[0].val, []) + self.assertListEqual(s_c[1].ind, [0]) + self.assertListEqual(s_c[1].val, [1.0]) + self.assertListEqual(s_c[2].ind, [0, 1]) + self.assertListEqual(s_c[2].val, [1.0, 2.0]) + self.assertListEqual(s_c[3].ind, [0, 1, 2]) + self.assertListEqual(s_c[3].val, [1.0, 2.0, 3.0]) def test_quad_num_nonzeros(self): """ test quad num non zeros """ @@ -243,34 +241,34 @@ def test_get_quadratic_components(self): self.assertListEqual(z, [0, 1]) self.assertEqual(q.get_num(), 2) - st = q.get_quadratic_components(1) - self.assertListEqual(st.ind1, [0, 1]) - self.assertListEqual(st.ind2, [0, 1]) - self.assertListEqual(st.val, [1.0, 2.0]) - - st = q.get_quadratic_components('q1', 1) - self.assertListEqual(st[0].ind1, [0]) - self.assertListEqual(st[0].ind2, [0]) - self.assertListEqual(st[0].val, [1.0]) - self.assertListEqual(st[1].ind1, [0, 1]) - self.assertListEqual(st[1].ind2, [0, 1]) - self.assertListEqual(st[1].val, [1.0, 2.0]) - - st = q.get_quadratic_components(['q2', 0]) - self.assertListEqual(st[0].ind1, [0, 1]) - self.assertListEqual(st[0].ind2, [0, 1]) - self.assertListEqual(st[0].val, [1.0, 2.0]) - self.assertListEqual(st[1].ind1, [0]) - self.assertListEqual(st[1].ind2, [0]) - self.assertListEqual(st[1].val, [1.0]) - - st = q.get_quadratic_components() - self.assertListEqual(st[0].ind1, [0]) - self.assertListEqual(st[0].ind2, [0]) - self.assertListEqual(st[0].val, [1.0]) - self.assertListEqual(st[1].ind1, [0, 1]) - self.assertListEqual(st[1].ind2, [0, 1]) - self.assertListEqual(st[1].val, [1.0, 2.0]) + s_t = q.get_quadratic_components(1) + self.assertListEqual(s_t.ind1, [0, 1]) + self.assertListEqual(s_t.ind2, [0, 1]) + self.assertListEqual(s_t.val, [1.0, 2.0]) + + s_t = q.get_quadratic_components('q1', 1) + self.assertListEqual(s_t[0].ind1, [0]) + self.assertListEqual(s_t[0].ind2, [0]) + self.assertListEqual(s_t[0].val, [1.0]) + self.assertListEqual(s_t[1].ind1, [0, 1]) + self.assertListEqual(s_t[1].ind2, [0, 1]) + self.assertListEqual(s_t[1].val, [1.0, 2.0]) + + s_t = q.get_quadratic_components(['q2', 0]) + self.assertListEqual(s_t[0].ind1, [0, 1]) + self.assertListEqual(s_t[0].ind2, [0, 1]) + self.assertListEqual(s_t[0].val, [1.0, 2.0]) + self.assertListEqual(s_t[1].ind1, [0]) + self.assertListEqual(s_t[1].ind2, [0]) + self.assertListEqual(s_t[1].val, [1.0]) + + s_t = q.get_quadratic_components() + self.assertListEqual(s_t[0].ind1, [0]) + self.assertListEqual(s_t[0].ind2, [0]) + self.assertListEqual(s_t[0].val, [1.0]) + self.assertListEqual(s_t[1].ind1, [0, 1]) + self.assertListEqual(s_t[1].ind2, [0, 1]) + self.assertListEqual(s_t[1].val, [1.0, 2.0]) def test_get_quadratic_components2(self): """ test get quadratic components 2 """ @@ -280,53 +278,53 @@ def test_get_quadratic_components2(self): [q.add(name=str(i), quad_expr=[range(i), range(i), [1.0 * (j + 1.0) for j in range(i)]]) for i in range(1, 11)] - s = q.get_quadratic_components(8) - self.assertListEqual(s.ind1, [0, 1, 2, 3, 4, 5, 6, 7, 8]) - self.assertListEqual(s.ind2, [0, 1, 2, 3, 4, 5, 6, 7, 8]) - self.assertListEqual(s.val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]) - - s = q.get_quadratic_components('1', 3) - self.assertEqual(len(s), 4) - self.assertListEqual(s[0].ind1, [0]) - self.assertListEqual(s[0].ind2, [0]) - self.assertListEqual(s[0].val, [1.0]) - self.assertListEqual(s[1].ind1, [0, 1]) - self.assertListEqual(s[1].ind2, [0, 1]) - self.assertListEqual(s[1].val, [1.0, 2.0]) - self.assertListEqual(s[2].ind1, [0, 1, 2]) - self.assertListEqual(s[2].ind2, [0, 1, 2]) - self.assertListEqual(s[2].val, [1.0, 2.0, 3.0]) - self.assertListEqual(s[3].ind1, [0, 1, 2, 3]) - self.assertListEqual(s[3].ind2, [0, 1, 2, 3]) - self.assertListEqual(s[3].val, [1.0, 2.0, 3.0, 4.0]) - - s = q.get_quadratic_components([2, '1', 5]) - self.assertEqual(len(s), 3) - self.assertListEqual(s[0].ind1, [0, 1, 2]) - self.assertListEqual(s[0].ind2, [0, 1, 2]) - self.assertListEqual(s[0].val, [1.0, 2.0, 3.0]) - self.assertListEqual(s[1].ind1, [0]) - self.assertListEqual(s[1].ind2, [0]) - self.assertListEqual(s[1].val, [1.0]) - self.assertListEqual(s[2].ind1, [0, 1, 2, 3, 4, 5]) - self.assertListEqual(s[2].ind2, [0, 1, 2, 3, 4, 5]) - self.assertListEqual(s[2].val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) + s_c = q.get_quadratic_components(8) + self.assertListEqual(s_c.ind1, [0, 1, 2, 3, 4, 5, 6, 7, 8]) + self.assertListEqual(s_c.ind2, [0, 1, 2, 3, 4, 5, 6, 7, 8]) + self.assertListEqual(s_c.val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]) + + s_c = q.get_quadratic_components('1', 3) + self.assertEqual(len(s_c), 4) + self.assertListEqual(s_c[0].ind1, [0]) + self.assertListEqual(s_c[0].ind2, [0]) + self.assertListEqual(s_c[0].val, [1.0]) + self.assertListEqual(s_c[1].ind1, [0, 1]) + self.assertListEqual(s_c[1].ind2, [0, 1]) + self.assertListEqual(s_c[1].val, [1.0, 2.0]) + self.assertListEqual(s_c[2].ind1, [0, 1, 2]) + self.assertListEqual(s_c[2].ind2, [0, 1, 2]) + self.assertListEqual(s_c[2].val, [1.0, 2.0, 3.0]) + self.assertListEqual(s_c[3].ind1, [0, 1, 2, 3]) + self.assertListEqual(s_c[3].ind2, [0, 1, 2, 3]) + self.assertListEqual(s_c[3].val, [1.0, 2.0, 3.0, 4.0]) + + s_c = q.get_quadratic_components([2, '1', 5]) + self.assertEqual(len(s_c), 3) + self.assertListEqual(s_c[0].ind1, [0, 1, 2]) + self.assertListEqual(s_c[0].ind2, [0, 1, 2]) + self.assertListEqual(s_c[0].val, [1.0, 2.0, 3.0]) + self.assertListEqual(s_c[1].ind1, [0]) + self.assertListEqual(s_c[1].ind2, [0]) + self.assertListEqual(s_c[1].val, [1.0]) + self.assertListEqual(s_c[2].ind1, [0, 1, 2, 3, 4, 5]) + self.assertListEqual(s_c[2].ind2, [0, 1, 2, 3, 4, 5]) + self.assertListEqual(s_c[2].val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) q.delete(4, 9) - s = q.get_quadratic_components() - self.assertEqual(len(s), 4) - self.assertListEqual(s[0].ind1, [0]) - self.assertListEqual(s[0].ind2, [0]) - self.assertListEqual(s[0].val, [1.0]) - self.assertListEqual(s[1].ind1, [0, 1]) - self.assertListEqual(s[1].ind2, [0, 1]) - self.assertListEqual(s[1].val, [1.0, 2.0]) - self.assertListEqual(s[2].ind1, [0, 1, 2]) - self.assertListEqual(s[2].ind2, [0, 1, 2]) - self.assertListEqual(s[2].val, [1.0, 2.0, 3.0]) - self.assertListEqual(s[3].ind1, [0, 1, 2, 3]) - self.assertListEqual(s[3].ind2, [0, 1, 2, 3]) - self.assertListEqual(s[3].val, [1.0, 2.0, 3.0, 4.0]) + s_c = q.get_quadratic_components() + self.assertEqual(len(s_c), 4) + self.assertListEqual(s_c[0].ind1, [0]) + self.assertListEqual(s_c[0].ind2, [0]) + self.assertListEqual(s_c[0].val, [1.0]) + self.assertListEqual(s_c[1].ind1, [0, 1]) + self.assertListEqual(s_c[1].ind2, [0, 1]) + self.assertListEqual(s_c[1].val, [1.0, 2.0]) + self.assertListEqual(s_c[2].ind1, [0, 1, 2]) + self.assertListEqual(s_c[2].ind2, [0, 1, 2]) + self.assertListEqual(s_c[2].val, [1.0, 2.0, 3.0]) + self.assertListEqual(s_c[3].ind1, [0, 1, 2, 3]) + self.assertListEqual(s_c[3].ind2, [0, 1, 2, 3]) + self.assertListEqual(s_c[3].val, [1.0, 2.0, 3.0, 4.0]) def test_get_names(self): """ test get names """ diff --git a/test/optimization/test_variables.py b/test/optimization/test_variables.py index 2046425a1c..f62dd4482d 100644 --- a/test/optimization/test_variables.py +++ b/test/optimization/test_variables.py @@ -15,16 +15,13 @@ """ Test VariablesInterface """ import unittest -from qiskit.optimization.problems import OptimizationProblem from test.optimization.optimization_test_case import QiskitOptimizationTestCase +from qiskit.optimization.problems import OptimizationProblem class TestVariables(QiskitOptimizationTestCase): """Test VariablesInterface.""" - def setUp(self): - super().setUp() - def test_type(self): """ test type """ op = OptimizationProblem() @@ -47,43 +44,43 @@ def test_initial(self): def test_get_num(self): """ test get num """ op = OptimizationProblem() - t = op.variables.type - op.variables.add(types=[t.continuous, t.binary, t.integer]) + typ = op.variables.type + op.variables.add(types=[typ.continuous, typ.binary, typ.integer]) self.assertEqual(op.variables.get_num(), 3) def test_get_num_continuous(self): """ test get num continuous """ op = OptimizationProblem() - t = op.variables.type - op.variables.add(types=[t.continuous, t.binary, t.integer]) + typ = op.variables.type + op.variables.add(types=[typ.continuous, typ.binary, typ.integer]) self.assertEqual(op.variables.get_num_continuous(), 1) def test_get_num_integer(self): """ test get num integer """ op = OptimizationProblem() - t = op.variables.type - op.variables.add(types=[t.continuous, t.binary, t.integer]) + typ = op.variables.type + op.variables.add(types=[typ.continuous, typ.binary, typ.integer]) self.assertEqual(op.variables.get_num_integer(), 1) def test_get_num_binary(self): """ test get num binary """ op = OptimizationProblem() - t = op.variables.type - op.variables.add(types=[t.semi_continuous, t.binary, t.integer]) + typ = op.variables.type + op.variables.add(types=[typ.semi_continuous, typ.binary, typ.integer]) self.assertEqual(op.variables.get_num_binary(), 1) def test_get_num_semicontinuous(self): """ test get num semi continuous """ op = OptimizationProblem() - t = op.variables.type - op.variables.add(types=[t.semi_continuous, t.semi_integer, t.semi_integer]) + typ = op.variables.type + op.variables.add(types=[typ.semi_continuous, typ.semi_integer, typ.semi_integer]) self.assertEqual(op.variables.get_num_semicontinuous(), 1) def test_get_num_semiinteger(self): """ test get num semi integer """ op = OptimizationProblem() - t = op.variables.type - op.variables.add(types=[t.semi_continuous, t.semi_integer, t.semi_integer]) + typ = op.variables.type + op.variables.add(types=[typ.semi_continuous, typ.semi_integer, typ.semi_integer]) self.assertEqual(op.variables.get_num_semiinteger(), 2) def test_add(self): @@ -142,8 +139,8 @@ def test_set_upper_bounds(self): def test_set_names(self): """ test set names """ op = OptimizationProblem() - t = op.variables.type - op.variables.add(types=[t.continuous, t.binary, t.integer]) + typ = op.variables.type + op.variables.add(types=[typ.continuous, typ.binary, typ.integer]) op.variables.set_names(0, "first") op.variables.set_names([(2, "third"), (1, "second")]) self.assertListEqual(op.variables.get_names(), ['first', 'second', 'third']) @@ -195,13 +192,13 @@ def test_get_names(self): self.assertListEqual(op.variables.get_names(), ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9']) - def test_set_types(self): - """ test set types """ + def test_set_types2(self): + """ test set types 2 """ op = OptimizationProblem() - t = op.variables.type + typ = op.variables.type op.variables.add(names=[str(i) for i in range(5)], - types=[t.continuous, t.integer, - t.binary, t.semi_continuous, t.semi_integer]) + types=[typ.continuous, typ.integer, + typ.binary, typ.semi_continuous, typ.semi_integer]) self.assertEqual(op.variables.get_num(), 5) self.assertEqual(op.variables.get_types(3), 'S') From 8ec5fb31f41b7f5a84e19237ca00c3daddd4b5cb Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Wed, 1 Apr 2020 19:34:18 -0400 Subject: [PATCH 140/323] fix lint --- ...qiskit.optimization.applications.ising.rst | 6 ++++ docs/apidocs/qiskit.optimization.ising.rst | 6 ---- qiskit/finance/applications/ising/__init__.py | 4 ++- .../algorithms/cobyla_optimizer.py | 1 + qiskit/optimization/applications/__init__.py | 31 +++++++++++++++++++ .../problems/linear_constraint.py | 2 ++ qiskit/optimization/problems/objective.py | 5 ++- qiskit/optimization/problems/problem_type.py | 6 +++- .../problems/quadratic_constraint.py | 22 +++++++------ qiskit/optimization/problems/variables.py | 24 +++++++++----- .../optimization/results/quality_metrics.py | 5 ++- qiskit/optimization/results/solution.py | 9 ++++-- qiskit/optimization/utils/base.py | 23 +++++++++----- .../utils/eigenvector_to_solutions.py | 7 +++-- qiskit/optimization/utils/helpers.py | 3 +- .../utils/qiskit_optimization_error.py | 2 ++ test/optimization/test_linear_constraints.py | 2 +- test/optimization/test_objective.py | 2 +- .../optimization/test_optimization_problem.py | 1 + .../test_quadratic_constraints.py | 30 +++++++++--------- 20 files changed, 134 insertions(+), 57 deletions(-) create mode 100644 docs/apidocs/qiskit.optimization.applications.ising.rst delete mode 100644 docs/apidocs/qiskit.optimization.ising.rst create mode 100644 qiskit/optimization/applications/__init__.py diff --git a/docs/apidocs/qiskit.optimization.applications.ising.rst b/docs/apidocs/qiskit.optimization.applications.ising.rst new file mode 100644 index 0000000000..ff1ebb7629 --- /dev/null +++ b/docs/apidocs/qiskit.optimization.applications.ising.rst @@ -0,0 +1,6 @@ +.. _qiskit-optimization-applications-ising: + +.. automodule:: qiskit.optimization.applications.ising + :no-members: + :no-inherited-members: + :no-special-members: diff --git a/docs/apidocs/qiskit.optimization.ising.rst b/docs/apidocs/qiskit.optimization.ising.rst deleted file mode 100644 index 353fbb5bd4..0000000000 --- a/docs/apidocs/qiskit.optimization.ising.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _qiskit-optimization-ising: - -.. automodule:: qiskit.optimization.ising - :no-members: - :no-inherited-members: - :no-special-members: diff --git a/qiskit/finance/applications/ising/__init__.py b/qiskit/finance/applications/ising/__init__.py index f5e4085191..3e0f201270 100644 --- a/qiskit/finance/applications/ising/__init__.py +++ b/qiskit/finance/applications/ising/__init__.py @@ -12,9 +12,11 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. + """ +======================================================= Ising Models (:mod:`qiskit.finance.applications.ising`) -========================================== +======================================================= Ising models for finance problems .. currentmodule:: qiskit.finance.applications.ising diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index 52e5eb5f59..f52df4d696 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -141,6 +141,7 @@ def objective(x): # add variable lower and upper bounds lbs = problem.variables.get_lower_bounds() ubs = problem.variables.get_upper_bounds() + # pylint: disable=invalid-sequence-index for i in range(num_vars): if lbs[i] > -infinity: constraints += [lambda x, lbs=lbs, i=i: x - lbs[i]] diff --git a/qiskit/optimization/applications/__init__.py b/qiskit/optimization/applications/__init__.py new file mode 100644 index 0000000000..abeb214e5d --- /dev/null +++ b/qiskit/optimization/applications/__init__.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" +================================================================================= +Optimization application stack for Aqua (:mod:`qiskit.optimization.applications`) +================================================================================= +This is the Optimization applications domain logic.... + +.. currentmodule:: qiskit.optimization.applications + +Submodules +========== + +.. autosummary:: + :toctree: + + ising + +""" diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index 3e24101578..c51c7693f8 100644 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -12,6 +12,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""Methods for adding, modifying, and querying linear constraints.""" + import copy from collections.abc import Sequence diff --git a/qiskit/optimization/problems/objective.py b/qiskit/optimization/problems/objective.py index 1c881a2eee..9dd3c6db28 100644 --- a/qiskit/optimization/problems/objective.py +++ b/qiskit/optimization/problems/objective.py @@ -12,6 +12,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +""" Problems Objective module""" + import copy import numbers from collections.abc import Sequence @@ -26,7 +28,7 @@ logger = getLogger(__name__) -class ObjSense(object): +class ObjSense: """Constants defining the sense of the objective function.""" maximize = CPX_MAX minimize = CPX_MIN @@ -47,6 +49,7 @@ def __getitem__(self, item): return 'maximize' if item == CPX_MIN: return 'minimize' + return None class ObjectiveInterface(BaseInterface): diff --git a/qiskit/optimization/problems/problem_type.py b/qiskit/optimization/problems/problem_type.py index 81ad8296e7..ce6a4b1b59 100644 --- a/qiskit/optimization/problems/problem_type.py +++ b/qiskit/optimization/problems/problem_type.py @@ -12,6 +12,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +""" Types of problems """ + # pylint: disable=invalid-name CPXPROB_LP = 0 @@ -27,7 +29,7 @@ CPXPROB_NODEQCP = 12 -class ProblemType(object): +class ProblemType: """ Types of problems the OptimizationProblem class can encapsulate. These types are compatible with those of IBM ILOG CPLEX. @@ -59,6 +61,7 @@ def __getitem__(self, item): >>> op.problem_type[0] 'LP' """ + # pylint: disable=too-many-return-statements if item == CPXPROB_LP: return 'LP' if item == CPXPROB_MILP: @@ -81,3 +84,4 @@ def __getitem__(self, item): return 'MIQCP' if item == CPXPROB_NODEQCP: return 'node_QCP' + return None diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index 527bc84869..b60904b333 100644 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -12,6 +12,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""Methods for adding, modifying, and querying quadratic constraints.""" + import copy from collections.abc import Sequence from logging import getLogger @@ -67,7 +69,7 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): Takes up to five keyword arguments: Args: - lin_expr : either a SparsePair or a list of two lists specifying + lin_expr(List): either a SparsePair or a list of two lists specifying the linear component of the constraint. Note @@ -76,7 +78,7 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): or a combination of index and name, an exception will be raised. - quad_expr : either a SparseTriple or a list of three lists + quad_expr(List): either a SparseTriple or a list of three lists specifying the quadratic component of the constraint. Note @@ -85,14 +87,14 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): names, or a combination of indices and names, an exception will be raised. - sense : either "L", "G", or "E" + sense(str): either "L", "G", or "E" - rhs : a float specifying the righthand side of the constraint. + rhs(float): a float specifying the righthand side of the constraint. - name : the name of the constraint. + name(str) : the name of the constraint. Returns: - The index of the added quadratic constraint. + int: The index of the added quadratic constraint. Raises: QiskitOptimizationError: if invalid argument is given. @@ -133,7 +135,7 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): for i, val in zip(ind, val): i_2 = self._varindex(i) if i_2 in lin_expr_dict: - logger.warning('lin_expr contains duplicate index: {}'.format(i)) + logger.warning('lin_expr contains duplicate index: %d', i) lin_expr_dict[i_2] = val self._lin_expr.append(lin_expr_dict) @@ -156,14 +158,14 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): if i_2 < j_2: i_2, j_2 = j_2, i_2 if (i_2, j_2) in quad_expr_dict: - logger.warning('quad_expr contains duplicate index: {} {}'.format(i, j)) + logger.warning('quad_expr contains duplicate index: %d %d', i, j) quad_expr_dict[i_2, j_2] = val self._quad_expr.append(quad_expr_dict) if sense not in ['L', 'G', 'E']: raise QiskitOptimizationError('Invalid sense: {}'.format(sense)) - else: - self._senses.append(sense) + + self._senses.append(sense) self._rhs.append(rhs) return self._index.convert(name) diff --git a/qiskit/optimization/problems/variables.py b/qiskit/optimization/problems/variables.py index 92123e01c0..0f4e3d30f0 100644 --- a/qiskit/optimization/problems/variables.py +++ b/qiskit/optimization/problems/variables.py @@ -12,6 +12,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +""" Constants defining variable types """ + import copy from qiskit.optimization import infinity @@ -28,7 +30,7 @@ CPX_SEMIINT = 'N' -class VarTypes(object): +class VarTypes: """Constants defining variable types These constants are compatible with IBM ILOG CPLEX. @@ -63,6 +65,7 @@ def __getitem__(self, item): return 'semi_integer' if item == CPX_SEMICONT: return 'semi_continuous' + return None class VariablesInterface(BaseInterface): @@ -208,22 +211,24 @@ def add(self, obj=None, lb=None, ub=None, types="", names=None, columns=None): Use `objective` and `linear_constraint` instead. Args: - obj: a list of floats specifying the linear objective coefficients of the variables. + obj(List): a list of floats specifying the linear objective coefficients + of the variables. - lb: a list of floats specifying the lower bounds on the variables. + lb(List): a list of floats specifying the lower bounds on the variables. - ub: a list of floats specifying the upper bounds on the variables. + ub(List): a list of floats specifying the upper bounds on the variables. - types: must be either a list of single-character strings or a string containing + types(List): must be either a list of single-character strings or a string containing the types of the variables. Note If types is specified, the problem type will be a MIP, even if all variables are specified to be continuous. - names: a list of strings. + names(List): a list of strings. - columns: may be either a list of sparse vectors or a matrix in list-of-lists format. + columns(List): may be either a list of sparse vectors or a matrix + in list-of-lists format. Note The entries of columns must not contain duplicate indices. @@ -232,7 +237,10 @@ def add(self, obj=None, lb=None, ub=None, types="", names=None, columns=None): an exception will be raised. Returns: - an iterator containing the indices of the added variables. + List: an iterator containing the indices of the added variables. + + Raises: + QiskitOptimizationError: Invalid arguments Example usage: diff --git a/qiskit/optimization/results/quality_metrics.py b/qiskit/optimization/results/quality_metrics.py index 4a33b52c90..39fc464486 100755 --- a/qiskit/optimization/results/quality_metrics.py +++ b/qiskit/optimization/results/quality_metrics.py @@ -12,8 +12,10 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +""" Quality Metrics """ -class QualityMetrics(object): + +class QualityMetrics: """A class containing measures of the quality of a solution. The __str__ method of this class prints all available measures of @@ -72,6 +74,7 @@ class QualityMetrics(object): """ def __init__(self, soln=-1): + # pylint: disable=unused-argument self._tostring = "" def __str__(self): diff --git a/qiskit/optimization/results/solution.py b/qiskit/optimization/results/solution.py index 56ad576a63..b08b33e20d 100755 --- a/qiskit/optimization/results/solution.py +++ b/qiskit/optimization/results/solution.py @@ -12,6 +12,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""Methods for querying the solution to an optimization problem.""" from qiskit.optimization.results.quality_metrics import QualityMetrics from qiskit.optimization.results.solution_status import SolutionStatus @@ -36,7 +37,8 @@ def __init__(self): as Cplex.solution. This constructor is not meant to be used externally. """ - super(SolutionInterface, self).__init__() + # pylint: disable=useless-super-delegation + super().__init__() # self.progress = ProgressInterface(self) # """See `ProgressInterface()` """ # self.MIP = MIPSolutionInterface(self) @@ -93,6 +95,7 @@ def get_status_string(self, status_code=None): >>> c.solution.get_status_string() 'optimal' """ + # pylint: disable=unused-argument # if status_code is None: # status_code = self.get_status() return None @@ -145,6 +148,7 @@ def get_values(self, *args): >>> c.solution.get_values([0, 4, 5]) [25.5, 0.0, 80.0] """ + # pylint: disable=unused-argument return None def get_integer_quality(self, which): @@ -249,4 +253,5 @@ def write(self, filename): >>> c.solve() >>> c.solution.write("lpex.sol") """ - None + # pylint: disable=unused-argument + pass diff --git a/qiskit/optimization/utils/base.py b/qiskit/optimization/utils/base.py index 64b3298baa..ed140e9ac7 100644 --- a/qiskit/optimization/utils/base.py +++ b/qiskit/optimization/utils/base.py @@ -12,15 +12,19 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""Common methods for sub-interfaces within Qiskit Optimization.""" from typing import Callable, Sequence, Union, Any, List - +from abc import ABC from qiskit.optimization.utils.helpers import NameIndex from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError -class BaseInterface(object): - """Common methods for sub-interfaces within Qiskit Optimization.""" +class BaseInterface(ABC): + """ + Abstract class with common methods + for sub-interfaces within Qiskit Optimization. + """ def __init__(self): """Creates a new BaseInterface. @@ -28,8 +32,6 @@ def __init__(self): This class is not meant to be instantiated directly nor used externally. """ - if type(self) == BaseInterface: - raise TypeError("BaseInterface must be sub-classed") self._index = NameIndex() def get_indices(self, *name) -> Union[int, List[int]]: @@ -65,7 +67,7 @@ def _setter(setfunc: Callable[[Union[str, int], Any], None], *args) -> None: """A generic setter method Args: - setfunc(index, val): A setter function of two parameters: `index` and `val`. + setfunc: A setter function of two parameters: `index` and `val`. Since `index` can be a string, users need to convert it into an appropriate index by applying `NameIndex.convert`. *args: A pair of index and value or a list of pairs of index and value. @@ -73,6 +75,10 @@ def _setter(setfunc: Callable[[Union[str, int], Any], None], *args) -> None: Returns: None + + Raises: + QiskitOptimizationError: Invalid argument + """ # check for all elements in args whether they are types if len(args) == 1 and \ @@ -93,8 +99,11 @@ def _getter(getfunc: Callable[[int], Any], *args) -> Any: `index` should be already converted by `NameIndex.convert`. *args: A single index or a list of indices. `getfunc` is invoked with args. - Returns: if `args` is a single index, this returns a single value generated by `getfunc`. + Returns: + List: if `args` is a single index, this returns a single value generated by `getfunc`. If `args` is a list of indices, this returns a list of values. + Raises: + QiskitOptimizationError: Invalid argument """ if len(args) == 0: raise QiskitOptimizationError('Empty arguments should be handled in the caller') diff --git a/qiskit/optimization/utils/eigenvector_to_solutions.py b/qiskit/optimization/utils/eigenvector_to_solutions.py index e25adc76b5..06ef7c4e6e 100644 --- a/qiskit/optimization/utils/eigenvector_to_solutions.py +++ b/qiskit/optimization/utils/eigenvector_to_solutions.py @@ -15,11 +15,10 @@ """Auxiliary methods to translate eigenvectors into optimization results.""" -from qiskit.aqua.operators import MatrixOperator from typing import Union, List, Tuple +import numpy from qiskit import QuantumCircuit, BasicAer, execute from qiskit.aqua.operators import WeightedPauliOperator, MatrixOperator -import numpy def eigenvector_to_solutions(eigenvector: Union[dict, numpy.ndarray], @@ -42,6 +41,10 @@ def eigenvector_to_solutions(eigenvector: Union[dict, numpy.ndarray], For each computational basis state contained in the eigenvector, return the basis state as bitstring along with the operator evaluated at that bitstring and the probability of sampling this bitstring from the eigenvector. + + Raises: + TypeError: Invalid Argument + """ solutions = [] if isinstance(eigenvector, dict): diff --git a/qiskit/optimization/utils/helpers.py b/qiskit/optimization/utils/helpers.py index 1d3c2045cb..4d0249134b 100644 --- a/qiskit/optimization/utils/helpers.py +++ b/qiskit/optimization/utils/helpers.py @@ -12,13 +12,14 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +""" Helper Utilities """ from typing import Union, List, Sequence from qiskit.optimization.utils import QiskitOptimizationError -class NameIndex(object): +class NameIndex: """Convert a string name into an integer index. This is used for the implementation of `BaseInterface.get_indices`. """ diff --git a/qiskit/optimization/utils/qiskit_optimization_error.py b/qiskit/optimization/utils/qiskit_optimization_error.py index 5a50d4bfd9..9d5b471707 100644 --- a/qiskit/optimization/utils/qiskit_optimization_error.py +++ b/qiskit/optimization/utils/qiskit_optimization_error.py @@ -12,6 +12,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +""" Optimization Exception """ + class QiskitOptimizationError(Exception): """Class for errors returned by Qiskit Optimization libraries functions. diff --git a/test/optimization/test_linear_constraints.py b/test/optimization/test_linear_constraints.py index 735f029c49..c4cd31ee1f 100644 --- a/test/optimization/test_linear_constraints.py +++ b/test/optimization/test_linear_constraints.py @@ -245,7 +245,7 @@ def test_get_names(self): def test_get_histogram(self): """ test get histogram """ op = OptimizationProblem() - self.assertRaises(NotImplementedError, lambda: op.linear_constraints.get_histogram()) + self.assertRaises(NotImplementedError, op.linear_constraints.get_histogram()) if __name__ == '__main__': diff --git a/test/optimization/test_objective.py b/test/optimization/test_objective.py index 3f8e6adf3d..d6a9d843a0 100644 --- a/test/optimization/test_objective.py +++ b/test/optimization/test_objective.py @@ -64,7 +64,7 @@ def test_set_empty_quadratic(self): """ test set empty quadratic """ op = OptimizationProblem() op.objective.set_quadratic([]) - self.assertRaises(TypeError, lambda: op.objective.set_quadratic()) + self.assertRaises(TypeError, op.objective.set_quadratic()) def test_set_quadratic(self): """ test set quadratic """ diff --git a/test/optimization/test_optimization_problem.py b/test/optimization/test_optimization_problem.py index f5ebb29d20..95aaf0cc05 100644 --- a/test/optimization/test_optimization_problem.py +++ b/test/optimization/test_optimization_problem.py @@ -99,6 +99,7 @@ def __init__(self): def write(self, byt): """ write """ + # pylint: disable=unused-argument self.was_called = True pass diff --git a/test/optimization/test_quadratic_constraints.py b/test/optimization/test_quadratic_constraints.py index 0f0cd72e77..95cdb18cd1 100644 --- a/test/optimization/test_quadratic_constraints.py +++ b/test/optimization/test_quadratic_constraints.py @@ -131,9 +131,9 @@ def test_get_linear_num_nonzeros(self): op.variables.add(names=[str(i) for i in range(11)], types="B" * 11) q = op.quadratic_constraints n = 10 - [q.add(name=str(i), - lin_expr=[range(i), [1.0 * (j + 1.0) for j in range(i)]]) - for i in range(n)] + _ = [q.add(name=str(i), + lin_expr=[range(i), [1.0 * (j + 1.0) for j in range(i)]]) + for i in range(n)] self.assertEqual(q.get_num(), n) self.assertEqual(q.get_linear_num_nonzeros(8), 8) self.assertListEqual(q.get_linear_num_nonzeros('1', 3), [1, 2, 3]) @@ -179,9 +179,9 @@ def test_get_linear_components2(self): op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(11)], types="B" * 11) q = op.quadratic_constraints - [q.add(name=str(i), - lin_expr=[range(i), [1.0 * (j + 1.0) for j in range(i)]]) - for i in range(10)] + _ = [q.add(name=str(i), + lin_expr=[range(i), [1.0 * (j + 1.0) for j in range(i)]]) + for i in range(10)] s_c = q.get_linear_components(8) self.assertListEqual(s_c.ind, [0, 1, 2, 3, 4, 5, 6, 7]) self.assertListEqual(s_c.val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]) @@ -221,9 +221,9 @@ def test_quad_num_nonzeros(self): op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(11)]) q = op.quadratic_constraints - [q.add(name=str(i), - quad_expr=[range(i), range(i), [1.0 * (j + 1.0) for j in range(i)]]) - for i in range(1, 11)] + _ = [q.add(name=str(i), + quad_expr=[range(i), range(i), [1.0 * (j + 1.0) for j in range(i)]]) + for i in range(1, 11)] self.assertEqual(q.get_num(), 10) self.assertEqual(q.get_quad_num_nonzeros(8), 9) self.assertListEqual(q.get_quad_num_nonzeros('1', 2), [1, 2, 3]) @@ -275,9 +275,9 @@ def test_get_quadratic_components2(self): op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(11)]) q = op.quadratic_constraints - [q.add(name=str(i), - quad_expr=[range(i), range(i), [1.0 * (j + 1.0) for j in range(i)]]) - for i in range(1, 11)] + _ = [q.add(name=str(i), + quad_expr=[range(i), range(i), [1.0 * (j + 1.0) for j in range(i)]]) + for i in range(1, 11)] s_c = q.get_quadratic_components(8) self.assertListEqual(s_c.ind1, [0, 1, 2, 3, 4, 5, 6, 7, 8]) self.assertListEqual(s_c.ind2, [0, 1, 2, 3, 4, 5, 6, 7, 8]) @@ -331,9 +331,9 @@ def test_get_names(self): op = OptimizationProblem() op.variables.add(names=[str(i) for i in range(11)]) q = op.quadratic_constraints - [q.add(name="q" + str(i), - quad_expr=[range(i), range(i), [1.0 * (j + 1.0) for j in range(i)]]) - for i in range(1, 11)] + _ = [q.add(name="q" + str(i), + quad_expr=[range(i), range(i), [1.0 * (j + 1.0) for j in range(i)]]) + for i in range(1, 11)] self.assertEqual(q.get_num(), 10) self.assertEqual(q.get_names(8), 'q9') self.assertListEqual(q.get_names(1, 3), ['q2', 'q3', 'q4']) From 115fc6ec0a91b69024ded005ca685030caf08140 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Wed, 1 Apr 2020 19:52:00 -0400 Subject: [PATCH 141/323] fix html --- qiskit/optimization/applications/ising/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/optimization/applications/ising/__init__.py b/qiskit/optimization/applications/ising/__init__.py index dc8bc60a5c..b9869755a3 100644 --- a/qiskit/optimization/applications/ising/__init__.py +++ b/qiskit/optimization/applications/ising/__init__.py @@ -17,7 +17,7 @@ ============================================================ Ising models for optimization problems -.. currentmodule:: qiskit.optimization.ising +.. currentmodule:: qiskit.optimization.applications.ising Ising Models ============ From 0d1dda7d25d97779b7900fa3bebffa671eef6f7a Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Wed, 1 Apr 2020 21:46:18 -0400 Subject: [PATCH 142/323] fix lint --- .../qiskit.finance.applications.ising.rst | 6 ---- qiskit/finance/__init__.py | 3 +- qiskit/finance/applications/__init__.py | 18 ---------- qiskit/optimization/__init__.py | 23 +++++++------ qiskit/optimization/algorithms/__init__.py | 23 ++++++------- .../algorithms/optimization_algorithm.py | 4 +-- qiskit/optimization/applications/__init__.py | 18 ---------- qiskit/optimization/converters/__init__.py | 24 +++++-------- qiskit/optimization/problems/__init__.py | 34 ++++++++++++------- qiskit/optimization/results/__init__.py | 4 +-- qiskit/optimization/utils/__init__.py | 21 ++++++------ 11 files changed, 70 insertions(+), 108 deletions(-) delete mode 100644 docs/apidocs/qiskit.finance.applications.ising.rst diff --git a/docs/apidocs/qiskit.finance.applications.ising.rst b/docs/apidocs/qiskit.finance.applications.ising.rst deleted file mode 100644 index 6a2bf3699a..0000000000 --- a/docs/apidocs/qiskit.finance.applications.ising.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _qiskit-finance-applications-ising: - -.. automodule:: qiskit.finance.applications.ising - :no-members: - :no-inherited-members: - :no-special-members: diff --git a/qiskit/finance/__init__.py b/qiskit/finance/__init__.py index dbcc811de7..07c208db61 100644 --- a/qiskit/finance/__init__.py +++ b/qiskit/finance/__init__.py @@ -23,7 +23,8 @@ ========== .. autosummary:: - :toctree: + :toctree: ../stubs/ + :nosignatures: applications components diff --git a/qiskit/finance/applications/__init__.py b/qiskit/finance/applications/__init__.py index 3786d60a5f..4317685c28 100644 --- a/qiskit/finance/applications/__init__.py +++ b/qiskit/finance/applications/__init__.py @@ -11,21 +11,3 @@ # 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. - -""" -======================================================================= -Finance application stack for Aqua (:mod:`qiskit.finance.applications`) -======================================================================= -This is the finance applications domain logic.... - -.. currentmodule:: qiskit.finance.applications - -Submodules -========== - -.. autosummary:: - :toctree: - - ising - -""" diff --git a/qiskit/optimization/__init__.py b/qiskit/optimization/__init__.py index f5b88e22dd..e9890f558b 100755 --- a/qiskit/optimization/__init__.py +++ b/qiskit/optimization/__init__.py @@ -25,18 +25,19 @@ .. autosummary:: :toctree: - OptimizationProblem - """ -from qiskit.optimization.infinity import infinity # must be at the top of the file -from qiskit.optimization.utils import QiskitOptimizationError -from qiskit.optimization.problems.linear_constraint import LinearConstraintInterface -from qiskit.optimization.problems.objective import ObjSense, ObjectiveInterface -from qiskit.optimization.problems.optimization_problem import OptimizationProblem +from .utils import QiskitOptimizationError +from .problems import OptimizationProblem from ._logging import (get_qiskit_optimization_logging, set_qiskit_optimization_logging) - -__all__ = ["OptimizationProblem", "QiskitOptimizationError", "LinearConstraintInterface", - "ObjSense", "ObjectiveInterface", "infinity", 'get_qiskit_optimization_logging', - 'set_qiskit_optimization_logging'] +from .infinity import infinity +from .util import get_qubo_solutions + +__all__ = ['OptimizationProblem', + 'QiskitOptimizationError', + 'get_qiskit_optimization_logging', + 'set_qiskit_optimization_logging', + 'infinity', + 'get_qubo_solutions' + ] diff --git a/qiskit/optimization/algorithms/__init__.py b/qiskit/optimization/algorithms/__init__.py index eac8aac70d..5ff48af2fd 100644 --- a/qiskit/optimization/algorithms/__init__.py +++ b/qiskit/optimization/algorithms/__init__.py @@ -13,11 +13,11 @@ # that they have been altered from the originals. """ -======================================================== -Optimization stack for Aqua (:mod:`qiskit.optimization`) -======================================================== +=================================================================== +Optimization stack for Aqua (:mod:`qiskit.optimization.algorithms`) +=================================================================== -Algorithms for optimization problems. +Algorithms for optimization algorithms. .. currentmodule:: qiskit.optimization.algorithms @@ -46,14 +46,13 @@ """ -from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer -from qiskit.optimization.algorithms.optimization_algorithm import OptimizationAlgorithm -from qiskit.optimization.algorithms.cplex_optimizer import CplexOptimizer -from qiskit.optimization.algorithms.cobyla_optimizer import CobylaOptimizer -from qiskit.optimization.algorithms.minimum_eigen_optimizer import MinimumEigenOptimizer -from qiskit.optimization.algorithms.recursive_minimum_eigen_optimizer import\ - RecursiveMinimumEigenOptimizer -from qiskit.optimization.algorithms.grover_minimum_finder import GroverMinimumFinder +from .admm_optimizer import ADMMOptimizer +from .optimization_algorithm import OptimizationAlgorithm +from .cplex_optimizer import CplexOptimizer +from .cobyla_optimizer import CobylaOptimizer +from .minimum_eigen_optimizer import MinimumEigenOptimizer +from .recursive_minimum_eigen_optimizer import RecursiveMinimumEigenOptimizer +from .grover_minimum_finder import GroverMinimumFinder __all__ = ["ADMMOptimizer", "OptimizationAlgorithm", "CplexOptimizer", "CobylaOptimizer", "MinimumEigenOptimizer", "RecursiveMinimumEigenOptimizer", "GroverMinimumFinder"] diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index de43f92a7c..49f6a167b6 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -15,7 +15,7 @@ """An abstract class for optimization algorithms in Qiskit Optimization.""" -from abc import abstractmethod +from abc import ABC, abstractmethod from typing import Optional @@ -23,7 +23,7 @@ from ..results.optimization_result import OptimizationResult -class OptimizationAlgorithm: +class OptimizationAlgorithm(ABC): """An abstract class for optimization algorithms in Qiskit Optimization.""" @abstractmethod diff --git a/qiskit/optimization/applications/__init__.py b/qiskit/optimization/applications/__init__.py index abeb214e5d..4317685c28 100644 --- a/qiskit/optimization/applications/__init__.py +++ b/qiskit/optimization/applications/__init__.py @@ -11,21 +11,3 @@ # 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. - -""" -================================================================================= -Optimization application stack for Aqua (:mod:`qiskit.optimization.applications`) -================================================================================= -This is the Optimization applications domain logic.... - -.. currentmodule:: qiskit.optimization.applications - -Submodules -========== - -.. autosummary:: - :toctree: - - ising - -""" diff --git a/qiskit/optimization/converters/__init__.py b/qiskit/optimization/converters/__init__.py index b80deb5224..7b2f90b8e4 100644 --- a/qiskit/optimization/converters/__init__.py +++ b/qiskit/optimization/converters/__init__.py @@ -14,7 +14,7 @@ """ ======================================================== -Optimization stack for Aqua (:mod:`qiskit.optimization`) +Optimization stack for Aqua (:mod:`qiskit.optimization.converters`) ======================================================== .. currentmodule:: qiskit.optimization.converters @@ -24,24 +24,18 @@ """ -from qiskit.optimization.converters.optimization_problem_to_negative_value_oracle import\ - OptimizationProblemToNegativeValueOracle -from qiskit.optimization.converters.inequality_to_equality_converter import\ - InequalityToEqualityConverter -from qiskit.optimization.converters.integer_to_binary_converter import\ - IntegerToBinaryConverter -from qiskit.optimization.converters.optimization_problem_to_operator import\ - OptimizationProblemToOperator -from qiskit.optimization.converters.penalize_linear_equality_constraints import\ - PenalizeLinearEqualityConstraints -from qiskit.optimization.converters.optimization_problem_to_qubo import\ - OptimizationProblemToQubo +from .inequality_to_equality_converter import InequalityToEqualityConverter +from .integer_to_binary_converter import IntegerToBinaryConverter +from .optimization_problem_to_negative_value_oracle import OptimizationProblemToNegativeValueOracle +from .optimization_problem_to_operator import OptimizationProblemToOperator +from .optimization_problem_to_qubo import OptimizationProblemToQubo +from .penalize_linear_equality_constraints import PenalizeLinearEqualityConstraints __all__ = [ "InequalityToEqualityConverter", "IntegerToBinaryConverter", - "OptimizationProblemToOperator", "OptimizationProblemToNegativeValueOracle", + "OptimizationProblemToOperator", + "OptimizationProblemToQubo", "PenalizeLinearEqualityConstraints", - "OptimizationProblemToQubo" ] diff --git a/qiskit/optimization/problems/__init__.py b/qiskit/optimization/problems/__init__.py index fa26930d24..b26caa97ab 100644 --- a/qiskit/optimization/problems/__init__.py +++ b/qiskit/optimization/problems/__init__.py @@ -13,9 +13,9 @@ # that they have been altered from the originals. """ -======================================================== -Optimization stack for Aqua (:mod:`qiskit.optimization`) -======================================================== +================================================================= +Optimization stack for Aqua (:mod:`qiskit.optimization.problems`) +================================================================= .. currentmodule:: qiskit.optimization.problems @@ -23,13 +23,15 @@ ========== .. autosummary:: - :toctree: + :toctree: ../stubs/ + :nosignatures: - OptimizationProblem - VariablesInterface - ObjectiveInterface LinearConstraintInterface + ObjSense + ObjectiveInterface + OptimizationProblem QuadraticConstraintInterface + VariablesInterface N.B. Additional classes LinearConstraintInterface, QuadraticConstraintInterface, ObjectiveInterface, and VariablesInterface @@ -38,8 +40,16 @@ """ -from qiskit.optimization.problems.optimization_problem import OptimizationProblem -from qiskit.optimization.problems.linear_constraint import LinearConstraintInterface -from qiskit.optimization.problems.objective import ObjSense, ObjectiveInterface - -__all__ = ["OptimizationProblem", "LinearConstraintInterface", "ObjSense", "ObjectiveInterface"] +from .linear_constraint import LinearConstraintInterface +from .objective import ObjSense, ObjectiveInterface +from .optimization_problem import OptimizationProblem +from .quadratic_constraint import QuadraticConstraintInterface +from .variables import VariablesInterface + +__all__ = ['LinearConstraintInterface', + 'ObjSense', + 'ObjectiveInterface', + 'OptimizationProblem', + 'QuadraticConstraintInterface', + 'VariablesInterface' + ] diff --git a/qiskit/optimization/results/__init__.py b/qiskit/optimization/results/__init__.py index a56ccaf39b..1a3750b465 100644 --- a/qiskit/optimization/results/__init__.py +++ b/qiskit/optimization/results/__init__.py @@ -28,10 +28,10 @@ grover_optimization_results ======================================================== -Optimization stack for Aqua (:mod:`qiskit.optimization`) +Optimization stack for Aqua (:mod:`qiskit.optimization.results`) ======================================================== -.. currentmodule:: qiskit.optimization.solutions +.. currentmodule:: qiskit.optimization.results Structures for defining a solution with metrics of its quality etc ========== diff --git a/qiskit/optimization/utils/__init__.py b/qiskit/optimization/utils/__init__.py index d9ac389fdb..8069176725 100644 --- a/qiskit/optimization/utils/__init__.py +++ b/qiskit/optimization/utils/__init__.py @@ -12,9 +12,9 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. """ -======================================================== -Optimization stack for Aqua (:mod:`qiskit.optimization`) -======================================================== +============================================================== +Optimization stack for Aqua (:mod:`qiskit.optimization.utils`) +============================================================== .. currentmodule:: qiskit.optimization.utils @@ -22,19 +22,18 @@ ========== .. autosummary:: - :toctree: + :toctree: ../stubs/ + :nosignatures: - QiskitOptimizationError BaseInterface - SparsePair - SparseTriple + QiskitOptimizationError N.B. Utility functions in .aux are intended for internal use. """ -from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError -from qiskit.optimization.utils.base import BaseInterface -from qiskit.optimization.utils.eigenvector_to_solutions import eigenvector_to_solutions +from .qiskit_optimization_error import QiskitOptimizationError +from .base import BaseInterface +from .eigenvector_to_solutions import eigenvector_to_solutions -__all__ = ["QiskitOptimizationError", "BaseInterface", "eigenvector_to_solutions"] +__all__ = ["BaseInterface", "eigenvector_to_solutions", "QiskitOptimizationError"] From 965180b1fd828d0e8282484ad3e1159a70538ac9 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Wed, 1 Apr 2020 22:35:54 -0400 Subject: [PATCH 143/323] fix lint --- docs/apidocs/qiskit.finance.applications.rst | 6 ------ qiskit/optimization/__init__.py | 2 +- qiskit/optimization/converters/__init__.py | 4 ++-- 3 files changed, 3 insertions(+), 9 deletions(-) delete mode 100644 docs/apidocs/qiskit.finance.applications.rst diff --git a/docs/apidocs/qiskit.finance.applications.rst b/docs/apidocs/qiskit.finance.applications.rst deleted file mode 100644 index f00c75d1f9..0000000000 --- a/docs/apidocs/qiskit.finance.applications.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _qiskit-finance-applications: - -.. automodule:: qiskit.finance.applications - :no-members: - :no-inherited-members: - :no-special-members: diff --git a/qiskit/optimization/__init__.py b/qiskit/optimization/__init__.py index e9890f558b..c5d0c44a50 100755 --- a/qiskit/optimization/__init__.py +++ b/qiskit/optimization/__init__.py @@ -27,11 +27,11 @@ """ +from .infinity import infinity # must be at the top of the file from .utils import QiskitOptimizationError from .problems import OptimizationProblem from ._logging import (get_qiskit_optimization_logging, set_qiskit_optimization_logging) -from .infinity import infinity from .util import get_qubo_solutions __all__ = ['OptimizationProblem', diff --git a/qiskit/optimization/converters/__init__.py b/qiskit/optimization/converters/__init__.py index 7b2f90b8e4..c8bd087b54 100644 --- a/qiskit/optimization/converters/__init__.py +++ b/qiskit/optimization/converters/__init__.py @@ -24,12 +24,12 @@ """ +from .optimization_problem_to_negative_value_oracle import OptimizationProblemToNegativeValueOracle from .inequality_to_equality_converter import InequalityToEqualityConverter from .integer_to_binary_converter import IntegerToBinaryConverter -from .optimization_problem_to_negative_value_oracle import OptimizationProblemToNegativeValueOracle from .optimization_problem_to_operator import OptimizationProblemToOperator -from .optimization_problem_to_qubo import OptimizationProblemToQubo from .penalize_linear_equality_constraints import PenalizeLinearEqualityConstraints +from .optimization_problem_to_qubo import OptimizationProblemToQubo __all__ = [ "InequalityToEqualityConverter", From 8b6be2e29a10c6b0bf9e19a2ba17633e7b2e0226 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Wed, 1 Apr 2020 23:10:16 -0400 Subject: [PATCH 144/323] fix unit test --- qiskit/finance/__init__.py | 4 +--- qiskit/optimization/problems/linear_constraint.py | 2 +- test/optimization/test_linear_constraints.py | 3 ++- test/optimization/test_objective.py | 3 ++- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/qiskit/finance/__init__.py b/qiskit/finance/__init__.py index 07c208db61..0dfe66797e 100644 --- a/qiskit/finance/__init__.py +++ b/qiskit/finance/__init__.py @@ -23,10 +23,8 @@ ========== .. autosummary:: - :toctree: ../stubs/ - :nosignatures: + :toctree: - applications components data_providers diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index c51c7693f8..8b0e2710e7 100644 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -809,4 +809,4 @@ def _get(i): def get_histogram(self): """ get histogram """ - raise NotImplementedError("histrogram is not implemented") + raise NotImplementedError("histogram is not implemented") diff --git a/test/optimization/test_linear_constraints.py b/test/optimization/test_linear_constraints.py index c4cd31ee1f..3624d5757f 100644 --- a/test/optimization/test_linear_constraints.py +++ b/test/optimization/test_linear_constraints.py @@ -245,7 +245,8 @@ def test_get_names(self): def test_get_histogram(self): """ test get histogram """ op = OptimizationProblem() - self.assertRaises(NotImplementedError, op.linear_constraints.get_histogram()) + with self.assertRaises(NotImplementedError): + op.linear_constraints.get_histogram() if __name__ == '__main__': diff --git a/test/optimization/test_objective.py b/test/optimization/test_objective.py index d6a9d843a0..38ebfd59ef 100644 --- a/test/optimization/test_objective.py +++ b/test/optimization/test_objective.py @@ -64,7 +64,8 @@ def test_set_empty_quadratic(self): """ test set empty quadratic """ op = OptimizationProblem() op.objective.set_quadratic([]) - self.assertRaises(TypeError, op.objective.set_quadratic()) + with self.assertRaises(TypeError): + op.objective.set_quadratic() def test_set_quadratic(self): """ test set quadratic """ From d600758f18592c2747bd546ba41d4dd304bff9d3 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Wed, 1 Apr 2020 23:28:15 -0400 Subject: [PATCH 145/323] fix html --- docs/apidocs/qiskit.optimization.applications.ising.rst | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 docs/apidocs/qiskit.optimization.applications.ising.rst diff --git a/docs/apidocs/qiskit.optimization.applications.ising.rst b/docs/apidocs/qiskit.optimization.applications.ising.rst deleted file mode 100644 index ff1ebb7629..0000000000 --- a/docs/apidocs/qiskit.optimization.applications.ising.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _qiskit-optimization-applications-ising: - -.. automodule:: qiskit.optimization.applications.ising - :no-members: - :no-inherited-members: - :no-special-members: From 5f641d2f0cfde2901859f8302b0f94e8798780cc Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Fri, 3 Apr 2020 11:16:36 -0400 Subject: [PATCH 146/323] sort .pylintdict ignore case --- .pylintdict | 56 ++++++++++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/.pylintdict b/.pylintdict index 6b80c5a63d..37cf30860b 100644 --- a/.pylintdict +++ b/.pylintdict @@ -1,37 +1,10 @@ -AOs -Aer's -Armijo -Chu -CircuitCache -Eckstein -Eq -FermionicOperator -GGLL -LinearConstraintInterface -MIP -Nakanishi -ObjectiveInterface -OptimizationProblem -Parikh -Peleato -ProblemType -QCP -QFactory -QiskitOptimizationError -QuadraticConstraintInterface -RunConfig -SolutionInterface -Todo -UCCS -VariablesInterface -ZZ -Zj abstractmethod adag adam ae aer +Aer's aerjob aij al @@ -46,11 +19,13 @@ ansatz ansatzes anticommute ao +AOs ap api appfactory arcsin args +Armijo asmatrix assertRaises ast @@ -90,7 +65,9 @@ cdf cdot ceil chernoff +Chu circ +CircuitCache clas clbits clifford @@ -171,6 +148,7 @@ dp dtype durr ecc +Eckstein ee eigen eigensolver @@ -187,6 +165,7 @@ enum eoh eom eps +Eq erdos eri et @@ -201,6 +180,7 @@ factr fcidump fcompiler fermionic +FermionicOperator fermions fileio filepath @@ -228,6 +208,7 @@ getattr getfunc getter gfortran +GGLL github globals gogolin @@ -316,6 +297,7 @@ lhs lih lijh lin +LinearConstraintInterface lmin lnot loglikelihood @@ -353,6 +335,7 @@ minibatching minima mintert minwidth +MIP mitarai mle moc @@ -371,6 +354,7 @@ msq multiclass multinomial multiprocess +Nakanishi namelist nan narray @@ -406,6 +390,7 @@ numpy nxd nxk nxn +ObjectiveInterface objval occ oe @@ -415,6 +400,7 @@ online onwards oplus optim +OptimizationProblem optimizer's optimizers orbsym @@ -423,12 +409,14 @@ outpath overfit params parentname +Parikh pauli pauli's paulis paulische pca pdf +Peleato penality ph piecewise @@ -442,6 +430,7 @@ prebuilt precomputed preoracle prepend +ProblemType probs programmatically proj @@ -461,11 +450,14 @@ qasm qbit qc qcmatrixio +QCP qeom +QFactory qft qfts qgan qiskit +QiskitOptimizationError qith qload qmolecule @@ -475,6 +467,7 @@ qp qpe qreg qrs +QuadraticConstraintInterface quandl quantized quantumcircuit @@ -512,6 +505,7 @@ rosen rsgtu rtype runarsson +RunConfig ry rz sanjiv @@ -541,6 +535,7 @@ sigmoid sj sklearn slsqp +SolutionInterface solutionstatus spsa sqrt @@ -583,6 +578,7 @@ timelimit timestamp tnc toctree +Todo toffoli tol tomo @@ -597,6 +593,7 @@ trunc ub ucc uccd +UCCS uccsd uhf ulimit @@ -619,6 +616,7 @@ usr utils validator vals +VariablesInterface variational vazirani vdag @@ -648,8 +646,10 @@ yao yc yy zi +Zj zmatrix zv +ZZ zzz äguivalenzverbot über From 70bcf31cef1a45e952641e8c58d238624ef7eca6 Mon Sep 17 00:00:00 2001 From: a-matsuo <47442626+a-matsuo@users.noreply.github.com> Date: Mon, 6 Apr 2020 18:36:10 +0900 Subject: [PATCH 147/323] Fixed docstrings of OptimizationProblemToQubo (#59) * modfied docstrings of OptimizationProblemToQubo --- .../converters/optimization_problem_to_qubo.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/qiskit/optimization/converters/optimization_problem_to_qubo.py b/qiskit/optimization/converters/optimization_problem_to_qubo.py index 9fdc73696a..e4c9c9bbda 100644 --- a/qiskit/optimization/converters/optimization_problem_to_qubo.py +++ b/qiskit/optimization/converters/optimization_problem_to_qubo.py @@ -45,14 +45,13 @@ def __init__(self, penalty: Optional[float] = 1e5) -> None: self._penalty = penalty def encode(self, problem: OptimizationProblem) -> OptimizationProblem: - """ Convert a problem with inequality constraints into new one with only equality - constraints. + """ Convert a problem with linear equality constraints into new one with a QUBO form. Args: - problem: The problem to be solved, that may contain inequality constraints. + problem: The problem with linear equality constraints to be solved. Returns: - The converted problem, that contain only equality constraints. + The problem converted in QUBO format. Raises: QiskitOptimizationError: In case of an incompatible problem. From 639ed23ec365a8a56c5543e1c4bc531d66ff22e4 Mon Sep 17 00:00:00 2001 From: adekusar-drl <62334182+adekusar-drl@users.noreply.github.com> Date: Mon, 6 Apr 2020 11:21:53 +0100 Subject: [PATCH 148/323] Bug fixing in ADMM + code optimization as discussed (#55) * fix max * added admm ref; linalg * fix max * formatting, linting, typing * changes as discussed * asserts in the admm tests to support ADMMOptimizationResult and ADMMState * handling case no bin vars, updated get_obj_val * minor * some fixes * prints * run e5 * run e5 * added ex6 as a unit test * removed miskp test * more unit tests, and variable bound fix in step3 * linter, rolling back requirements.txt * ADMMResult fixes. Co-authored-by: Claudio Gambella Co-authored-by: andrea-simonetto --- .../optimization/algorithms/admm_optimizer.py | 146 ++++++------ test/optimization/test_admm.py | 145 ++++++++++++ test/optimization/test_admm_miskp.py | 216 ------------------ 3 files changed, 225 insertions(+), 282 deletions(-) create mode 100644 test/optimization/test_admm.py delete mode 100644 test/optimization/test_admm_miskp.py diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 9e70254944..b2fbed0365 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -15,7 +15,7 @@ """An implementation of the ADMM algorithm.""" import logging import time -from typing import List, Optional +from typing import List, Optional, Any import numpy as np from cplex import SparsePair @@ -36,8 +36,7 @@ def __init__(self, rho_initial: float = 10000, factor_c: float = 100000, beta: f max_iter: int = 10, tol: float = 1.e-4, max_time: float = np.inf, three_block: bool = True, vary_rho: int = UPDATE_RHO_BY_TEN_PERCENT, tau_incr: float = 2, tau_decr: float = 2, mu_res: float = 10, - mu_merit: float = 1000, qubo_optimizer: Optional[OptimizationAlgorithm] = None, - continuous_optimizer: Optional[OptimizationAlgorithm] = None) -> None: + mu_merit: float = 1000) -> None: """Defines parameters for ADMM optimizer and their default values. Args: @@ -60,10 +59,6 @@ def __init__(self, rho_initial: float = 10000, factor_c: float = 100000, beta: f tau_decr: Parameter used in the rho update (UPDATE_RHO_BY_RESIDUALS). mu_res: Parameter used in the rho update (UPDATE_RHO_BY_RESIDUALS). mu_merit: Penalization for constraint residual. Used to compute the merit values. - qubo_optimizer: An instance of OptimizationAlgorithm that can effectively solve - QUBO problems. - continuous_optimizer: An instance of OptimizationAlgorithm that can solve - continuous problems. """ super().__init__() self.mu_merit = mu_merit @@ -78,8 +73,6 @@ def __init__(self, rho_initial: float = 10000, factor_c: float = 100000, beta: f self.factor_c = factor_c self.beta = beta self.rho_initial = rho_initial - self.qubo_optimizer = qubo_optimizer or CplexOptimizer() - self.continuous_optimizer = continuous_optimizer or CplexOptimizer() class ADMMState: @@ -155,13 +148,37 @@ def __init__(self, self.rho = rho_initial +class ADMMOptimizerResult(OptimizationResult): + """ ADMMOptimizer Result.""" + + def __init__(self, x: Optional[Any] = None, fval: Optional[Any] = None, + state: Optional[ADMMState] = None, results: Optional[Any] = None) -> None: + super().__init__(x, fval, results) + self._state = state + + @property + def state(self) -> Optional[ADMMState]: + """ returns state """ + return self._state + + class ADMMOptimizer(OptimizationAlgorithm): - """An implementation of the ADMM algorithm.""" + """An implementation of the ADMM-based heuristic introduced here: + Gambella, C., & Simonetto, A. (2020). + Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical and Quantum Computers. + arXiv preprint arXiv:2001.02069. + """ - def __init__(self, params: Optional[ADMMParameters] = None) -> None: + def __init__(self, qubo_optimizer: Optional[OptimizationAlgorithm] = None, + continuous_optimizer: Optional[OptimizationAlgorithm] = None, + params: Optional[ADMMParameters] = None) -> None: """Constructs an instance of ADMMOptimizer. Args: + qubo_optimizer: An instance of OptimizationAlgorithm that can effectively solve + QUBO problems. + continuous_optimizer: An instance of OptimizationAlgorithm that can solve + continuous problems. params: An instance of ADMMParameters. """ @@ -169,23 +186,11 @@ def __init__(self, params: Optional[ADMMParameters] = None) -> None: self._log = logging.getLogger(__name__) # create default params if not present - params = params or ADMMParameters() - self._three_block = params.three_block - self._max_time = params.max_time - self._tol = params.tol - self._max_iter = params.max_iter - self._factor_c = params.factor_c - self._beta = params.beta - self._mu_res = params.mu_res - self._tau_decr = params.tau_decr - self._tau_incr = params.tau_incr - self._vary_rho = params.vary_rho - self._three_block = params.three_block - self._mu_merit = params.mu_merit - self._rho_initial = params.rho_initial - - self._qubo_optimizer = params.qubo_optimizer - self._continuous_optimizer = params.continuous_optimizer + self._params = params or ADMMParameters() + + # create optimizers if not specified + self._qubo_optimizer = qubo_optimizer or CplexOptimizer() + self._continuous_optimizer = continuous_optimizer or CplexOptimizer() # internal state where we'll keep intermediate solution # here, we just declare the class variable, the variable is initialized in kept in @@ -227,7 +232,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: return None - def solve(self, problem: OptimizationProblem) -> OptimizationResult: + def solve(self, problem: OptimizationProblem) -> ADMMOptimizerResult: """Tries to solves the given problem using ADMM algorithm. Args: @@ -244,7 +249,8 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: continuous_indices = self._get_variable_indices(problem, CPX_CONTINUOUS) # create our computation state. - self._state = ADMMState(problem, binary_indices, continuous_indices, self._rho_initial) + self._state = ADMMState(problem, binary_indices, + continuous_indices, self._params.rho_initial) # convert optimization problem to a set of matrices and vector that are used # at each iteration. @@ -256,10 +262,13 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: iteration = 0 residual = 1.e+2 - while (iteration < self._max_iter and residual > self._tol) \ - and (elapsed_time < self._max_time): - op1 = self._create_step1_problem() - self._state.x0 = self._update_x0(op1) + while (iteration < self._params.max_iter and residual > self._params.tol) \ + and (elapsed_time < self._params.max_time): + if binary_indices: + op1 = self._create_step1_problem() + self._state.x0 = self._update_x0(op1) + # else, no binary variables exist, + # and no update to be done in this case. # debug self._log.debug("x0=%s", self._state.x0) @@ -269,13 +278,14 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: self._log.debug("u=%s", self._state.u) self._log.debug("z=%s", self._state.z) - if self._three_block: - op3 = self._create_step3_problem() - self._state.y = self._update_y(op3) + if self._params.three_block: + if binary_indices: + op3 = self._create_step3_problem() + self._state.y = self._update_y(op3) # debug self._log.debug("y=%s", self._state.y) - lambda_mult = self._update_lambda_mult() + self._state.lambda_mult = self._update_lambda_mult() cost_iterate = self._get_objective_value() constraint_residual = self._get_constraint_residual() @@ -291,7 +301,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: self._state.dual_residuals.append(dual_residual) self._state.cons_r.append(constraint_residual) self._state.merits.append(merit) - self._state.lambdas.append(np.linalg.norm(lambda_mult)) + self._state.lambdas.append(np.linalg.norm(self._state.lambda_mult)) self._state.x0_saved.append(self._state.x0) self._state.u_saved.append(self._state.u) @@ -307,7 +317,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: solution = self._revert_solution_indexes(solution) # third parameter is our internal state of computations. - result = OptimizationResult(solution, objective_value, self._state) + result = ADMMOptimizerResult(solution, objective_value, self._state) # debug self._log.debug("solution=%s, objective=%s at iteration=%s", solution, objective_value, iteration) @@ -592,19 +602,20 @@ def _create_step1_problem(self) -> OptimizationProblem: # prepare and set quadratic objective. # NOTE: The multiplication by 2 is needed for the solvers to parse # the quadratic coefficients. - quadratic_objective = 2 * ( - self._state.q0 + - self._factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) + - self._state.rho / 2 * np.eye(binary_size) - ) + quadratic_objective = self._state.q0 +\ + 2 * ( + self._params.factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) + + self._state.rho / 2 * np.eye(binary_size) + ) for i in range(binary_size): for j in range(i, binary_size): op1.objective.set_quadratic_coefficients(i, j, quadratic_objective[i, j]) # prepare and set linear objective. linear_objective = self._state.c0 - \ - self._factor_c * np.dot(self._state.b0, self._state.a0) + \ - self._state.rho * (self._state.y - self._state.z) + self._params.factor_c * np.dot(self._state.b0, self._state.a0) + \ + self._state.rho * (- self._state.y - self._state.z) + \ + self._state.lambda_mult for i in range(binary_size): op1.objective.set_linear(i, linear_objective[i]) @@ -635,9 +646,7 @@ def _create_step2_problem(self) -> OptimizationProblem: # set quadratic objective coefficients for u variables. if continuous_size: - # NOTE: The multiplication by 2 is needed for the solvers to parse - # the quadratic coefficients. - q_u = 2 * self._state.q1 + q_u = self._state.q1 for i in range(continuous_size): for j in range(i, continuous_size): op2.objective.set_quadratic_coefficients(i, j, q_u[i, j]) @@ -658,7 +667,7 @@ def _create_step2_problem(self) -> OptimizationProblem: op2.objective.set_linear(i, linear_u[i]) # set linear objective for z variables. - linear_z = -1 * self._state.lambda_mult - self._state.rho * (self._state.x0 + self._state.y) + linear_z = -1 * self._state.lambda_mult - self._state.rho * (self._state.x0 - self._state.y) for i in range(binary_size): op2.objective.set_linear(i + continuous_size, linear_z[i]) @@ -708,16 +717,19 @@ def _create_step3_problem(self) -> OptimizationProblem: # add y variables. binary_size = len(self._state.binary_indices) op3.variables.add(names=["y_" + str(i + 1) for i in range(binary_size)], - types=["C"] * binary_size) + types=["C"] * binary_size, lb=[-np.inf] * binary_size, + ub=[np.inf] * binary_size) # set quadratic objective. # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coeff-s. - q_y = 2 * (self._beta / 2 * np.eye(binary_size) + self._state.rho / 2 * np.eye(binary_size)) + q_y = 2 * (self._params.beta / 2 * np.eye(binary_size) + + self._state.rho / 2 * np.eye(binary_size)) for i in range(binary_size): for j in range(i, binary_size): op3.objective.set_quadratic_coefficients(i, j, q_y[i, j]) - linear_y = self._state.lambda_mult + self._state.rho * (self._state.x0 - self._state.z) + linear_y = - self._state.lambda_mult - self._state.rho * ( + self._state.x0 - self._state.z) for i in range(binary_size): op3.objective.set_linear(i, linear_y[i]) @@ -776,7 +788,7 @@ def _get_best_merit_solution(self) -> (List[np.ndarray], float): """ it_best_merits = self._state.merits.index( - min(list(map(lambda x: self._state.sense * x, self._state.merits)))) + self._state.sense * min(list(map(lambda x: self._state.sense * x, self._state.merits)))) x_0 = self._state.x0_saved[it_best_merits] u_s = self._state.u_saved[it_best_merits] sol = [x_0, u_s] @@ -792,7 +804,7 @@ def _update_lambda_mult(self) -> np.ndarray: """ return self._state.lambda_mult + \ - self._state.rho * (self._state.x0 - self._state.z + self._state.y) + self._state.rho * (self._state.x0 - self._state.z - self._state.y) def _update_rho(self, primal_residual: float, dual_residual: float) -> None: """Updating the rho parameter in ADMM. @@ -802,15 +814,15 @@ def _update_rho(self, primal_residual: float, dual_residual: float) -> None: dual_residual: dual residual """ - if self._vary_rho == UPDATE_RHO_BY_TEN_PERCENT: + if self._params.vary_rho == UPDATE_RHO_BY_TEN_PERCENT: # Increase rho, to aid convergence. if self._state.rho < 1.e+10: self._state.rho *= 1.1 - elif self._vary_rho == UPDATE_RHO_BY_RESIDUALS: - if primal_residual > self._mu_res * dual_residual: - self._state.rho = self._tau_incr * self._state.rho - elif dual_residual > self._mu_res * primal_residual: - self._state.rho = self._tau_decr * self._state.rho + elif self._params.vary_rho == UPDATE_RHO_BY_RESIDUALS: + if primal_residual > self._params.mu_res * dual_residual: + self._state.rho = self._params.tau_incr * self._state.rho + elif dual_residual > self._params.mu_res * primal_residual: + self._state.rho = self._params.tau_decr * self._state.rho def _get_constraint_residual(self) -> float: """Compute violation of the constraints of the original problem, as: @@ -843,7 +855,7 @@ def _get_merit(self, cost_iterate: float, constraint_residual: float) -> float: Returns: Merit value as a float """ - return cost_iterate + self._mu_merit * constraint_residual + return cost_iterate + self._params.mu_merit * constraint_residual def _get_objective_value(self) -> float: """Computes the value of the objective function. @@ -853,11 +865,13 @@ def _get_objective_value(self) -> float: """ def quadratic_form(matrix, x, c): - return np.dot(x.T, np.dot(matrix, x)) + np.dot(c.T, x) + return np.dot(x.T, np.dot(matrix / 2, x)) + np.dot(c.T, x) obj_val = quadratic_form(self._state.q0, self._state.x0, self._state.c0) obj_val += quadratic_form(self._state.q1, self._state.u, self._state.c1) + obj_val += self._state.op.objective.get_offset() + return obj_val def _get_solution_residuals(self, iteration: int) -> (float, float): @@ -870,11 +884,11 @@ def _get_solution_residuals(self, iteration: int) -> (float, float): r, s as primary and dual residuals. """ elements = self._state.x0 - self._state.z - self._state.y - primal_residual = pow(sum(e ** 2 for e in elements), 0.5) + primal_residual = np.linalg.norm(elements) if iteration > 0: elements_dual = self._state.z - self._state.z_saved[iteration - 1] else: elements_dual = self._state.z - self._state.z_init - dual_residual = self._state.rho * pow(sum(e ** 2 for e in elements_dual), 0.5) + dual_residual = self._state.rho * np.linalg.norm(elements_dual) return primal_residual, dual_residual diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py new file mode 100644 index 0000000000..79cf2f0226 --- /dev/null +++ b/test/optimization/test_admm.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +"""Tests of the ADMM algorithm.""" + +from test.optimization import QiskitOptimizationTestCase + +from docplex.mp.model import Model + +import numpy as np +from qiskit.aqua.algorithms import NumPyMinimumEigensolver +from qiskit.optimization.algorithms import CplexOptimizer, MinimumEigenOptimizer +from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters, \ + ADMMOptimizerResult, ADMMState +from qiskit.optimization.problems import OptimizationProblem + +class TestADMMOptimizer(QiskitOptimizationTestCase): + """ADMM Optimizer Tests""" + + def test_admm_maximization(self): + """Tests a simple maximization problem using ADMM optimizer""" + mdl = Model('test') + c = mdl.continuous_var(lb=0, ub=10, name='c') + x = mdl.binary_var(name='x') + mdl.maximize(c + x * x) + op = OptimizationProblem() + op.from_docplex(mdl) + self.assertIsNotNone(op) + + admm_params = ADMMParameters() + + qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) + continuous_optimizer = CplexOptimizer() + + solver = ADMMOptimizer(qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer, + params=admm_params) + solution: ADMMOptimizerResult = solver.solve(op) + self.assertIsNotNone(solution) + self.assertIsInstance(solution, ADMMOptimizerResult) + + self.assertIsNotNone(solution.x) + np.testing.assert_almost_equal([10, 0], solution.x, 3) + self.assertIsNotNone(solution.fval) + np.testing.assert_almost_equal(10, solution.fval, 3) + self.assertIsNotNone(solution.state) + self.assertIsInstance(solution.state, ADMMState) + + def test_admm_ex6(self): + """Example 6 as a unit test. Example 6 is reported in: + Gambella, C., & Simonetto, A. (2020). + Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical + and Quantum Computers. + arXiv preprint arXiv:2001.02069.""" + mdl = Model('ex6') + + # pylint:disable=invalid-name + v = mdl.binary_var(name='v') + w = mdl.binary_var(name='w') + t = mdl.binary_var(name='t') + u = mdl.continuous_var(name='u') + + mdl.minimize(v + w + t + 5 * (u - 2) ** 2) + mdl.add_constraint(v + 2 * w + t + u <= 3, "cons1") + mdl.add_constraint(v + w + t >= 1, "cons2") + mdl.add_constraint(v + w == 1, "cons3") + + op = OptimizationProblem() + op.from_docplex(mdl) + + qubo_optimizer = CplexOptimizer() + continuous_optimizer = CplexOptimizer() + + admm_params = ADMMParameters( + rho_initial=1001, beta=1000, factor_c=900, + max_iter=100, three_block=True, tol=1.e-6 + ) + + solver = ADMMOptimizer(params=admm_params, qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer) + solution = solver.solve(op) + self.assertIsNotNone(solution) + self.assertIsInstance(solution, ADMMOptimizerResult) + + self.assertIsNotNone(solution.x) + np.testing.assert_almost_equal([1., 0., 0., 2.], solution.x, 3) + self.assertIsNotNone(solution.fval) + np.testing.assert_almost_equal(1., solution.fval, 3) + self.assertIsNotNone(solution.state) + self.assertIsInstance(solution.state, ADMMState) + + def test_admm_ex5(self): + """Example 5 as a unit test. Example 5 is reported in: + Gambella, C., & Simonetto, A. (2020). + Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical + and Quantum Computers. + arXiv preprint arXiv:2001.02069.""" + mdl = Model('ex5') + + # pylint:disable=invalid-name + v = mdl.binary_var(name='v') + w = mdl.binary_var(name='w') + t = mdl.binary_var(name='t') + + mdl.minimize(v + w + t) + mdl.add_constraint(2 * v + 2 * w + t <= 3, "cons1") + mdl.add_constraint(v + w + t >= 1, "cons2") + mdl.add_constraint(v + w == 1, "cons3") + + op = OptimizationProblem() + op.from_docplex(mdl) + + qubo_optimizer = CplexOptimizer() + + continuous_optimizer = CplexOptimizer() + + admm_params = ADMMParameters( + rho_initial=1001, beta=1000, factor_c=900, + max_iter=100, three_block=False + ) + + solver = ADMMOptimizer(params=admm_params, qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer, ) + solution = solver.solve(op) + + self.assertIsNotNone(solution) + self.assertIsInstance(solution, ADMMOptimizerResult) + + self.assertIsNotNone(solution.x) + np.testing.assert_almost_equal([1., 0., 1.], solution.x, 3) + self.assertIsNotNone(solution.fval) + np.testing.assert_almost_equal(2., solution.fval, 3) + self.assertIsNotNone(solution.state) + self.assertIsInstance(solution.state, ADMMState) diff --git a/test/optimization/test_admm_miskp.py b/test/optimization/test_admm_miskp.py deleted file mode 100644 index 47abb30b03..0000000000 --- a/test/optimization/test_admm_miskp.py +++ /dev/null @@ -1,216 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""Tests of the ADMM algorithm.""" - -import unittest -from typing import Optional - -from test.optimization import QiskitOptimizationTestCase - -import numpy as np -from cplex import SparsePair -from qiskit.aqua.algorithms import NumPyMinimumEigensolver -from qiskit.optimization.algorithms import CplexOptimizer, MinimumEigenOptimizer -from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters -from qiskit.optimization.problems import OptimizationProblem - - -class TestADMMOptimizerMiskp(QiskitOptimizationTestCase): - """ADMM Optimizer Tests based on Mixed-Integer Setup Knapsack Problem""" - - def test_admm_optimizer_miskp_eigen(self): - """ADMM Optimizer Test based on Mixed-Integer Setup Knapsack Problem - using NumPy eigen optimizer""" - miskp = Miskp(*self._get_problem_params()) - op: OptimizationProblem = miskp.create_problem() - self.assertIsNotNone(op) - - # use numpy exact diagonalization - qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) - continuous_optimizer = CplexOptimizer() - - admm_params = ADMMParameters(qubo_optimizer=qubo_optimizer, - continuous_optimizer=continuous_optimizer) - - solver = ADMMOptimizer(params=admm_params) - solution = solver.solve(op) - self.assertIsNotNone(solution) - - correct_solution = [0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, 0.009127, - 0.009127, 0.009127, 0.009127, 0.006151, 0.006151, 0.006151, 0.006151, - 0.006151, 0.006151, 0.006151, 0.006151, 0.006151, 0.006151, - 0., 0.] - correct_objective = -1.2113693 - - self.assertIsNotNone(solution.x) - np.testing.assert_almost_equal(correct_solution, solution.x, 3) - self.assertIsNotNone(solution.fval) - np.testing.assert_almost_equal(correct_objective, solution.fval, 3) - - @staticmethod - def _get_problem_params() -> (int, int, float, np.ndarray, np.ndarray, np.ndarray): - """Fills in parameters for a Mixed Integer Setup Knapsack Problem (MISKP) instance.""" - - family_count = 2 - items_per_family = 10 - knapsack_capacity = 45.10 - setup_costs = np.asarray([75.61, 75.54]) - - resource_values = np.asarray([9.78, 8.81, 9.08, 2.03, 8.9, 9, 4.12, 8.16, 6.55, 4.84, 3.78, - 5.13, 4.72, 7.6, 2.83, 1.44, 2.45, 2.24, 6.3, 5.02]) \ - .reshape((family_count, items_per_family)) - - cost_values = np.asarray([-11.78, -10.81, -11.08, -4.03, -10.90, -11.00, -6.12, -10.16, - -8.55, -6.84, -5.78, -7.13, -6.72, -9.60, -4.83, -3.44, -4.45, - -4.24, -8.30, -7.02]) \ - .reshape((family_count, items_per_family)) - - return family_count, items_per_family, knapsack_capacity, setup_costs, resource_values,\ - cost_values - - -class Miskp: - """A Helper class to generate Mixed Integer Setup Knapsack problems""" - - def __init__(self, family_count: int, items_per_family: int, knapsack_capacity: float, - setup_costs: np.ndarray, resource_values: np.ndarray, cost_values: np.ndarray)\ - -> None: - """Constructs an instance of this helper class to create suitable ADMM problems. - - Args: - family_count: number of families - items_per_family: number of items in each family - knapsack_capacity: capacity of the knapsack - setup_costs: setup cost to include family k in the knapsack - resource_values: resources consumed if item t in family k is included in the knapsack - cost_values: value of including item t in family k in the knapsack - """ - - self.knapsack_capacity = knapsack_capacity - self.setup_costs = setup_costs - self.resource_values = resource_values - self.cost_values = cost_values - self.items_per_family = items_per_family - self.family_count = family_count - - # definitions of the internal variables - self.op = None - self.range_family = None - self.range_items = None - self.n_x_vars = None - self.n_y_vars = None - self.range_x_vars = None - self.range_y_vars = None - - @staticmethod - def _var_name(stem: str, index1: int, index2: Optional[int] = None, - index3: Optional[int] = None) -> str: - """A method to return a string representation of the name of a decision variable or - a constraint, given its indices. - - Args: - stem: Element name. - index1: Element indices - index2: Element indices - index3: Element indices - - Returns: - Textual representation of the variable name based on the parameters - """ - if index2 is None: - return stem + "(" + str(index1) + ")" - if index3 is None: - return stem + "(" + str(index1) + "," + str(index2) + ")" - return stem + "(" + str(index1) + "," + str(index2) + "," + str(index3) + ")" - - def _create_params(self) -> None: - self.range_family = range(self.family_count) - self.range_items = range(self.items_per_family) - - # make sure instance params are floats - self.setup_costs = [float(val) for val in self.setup_costs] - self.cost_values = self.cost_values.astype(float) - self.resource_values = self.resource_values.astype(float) - - self.n_x_vars = self.family_count * self.items_per_family - self.n_y_vars = self.family_count - - self.range_x_vars = [(k, t) for k in self.range_family for t in self.range_items] - self.range_y_vars = self.range_family - - def _create_vars(self) -> None: - self.op.variables.add( - lb=[0.0] * self.n_x_vars, - names=[self._var_name("x", i, j) for i, j in self.range_x_vars]) - - self.op.variables.add( - # lb=[0.0] * self.n_y_vars, - # ub=[1.0] * self.n_y_vars, - types=["B"] * self.n_y_vars, - names=[self._var_name("y", i) for i in self.range_y_vars]) - - def _create_constraint_capacity(self) -> None: - self.op.linear_constraints.add( - lin_expr=[ - SparsePair( - ind=[self._var_name("x", i, j) for i, j in self.range_x_vars], - val=[self.resource_values[i, j] for i, j in self.range_x_vars]) - ], - senses="L", - rhs=[self.knapsack_capacity], - names=["CAPACITY"]) - - def _create_constraint_allocation(self) -> None: - self.op.linear_constraints.add( - lin_expr=[ - SparsePair( - ind=[self._var_name("x", k, t)] + [self._var_name("y", k)], - val=[1.0, -1.0]) - for k, t in self.range_x_vars - ], - senses="L" * self.n_x_vars, - rhs=[0.0] * self.n_x_vars, - names=[self._var_name("ALLOCATION", k, t) for k, t in self.range_x_vars]) - - def _create_constraints(self) -> None: - self._create_constraint_capacity() - self._create_constraint_allocation() - - def _create_objective(self) -> None: - self.op.objective.set_linear([(self._var_name("y", k), self.setup_costs[k]) - for k in self.range_family] + - [(self._var_name("x", k, t), self.cost_values[k, t]) - for k, t in - self.range_x_vars] - ) - - def create_problem(self) -> OptimizationProblem: - """Creates an instance of optimization problem based on parameters specified. - - Returns: - an instance of optimization problem. - """ - self.op = OptimizationProblem() - - self._create_params() - self._create_vars() - self._create_objective() - self._create_constraints() - - return self.op - - -if __name__ == '__main__': - unittest.main() From cc9d3818078d2dec7c5141529a5586ac585454ec Mon Sep 17 00:00:00 2001 From: a-matsuo <47442626+a-matsuo@users.noreply.github.com> Date: Mon, 6 Apr 2020 19:32:45 +0900 Subject: [PATCH 149/323] Added new/extra tests for converters (#46) * added new/extra tests for converters * added new tests, test_binary_to_integer and test_optimizationproblem_to_operator * added test_valid_valiable_type and test_quadratic_constraints * minor fix * fixed pylint and raise errors for unsupported quadratic constraints instead of warnings * changed docstrings * modified docstrings * Update qiskit/optimization/converters/integer_to_binary_converter.py * Update qiskit/optimization/converters/inequality_to_equality_converter.py Co-authored-by: Julien Gacon --- .../inequality_to_equality_converter.py | 4 + .../converters/integer_to_binary_converter.py | 15 +- .../optimization_problem_to_operator.py | 6 +- test/optimization/test_converters.py | 219 +++++++++++++++++- 4 files changed, 219 insertions(+), 25 deletions(-) diff --git a/qiskit/optimization/converters/inequality_to_equality_converter.py b/qiskit/optimization/converters/inequality_to_equality_converter.py index 12a06ca018..8930ca5843 100644 --- a/qiskit/optimization/converters/inequality_to_equality_converter.py +++ b/qiskit/optimization/converters/inequality_to_equality_converter.py @@ -152,6 +152,10 @@ def encode(self, op: OptimizationProblem, name: Optional[str] = None, else: raise QiskitOptimizationError('Type of sense in ' + variable + 'is not supported') + # TODO: add quadratic constraints + if self._src.quadratic_constraints.get_num() > 0: + raise QiskitOptimizationError('Quadratic constraints are not yet supported.') + return self._dst def decode(self, result: OptimizationResult) -> OptimizationResult: diff --git a/qiskit/optimization/converters/integer_to_binary_converter.py b/qiskit/optimization/converters/integer_to_binary_converter.py index 65c969ccd2..d2d9cb9b42 100644 --- a/qiskit/optimization/converters/integer_to_binary_converter.py +++ b/qiskit/optimization/converters/integer_to_binary_converter.py @@ -15,13 +15,14 @@ """The converter to convert an integer problem to a binary problem.""" import copy -from typing import List, Tuple, Dict, Optional +from typing import Dict, List, Optional, Tuple import numpy as np from cplex import SparsePair from ..problems.optimization_problem import OptimizationProblem from ..results.optimization_result import OptimizationResult +from ..utils.qiskit_optimization_error import QiskitOptimizationError class IntegerToBinaryConverter: @@ -80,14 +81,6 @@ def encode(self, op: OptimizationProblem, name: Optional[str] = None) -> Optimiz self._dst.variables.add(names=[variable], types=typ, lb=[lower_bounds[i]], ub=[upper_bounds[i]]) - # replace integer variables with binary variables in the objective function - # self.objective.subs(self._conv) - - # replace integer variables with binary variables in the constrains - # self.linear_constraints.subs(self._conv) - # self.quadratic_constraints.subs(self._conv) - # note: `subs` substitutes variables with sets of auxiliary variables - self._substitute_int_var() return self._dst @@ -195,6 +188,10 @@ def _substitute_int_var(self): self._dst.linear_constraints.add(lin_expr, linear_sense, linear_rhs, linear_ranges, linear_names) + # TODO: add quadratic constraints + if self._src.quadratic_constraints.get_num() > 0: + raise QiskitOptimizationError('Quadratic constraints are not yet supported.') + def decode(self, result: OptimizationResult) -> OptimizationResult: """Convert the encoded problem (binary variables) back to the original (integer variables). diff --git a/qiskit/optimization/converters/optimization_problem_to_operator.py b/qiskit/optimization/converters/optimization_problem_to_operator.py index f059a7b9ad..658e9e76b4 100644 --- a/qiskit/optimization/converters/optimization_problem_to_operator.py +++ b/qiskit/optimization/converters/optimization_problem_to_operator.py @@ -57,13 +57,11 @@ def encode(self, op: OptimizationProblem) -> Tuple[WeightedPauliOperator, float] raise QiskitOptimizationError('The type of variable must be a binary variable.') # if constraints exist, raise an error - linear_names = self._src.linear_constraints.get_names() - if len(linear_names) > 0: + if self._src.linear_constraints.get_num() > 0 \ + or self._src.quadratic_constraints.get_num() > 0: raise QiskitOptimizationError('An constraint exists. ' 'The method supports only model with no constraints.') - # TODO: check for quadratic constraints as well - # assign variables of the model to qubits. _q_d = {} qubit_index = 0 diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index 97b1b9ae67..acc610634e 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -19,15 +19,41 @@ from cplex import SparsePair from qiskit.optimization import OptimizationProblem, QiskitOptimizationError +from qiskit.optimization.results import OptimizationResult from qiskit.optimization.converters import InequalityToEqualityConverter, \ OptimizationProblemToOperator, IntegerToBinaryConverter, PenalizeLinearEqualityConstraints +from qiskit.aqua.operators import WeightedPauliOperator +from qiskit.quantum_info import Pauli + +QUBIT_OP_MAXIMIZE_SAMPLE = WeightedPauliOperator( + paulis=[[(-199999.5+0j), Pauli(z=[True, False, False, False], + x=[False, False, False, False])], + [(-399999.5+0j), Pauli(z=[False, True, False, False], + x=[False, False, False, False])], + [(-599999.5+0j), Pauli(z=[False, False, True, False], + x=[False, False, False, False])], + [(-799999.5+0j), Pauli(z=[False, False, False, True], + x=[False, False, False, False])], + [(100000+0j), Pauli(z=[True, True, False, False], + x=[False, False, False, False])], + [(150000+0j), Pauli(z=[True, False, True, False], + x=[False, False, False, False])], + [(200000+0j), Pauli(z=[True, False, False, True], + x=[False, False, False, False])], + [(300000+0j), Pauli(z=[False, True, True, False], + x=[False, False, False, False])], + [(400000+0j), Pauli(z=[False, True, False, True], + x=[False, False, False, False])], + [(600000+0j), Pauli(z=[False, False, True, True], + x=[False, False, False, False])]]) +OFFSET_MAXIMIZE_SAMPLE = 1149998 class TestConverters(QiskitOptimizationTestCase): """Test Converters""" def test_empty_problem(self): - """ test empty problem """ + """ Test empty problem """ op = OptimizationProblem() conv = InequalityToEqualityConverter() op = conv.encode(op) @@ -39,8 +65,48 @@ def test_empty_problem(self): _, shift = conv.encode(op) self.assertEqual(shift, 0.0) + def test_valid_variable_type(self): + """Validate the types of the variables for OptimizationProblemToOperator.""" + # Integer variable + with self.assertRaises(QiskitOptimizationError): + op = OptimizationProblem() + op.variables.add(names=['x'], types='I') + conv = OptimizationProblemToOperator() + _ = conv.encode(op) + # Continuous variable + with self.assertRaises(QiskitOptimizationError): + op = OptimizationProblem() + op.variables.add(names=['x'], types='C') + conv = OptimizationProblemToOperator() + _ = conv.encode(op) + # Semi-Continuous variable + with self.assertRaises(QiskitOptimizationError): + op = OptimizationProblem() + op.variables.add(names=['x'], types='S') + conv = OptimizationProblemToOperator() + _ = conv.encode(op) + # Semi-Integer variable + with self.assertRaises(QiskitOptimizationError): + op = OptimizationProblem() + op.variables.add(names=['x'], types='N') + conv = OptimizationProblemToOperator() + _ = conv.encode(op) + # validate the types of the variables for InequalityToEqualityConverter + # Semi-Continuous variable + with self.assertRaises(QiskitOptimizationError): + op = OptimizationProblem() + op.variables.add(names=['x'], types='S') + conv = InequalityToEqualityConverter() + _ = conv.encode(op) + # Semi-Integer variable + with self.assertRaises(QiskitOptimizationError): + op = OptimizationProblem() + op.variables.add(names=['x'], types='N') + conv = InequalityToEqualityConverter() + _ = conv.encode(op) + def test_inequality_binary(self): - """ test inequality binary """ + """ Test InequalityToEqualityConverter with binary variables """ op = OptimizationProblem() op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( @@ -59,11 +125,15 @@ def test_inequality_binary(self): self.assertListEqual(cst.get_names(), ['xy', 'yz', 'zx']) self.assertListEqual(cst.get_senses(), ['E', 'E', 'E']) self.assertListEqual(cst.get_rhs(), [1, 2, 3]) + var = op2.variables + self.assertListEqual(var.get_lower_bounds(3, 4), [0, 0]) + self.assertListEqual(var.get_upper_bounds(3, 4), [3, 0]) def test_inequality_integer(self): - """ test inequality integer """ + """ Test InequalityToEqualityConverter with integer variables """ op = OptimizationProblem() - op.variables.add(names=['x', 'y', 'z'], types='I' * 3, lb=[-3] * 3, ub=[3] * 3) + op.variables.add(names=['x', 'y', 'z'], + types='I' * 3, lb=[-3] * 3, ub=[3] * 3) op.linear_constraints.add( lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), SparsePair(ind=['y', 'z'], val=[1, -1]), @@ -80,9 +150,63 @@ def test_inequality_integer(self): self.assertListEqual(cst.get_names(), ['xy', 'yz', 'zx']) self.assertListEqual(cst.get_senses(), ['E', 'E', 'E']) self.assertListEqual(cst.get_rhs(), [1, 2, 3]) + var = op2.variables + self.assertListEqual(var.get_lower_bounds(3, 4), [0, 0]) + self.assertListEqual(var.get_upper_bounds(3, 4), [8, 6]) + + def test_inequality_mode_integer(self): + """ Test integer mode of InequalityToEqualityConverter() """ + op = OptimizationProblem() + op.variables.add(names=['x', 'y', 'z'], types='B' * 3) + op.linear_constraints.add( + lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), + SparsePair(ind=['y', 'z'], val=[1, -1]), + SparsePair(ind=['z', 'x'], val=[1, 2])], + senses=['E', 'L', 'G'], + rhs=[1, 2, 3], + names=['xy', 'yz', 'zx'] + ) + conv = InequalityToEqualityConverter() + op2 = conv.encode(op, mode='integer') + var = op2.variables + self.assertListEqual(var.get_types(3, 4), ['I', 'I']) + + def test_inequality_mode_continuous(self): + """ Test continuous mode of InequalityToEqualityConverter() """ + op = OptimizationProblem() + op.variables.add(names=['x', 'y', 'z'], types='B' * 3) + op.linear_constraints.add( + lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), + SparsePair(ind=['y', 'z'], val=[1, -1]), + SparsePair(ind=['z', 'x'], val=[1, 2])], + senses=['E', 'L', 'G'], + rhs=[1, 2, 3], + names=['xy', 'yz', 'zx'] + ) + conv = InequalityToEqualityConverter() + op2 = conv.encode(op, mode='continuous') + var = op2.variables + self.assertListEqual(var.get_types(3, 4), ['C', 'C']) + + def test_inequality_mode_auto(self): + """ Test auto mode of InequalityToEqualityConverter() """ + op = OptimizationProblem() + op.variables.add(names=['x', 'y', 'z'], types='B' * 3) + op.linear_constraints.add( + lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), + SparsePair(ind=['y', 'z'], val=[1, -1]), + SparsePair(ind=['z', 'x'], val=[1.1, 2.2])], + senses=['E', 'L', 'G'], + rhs=[1, 2, 3.3], + names=['xy', 'yz', 'zx'] + ) + conv = InequalityToEqualityConverter() + op2 = conv.encode(op, mode='auto') + var = op2.variables + self.assertListEqual(var.get_types(3, 4), ['I', 'C']) def test_penalize_sense(self): - """ test penalize sense """ + """ Test PenalizeLinearEqualityConstraints with senses """ op = OptimizationProblem() op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( @@ -98,7 +222,7 @@ def test_penalize_sense(self): self.assertRaises(QiskitOptimizationError, lambda: conv.encode(op)) def test_penalize_binary(self): - """ test penalize binary """ + """ Test PenalizeLinearEqualityConstraints with binary variables """ op = OptimizationProblem() op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( @@ -114,9 +238,10 @@ def test_penalize_binary(self): self.assertEqual(op2.linear_constraints.get_num(), 0) def test_penalize_integer(self): - """ test penalize integer """ + """ Test PenalizeLinearEqualityConstraints with integer variables """ op = OptimizationProblem() - op.variables.add(names=['x', 'y', 'z'], types='I' * 3, lb=[-3] * 3, ub=[3] * 3) + op.variables.add(names=['x', 'y', 'z'], + types='I' * 3, lb=[-3] * 3, ub=[3] * 3) op.linear_constraints.add( lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), SparsePair(ind=['y', 'z'], val=[1, -1])], @@ -130,13 +255,20 @@ def test_penalize_integer(self): self.assertEqual(op2.linear_constraints.get_num(), 0) def test_integer_to_binary(self): - """ test integer to binary """ + """ Test integer to binary """ op = OptimizationProblem() - op.variables.add(names=['x', 'y', 'z'], types='BIC', lb=[0, 0, 0], ub=[1, 5, 10]) + op.variables.add(names=['x', 'y', 'z'], types='BIC', + lb=[0, 0, 0], ub=[1, 6, 10]) + op.objective.set_linear([('x', 1), ('y', 2), ('z', 1)]) + op.linear_constraints.add( + lin_expr=[SparsePair(ind=['x', 'y', 'z'], val=[1, 3, 1])], + senses=['L'], + rhs=[10], + names=['xyz'] + ) self.assertEqual(op.variables.get_num(), 3) conv = IntegerToBinaryConverter() op2 = conv.encode(op) - print(op2.variables.get_num()) names = op2.variables.get_names() self.assertIn('x', names) self.assertIn('z', names) @@ -145,7 +277,70 @@ def test_integer_to_binary(self): self.assertEqual(variables.get_lower_bounds('z'), 0.0) self.assertEqual(variables.get_upper_bounds('x'), 1.0) self.assertEqual(variables.get_upper_bounds('z'), 10.0) - self.assertListEqual(variables.get_types(['x', 'z']), ['B', 'C']) + self.assertListEqual(variables.get_types(['x', 'y@0', 'y@1', 'y@2', 'z']), + ['B', 'B', 'B', 'B', 'C']) + self.assertListEqual(op2.objective.get_linear(['y@0', 'y@1', 'y@2']), [2, 4, 6]) + self.assertListEqual(op2.linear_constraints.get_rows()[0].val, [1, 3, 6, 9, 1]) + + def test_binary_to_integer(self): + """ Test binary to integer """ + op = OptimizationProblem() + op.variables.add(names=['x', 'y', 'z'], types='BIB', lb=[ + 0, 0, 0], ub=[1, 7, 1]) + op.objective.set_linear([('x', 2), ('y', 1), ('z', 1)]) + op.linear_constraints.add( + lin_expr=[SparsePair(ind=['x', 'y', 'z'], val=[1, 1, 1])], + senses=['L'], + rhs=[7], + names=['xyz'] + ) + op.objective.set_sense(-1) + conv = IntegerToBinaryConverter() + _ = conv.encode(op) + result = OptimizationResult(x=[1, 0., 1, 1, 0], fval=8) + new_result = conv.decode(result) + self.assertListEqual(new_result.x, [1, 6, 0]) + self.assertEqual(new_result.fval, 8) + + def test_optimizationproblem_to_operator(self): + """ Test optimization problem to operators""" + op = OptimizationProblem() + op.variables.add(names=['a', 'b', 'c', 'd'], types='B'*4) + op.objective.set_linear([('a', 1), ('b', 1), ('c', 1), ('d', 1)]) + op.linear_constraints.add( + lin_expr=[SparsePair(ind=['a', 'b', 'c', 'd'], val=[1, 2, 3, 4])], + senses=['E'], + rhs=[3], + names=['abcd'] + ) + op.objective.set_sense(-1) + penalize = PenalizeLinearEqualityConstraints() + op2ope = OptimizationProblemToOperator() + op2 = penalize.encode(op) + qubitop, offset = op2ope.encode(op2) + self.assertListEqual(qubitop.paulis, QUBIT_OP_MAXIMIZE_SAMPLE.paulis) + self.assertEqual(offset, OFFSET_MAXIMIZE_SAMPLE) + + def test_quadratic_constraints(self): + """ Test quadratic constraints""" + # IntegerToBinaryConverter + with self.assertRaises(QiskitOptimizationError): + op = OptimizationProblem() + op.variables.add(names=['x', 'y']) + l_expr = SparsePair(ind=['x'], val=[1.0]) + q_expr = [['x'], ['y'], [1]] + op.quadratic_constraints.add(name=str(1), lin_expr=l_expr, quad_expr=q_expr) + conv = IntegerToBinaryConverter() + _ = conv.encode(op) + # InequalityToEqualityConverter + with self.assertRaises(QiskitOptimizationError): + op = OptimizationProblem() + op.variables.add(names=['x', 'y']) + l_expr = SparsePair(ind=['x'], val=[1.0]) + q_expr = [['x'], ['y'], [1]] + op.quadratic_constraints.add(name=str(1), lin_expr=l_expr, quad_expr=q_expr) + conv = InequalityToEqualityConverter() + _ = conv.encode(op) if __name__ == '__main__': From f176485c2d289cfe879a212319bf886f5a7efe04 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Mon, 6 Apr 2020 13:52:38 +0200 Subject: [PATCH 150/323] fix missing breakline --- test/optimization/test_admm.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index 79cf2f0226..cd19a09fe5 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -25,6 +25,7 @@ ADMMOptimizerResult, ADMMState from qiskit.optimization.problems import OptimizationProblem + class TestADMMOptimizer(QiskitOptimizationTestCase): """ADMM Optimizer Tests""" From c0727cb0e07aba97044774c88240eda228b1d931 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com> Date: Tue, 7 Apr 2020 01:28:11 +0900 Subject: [PATCH 151/323] Refactor OptimizationProblem (#45) * (wip) refactor OptimizationProblem * refactor * refactor * complete to_cplex * stop using objective.set_quadratic_coefficients * implement substitute_variables for quadratic constraints * fix naming conventions * small fixes * add tests of range * cleanup * fix objective.get_ methods and introduce symmetric matrix class for quadratic terms * fix opt prob to pass tests * rename sym mat with sparse mat * revise docstrings * fix quadratic_constrints.add to align with spec; add test for sparse matrix * add type hints and docstrings of SparseMatrix * (wip) revise subs * add tests for variable bounds * add tests for subs of linear csts * (wip) revise subs * fix name index to be more informative * fix variable order in substitution * completed substitutions and tests * add problem type detection and tests * add problem status and associated tests * separete substitute_variables because it gets large * add example of substitute_variables * update modules depending on OptimizationProblem * small fixes * (wip) pylint * fix bugs * rename substitute status with substitution status * fix lint * replace SparseMatrix with dok_matrix * add type hints and fix a bug of variables.add * pylint * delete more pylint disables * pylint * type hint and lint * fix a typo * replace objective.get_linear() and get_quadratic() with get_linear_dict() and get_quadratic_dict() --- .../algorithms/cobyla_optimizer.py | 9 +- .../recursive_minimum_eigen_optimizer.py | 10 +- .../inequality_to_equality_converter.py | 7 +- .../converters/integer_to_binary_converter.py | 38 +- ...zation_problem_to_negative_value_oracle.py | 24 +- .../optimization_problem_to_operator.py | 47 +- .../penalize_linear_equality_constraints.py | 16 +- .../problems/linear_constraint.py | 131 +-- qiskit/optimization/problems/objective.py | 219 ++-- .../problems/optimization_problem.py | 992 +++++++++++------- qiskit/optimization/problems/problem_type.py | 16 +- .../problems/quadratic_constraint.py | 83 +- qiskit/optimization/problems/variables.py | 104 +- qiskit/optimization/utils/base.py | 29 +- qiskit/optimization/utils/helpers.py | 63 +- test/optimization/test_converters.py | 3 +- test/optimization/test_helpers.py | 44 +- test/optimization/test_linear_constraints.py | 16 +- test/optimization/test_objective.py | 100 +- .../optimization/test_optimization_problem.py | 337 +++++- ...zation_problem_to_negative_value_oracle.py | 6 +- .../test_quadratic_constraints.py | 195 ++-- test/optimization/test_variables.py | 36 +- 23 files changed, 1598 insertions(+), 927 deletions(-) diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index f52df4d696..c84c50a407 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -119,15 +119,14 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: # construct objective function from linear and quadratic part of objective offset = problem.objective.get_offset() - linear_dict = problem.objective.get_linear() - quadratic_dict = problem.objective.get_quadratic() + linear_dict = problem.objective.get_linear_dict() + quadratic_dict = problem.objective.get_quadratic_dict() linear = np.zeros(num_vars) quadratic = np.zeros((num_vars, num_vars)) for i, v in linear_dict.items(): linear[i] = v - for i, v_i in quadratic_dict.items(): - for j, v in v_i.items(): - quadratic[i, j] = v + for (i, j), v in quadratic_dict.items(): + quadratic[i, j] = v def objective(x): value = problem.objective.get_sense() * ( diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 4ce878f861..de926d2d29 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -1,4 +1,3 @@ - # -*- coding: utf-8 -*- # This code is part of Qiskit. @@ -128,7 +127,10 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: x_j = problem_.variables.get_names(j) if correlations[i, j] > 0: # set x_i = x_j - problem_ = problem_.substitute_variables(variables=SparseTriple([i], [j], [1])) + problem_, status = problem_.substitute_variables( + variables=SparseTriple([i], [j], [1])) + if status == problem_.substitution_status.infeasible: + raise QiskitOptimizationError('Infeasible due to variable substitution') replacements[x_i] = (x_j, 1) else: # set x_i = 1 - x_j, this is done in two steps: @@ -149,8 +151,10 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: problem_.objective.set_linear(k, coeff) # 2. replace x_i by -x_j - problem_ = problem_.substitute_variables( + problem_, status = problem_.substitute_variables( variables=SparseTriple([i], [j], [-1])) + if status == problem_.substitution_status.infeasible: + raise QiskitOptimizationError('Infeasible due to variable substitution') replacements[x_i] = (x_j, -1) # solve remaining problem diff --git a/qiskit/optimization/converters/inequality_to_equality_converter.py b/qiskit/optimization/converters/inequality_to_equality_converter.py index 8930ca5843..37dec752e0 100644 --- a/qiskit/optimization/converters/inequality_to_equality_converter.py +++ b/qiskit/optimization/converters/inequality_to_equality_converter.py @@ -99,13 +99,12 @@ def encode(self, op: OptimizationProblem, name: Optional[str] = None, self._dst.objective.set_offset(self._src.objective.get_offset()) # set linear objective terms - for i, v in self._src.objective.get_linear().items(): + for i, v in self._src.objective.get_linear_dict().items(): self._dst.objective.set_linear(i, v) # set quadratic objective terms - for i, v_i in self._src.objective.get_quadratic().items(): - for j, v in v_i.items(): - self._dst.objective.set_quadratic_coefficients(i, j, v) + for (i, j), v in self._src.objective.get_quadratic_dict().items(): + self._dst.objective.set_quadratic_coefficients(i, j, v) # set linear constraints names = self._src.linear_constraints.get_names() diff --git a/qiskit/optimization/converters/integer_to_binary_converter.py b/qiskit/optimization/converters/integer_to_binary_converter.py index d2d9cb9b42..923776c2d8 100644 --- a/qiskit/optimization/converters/integer_to_binary_converter.py +++ b/qiskit/optimization/converters/integer_to_binary_converter.py @@ -113,7 +113,7 @@ def _substitute_int_var(self): self._dst.objective.set_offset(self._src.objective.get_offset()) # set linear terms of objective function - src_obj_linear = self._src.objective.get_linear() + src_obj_linear = self._src.objective.get_linear_dict() for src_var_index in src_obj_linear: coef = src_obj_linear[src_var_index] @@ -127,32 +127,30 @@ def _substitute_int_var(self): self._dst.objective.set_linear(var_name, coef) # set quadratic terms of objective function - src_obj_quad = self._src.objective.get_quadratic() + src_obj_quad = self._src.objective.get_quadratic_dict() num_var = self._dst.variables.get_num() new_quad = np.zeros((num_var, num_var)) - for row in src_obj_quad: - for col in src_obj_quad[row]: - row_var_name = self._src.variables.get_names(row) - col_var_name = self._src.variables.get_names(col) - coef = src_obj_quad[row][col] + for (row, col), coef in src_obj_quad.items(): + row_var_name = self._src.variables.get_names(row) + col_var_name = self._src.variables.get_names(col) - if row_var_name in self._conv: - row_vars = self._conv[row_var_name] - else: - row_vars = [(row_var_name, 1)] + if row_var_name in self._conv: + row_vars = self._conv[row_var_name] + else: + row_vars = [(row_var_name, 1)] - if col_var_name in self._conv: - col_vars = self._conv[col_var_name] - else: - col_vars = [(col_var_name, 1)] + if col_var_name in self._conv: + col_vars = self._conv[col_var_name] + else: + col_vars = [(col_var_name, 1)] - for new_row, row_coef in row_vars: - for new_col, col_coef in col_vars: - row_index = self._dst.variables.get_indices(new_row) - col_index = self._dst.variables.get_indices(new_col) - new_quad[row_index, col_index] = coef * row_coef * col_coef + for new_row, row_coef in row_vars: + for new_col, col_coef in col_vars: + row_index = self._dst.variables.get_indices(new_row) + col_index = self._dst.variables.get_indices(new_col) + new_quad[row_index, col_index] = coef * row_coef * col_coef ind = list(range(num_var)) lst = [] diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index 72fc91880a..63a9f1d768 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -16,12 +16,13 @@ import logging from typing import Tuple, Dict, Union + import numpy as np from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister -from qiskit.aqua.components.oracles import CustomCircuitOracle from qiskit.aqua.components.initial_states import Custom from qiskit.aqua.components.iqfts import Standard as IQFT +from qiskit.aqua.components.oracles import CustomCircuitOracle from qiskit.optimization.problems import OptimizationProblem logger = logging.getLogger(__name__) @@ -59,23 +60,22 @@ def encode(self, problem: OptimizationProblem) -> \ """ # get linear part of objective - linear_dict = problem.objective.get_linear() + linear_dict = problem.objective.get_linear_dict() linear_coeff = np.zeros(problem.variables.get_num()) for i, v in linear_dict.items(): linear_coeff[i] = v # get quadratic part of objective - quadratic_dict = problem.objective.get_quadratic() + quadratic_dict = problem.objective.get_quadratic_dict() quadratic_coeff = {} - for i, j_value_dict in quadratic_dict.items(): - for j, value in j_value_dict.items(): - if i <= j: - # divide by 2 since problem considers xQx/2. - coeff = quadratic_coeff.get((i, j), 0) - quadratic_coeff[(i, j)] = value / 2 + coeff - else: - coeff = quadratic_coeff.get((j, i), 0) - quadratic_coeff[(j, i)] = value / 2 + coeff + for (i, j), value in quadratic_dict.items(): + if i <= j: + # divide by 2 since problem considers xQx/2. + coeff = quadratic_coeff.get((i, j), 0) + quadratic_coeff[(i, j)] = value / 2 + coeff + else: + coeff = quadratic_coeff.get((j, i), 0) + quadratic_coeff[(j, i)] = value / 2 + coeff constant = problem.objective.get_offset() diff --git a/qiskit/optimization/converters/optimization_problem_to_operator.py b/qiskit/optimization/converters/optimization_problem_to_operator.py index 658e9e76b4..d34e00c672 100644 --- a/qiskit/optimization/converters/optimization_problem_to_operator.py +++ b/qiskit/optimization/converters/optimization_problem_to_operator.py @@ -84,7 +84,7 @@ def encode(self, op: OptimizationProblem) -> Tuple[WeightedPauliOperator, float] shift += self._src.objective.get_offset() * sense # convert linear parts of the object function into Hamiltonian. - for i, coef in self._src.objective.get_linear().items(): + for i, coef in self._src.objective.get_linear_dict().items(): z_p = np.zeros(num_nodes, dtype=np.bool) qubit_index = _q_d[i] weight = coef * sense / 2 @@ -94,33 +94,32 @@ def encode(self, op: OptimizationProblem) -> Tuple[WeightedPauliOperator, float] shift += weight # convert quadratic parts of the object function into Hamiltonian. - for i, v_i in self._src.objective.get_quadratic().items(): - for j, coef in v_i.items(): - if j < i: - continue - qubit_index_1 = _q_d[i] - qubit_index_2 = _q_d[j] - if i == j: - coef = coef / 2 - weight = coef * sense / 4 - - if qubit_index_1 == qubit_index_2: - shift += weight - else: - z_p = np.zeros(num_nodes, dtype=np.bool) - z_p[qubit_index_1] = True - z_p[qubit_index_2] = True - pauli_list.append([weight, Pauli(z_p, zero)]) - + for (i, j), coef in self._src.objective.get_quadratic_dict().items(): + if j < i: + continue + qubit_index_1 = _q_d[i] + qubit_index_2 = _q_d[j] + if i == j: + coef = coef / 2 + weight = coef * sense / 4 + + if qubit_index_1 == qubit_index_2: + shift += weight + else: z_p = np.zeros(num_nodes, dtype=np.bool) z_p[qubit_index_1] = True - pauli_list.append([-weight, Pauli(z_p, zero)]) - - z_p = np.zeros(num_nodes, dtype=np.bool) z_p[qubit_index_2] = True - pauli_list.append([-weight, Pauli(z_p, zero)]) + pauli_list.append([weight, Pauli(z_p, zero)]) - shift += weight + z_p = np.zeros(num_nodes, dtype=np.bool) + z_p[qubit_index_1] = True + pauli_list.append([-weight, Pauli(z_p, zero)]) + + z_p = np.zeros(num_nodes, dtype=np.bool) + z_p[qubit_index_2] = True + pauli_list.append([-weight, Pauli(z_p, zero)]) + + shift += weight # Remove paulis whose coefficients are zeros. qubit_op = WeightedPauliOperator(paulis=pauli_list) diff --git a/qiskit/optimization/converters/penalize_linear_equality_constraints.py b/qiskit/optimization/converters/penalize_linear_equality_constraints.py index 0224d5b357..e9b17a16e0 100644 --- a/qiskit/optimization/converters/penalize_linear_equality_constraints.py +++ b/qiskit/optimization/converters/penalize_linear_equality_constraints.py @@ -75,14 +75,13 @@ def encode(self, op: OptimizationProblem, penalty_factor: float = 1e5, offset = self._src.objective.get_offset() # store original linear objective terms - linear_terms = defaultdict(int) - for i, v in self._src.objective.get_linear().items(): + linear_terms = defaultdict(float) + for i, v in self._src.objective.get_linear_dict().items(): linear_terms[i] = v # store original quadratic objective terms - quadratic_terms = defaultdict(lambda: defaultdict(int)) - for i, v in self._src.objective.get_quadratic().items(): - quadratic_terms[i].update(v) + quadratic_terms = defaultdict(float) + quadratic_terms.update(self._src.objective.get_quadratic_dict().items()) # get linear constraints' data linear_rows = self._src.linear_constraints.get_rows() @@ -119,7 +118,7 @@ def encode(self, op: OptimizationProblem, penalty_factor: float = 1e5, # according to implementation of quadratic terms in OptimizationModel, # multiply by 2 - quadratic_terms[var_ind_1][var_ind_2] += penalty_factor * coef_1 * coef_2 * 2 + quadratic_terms[var_ind_1, var_ind_2] += penalty_factor * coef_1 * coef_2 * 2 # set objective offset self._dst.objective.set_offset(offset) @@ -129,8 +128,7 @@ def encode(self, op: OptimizationProblem, penalty_factor: float = 1e5, self._dst.objective.set_linear(i, v) # set quadratic objective terms - for i, v_i in quadratic_terms.items(): - for j, v in v_i.items(): - self._dst.objective.set_quadratic_coefficients(i, j, v) + for (i, j), v in quadratic_terms.items(): + self._dst.objective.set_quadratic_coefficients(i, j, v) return self._dst diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index 8b0e2710e7..fd4c46e193 100644 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -12,33 +12,23 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Methods for adding, modifying, and querying linear constraints.""" +"""Linear constraints interface""" import copy from collections.abc import Sequence +from typing import Callable, Optional, List, Union + +from cplex import SparsePair from qiskit.optimization.utils.base import BaseInterface from qiskit.optimization.utils.helpers import init_list_args, NameIndex from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError -# TODO: can we delete these? -CPX_CON_LOWER_BOUND = 1 -CPX_CON_UPPER_BOUND = 2 -CPX_CON_LINEAR = 3 -CPX_CON_QUADRATIC = 4 -CPX_CON_SOS = 5 -CPX_CON_INDICATOR = 6 -CPX_CON_PWL = 7 -CPX_CON_ABS = 7 -CPX_CON_MINEXPR = 8 -CPX_CON_MAXEXPR = 9 -CPX_CON_LAST_CONTYPE = 10 - class LinearConstraintInterface(BaseInterface): """Methods for adding, modifying, and querying linear constraints.""" - def __init__(self, varindex): + def __init__(self, varindex: Callable): """Creates a new LinearConstraintInterface. The linear constraints interface is exposed by the top-level @@ -54,21 +44,22 @@ def __init__(self, varindex): self._index = NameIndex() self._varindex = varindex - def get_num(self): + def get_num(self) -> int: """Returns the number of linear constraints. Example usage: >>> from qiskit.optimization import OptimizationProblem - >>> op = qiskit.optimization.OptimizationProblem() + >>> op = OptimizationProblem() >>> indices = op.linear_constraints.add(names = ["c1", "c2", "c3"]) >>> op.linear_constraints.get_num() 3 """ return len(self._names) - def add(self, lin_expr=None, senses="", rhs=None, range_values=None, - names=None): + def add(self, lin_expr: Optional[List[SparsePair]] = None, senses: str = "", + rhs: Optional[List[float]] = None, range_values: Optional[List[float]] = None, + names: Optional[List[str]] = None) -> range: """Adds linear constraints to the problem. linear_constraints.add accepts the keyword arguments lin_expr, @@ -108,7 +99,7 @@ def add(self, lin_expr=None, senses="", rhs=None, range_values=None, constraints. >>> from qiskit.optimization import OptimizationProblem - >>> op = qiskit.optimization.OptimizationProblem() + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = ["x1", "x2", "x3"]) >>> indices = op.linear_constraints.add(\ lin_expr = [SparsePair(ind = ["x1", "x3"], val = [1.0, -1.0]),\ @@ -122,45 +113,41 @@ def add(self, lin_expr=None, senses="", rhs=None, range_values=None, >>> op.linear_constraints.get_rhs() [0.0, 1.0, -1.0, 2.0] """ - from cplex import SparsePair + start = self.get_num() arg_list = init_list_args(lin_expr, senses, rhs, range_values, names) arg_lengths = [len(x) for x in arg_list] if len(arg_lengths) == 0: - return range(0) + return range(start, start) max_length = max(arg_lengths) + if max_length == 0: + return range(start, start) for arg_length in arg_lengths: if arg_length > 0 and arg_length != max_length: raise QiskitOptimizationError("inconsistent arguments in linear_constraints.add().") - if max_length == 0: - return range(len(self._names), len(self._names)) - assert max_length > 0 - if not rhs: - rhs = [0.0] * max_length + rhs = rhs or [0.0] * max_length self._rhs.extend(rhs) - if not senses: - senses = "E" * max_length + senses = senses or 'E' * max_length self._senses.extend(senses) - if not range_values: - range_values = [0.0] * max_length + range_values = range_values or [0.0] * max_length self._range_values.extend(range_values) - if not names: - names = ["c" + str(cnt) for cnt in range(len(self._names), - len(self._names) + max_length)] + names = names or [''] * max_length + for i, name in enumerate(names): + if name == '': + names[i] = 'c' + str(start + i + 1) self._names.extend(names) self._index.build(self._names) - if not lin_expr: - lin_expr = [SparsePair()] * max_length - for s_p in lin_expr: + lin_expr = lin_expr or [SparsePair()] * max_length + for spair in lin_expr: lin_expr_dict = {} - if isinstance(s_p, SparsePair): - zip_iter = zip(s_p.ind, s_p.val) - elif isinstance(s_p, Sequence) and len(s_p) == 2: - zip_iter = zip(s_p[0], s_p[1]) + if isinstance(spair, SparsePair): + zip_iter = zip(spair.ind, spair.val) + elif isinstance(spair, Sequence) and len(spair) == 2: + zip_iter = zip(spair[0], spair[1]) else: raise QiskitOptimizationError('Invalid lin_expr: {}'.format(lin_expr)) for i, val in zip_iter: @@ -171,7 +158,7 @@ def add(self, lin_expr=None, senses="", rhs=None, range_values=None, lin_expr_dict[i] = val self._lin_expr.append(lin_expr_dict) - return range(len(self._names) - max_length, len(self._names)) + return range(start, start + max_length) def delete(self, *args): """Removes linear constraints from the problem. @@ -199,13 +186,10 @@ def delete(self, *args): give the best performance when deleting batches of linear constraints. - See CPXdelrows in the Callable Library Reference Manual for - more detail. - Example usage: >>> from qiskit.optimization import OptimizationProblem - >>> op = qiskit.optimization.OptimizationProblem() + >>> op = OptimizationProblem() >>> indices = op.linear_constraints.add(names=[str(i) for i in range(10)]) >>> op.linear_constraints.get_num() 10 @@ -261,7 +245,7 @@ def set_rhs(self, *args): [linear_constraints.set_rhs(pair[0], pair[1]) for pair in seq_of_pairs]. >>> from qiskit.optimization import OptimizationProblem - >>> op = qiskit.optimization.OptimizationProblem() + >>> op = OptimizationProblem() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) >>> op.linear_constraints.get_rhs() [0.0, 0.0, 0.0, 0.0] @@ -296,7 +280,7 @@ def set_names(self, *args): [linear_constraints.set_names(pair[0], pair[1]) for pair in seq_of_pairs]. >>> from qiskit.optimization import OptimizationProblem - >>> op = qiskit.optimization.OptimizationProblem() + >>> op = OptimizationProblem() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) >>> op.linear_constraints.set_names("c1", "second") >>> op.linear_constraints.get_names(1) @@ -333,7 +317,7 @@ def set_senses(self, *args): ranged constraints, respectively. >>> from qiskit.optimization import OptimizationProblem - >>> op = qiskit.optimization.OptimizationProblem() + >>> op = OptimizationProblem() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) >>> op.linear_constraints.get_senses() ['E', 'E', 'E', 'E'] @@ -373,7 +357,7 @@ def set_linear_components(self, *args): [linear_constraints.set_linear_components(pair[0], pair[1]) for pair in seq_of_pairs]. >>> from qiskit.optimization import OptimizationProblem - >>> op = qiskit.optimization.OptimizationProblem() + >>> op = OptimizationProblem() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) >>> indices = op.variables.add(names = ["x0", "x1"]) >>> op.linear_constraints.set_linear_components("c0", [["x0"], [1.0]]) @@ -388,7 +372,6 @@ def set_linear_components(self, *args): """ def _set(i, v): - from cplex import SparsePair if isinstance(v, SparsePair): zip_iter = zip(v.ind, v.val) elif isinstance(v, Sequence) and len(v) == 2: @@ -426,7 +409,7 @@ def set_range_values(self, *args): the previous range value was 0 (zero) and the constraint sense was not 'R'. Similarly, changing the range coefficient from a nonzero value to 0 (zero) will not change the constraint sense from 'R" to "E"; an - additional call of setsenses() is required to accomplish that. + additional call of set_senses() is required to accomplish that. There are two forms by which linear_constraints.set_range_values may be called. @@ -444,7 +427,7 @@ def set_range_values(self, *args): [linear_constraints.set_range_values(pair[0], pair[1]) for pair in seq_of_pairs]. >>> from qiskit.optimization import OptimizationProblem - >>> op = qiskit.optimization.OptimizationProblem() + >>> op = OptimizationProblem() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) >>> op.linear_constraints.set_range_values("c1", 1.0) >>> op.linear_constraints.get_range_values() @@ -456,7 +439,6 @@ def set_range_values(self, *args): def _set(i, v): self._range_values[self._index.convert(i)] = v - # TODO: raise QiskitOptimizationError("Wrong range!") self._setter(_set, *args) @@ -476,7 +458,7 @@ def set_coefficients(self, *args): described above. >>> from qiskit.optimization import OptimizationProblem - >>> op = qiskit.optimization.OptimizationProblem() + >>> op = OptimizationProblem() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) >>> indices = op.variables.add(names = ["x0", "x1"]) >>> op.linear_constraints.set_coefficients("c0", "x1", 1.0) @@ -502,7 +484,7 @@ def set_coefficients(self, *args): else: self._lin_expr[i][j] = v - def get_rhs(self, *args): + def get_rhs(self, *args) -> Union[float, List[float]]: """Returns the righthand side of constraints from the problem. Can be called by four forms. @@ -523,7 +505,7 @@ def get_rhs(self, *args): [linear_constraints.get_rhs(i) for i in s] >>> from qiskit.optimization import OptimizationProblem - >>> op = qiskit.optimization.OptimizationProblem() + >>> op = OptimizationProblem() >>> indices = op.linear_constraints.add(rhs = [1.5 * i for i in range(10)],\ names = [str(i) for i in range(10)]) >>> op.linear_constraints.get_num() @@ -544,7 +526,7 @@ def _get(i): keys = self._index.convert(*args) return self._getter(_get, keys) - def get_senses(self, *args): + def get_senses(self, *args) -> Union[str, List[str]]: """Returns the senses of constraints from the problem. Can be called by four forms. @@ -564,7 +546,7 @@ def get_senses(self, *args): [linear_constraints.get_senses(i) for i in s] >>> from qiskit.optimization import OptimizationProblem - >>> op = qiskit.optimization.OptimizationProblem() + >>> op = OptimizationProblem() >>> indices = op.linear_constraints.add( ... senses=["E", "G", "L", "R"], ... names=[str(i) for i in range(4)]) @@ -588,7 +570,7 @@ def _get(i): keys = self._index.convert(*args) return self._getter(_get, keys) - def get_range_values(self, *args): + def get_range_values(self, *args) -> Union[float, List[float]]: """Returns the range values of linear constraints from the problem. That is, this method returns the lefthand side (lhs) for each @@ -620,7 +602,7 @@ def get_range_values(self, *args): [linear_constraints.get_range_values(i) for i in s] >>> from qiskit.optimization import OptimizationProblem - >>> op = qiskit.optimization.OptimizationProblem() + >>> op = OptimizationProblem() >>> indices = op.linear_constraints.add(\ range_values = [1.5 * i for i in range(10)],\ senses = ["R"] * 10,\ @@ -645,7 +627,7 @@ def _get(i): keys = self._index.convert(*args) return self._getter(_get, keys) - def get_coefficients(self, *args): + def get_coefficients(self, *args) -> Union[float, List[float]]: """Returns coefficients by row, column coordinates. There are three forms by which @@ -663,7 +645,7 @@ def get_coefficients(self, *args): returns a list of coefficients. >>> from qiskit.optimization import OptimizationProblem - >>> op = qiskit.optimization.OptimizationProblem() + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = ["x0", "x1"]) >>> indices = op.linear_constraints.add(\ names = ["c0", "c1"],\ @@ -694,7 +676,7 @@ def _get(args): raise QiskitOptimizationError( "Wrong number of arguments. Please use 2 or one list of pairs: {}".format(args)) - def get_rows(self, *args): + def get_rows(self, *args) -> Union[SparsePair, List[SparsePair]]: """Returns a set of rows of the linear constraint matrix. Returns a list of SparsePair instances or a single SparsePair @@ -716,7 +698,7 @@ def get_rows(self, *args): [linear_constraints.get_rows(i) for i in s] >>> from qiskit.optimization import OptimizationProblem - >>> op = qiskit.optimization.OptimizationProblem() + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = ["x1", "x2", "x3"]) >>> indices = op.linear_constraints.add(\ names = ["c0", "c1", "c2", "c3"],\ @@ -739,7 +721,6 @@ def get_rows(self, *args): """ def _get(i): - from cplex import SparsePair keys = list(self._lin_expr[i].keys()) keys.sort() return SparsePair(keys, [self._lin_expr[i][k] for k in keys]) @@ -747,13 +728,13 @@ def _get(i): keys = self._index.convert(*args) return self._getter(_get, keys) - def get_num_nonzeros(self): + def get_num_nonzeros(self) -> int: """Returns the number of nonzeros in the linear constraint matrix. Example usage: >>> from qiskit.optimization import OptimizationProblem - >>> op = qiskit.optimization.OptimizationProblem() + >>> op = OptimizationProblem() >>> indices = op.variables.add(names = ["x1", "x2", "x3"]) >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"],\ lin_expr = [SparsePair(ind = ["x1", "x3"], val = [1.0, -1.0]),\ @@ -764,13 +745,13 @@ def get_num_nonzeros(self): 9 """ nnz = 0 - for c in self._lin_expr: - for e_e in c.values(): - if e_e != 0.0: + for dic in self._lin_expr: + for val in dic.values(): + if val != 0.0: nnz += 1 return nnz - def get_names(self, *args): + def get_names(self, *args) -> Union[str, List[str]]: """Returns the names of linear constraints from the problem. There are four forms by which linear_constraints.get_names may be called. @@ -787,7 +768,7 @@ def get_names(self, *args): Equivalent to [linear_constraints.get_names(i) for i in s] >>> from qiskit.optimization import OptimizationProblem - >>> op = qiskit.optimization.OptimizationProblem() + >>> op = OptimizationProblem() >>> indices = op.linear_constraints.add(names = ["c" + str(i) for i in range(10)]) >>> op.linear_constraints.get_num() 10 @@ -808,5 +789,5 @@ def _get(i): return self._getter(_get, keys) def get_histogram(self): - """ get histogram """ - raise NotImplementedError("histogram is not implemented") + """histogram is not supported""" + raise NotImplementedError("histogram is not supported") diff --git a/qiskit/optimization/problems/objective.py b/qiskit/optimization/problems/objective.py index 9dd3c6db28..d0ea30e121 100644 --- a/qiskit/optimization/problems/objective.py +++ b/qiskit/optimization/problems/objective.py @@ -12,30 +12,39 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" Problems Objective module""" +"""Objective function interface""" import copy import numbers from collections.abc import Sequence from logging import getLogger -from typing import Callable, List +from typing import Callable, List, Union, Dict, Tuple + +from cplex import SparsePair +from scipy.sparse import dok_matrix from qiskit.optimization.utils import BaseInterface, QiskitOptimizationError +logger = getLogger(__name__) + CPX_MAX = -1 CPX_MIN = 1 -logger = getLogger(__name__) - class ObjSense: """Constants defining the sense of the objective function.""" maximize = CPX_MAX minimize = CPX_MIN - def __getitem__(self, item): + def __getitem__(self, item: int) -> str: """Converts a constant to a string. + Returns: + Sense name. + + Raises: + QiskitOptimizationError: if the argument is not a valid number. + Example usage: >>> from qiskit.optimization import OptimizationProblem @@ -49,20 +58,25 @@ def __getitem__(self, item): return 'maximize' if item == CPX_MIN: return 'minimize' - return None + raise QiskitOptimizationError("Invalid sense: {}".format(item)) class ObjectiveInterface(BaseInterface): """Contains methods for querying and modifying the objective function.""" sense = ObjSense() - """See `ObjSense()`""" def __init__(self, varindex: Callable): + """Creates a new ObjectiveInterface. + + The objective function interface is exposed by the top-level + `OptimizationProblem` class as `OptimizationProblem.objective`. + This constructor is not meant to be used externally. + """ super(ObjectiveInterface, self).__init__() self._linear = {} - self._quadratic = {} - self._name = 'Objective' + self._quadratic = dok_matrix((0, 0)) + self._name = 'obj1' self._sense = ObjSense.minimize self._offset = 0.0 self._varindex = varindex @@ -107,7 +121,7 @@ def _set(i, v): self._setter(_set, *args) - def set_quadratic(self, args: List): + def set_quadratic(self, coef: Union[List[float], List[SparsePair], List[Sequence]]): """Sets the quadratic part of the objective function. Call this method with a list with length equal to the number @@ -143,56 +157,51 @@ def set_quadratic(self, args: List): [SparsePair(ind = [0], val = [1.0]), SparsePair(ind = [1], val = [2.0]), SparsePair(ind = [2], val = [3.0])] """ - from cplex import SparsePair + # clear data - self._quadratic = {} + self._quadratic = dok_matrix((0, 0)) def _set(i, j, val): if val == 0: return i = self._varindex(i) j = self._varindex(j) - if i not in self._quadratic: - self._quadratic[i] = {} - if j not in self._quadratic: - self._quadratic[j] = {} - self._quadratic[i][j] = self._quadratic[j][i] = val - - if len(args) == 0: - logger.warning('Empty argument %s', args) - elif isinstance(args[0], numbers.Number): - for i, val in enumerate(args): + max_ij = max(i, j) + if max_ij >= self._quadratic.shape[0]: + self._quadratic.resize(max_ij + 1, max_ij + 1) + self._quadratic[i, j] = val + + if len(coef) == 0: + logger.warning('Empty argument for objective.set_quadratic') + elif isinstance(coef[0], numbers.Number): + for i, val in enumerate(coef): _set(i, i, val) else: - for i, s_p in enumerate(args): - if isinstance(s_p, SparsePair): - for j, val in zip(s_p.ind, s_p.val): + for i, spair in enumerate(coef): + if isinstance(spair, SparsePair): + for j, val in zip(spair.ind, spair.val): _set(i, j, val) - elif isinstance(s_p, Sequence) and len(s_p) == 2: - for j, val in zip(s_p[0], s_p[1]): + elif isinstance(spair, Sequence) and len(spair) == 2: + for j, val in zip(spair[0], spair[1]): _set(i, j, val) else: raise QiskitOptimizationError( "set_quadratic expects a list of the length equal to the number of " "variables, where each entry has a pair of the indices of the other " - "variables and values, or the corresponding SparsePair") + "variables and values, or the corresponding SparsePair" + "{}".format(coef) + ) def set_quadratic_coefficients(self, *args): """Sets coefficients of the quadratic component of the objective function. - To set a single coefficient, call this method as - objective.set_quadratic_coefficients(v1, v2, val) - - where v1 and v2 are names or indices of variables and val is - the value for the coefficient. - - To set multiple coefficients, call this method as + set a single coefficient where v1 and v2 are names or indices of variables and val is + the value for the coefficient. objective.set_quadratic_coefficients(sequence) - - where sequence is a list or tuple of triples (v1, v2, val) as - described above. + set multiple coefficients where sequence is a list or tuple of triples (v1, v2, val) as + described above. Note Since the quadratic objective function must be symmetric, each @@ -224,38 +233,27 @@ def set_quadratic_coefficients(self, *args): """ def _set(i, j, val): - # set a value or delete an element if val is zero i = self._varindex(i) j = self._varindex(j) - if val == 0: - if i in self._quadratic and j in self._quadratic[i]: - del self._quadratic[i][j] - if len(self._quadratic[i]) == 0: - del self._quadratic[i] - if j in self._quadratic and i in self._quadratic[j]: - del self._quadratic[j][i] - if len(self._quadratic[j]) == 0: - del self._quadratic[j] - else: - if i not in self._quadratic: - self._quadratic[i] = {} - if j not in self._quadratic: - self._quadratic[j] = {} - self._quadratic[i][j] = self._quadratic[j][i] = val - - if (len(args) == 1 and isinstance(args[0], Sequence)) or len(args) == 3: - # valid arguments. go through. - pass - else: - raise QiskitOptimizationError("Wrong number of arguments: {}".format(args)) - if len(args) == 3: + max_ij = max(i, j) + if max_ij >= self._quadratic.shape[0]: + self._quadratic.resize(max_ij + 1, max_ij + 1) + # set a value at symmetric positions + self._quadratic[i, j] = val + self._quadratic[j, i] = val + + if len(args) == 1: + if not isinstance(args[0], Sequence): + raise QiskitOptimizationError("Wrong number of arguments: {}".format(args)) + arg_list = args[0] + elif len(args) == 3: arg_list = [args] else: - arg_list = args[0] + raise QiskitOptimizationError("Wrong number of arguments: {}".format(args)) for i, j, val in arg_list: _set(i, j, val) - def set_sense(self, sense): + def set_sense(self, sense: int): """Sets the sense of the objective function. The argument to this method must be either @@ -279,7 +277,7 @@ def set_sense(self, sense): "sense should be one of [CPX_MAX, CPX_MIN], i.e., objective.sense.minimize or " + "objective.sense.maximize.") - def set_name(self, name): + def set_name(self, name: str): """Sets the name of the objective function. Example usage: @@ -292,7 +290,7 @@ def set_name(self, name): """ self._name = name - def get_linear(self, *args): + def get_linear(self, *args) -> Union[float, List[float]]: """Returns the linear coefficients of a set of variables. Can be called by four forms. @@ -329,12 +327,24 @@ def get_linear(self, *args): def _get(i): return self._linear.get(i, 0.0) - if len(args) == 0: - return copy.deepcopy(self._linear) keys = self._varindex(*args) return self._getter(_get, keys) - def get_quadratic(self, *args): + def get_linear_dict(self) -> Dict[int, float]: + """Return the linear coefficients of a set of variables in a dictionary form. + + Example usage: + + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() + >>> op.variables.add(names=['x', 'y']) + >>> op.objective.set_linear([('x', 1), ('y', 2)]) + >>> print(op.objective.get_linear_dict()) + {0: 1, 1: 2} + """ + return copy.deepcopy(self._linear) + + def get_quadratic(self, *args) -> Union[SparsePair, List[SparsePair]]: """Returns a set of columns of the quadratic component of the objective function. Returns a SparsePair instance or a list of SparsePair instances. @@ -363,6 +373,11 @@ def get_quadratic(self, *args): >>> op.objective.set_quadratic([1.5 * i for i in range(10)]) >>> op.objective.get_quadratic(8) SparsePair(ind = [8], val = [12.0]) + >>> for q in c.objective.get_quadratic("1", 3): + ... print(q) + SparsePair(ind = [1], val = [1.5]) + SparsePair(ind = [2], val = [3.0]) + SparsePair(ind = [3], val = [4.5]) >>> op.objective.get_quadratic([3,"1",5]) [SparsePair(ind = [3], val = [4.5]), SparsePair(ind = [1], val = [1.5]), SparsePair(ind = [5], val = [7.5])] @@ -375,30 +390,35 @@ def get_quadratic(self, *args): """ def _get(i): - from cplex import SparsePair - q_i = self._quadratic.get(i, {}) - return SparsePair(list(q_i.keys()), list(q_i.values())) + row = self._quadratic[i] if i < self._quadratic.shape[0] else {} + return SparsePair([j for _, j in row.keys()], list(row.values())) - if len(args) == 0: - return copy.deepcopy(self._quadratic) keys = self._varindex(*args) return self._getter(_get, keys) - def get_quadratic_coefficients(self, *args): - """Returns individual coefficients from the quadratic objective function. + def get_quadratic_dict(self) -> Dict[Tuple[int, int], float]: + """Return the linear coefficients of a set of variables in a dictionary form. - To query a single coefficient, call this as + Example usage: - objective.get_quadratic_coefficients(v1, v2) + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() + >>> op.variables.add(names=['x', 'y']) + >>> op.objective.set_quadratic_coefficients([('x', 'x', 1), ('x', 'y', 2)]) + >>> print(op.objective.get_quadratic_dict()) + {(0, 0): 1, (0, 1): 2, (1, 0): 2} + """ + return dict(self._quadratic.items()) - where v1 and v2 are indices or names of variables. + def get_quadratic_coefficients(self, *args) -> Union[float, List[float]]: + """Returns individual coefficients from the quadratic objective function. - To query multiple coefficients, call this method as + objective.get_quadratic_coefficients(v1, v2) + query a single coefficient where v1 and v2 are indices or names of variables. objective.get_quadratic_coefficients(sequence) - - where sequence is a list or tuple of pairs (v1, v2) as - described above. + query multiple coefficients where sequence is a list or tuple of pairs (v1, v2) as + described above. >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() @@ -411,13 +431,16 @@ def get_quadratic_coefficients(self, *args): [5.0, 2.0, 3.0] """ - def _get(args): - i, j = args - return self._quadratic.get(i, {}).get(j, 0) + def _get(pair): + i, j = pair + max_ij = max(i, j) + if max_ij >= self._quadratic.shape[0]: + return 0 + return self._quadratic.get((i, j), 0) if len(args) == 0: - return copy.deepcopy(self._quadratic) - elif len(args) == 1 and isinstance(args[0], Sequence): + raise QiskitOptimizationError('Wrong number of arguments') + if len(args) == 1 and isinstance(args[0], Sequence): i, j = zip(*args[0]) i = self._varindex(i) j = self._varindex(j) @@ -430,7 +453,7 @@ def _get(args): else: raise QiskitOptimizationError('Invalid arguments {}'.format(args)) - def get_sense(self): + def get_sense(self) -> int: """Returns the sense of the objective function. Example usage: @@ -448,7 +471,7 @@ def get_sense(self): """ return self._sense - def get_name(self): + def get_name(self) -> str: """Returns the name of the objective function. Example usage: @@ -459,11 +482,9 @@ def get_name(self): >>> op.objective.get_name() 'cost' """ - if not self._name: - logger.warning('No name of exists for objective') return self._name - def get_num_quadratic_variables(self): + def get_num_quadratic_variables(self) -> int: """Returns the number of variables with quadratic coefficients. Example usage: @@ -481,9 +502,13 @@ def get_num_quadratic_variables(self): >>> op.objective.get_num_quadratic_variables() 3 """ - return len(self._quadratic) + num = 0 + for i in range(self._quadratic.shape[0]): + if self._quadratic[i].nnz > 0: + num += 1 + return num - def get_num_quadratic_nonzeros(self): + def get_num_quadratic_nonzeros(self) -> int: """Returns the number of nonzeros in the quadratic objective function. Example usage: @@ -501,9 +526,9 @@ def get_num_quadratic_nonzeros(self): >>> op.objective.get_num_quadratic_nonzeros() 3 """ - return sum(len(v) for v in self._quadratic.values()) + return self._quadratic.nnz - def get_offset(self): + def get_offset(self) -> float: """Returns the constant offset of the objective function for a problem. Example usage: @@ -516,7 +541,7 @@ def get_offset(self): """ return self._offset - def set_offset(self, offset): + def set_offset(self, offset: float): """Sets the constant offset of the objective function for a problem. Example usage: diff --git a/qiskit/optimization/problems/optimization_problem.py b/qiskit/optimization/problems/optimization_problem.py index c8fbed084b..a637e6ae5a 100644 --- a/qiskit/optimization/problems/optimization_problem.py +++ b/qiskit/optimization/problems/optimization_problem.py @@ -12,9 +12,17 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""The optimization problem.""" +"""Mixed integer quadratically constrained quadratic program""" -from docplex.mp.model import Model +from enum import Enum +from logging import getLogger +from math import fsum +from typing import Optional, Tuple + +from cplex import Cplex, SparsePair +from cplex import SparseTriple, infinity +from cplex.exceptions import CplexSolverError +from docplex.mp.model import Model as DocplexModel from qiskit.optimization.problems.linear_constraint import LinearConstraintInterface from qiskit.optimization.problems.objective import ObjectiveInterface @@ -24,6 +32,8 @@ from qiskit.optimization.results.solution import SolutionInterface from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError +logger = getLogger(__name__) + class OptimizationProblem: """A class encapsulating an optimization problem, modeled after Python CPLEX API. @@ -33,226 +43,214 @@ class OptimizationProblem: querying aspects of the solution. """ - def __init__(self, *args): + def __init__(self, file_name: Optional[str] = None): """Constructor of the OptimizationProblem class. The OptimizationProblem constructor accepts four types of argument lists. - op = qiskit.optimization.OptimizationProblem() + Args: + file_name: read a model from a file. + + Raises: + QiskitOptimizationError: if it cannot load a file. + + Examples: + + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() op is a new problem with no data - op = qiskit.optimization.OptimizationProblem("filename") + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem("filename") op is a new problem containing the data in filename. If filename does not exist, an exception is raised. The OptimizationProblem object is a context manager and can be used, like so: - with qiskit.optimization.OptimizationProblem() as op: - # do stuff - op.solve() + >>> from qiskit.optimization import OptimizationProblem + >>> with OptimizationProblem() as op: + >>> # do stuff + >>> op.solve() - When the with block is finished, the end() method will be called - automatically. + When the with block is finished, the end() method will be called automatically. """ - from cplex.exceptions import CplexSolverError - if len(args) > 1: - raise QiskitOptimizationError("Too many arguments to OptimizationProblem()") - self._disposed = False - self._name = None - - # see `qiskit.optimization.VariablesInterface()` - self.variables = VariablesInterface() - # see `qiskit.optimization.LinearConstraintInterface()` - self.linear_constraints = LinearConstraintInterface(varindex=self.variables.get_indices) + self._name = '' - # see `qiskit.optimization.QuadraticConstraintInterface()` - self.quadratic_constraints = QuadraticConstraintInterface( - varindex=self.variables.get_indices - ) + self.variables = VariablesInterface() - # see `qiskit.optimization.ObjectiveInterface()` - # pylint: disable=unexpected-keyword-arg - self.objective = ObjectiveInterface(varindex=self.variables.get_indices) + # convert variable names into indices + varindex = self.variables.get_indices - # see `qiskit.optimization.SolutionInterface()` + self.linear_constraints = LinearConstraintInterface(varindex=varindex) + self.quadratic_constraints = QuadraticConstraintInterface(varindex=varindex) + self.objective = ObjectiveInterface(varindex=varindex) self.solution = SolutionInterface() - - # see `qiskit.optimization.ProblemType()` -- essentially conversions from integers to - # strings and back self.problem_type = ProblemType() - self.my_problem_type = 0 + + # None means it will be detected automatically + self._problem_type = None + + self.substitution_status = SubstitutionStatus # read from file in case filename is given - if len(args) == 1: + if file_name: try: - self.read(args[0]) + self.read(file_name) except CplexSolverError: - raise QiskitOptimizationError('Could not load file: %s' % args[0]) + raise QiskitOptimizationError('Could not load file: {}'.format(file_name)) - def from_cplex(self, op): - """ from cplex """ - # make sure current problem is clean - from cplex.exceptions import CplexSolverError - self._disposed = False - try: - self._name = op.get_problem_name() - except CplexSolverError: - self._name = None - self.variables = VariablesInterface() - self.linear_constraints = LinearConstraintInterface(varindex=self.variables.get_indices) - self.quadratic_constraints = QuadraticConstraintInterface( - varindex=self.variables.get_indices) - self.objective = ObjectiveInterface(varindex=self.variables.get_indices) - self.solution = SolutionInterface() + def from_cplex(self, op: Cplex): + """Loads an optimization problem from a Cplex object - # set problem name - if op.get_problem_name(): - self.set_problem_name(op.get_problem_name()) + Args: + op: a Cplex object + """ + # make sure the current problem is clean + self.end() - # TODO: how to choose problem type? - # set problem type - if op.get_problem_type(): - self.set_problem_type(op.get_problem_type()) + self.set_problem_name(op.get_problem_name()) + self.set_problem_type(op.get_problem_type()) - # TODO: There seems to be a bug in CPLEX, it raises a "Not a MIP (3003)"-error - # if the problem never had a non-cts. variable + # Note: CPLEX raises a "Not a MIP (3003)"-error if there is no variable whose type is not + # specified. As a workaround, we add an dummy variable with a type and then delete it. idx = op.variables.add(types='B') op.variables.delete(idx[0]) # set variables (obj is set via objective interface) - var_names = op.variables.get_names() - var_lbs = op.variables.get_lower_bounds() - var_ubs = op.variables.get_upper_bounds() - var_types = op.variables.get_types() - self.variables.add(lb=var_lbs, ub=var_ubs, types=var_types, names=var_names) - - # set objective sense - self.objective.set_sense(op.objective.get_sense()) + lowerbounds = op.variables.get_lower_bounds() + upperbounds = op.variables.get_upper_bounds() + types = op.variables.get_types() + names = op.variables.get_names() + self.variables.add(lb=lowerbounds, ub=upperbounds, types=types, names=names) - # set objective name + # set objective function try: - self.objective.set_name(op.objective.get_name()) + # if no name is set for objective function, CPLEX raises CplexSolverError + obj_name = op.objective.get_name() except CplexSolverError: - pass - - # set linear objective terms - for i, v in enumerate(op.objective.get_linear()): - self.objective.set_linear(i, v) - - # set quadratic objective terms - for i, sparse_pair in enumerate(op.objective.get_quadratic()): - for j, v in zip(sparse_pair.ind, sparse_pair.val): - self.objective.set_quadratic_coefficients(i, j, v) - - # set objective offset + obj_name = '' + self.objective.set_name(obj_name) + self.objective.set_sense(op.objective.get_sense()) self.objective.set_offset(op.objective.get_offset()) + self.objective.set_linear((i, v) for i, v in enumerate(op.objective.get_linear())) + if op.objective.get_num_quadratic_nonzeros() > 0: + self.objective.set_quadratic(op.objective.get_quadratic()) # set linear constraints - linear_rows = op.linear_constraints.get_rows() - linear_sense = op.linear_constraints.get_senses() - linear_rhs = op.linear_constraints.get_rhs() - linear_ranges = op.linear_constraints.get_range_values() - linear_names = op.linear_constraints.get_names() - self.linear_constraints.add(linear_rows, linear_sense, - linear_rhs, linear_ranges, linear_names) - - # TODO: add quadratic constraints - - def from_docplex(self, model: Model): - """ from docplex """ - from cplex.exceptions import CplexSolverError - cplex = model.get_cplex() - try: - cplex.set_problem_name(model.get_name()) - except CplexSolverError: - cplex.set_problem_name('') - cplex.objective.set_name('Objective') - self.from_cplex(cplex) + lin_expr = op.linear_constraints.get_rows() + senses = op.linear_constraints.get_senses() + rhs = op.linear_constraints.get_rhs() + range_values = op.linear_constraints.get_range_values() + names = op.linear_constraints.get_names() + self.linear_constraints.add( + lin_expr=lin_expr, senses=senses, rhs=rhs, range_values=range_values, names=names) + + # set quadratic constraints + names = op.quadratic_constraints.get_names() + senses = op.quadratic_constraints.get_senses() + rhs = op.quadratic_constraints.get_rhs() + lin_expr = op.quadratic_constraints.get_linear_components() + quad_expr = op.quadratic_constraints.get_quadratic_components() + for i in range(op.quadratic_constraints.get_num()): + self.quadratic_constraints.add( + lin_expr=lin_expr[i], quad_expr=quad_expr[i], sense=senses[i], rhs=rhs[i], + name=names[i]) + + def from_docplex(self, model: DocplexModel): + """Loads an optimization problem from a Docplex model + + Args: + model: Docplex model + """ + cpl = model.get_cplex() + self.from_cplex(cpl) + # Docplex does not copy the model name. We need to do it manually. + self.set_problem_name(model.get_name()) - def to_cplex(self): - """ to cplex """ - from cplex import Cplex - # create empty CPLEX model - op = Cplex() - if self.get_problem_name() is not None: - op.set_problem_name(self.get_problem_name()) - else: - op.set_problem_name('') - # TODO: what about problem type? + def to_cplex(self) -> Cplex: + """Converts the optimization problem into a Cplex object. - # set variables (obj is set via objective interface) - var_names = self.variables.get_names() - var_lbs = self.variables.get_lower_bounds() - var_ubs = self.variables.get_upper_bounds() - var_types = self.variables.get_types() - # TODO: what about columns? - op.variables.add(lb=var_lbs, ub=var_ubs, types=var_types, names=var_names) - - # set objective sense - op.objective.set_sense(self.objective.get_sense()) + Returns: Cplex object + """ - # set objective name - op.objective.set_name(self.objective.get_name()) + # create a new CPLEX model + op = Cplex() + op.set_problem_name(self.get_problem_name()) - # set linear objective terms - for i, v in self.objective.get_linear().items(): - op.objective.set_linear(i, v) + # problem type will be set automatically by CPLEX - # set quadratic objective terms - for i, v_i in self.objective.get_quadratic().items(): - for j, v in v_i.items(): - op.objective.set_quadratic_coefficients(i, j, v) + # set variables + names = self.variables.get_names() + lowerbounds = self.variables.get_lower_bounds() + upperbounds = self.variables.get_upper_bounds() + types = self.variables.get_types() + op.variables.add(lb=lowerbounds, ub=upperbounds, types=types, names=names) - # set objective offset + # set objective function + op.objective.set_name(self.objective.get_name()) + op.objective.set_sense(self.objective.get_sense()) op.objective.set_offset(self.objective.get_offset()) + op.objective.set_linear((i, v) for i, v in self.objective.get_linear_dict().items()) + if self.objective.get_num_quadratic_nonzeros() > 0: + op.objective.set_quadratic(self.objective.get_quadratic()) # set linear constraints - linear_rows = self.linear_constraints.get_rows() - linear_sense = self.linear_constraints.get_senses() - linear_rhs = self.linear_constraints.get_rhs() - linear_ranges = self.linear_constraints.get_range_values() - linear_names = self.linear_constraints.get_names() - op.linear_constraints.add(linear_rows, linear_sense, linear_rhs, - linear_ranges, linear_names) - - # TODO: add quadratic constraints - + lin_expr = self.linear_constraints.get_rows() + senses = self.linear_constraints.get_senses() + rhs = self.linear_constraints.get_rhs() + range_values = self.linear_constraints.get_range_values() + names = self.linear_constraints.get_names() + op.linear_constraints.add( + lin_expr=lin_expr, senses=senses, rhs=rhs, range_values=range_values, names=names) + + # set quadratic constraints + names = self.quadratic_constraints.get_names() + senses = self.quadratic_constraints.get_senses() + rhs = self.quadratic_constraints.get_rhs() + lin_expr = self.quadratic_constraints.get_linear_components() + quad_expr = self.quadratic_constraints.get_quadratic_components() + for i in range(self.quadratic_constraints.get_num()): + op.quadratic_constraints.add( + lin_expr=lin_expr[i], quad_expr=quad_expr[i], sense=senses[i], rhs=rhs[i], + name=names[i]) return op def end(self): """Releases the OptimizationProblem object.""" - if self._disposed: - return - self._disposed = True - - def __del__(self): - """non-public""" - self.end() + self._name = '' + self.variables = VariablesInterface() + varindex = self.variables.get_indices + self.linear_constraints = LinearConstraintInterface(varindex=varindex) + self.quadratic_constraints = QuadraticConstraintInterface(varindex=varindex) + self.objective = ObjectiveInterface(varindex=varindex) + self.solution = SolutionInterface() + self._problem_type = None - def __enter__(self): + def __enter__(self) -> 'OptimizationProblem': """To implement a ContextManager, as in Cplex.""" return self - def __exit__(self, *exc): + def __exit__(self, *exc) -> bool: """To implement a ContextManager, as in Cplex.""" + self.end() return False - def read(self, filename, filetype=""): + def read(self, filename: str, filetype: str = ""): """Reads a problem from file. The first argument is a string specifying the filename from which the problem will be read. - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> op.read("lpex.mps") """ - from cplex import Cplex cplex = Cplex() cplex.read(filename, filetype) self.from_cplex(cplex) - def write(self, filename, filetype=""): + def write(self, filename: str, filetype: str = ""): """Writes a problem to file. The first argument is a string specifying the filename to @@ -260,14 +258,15 @@ def write(self, filename, filetype=""): Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names=['x1', 'x2', 'x3']) >>> op.write("example.lp") """ cplex = self.to_cplex() cplex.write(filename, filetype) - def write_to_stream(self, stream, filetype='LP', comptype=''): + def write_to_stream(self, stream: object, filetype: str = 'LP', comptype: str = ''): """Writes a problem to a file-like object in the given file format. The filetype argument can be any of "sav" (a binary format), "lp" @@ -277,9 +276,13 @@ def write_to_stream(self, stream, filetype='LP', comptype=''): If comptype is "bz2" (for BZip2) or "gz" (for GNU Zip), a compressed file is written. + Raises: + QiskitOptimizationError: if `stream` does not have methods `write` and `flush`. + Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names=['x1', 'x2', 'x3']) >>> class NoOpStream(object): ... def __init__(self): @@ -294,21 +297,18 @@ def write_to_stream(self, stream, filetype='LP', comptype=''): >>> stream.was_called True """ - try: - callable(stream.write) - except AttributeError: + if not hasattr(stream, 'write') or not callable(stream.write): raise QiskitOptimizationError("stream must have a write method") - try: - callable(stream.flush) - except AttributeError: + if not hasattr(stream, 'flush') or not callable(stream.flush): raise QiskitOptimizationError("stream must have a flush method") op = self.to_cplex() - return op.write_to_stream(stream, filetype, comptype) + op.write_to_stream(stream, filetype, comptype) - def write_as_string(self, filetype='LP', comptype=''): + def write_as_string(self, filetype: str = 'LP', comptype: str = '') -> str: """Writes a problem as a string in the given file format. - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> indices = op.variables.add(names=['x1', 'x2', 'x3']) >>> lp_str = op.write_as_string("lp") >>> len(lp_str) > 0 @@ -317,22 +317,34 @@ def write_as_string(self, filetype='LP', comptype=''): op = self.to_cplex() return op.write_as_string(filetype, comptype) - def get_problem_type(self): + def get_problem_type(self) -> int: """Returns the problem type. The return value is an attribute of self.problem_type. - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> op.read("lpex.mps") >>> op.get_problem_type() 0 >>> op.problem_type[op.get_problem_type()] 'LP' """ - # TODO: A better option would be to scan the variables to check their types, etc. - return self.my_problem_type - - def set_problem_type(self, _type): + if self._problem_type: + return self._problem_type + return self._detect_problem_type() + + def _detect_problem_type(self) -> int: + typ = self.problem_type.LP + if self.variables.get_num() > 0: + typ = self.problem_type.MILP + if self.objective.get_num_quadratic_nonzeros() > 0: + typ = self.problem_type.MIQP + if self.quadratic_constraints.get_num() > 0: + typ = self.problem_type.MIQCP + return typ + + def set_problem_type(self, problem_type): """Changes the problem type. If only one argument is given, that argument specifies the new @@ -347,227 +359,469 @@ def set_problem_type(self, _type): qiskit.optimization.problem_type.QCP qiskit.optimization.problem_type.MIQCP """ - self.my_problem_type = _type + self._problem_type = problem_type def solve(self): - """Solves the problem. - - Note - The solve method returning normally does not necessarily mean - that an optimal or feasible solution has been found. Use - OptimizationProblem.solution.get_status() to query the status of the current - solution. + """Prints out a message to ask users to use `OptimizationAlgorithm`. + Users need to apply one of `OptimiztionAlgorithm`s instead of this method. """ - # TODO: Implement me - pass + logger.warning('`OptimizationProblem.solve` is intentionally empty.' + 'You can solve it by applying `OptimizationAlgorithm.solve`.') - def set_problem_name(self, name): - """Set the problem name.""" + def set_problem_name(self, name: str): + """Sets the problem name""" self._name = name - def get_problem_name(self): - """Get the problem name.""" + def get_problem_name(self) -> str: + """Returns the problem name""" return self._name - def substitute_variables(self, constants=None, variables=None): - """Substitute variables of the problem. + def substitute_variables(self, constants: Optional[SparsePair] = None, + variables: Optional[SparseTriple] = None) \ + -> Tuple['OptimizationProblem', 'SubstitutionStatus']: + """Substitutes variables with constants or other variables. - constants: SparsePair (replace variable by constant) - variables: SparseTriple (replace variables by weighted other variable - need to copy everything using name reference to make sure that indices are matched correctly + Args: + constants: replace variable by constant + i.e., SparsePair.ind (variable) -> SparsePair.val (constant) + + variables: replace variables by weighted other variable + need to copy everything using name reference to make sure that indices are matched + correctly. The lower and upper bounds are updated accordingly. + i.e., SparseTriple.ind1 (variable) + -> SparseTriple.ind2 (variable) * SparseTriple.val (constant) + + Returns: + An optimization problem by substituting variables and the status. + If the resulting problem has no issue, the status is `success`. + Otherwise, an empty problem and status `infeasible` are returned. + + Raises: + QiskitOptimizationError: if the substitution is invalid as follows. + - Same variable is substituted multiple times. + - Coefficient of variable substitution is zero. + + Example usage: + + >>> from qiskit.optimization import OptimizationProblem + >>> from cplex import SparsePair, SparseTriple + >>> op = OptimizationProblem() + >>> op.variables.add(names=['x', 'y'], types='I'*2, lb=[-1]*2, ub=[2]*2) + >>> op.objective.set_sense(op.objective.sense.minimize) + >>> op.objective.set_linear([('x', 1), ('y', 2)]) + >>> op.linear_constraints.add(lin_expr=[(['x', 'y'], [1.0, -1.0])], senses=['L'], rhs=[1.0]) + >>> print(op.write_as_string()) + \\ENCODING=ISO-8859-1 + \\Problem name: + + Minimize + obj1: x + 2 y + Subject To + c1: x - y <= 1 + Bounds + -1 <= x <= 2 + -1 <= y <= 2 + Generals + x y + End + >>> # substitute x <- 2 + >>> op2, st = op.substitute_variables(constants=SparsePair(ind=['x'], val=[2])) + >>> print(st) + SubstitutionStatus.success + >>> print(op2.write_as_string()) + \\ENCODING=ISO-8859-1 + \\Problem name: + + Minimize + obj1: 2 y + 2 + Subject To + c1: - y <= -1 + Bounds + -1 <= y <= 2 + Generals + y + End + >>> # substitute y <- -x + >>> op3, st = op.substitute_variables(variables=SparseTriple(\ + ind1=['y'], ind2=['x'], val=[-1])) + >>> print(st) + SubstitutionStatus.success + >>> print(op3.write_as_string()) + \\ENCODING=ISO-8859-1 + \\Problem name: + + Minimize + obj1: - x + Subject To + c1: 2 x <= 1 + Bounds + -1 <= x <= 1 + Generals + x + End """ - from cplex import SparsePair + subs = SubstituteVariables() + return subs.substitute_variables(src=self, constants=constants, variables=variables) + + +class SubstitutionStatus(Enum): + """Status of `OptimizationProblem.substitute_variables`""" + success = 1 + infeasible = 2 + + +class SubstituteVariables: + """A class to substitute variables of an optimization problem with constants for other + variables""" + + CONST = -1 + + def __init__(self): + self._src: OptimizationProblem = None + self._dst: OptimizationProblem = None + self._subs = {} + + def substitute_variables(self, src: OptimizationProblem, + constants: Optional[SparsePair] = None, + variables: Optional[SparseTriple] = None) \ + -> Tuple[OptimizationProblem, SubstitutionStatus]: + """Substitutes variables with constants or other variables. + + Args: + src: an optimization problem whose variables will be substituted + constants: replace variable by constant + i.e., SparsePair.ind (variable) -> SparsePair.val (constant) + + variables: replace variables by weighted other variable + need to copy everything using name reference to make sure that indices are matched + correctly + i.e., SparseTriple.ind1 (variable) + -> SparseTriple.ind2 (variable) * SparseTriple.val (constant) + + Returns: + An optimization problem by substituting variables and the status. + If the resulting problem has no issue, the status is `success`. + Otherwise, an empty problem and status `infeasible` are returned. + + Raises: + QiskitOptimizationError: if the substitution is invalid as follows. + - Same variable is substituted multiple times. + - Coefficient of variable substitution is zero. + """ + + self._src = src + self._dst = OptimizationProblem() + self._dst.set_problem_name(src.get_problem_name()) + # do not set problem type, then it detects its type automatically + + self._subs_dict(constants, variables) + + results = [ + self._variables(), + self._objective(), + self._linear_constraints(), + self._quadratic_constraints(), + ] + if any(r == SubstitutionStatus.infeasible for r in results): + ret = SubstitutionStatus.infeasible + else: + ret = SubstitutionStatus.success + return self._dst, ret + + @staticmethod + def _feasible(sense: str, rhs: float, range_value: float = 0) -> bool: + """Checks feasibility of the following condition + 0 `sense` rhs + """ + # I use the following pylint option because `rhs` should come to right + # pylint: disable=misplaced-comparison-constant + if sense == 'E': + if 0 == rhs: + return True + elif sense == 'L': + if 0 <= rhs: + return True + elif sense == 'G': + if 0 >= rhs: + return True + else: # sense == 'R' + if range_value >= 0: + if rhs <= 0 <= rhs + range_value: + return True + else: + if rhs + range_value <= 0 <= rhs: + return True + return False + + @staticmethod + def _replace_dict_keys_with_names(op, dic): + key = [] + val = [] + for k in sorted(dic.keys()): + key.append(op.variables.get_names(k)) + val.append(dic[k]) + return key, val + + def _subs_dict(self, constants, variables): + src = self._src + # guarantee that there is no overlap between variables to be replaced and combine input - vars_to_be_replaced = {} + subs = {} if constants is not None: + if not isinstance(constants, SparsePair): + raise QiskitOptimizationError( + 'substitution with constant should be SparsePair: {}'.format(constants)) for i, v in zip(constants.ind, constants.val): - i = self.variables.get_indices(i) - name = self.variables.get_names(i) - if i in vars_to_be_replaced: - raise QiskitOptimizationError('cannot substitute the same variable twice') - vars_to_be_replaced[name] = [v] + # substitute i <- v + i_2 = src.variables.get_indices(i) + if i_2 in subs: + raise QiskitOptimizationError( + 'cannot substitute the same variable twice: {} <- {}'.format(i, v)) + subs[i_2] = (self.CONST, v) + if variables is not None: + if not isinstance(variables, SparseTriple): + raise QiskitOptimizationError( + 'substitution with variable should be SparseTriple: {}'.format(variables)) for i, j, v in zip(variables.ind1, variables.ind2, variables.val): - i = self.variables.get_indices(i) - j = self.variables.get_indices(j) - name1 = self.variables.get_names(i) - name2 = self.variables.get_names(j) - if name1 in vars_to_be_replaced: - raise QiskitOptimizationError('Cannot substitute the same variable twice') - if name2 in vars_to_be_replaced.keys(): + if v == 0: raise QiskitOptimizationError( - 'Cannot substitute by variable that gets substituted it self.') - vars_to_be_replaced[name1] = [name2, v] - - # get variables to be kept - vars_to_be_kept = set() - for name in self.variables.get_names(): - if name not in vars_to_be_replaced: - vars_to_be_kept.add(name) + 'coefficient should not be zero: {} {} {}'.format(i, j, v)) + # substitute i <- j * v + i_2 = src.variables.get_indices(i) + j_2 = src.variables.get_indices(j) + if i_2 == j_2: + raise QiskitOptimizationError( + 'Cannot substitute the same variable: {} <- {} {}'.format(i, j, v)) + if i_2 in subs: + raise QiskitOptimizationError( + 'Cannot substitute the same variable twice: {} <- {} {}'.format(i, j, v)) + if j_2 in subs: + raise QiskitOptimizationError( + 'Cannot substitute by variable that gets substituted itself: ' + '{} <- {} {}'.format(i, j, v)) + subs[i_2] = (j_2, v) - # construct new problem - op = OptimizationProblem() + self._subs = subs - # set problem name - op.set_problem_name(self.get_problem_name()) + def _variables(self) -> SubstitutionStatus: + src = self._src + dst = self._dst + subs = self._subs # copy variables that are not replaced - # TODO: what about columns? - for name, var_type, lower_bound, upper_bound in zip( - self.variables.get_names(), - self.variables.get_types(), - self.variables.get_lower_bounds(), - self.variables.get_upper_bounds(), + for name, var_type, lowerbound, upperbound in zip( + src.variables.get_names(), + src.variables.get_types(), + src.variables.get_lower_bounds(), + src.variables.get_upper_bounds(), ): - if name not in vars_to_be_replaced: - op.variables.add(lb=[lower_bound], ub=[upper_bound], types=[var_type], names=[name]) + i = src.variables.get_indices(name) + if i not in subs: + dst.variables.add(lb=[lowerbound], ub=[upperbound], types=var_type, names=[name]) + + for i, (j, v) in subs.items(): + var_i = src.variables.get_names(i) + lb_i = src.variables.get_lower_bounds(i) + ub_i = src.variables.get_upper_bounds(i) + if j == self.CONST: + if not lb_i <= v <= ub_i: + logger.warning( + 'Infeasible substitution for variable: %s', var_i) + return SubstitutionStatus.infeasible else: - # check that replacement satisfies bounds - repl = vars_to_be_replaced[name] - if len(repl) == 1: - if not lower_bound <= repl[0] <= upper_bound: - raise QiskitOptimizationError('Infeasible substitution for variable') - - # initialize offset - offset = self.objective.get_offset() - - # construct linear part of objective - for i, v in self.objective.get_linear().items(): - i = self.variables.get_indices(i) - i_name = self.variables.get_names(i) - i_repl = vars_to_be_replaced.get(i_name, None) - if i_repl is not None: - w_i = self.objective.get_linear(i_name) - if len(i_repl) == 1: - offset += i_repl[0] * w_i - else: # len == 2 - w_i = i_repl[1] * w_i + op.objective.get_linear(i_repl[0]) - op.objective.set_linear(i_repl[0], w_i) - else: - w_i = self.objective.get_linear(i_name) + op.objective.get_linear(i_name) - op.objective.set_linear(i_name, w_i) - - # construct quadratic part of objective - for i, v_i in self.objective.get_quadratic().items(): - for j, v in v_i.items(): - i = self.variables.get_indices(i) - j = self.variables.get_indices(j) - i_name = self.variables.get_names(i) - j_name = self.variables.get_names(j) - i_repl = vars_to_be_replaced.get(i_name, None) - j_repl = vars_to_be_replaced.get(j_name, None) - w_ij = self.objective.get_quadratic_coefficients(i_name, j_name) - if i_repl is not None and j_repl is None: - if len(i_repl) == 1: - # if x_i is replaced, the term needs to be added to the linear part of x_j - w_j = op.objective.get_linear(j_name) - w_j += i_repl[0] * w_ij / 2 - op.objective.set_linear(j_name, w_j) - else: # len == 2 - k = self.variables.get_indices(i_repl[0]) - k_name = self.variables.get_names(k) - if k_name in vars_to_be_replaced.keys(): - raise QiskitOptimizationError( - 'Cannot substitute by variable that gets substituted itself.') - w_jk = op.objective.get_quadratic_coefficients(j_name, k_name) - w_jk += i_repl[1] * w_ij - op.objective.set_quadratic_coefficients(j_name, k_name, w_jk) - elif i_repl is None and j_repl is not None: - if len(j_repl) == 1: - # if x_j is replaced, the term needs to be added to the linear part of x_i - w_i = op.objective.get_linear(i_name) - w_i += j_repl[0] * w_ij / 2 - op.objective.set_linear(i_name, w_i) - else: # len == 2 - k = self.variables.get_indices(j_repl[0]) - k_name = self.variables.get_names(k) - if k_name in vars_to_be_replaced.keys(): - raise QiskitOptimizationError( - 'Cannot substitute by variable that gets substituted itself.') - w_ik = op.objective.get_quadratic_coefficients(i_name, k_name) - w_ik += j_repl[1] * w_ij - op.objective.set_quadratic_coefficients(i_name, k_name, w_ik) - elif i_repl is not None and j_repl is not None: - if len(i_repl) == 1 and len(j_repl) == 1: - offset += w_ij * i_repl[0] * j_repl[0] / 2 - elif len(i_repl) == 1 and len(j_repl) == 2: - k = self.variables.get_indices(j_repl[0]) - k_name = self.variables.get_names(k) - if k_name in vars_to_be_replaced.keys(): - raise QiskitOptimizationError( - 'Cannot substitute by variable that gets substituted itself.') - w_k = op.objective.get_linear(k_name) - w_k += w_ij * i_repl[0] * j_repl[1] / 2 - op.objective.set_linear(k_name, w_k) - elif len(i_repl) == 2 and len(j_repl) == 1: - k = self.variables.get_indices(i_repl[0]) - k_name = self.variables.get_names(k) - if k_name in vars_to_be_replaced.keys(): - raise QiskitOptimizationError( - 'Cannot substitute by variable that gets substituted itself.') - w_k = op.objective.get_linear(k_name) - w_k += w_ij * j_repl[0] * i_repl[1] / 2 - op.objective.set_linear(k_name, w_k) - else: # both len(repl) == 2 - k = self.variables.get_indices(i_repl[0]) - k_name = self.variables.get_names(k) - if k_name in vars_to_be_replaced.keys(): - raise QiskitOptimizationError( - 'Cannot substitute by variable that gets substituted itself.') - m = self.variables.get_indices(j_repl[0]) - m_name = self.variables.get_names(m) - if m_name in vars_to_be_replaced.keys(): - raise QiskitOptimizationError( - 'Cannot substitute by variable that gets substituted itself.') - w_kl = op.objective.get_quadratic_coefficients(k_name, m_name) - w_kl += w_ij * i_repl[1] * j_repl[1] - op.objective.set_quadratic_coefficients(k_name, m_name, w_kl) + # substitute i <- j * v + # lb_i <= i <= ub_i --> lb_i / v <= j <= ub_i / v if v > 0 + # ub_i / v <= j <= lb_i / v if v < 0 + if v == 0: + raise QiskitOptimizationError( + 'Coefficient of variable substitution should be nonzero: ' + '{} {} {}'.format(i, j, v)) + var_j = src.variables.get_names(j) + if abs(lb_i) < infinity: + new_lb_i = lb_i / v + else: + new_lb_i = lb_i if v > 0 else -lb_i + if abs(ub_i) < infinity: + new_ub_i = ub_i / v else: - # nothing to be replaced, just copy coefficients - if i == j: - w_ij = sum([self.objective.get_quadratic_coefficients(i_name, j_name), - op.objective.get_quadratic_coefficients(i_name, j_name)]) - else: - w_ij = sum([self.objective.get_quadratic_coefficients(i_name, j_name) / 2, - op.objective.get_quadratic_coefficients(i_name, j_name)]) - op.objective.set_quadratic_coefficients(i_name, j_name, w_ij) - - # set offset - op.objective.set_offset(offset) - - # construct linear constraints + new_ub_i = ub_i if v > 0 else -ub_i + lb_j = dst.variables.get_lower_bounds(var_j) + ub_j = dst.variables.get_upper_bounds(var_j) + if v > 0: + dst.variables.set_lower_bounds(var_j, max(lb_j, new_lb_i)) + dst.variables.set_upper_bounds(var_j, min(ub_j, new_ub_i)) + else: + dst.variables.set_lower_bounds(var_j, max(lb_j, new_ub_i)) + dst.variables.set_upper_bounds(var_j, min(ub_j, new_lb_i)) + + for var in dst.variables.get_names(): + lowerbound = dst.variables.get_lower_bounds(var) + upperbound = dst.variables.get_upper_bounds(var) + if lowerbound > upperbound: + logger.warning( + 'Infeasible lower and upper bound: %s %f %f', var, lowerbound, upperbound) + return SubstitutionStatus.infeasible + + return SubstitutionStatus.success + + def _objective(self) -> SubstitutionStatus: + src = self._src + dst = self._dst + subs = self._subs + + # initialize + offset = [src.objective.get_offset()] + lin_dict = {} + quad_dict = {} + + # substitute quadratic terms of the objective function + for (i, j), w_ij in src.objective.get_quadratic_dict().items(): + repl_i = subs[i] if i in subs else (i, 1) + repl_j = subs[j] if j in subs else (j, 1) + idx = tuple(x for x, _ in [repl_i, repl_j] if x != self.CONST) + prod = w_ij * repl_i[1] * repl_j[1] + if len(idx) == 2: + if idx not in quad_dict: + quad_dict[idx] = 0 + quad_dict[idx] += prod + elif len(idx) == 1: + k = idx[0] + if k not in lin_dict: + lin_dict[k] = 0 + lin_dict[k] += prod / 2 + else: + offset.append(prod / 2) + + # substitute linear terms of the objective function + for i, w_i in src.objective.get_linear_dict().items(): + repl_i = subs[i] if i in subs else (i, 1) + prod = w_i * repl_i[1] + if repl_i[0] == self.CONST: + offset.append(prod) + else: + k = repl_i[0] + if k not in lin_dict: + lin_dict[k] = 0 + lin_dict[k] += prod + + dst.objective.set_offset(fsum(offset)) + if len(lin_dict) > 0: + ind, val = self._replace_dict_keys_with_names(src, lin_dict) + dst.objective.set_linear([(i, v) for i, v in zip(ind, val) if v != 0]) + if len(quad_dict) > 0: + ind_pair, val = self._replace_dict_keys_with_names(src, quad_dict) + ind1, ind2 = zip(*ind_pair) + dst.objective.set_quadratic_coefficients( + [(i, j, v) for i, j, v in zip(ind1, ind2, val) if v != 0]) + + return SubstitutionStatus.success + + def _linear_constraints(self) -> SubstitutionStatus: + src = self._src + dst = self._dst + subs = self._subs + for name, row, rhs, sense, range_value in zip( - self.linear_constraints.get_names(), - self.linear_constraints.get_rows(), - self.linear_constraints.get_rhs(), - self.linear_constraints.get_senses(), - self.linear_constraints.get_range_values() + src.linear_constraints.get_names(), + src.linear_constraints.get_rows(), + src.linear_constraints.get_rhs(), + src.linear_constraints.get_senses(), + src.linear_constraints.get_range_values() ): - # print(name, row, rhs, sense, range_value) - new_vals = {} - for i, v in zip(row.ind, row.val): - i = self.variables.get_indices(i) - i_name = self.variables.get_names(i) - i_repl = vars_to_be_replaced.get(i_name, None) - if i_repl is not None: - if len(i_repl) == 1: - rhs -= v * i_repl[0] - else: - j = self.variables.get_indices(i_repl[0]) - j_name = self.variables.get_names(j) - new_vals[j_name] = v * i_repl[1] + new_vals.get(i_name, 0) + lin_dict = {} + rhs = [rhs] + for i, w_i in zip(row.ind, row.val): + repl_i = subs[i] if i in subs else (i, 1) + prod = w_i * repl_i[1] + if repl_i[0] == self.CONST: + rhs.append(-prod) else: - # nothing to replace, just add value - new_vals[i_name] = v + new_vals.get(i_name, 0) - new_ind = list(new_vals.keys()) - new_val = [new_vals[i] for i in new_ind] - new_row = SparsePair(new_ind, new_val) - op.linear_constraints.add( - lin_expr=[new_row], senses=[sense], rhs=[rhs], range_values=[range_value], - names=[name]) - - # TODO: quadratic constraints - - # TODO: amend self.my_problem_type + k = repl_i[0] + if k not in lin_dict: + lin_dict[k] = 0 + lin_dict[k] += prod + if len(lin_dict) > 0: + ind, val = self._replace_dict_keys_with_names(src, lin_dict) + dst.linear_constraints.add( + lin_expr=[SparsePair(ind=ind, val=val)], + senses=sense, rhs=[fsum(rhs)], range_values=[range_value], names=[name]) + else: + if not self._feasible(sense, fsum(rhs), range_value): + logger.warning('constraint %s is infeasible due to substitution', name) + return SubstitutionStatus.infeasible + + return SubstitutionStatus.success + + def _quadratic_constraints(self) -> SubstitutionStatus: + src = self._src + dst = self._dst + subs = self._subs + + for name, lin_expr, quad_expr, sense, rhs in zip( + src.quadratic_constraints.get_names(), + src.quadratic_constraints.get_linear_components(), + src.quadratic_constraints.get_quadratic_components(), + src.quadratic_constraints.get_senses(), + src.quadratic_constraints.get_rhs() + ): + quad_dict = {} + lin_dict = {} + rhs = [rhs] + for i, j, w_ij in zip(quad_expr.ind1, quad_expr.ind2, quad_expr.val): + repl_i = subs[i] if i in subs else (i, 1) + repl_j = subs[j] if j in subs else (j, 1) + idx = tuple(x for x, _ in [repl_i, repl_j] if x != self.CONST) + prod = w_ij * repl_i[1] * repl_j[1] + if len(idx) == 2: + if idx[0] < idx[1]: + idx = (idx[1], idx[0]) + if idx not in quad_dict: + quad_dict[idx] = 0 + quad_dict[idx] += prod + elif len(idx) == 1: + k = idx[0] + if k not in lin_dict: + lin_dict[k] = 0 + lin_dict[k] += prod + else: + rhs.append(-prod) + for i, w_i in zip(lin_expr.ind, lin_expr.val): + repl_i = subs[i] if i in subs else (i, 1) + prod = w_i * repl_i[1] + if repl_i[0] == self.CONST: + rhs.append(-prod) + else: + k = repl_i[0] + if k not in lin_dict: + lin_dict[k] = 0 + lin_dict[k] += prod + ind, val = self._replace_dict_keys_with_names(src, lin_dict) + lin_expr = SparsePair(ind=ind, val=val) + if len(quad_dict) > 0: + ind_pair, val = self._replace_dict_keys_with_names(src, quad_dict) + ind1, ind2 = zip(*ind_pair) + quad_expr = SparseTriple(ind1=ind1, ind2=ind2, val=val) + dst.quadratic_constraints.add( + name=name, + lin_expr=lin_expr, + quad_expr=quad_expr, + sense=sense, + rhs=fsum(rhs) + ) + elif len(lin_dict) > 0: + lin_names = set(dst.linear_constraints.get_names()) + while name in lin_names: + name = '_' + name + dst.linear_constraints.add( + names=[name], + lin_expr=[lin_expr], + senses=sense, + rhs=[fsum(rhs)] + ) + else: + if not self._feasible(sense, fsum(rhs)): + logger.warning('constraint %s is infeasible due to substitution', name) + return SubstitutionStatus.infeasible - return op + return SubstitutionStatus.success diff --git a/qiskit/optimization/problems/problem_type.py b/qiskit/optimization/problems/problem_type.py index ce6a4b1b59..bbc38d18a7 100644 --- a/qiskit/optimization/problems/problem_type.py +++ b/qiskit/optimization/problems/problem_type.py @@ -14,6 +14,8 @@ """ Types of problems """ +from qiskit.optimization import QiskitOptimizationError + # pylint: disable=invalid-name CPXPROB_LP = 0 @@ -38,6 +40,7 @@ class ProblemType: for LP, QP, and QCP or the topic titled Discrete Optimization for MILP, FIXEDMILP, NODELP, NODEQP, MIQCP, NODEQCP. """ + # pylint: disable=invalid-name LP = CPXPROB_LP MILP = CPXPROB_MILP fixed_MILP = CPXPROB_FIXEDMILP @@ -50,12 +53,19 @@ class ProblemType: MIQCP = CPXPROB_MIQCP node_QCP = CPXPROB_NODEQCP - def __getitem__(self, item): + def __getitem__(self, item: int) -> str: """Converts a constant to a string. + Returns: + Problem type name. + + Raises: + QiskitOptimizationError: if the argument is not valid. + Example usage: - >>> op = qiskit.optimization.OptimizationProblem() + >>> from qiskit.optimization import OptimizationProblem + >>> op = OptimizationProblem() >>> op.problem_type.LP 0 >>> op.problem_type[0] @@ -84,4 +94,4 @@ def __getitem__(self, item): return 'MIQCP' if item == CPXPROB_NODEQCP: return 'node_QCP' - return None + raise QiskitOptimizationError('Invalid problem type: {}'.format(item)) diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index b60904b333..9748f4cda2 100644 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -12,13 +12,16 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Methods for adding, modifying, and querying quadratic constraints.""" +"""Quadratic constraints interface""" import copy from collections.abc import Sequence from logging import getLogger -from typing import List, Dict, Tuple, Callable +from typing import List, Dict, Callable, Union, Optional + from cplex import SparsePair, SparseTriple +from scipy.sparse import dok_matrix + from qiskit.optimization.utils.base import BaseInterface from qiskit.optimization.utils.helpers import NameIndex from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError @@ -41,7 +44,7 @@ def __init__(self, varindex: Callable): self._senses = [] self._names = [] self._lin_expr: List[Dict[int, float]] = [] - self._quad_expr: List[Dict[Tuple[int, int], float]] = [] + self._quad_expr: List[dok_matrix] = [] self._index = NameIndex() self._varindex = varindex @@ -63,13 +66,14 @@ def get_num(self) -> int: """ return len(self._names) - def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): + def add(self, lin_expr: Optional[SparsePair] = None, quad_expr: Optional[SparseTriple] = None, + sense: str = "L", rhs: float = 0.0, name: str = "") -> int: """Adds a quadratic constraint to the problem. - Takes up to five keyword arguments: + Takes up to following five keyword arguments. Args: - lin_expr(List): either a SparsePair or a list of two lists specifying + lin_expr: either a SparsePair or a list of two lists specifying the linear component of the constraint. Note @@ -78,7 +82,7 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): or a combination of index and name, an exception will be raised. - quad_expr(List): either a SparseTriple or a list of three lists + quad_expr: either a SparseTriple or a list of three lists specifying the quadratic component of the constraint. Note @@ -87,17 +91,17 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): names, or a combination of indices and names, an exception will be raised. - sense(str): either "L", "G", or "E" + sense: either "L", "G", or "E" - rhs(float): a float specifying the righthand side of the constraint. + rhs: a float specifying the righthand side of the constraint. - name(str) : the name of the constraint. + name: the name of the constraint. Returns: - int: The index of the added quadratic constraint. + The index of the added quadratic constraint. Raises: - QiskitOptimizationError: if invalid argument is given. + QiskitOptimizationError: if arguments are not valid. >>> from qiskit.optimization import OptimizationProblem >>> op = OptimizationProblem() @@ -115,10 +119,11 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): # check constraint name if name == '': - name = 'q{}'.format(len(self._names)) + name = 'q{}'.format(1 + self.get_num()) if name in self._index: raise QiskitOptimizationError('Duplicate quadratic constraint name: {}'.format(name)) self._names.append(name) + self._index.build(self._names) # linear terms lin_expr_dict = {} @@ -135,12 +140,12 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): for i, val in zip(ind, val): i_2 = self._varindex(i) if i_2 in lin_expr_dict: - logger.warning('lin_expr contains duplicate index: %d', i) + raise QiskitOptimizationError('lin_expr contains duplicate index: {}'.format(i)) lin_expr_dict[i_2] = val self._lin_expr.append(lin_expr_dict) # quadratic terms - quad_expr_dict = {} + quad_matrix = dok_matrix((0, 0)) if quad_expr is None: ind1, ind2, val = [], [], [] elif isinstance(quad_expr, SparseTriple): @@ -156,15 +161,19 @@ def add(self, lin_expr=None, quad_expr=None, sense="L", rhs=0.0, name=""): i_2 = self._varindex(i) j_2 = self._varindex(j) if i_2 < j_2: + # to reproduce CPLEX's behavior, swap i_2 and j_2 so that i_2 >= j_2 i_2, j_2 = j_2, i_2 - if (i_2, j_2) in quad_expr_dict: - logger.warning('quad_expr contains duplicate index: %d %d', i, j) - quad_expr_dict[i_2, j_2] = val - self._quad_expr.append(quad_expr_dict) + if (i_2, j_2) in quad_matrix: + raise QiskitOptimizationError( + 'quad_expr contains duplicate index: {} {}'.format(i, j)) + max_ij = max(i_2, j_2) + if max_ij >= quad_matrix.shape[0]: + quad_matrix.resize(max_ij + 1, max_ij + 1) + quad_matrix[i_2, j_2] = val + self._quad_expr.append(quad_matrix) if sense not in ['L', 'G', 'E']: raise QiskitOptimizationError('Invalid sense: {}'.format(sense)) - self._senses.append(sense) self._rhs.append(rhs) @@ -197,9 +206,6 @@ def delete(self, *args): give the best performance when deleting batches of quadratic constraints. - See CPXdelqconstrs in the Callable Library Reference Manual for - more detail. - Example usage: >>> from qiskit.optimization import OptimizationProblem @@ -247,7 +253,7 @@ def delete(self, *args): del self._quad_expr[i] self._index.build(self._names) - def get_rhs(self, *args): + def get_rhs(self, *args) -> Union[float, List[float]]: """Returns the righthand side of a set of quadratic constraints. Can be called by four forms. @@ -300,7 +306,7 @@ def _get(i): keys = self._index.convert(*args) return self._getter(_get, keys) - def get_senses(self, *args): + def get_senses(self, *args) -> Union[str, List[str]]: """Returns the senses of a set of quadratic constraints. Can be called by four forms. @@ -352,7 +358,7 @@ def _get(i): keys = self._index.convert(*args) return self._getter(_get, keys) - def get_linear_num_nonzeros(self, *args): + def get_linear_num_nonzeros(self, *args) -> Union[int, List[int]]: """Returns the number of nonzeros in the linear part of a set of quadratic constraints. Can be called by four forms. @@ -406,7 +412,7 @@ def _nonzero(i) -> int: keys = self._index.convert(*args) return self._getter(_nonzero, keys) - def get_linear_components(self, *args): + def get_linear_components(self, *args) -> Union[SparsePair, List[SparsePair]]: """Returns the linear part of a set of quadratic constraints. Returns a list of SparsePair instances or one SparsePair instance. @@ -474,7 +480,7 @@ def _linear_component(i) -> SparsePair: keys = self._index.convert(*args) return self._getter(_linear_component, keys) - def get_quad_num_nonzeros(self, *args): + def get_quad_num_nonzeros(self, *args) -> Union[int, List[int]]: """Returns the number of nonzeros in the quadratic part of a set of quadratic constraints. Can be called by four forms. @@ -522,13 +528,12 @@ def get_quad_num_nonzeros(self, *args): """ def _nonzero(i) -> int: - tab = self._quad_expr[i] - return len([0 for v in tab.values() if v != 0.0]) + return self._quad_expr[i].nnz keys = self._index.convert(*args) return self._getter(_nonzero, keys) - def get_quadratic_components(self, *args): + def get_quadratic_components(self, *args) -> Union[SparseTriple, List[SparseTriple]]: """Returns the quadratic part of a set of quadratic constraints. Can be called by four forms. @@ -584,15 +589,21 @@ def get_quadratic_components(self, *args): SparseTriple(ind1 = [0, 1], ind2 = [0, 1], val = [1.0, 2.0]) """ - def _quadratic_component(i) -> SparseTriple: - tab = self._quad_expr[i] - ind1, ind2 = zip(*tab.keys()) - return SparseTriple(ind1=list(ind1), ind2=list(ind2), val=list(tab.values())) + def _quadratic_component(k) -> SparseTriple: + ind1 = [] + ind2 = [] + val = [] + mat = self._quad_expr[k] + for (i, j), v in mat.items(): + ind1.append(i) + ind2.append(j) + val.append(v) + return SparseTriple(ind1=ind1, ind2=ind2, val=val) keys = self._index.convert(*args) return self._getter(_quadratic_component, keys) - def get_names(self, *args): + def get_names(self, *args) -> Union[str, List[str]]: """Returns the names of a set of quadratic constraints. Can be called by four forms. diff --git a/qiskit/optimization/problems/variables.py b/qiskit/optimization/problems/variables.py index 0f4e3d30f0..d0a75c6c9d 100644 --- a/qiskit/optimization/problems/variables.py +++ b/qiskit/optimization/problems/variables.py @@ -12,17 +12,16 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" Constants defining variable types """ +"""Variable interface""" import copy +from typing import List, Optional, Union from qiskit.optimization import infinity from qiskit.optimization.utils.base import BaseInterface from qiskit.optimization.utils.helpers import init_list_args, NameIndex from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError -# pylint: disable=invalid-name - CPX_CONTINUOUS = 'C' CPX_BINARY = 'B' CPX_INTEGER = 'I' @@ -43,9 +42,15 @@ class VarTypes: semi_integer = CPX_SEMIINT semi_continuous = CPX_SEMICONT - def __getitem__(self, item): + def __getitem__(self, item: str) -> str: """Converts a constant to a string. + Returns: + Variable type name. + + Raises: + QiskitOptimizationError: if the argument is not a valid type. + Example usage: >>> from qiskit.optimization.problems import OptimizationProblem @@ -65,7 +70,7 @@ def __getitem__(self, item): return 'semi_integer' if item == CPX_SEMICONT: return 'semi_continuous' - return None + raise QiskitOptimizationError('Invalid variable type: {}'.format(item)) class VariablesInterface(BaseInterface): @@ -97,7 +102,6 @@ class VariablesInterface(BaseInterface): """ type = VarTypes() - """See `VarTypes()` """ def __init__(self): """Creates a new VariablesInterface. @@ -115,7 +119,7 @@ def __init__(self): # self._columns = [] self._index = NameIndex() - def get_num(self): + def get_num(self) -> int: """Returns the number of variables in the problem. Example usage: @@ -129,7 +133,7 @@ def get_num(self): """ return len(self._names) - def get_num_continuous(self): + def get_num_continuous(self) -> int: """Returns the number of continuous variables in the problem. Example usage: @@ -143,7 +147,7 @@ def get_num_continuous(self): """ return self._types.count(VarTypes.continuous) - def get_num_integer(self): + def get_num_integer(self) -> int: """Returns the number of integer variables in the problem. Example usage: @@ -157,7 +161,7 @@ def get_num_integer(self): """ return self._types.count(VarTypes.integer) - def get_num_binary(self): + def get_num_binary(self) -> int: """Returns the number of binary variables in the problem. Example usage: @@ -171,7 +175,7 @@ def get_num_binary(self): """ return self._types.count(VarTypes.binary) - def get_num_semicontinuous(self): + def get_num_semicontinuous(self) -> int: """Returns the number of semi-continuous variables in the problem. Example usage: @@ -185,7 +189,7 @@ def get_num_semicontinuous(self): """ return self._types.count(VarTypes.semi_continuous) - def get_num_semiinteger(self): + def get_num_semiinteger(self) -> int: """Returns the number of semi-integer variables in the problem. Example usage: @@ -199,11 +203,13 @@ def get_num_semiinteger(self): """ return self._types.count(VarTypes.semi_integer) - def add(self, obj=None, lb=None, ub=None, types="", names=None, columns=None): + # pylint: disable=invalid-name + def add(self, obj: None = None, lb: Optional[List[float]] = None, + ub: Optional[List[float]] = None, types: str = "", names: Optional[List[str]] = None, + columns: None = None) -> range: """Adds variables and related data to the problem. variables.add accepts the keyword arguments obj, lb, ub, types, names, and columns. - If more than one argument is specified, all arguments must have the same length. Note @@ -211,36 +217,28 @@ def add(self, obj=None, lb=None, ub=None, types="", names=None, columns=None): Use `objective` and `linear_constraint` instead. Args: - obj(List): a list of floats specifying the linear objective coefficients - of the variables. - - lb(List): a list of floats specifying the lower bounds on the variables. + lb: a list of floats specifying the lower bounds on the variables. - ub(List): a list of floats specifying the upper bounds on the variables. + ub: a list of floats specifying the upper bounds on the variables. - types(List): must be either a list of single-character strings or a string containing + types: must be either a list of single-character strings or a string containing the types of the variables. Note If types is specified, the problem type will be a MIP, even if all variables are specified to be continuous. - names(List): a list of strings. + names: a list of strings. - columns(List): may be either a list of sparse vectors or a matrix - in list-of-lists format. + obj: not supported by Qiskit Aqua. Use `objective` instead. - Note - The entries of columns must not contain duplicate indices. - If an entry of columns references a row more than once, - either by index, name, or a combination of index and name, - an exception will be raised. + columns: not supported by Qiskit Aqua. Use `linear_constraints` instead. Returns: - List: an iterator containing the indices of the added variables. + an iterator containing the indices of the added variables. Raises: - QiskitOptimizationError: Invalid arguments + QiskitOptimizationError: if arguments are not valid. Example usage: @@ -270,37 +268,36 @@ def add(self, obj=None, lb=None, ub=None, types="", names=None, columns=None): raise QiskitOptimizationError( "Please use LinearConstraintInterface instead of columns.") + start = self.get_num() arg_list = init_list_args(lb, ub, types, names) arg_lengths = [len(x) for x in arg_list] if len(arg_lengths) == 0: - return range(0) + return range(start, start) max_length = max(arg_lengths) + if max_length == 0: + return range(start, start) for arg_length in arg_lengths: if arg_length > 0 and arg_length != max_length: raise QiskitOptimizationError("inconsistent arguments") - if not lb: - lb = [0.0] * max_length + lb = lb or [0] * max_length + ub = ub or [infinity] * max_length + types = types or [VarTypes.continuous] * max_length + for i, t in enumerate(types): + if t == VarTypes.binary and ub[i] == infinity: + ub[i] = 1 self._lb.extend(lb) - - if not ub: - ub = [infinity] * max_length self._ub.extend(ub) - - if not types: - types = [VarTypes.continuous] * max_length - for i, t in enumerate(types): - if t == VarTypes.binary: - self._ub[i] = 1.0 self._types.extend(types) - if not names: - names = ["x" + str(cnt) - for cnt in range(len(self._names), len(self._names) + max_length)] + names = names or [''] * max_length + for i, name in enumerate(names): + if name == '': + names[i] = 'x' + str(start + i + 1) self._names.extend(names) self._index.build(self._names) - return range(len(self._names) - max_length, len(self._names)) + return range(start, start + max_length) def delete(self, *args): """Deletes variables from the problem. @@ -326,9 +323,6 @@ def delete(self, *args): variables.delete(range(begin, end + 1)). This will give the best performance when deleting batches of variables. - See CPXdelcols in the Callable Library Reference Manual for - more detail. - Example usage: >>> from qiskit.optimization import OptimizationProblem @@ -514,7 +508,7 @@ def _set(i, v): self._setter(_set, *args) - def get_lower_bounds(self, *args): + def get_lower_bounds(self, *args) -> Union[float, List[float]]: """Returns the lower bounds on variables from the problem. There are four forms by which variables.get_lower_bounds may be called. @@ -556,7 +550,7 @@ def _get(i): keys = self._index.convert(*args) return self._getter(_get, keys) - def get_upper_bounds(self, *args): + def get_upper_bounds(self, *args) -> Union[float, List[float]]: """Returns the upper bounds on variables from the problem. There are four forms by which variables.get_upper_bounds may be called. @@ -604,7 +598,7 @@ def _get(i): keys = self._index.convert(*args) return self._getter(_get, keys) - def get_names(self, *args): + def get_names(self, *args) -> Union[str, List[str]]: """Returns the names of variables from the problem. There are four forms by which variables.get_names may be called. @@ -643,7 +637,7 @@ def _get(i): keys = self._index.convert(*args) return self._getter(_get, keys) - def get_types(self, *args): + def get_types(self, *args) -> Union[str, List[str]]: """Returns the types of variables from the problem. There are four forms by which variables.types may be called. @@ -687,9 +681,9 @@ def _get(i): return self._getter(_get, keys) def get_cols(self, *args): - """ get cols """ + """get_cols is not supported""" raise NotImplementedError("Please use LinearConstraintInterface instead.") def get_obj(self, *args): - """ get obj """ + """get_obj is not supported""" raise NotImplementedError("Please use ObjectiveInterface instead.") diff --git a/qiskit/optimization/utils/base.py b/qiskit/optimization/utils/base.py index ed140e9ac7..412f53c2ba 100644 --- a/qiskit/optimization/utils/base.py +++ b/qiskit/optimization/utils/base.py @@ -12,10 +12,10 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Common methods for sub-interfaces within Qiskit Optimization.""" -from typing import Callable, Sequence, Union, Any, List from abc import ABC +from typing import Callable, Sequence, Union, Any, List, Generator + from qiskit.optimization.utils.helpers import NameIndex from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError @@ -67,24 +67,20 @@ def _setter(setfunc: Callable[[Union[str, int], Any], None], *args) -> None: """A generic setter method Args: - setfunc: A setter function of two parameters: `index` and `val`. + setfunc(index, val): A setter function of two parameters: `index` and `val`. Since `index` can be a string, users need to convert it into an appropriate index by applying `NameIndex.convert`. *args: A pair of index and value or a list of pairs of index and value. `setfunc` is invoked with `args`. - - Returns: - None - - Raises: - QiskitOptimizationError: Invalid argument - """ # check for all elements in args whether they are types - if len(args) == 1 and \ - all(isinstance(pair, Sequence) and len(pair) == 2 for pair in args[0]): - for pair in args[0]: - setfunc(*pair) + if len(args) == 1 and isinstance(args[0], (Sequence, Generator)): + arg0 = list(args[0]) if isinstance(args[0], Generator) else args[0] + if all(isinstance(pair, Sequence) and len(pair) == 2 for pair in arg0): + for pair in arg0: + setfunc(*pair) + else: + raise QiskitOptimizationError("Invalid arguments: {}".format(args)) elif len(args) == 2: setfunc(*args) else: @@ -99,11 +95,8 @@ def _getter(getfunc: Callable[[int], Any], *args) -> Any: `index` should be already converted by `NameIndex.convert`. *args: A single index or a list of indices. `getfunc` is invoked with args. - Returns: - List: if `args` is a single index, this returns a single value generated by `getfunc`. + Returns: if `args` is a single index, this returns a single value genereted by `getfunc`. If `args` is a list of indices, this returns a list of values. - Raises: - QiskitOptimizationError: Invalid argument """ if len(args) == 0: raise QiskitOptimizationError('Empty arguments should be handled in the caller') diff --git a/qiskit/optimization/utils/helpers.py b/qiskit/optimization/utils/helpers.py index 4d0249134b..2f8b731ccd 100644 --- a/qiskit/optimization/utils/helpers.py +++ b/qiskit/optimization/utils/helpers.py @@ -14,7 +14,7 @@ """ Helper Utilities """ -from typing import Union, List, Sequence +from typing import Union, List, Sequence, Tuple from qiskit.optimization.utils import QiskitOptimizationError @@ -45,32 +45,51 @@ def build(self, names: List[str]) -> None: Args: names: a list of names - """ - self._dict = {e: i for i, e in enumerate(names)} - def _convert_one(self, arg: Union[str, int]) -> int: - if isinstance(arg, int): - return arg - if not isinstance(arg, str): - raise QiskitOptimizationError('Invalid argument" {}'.format(arg)) - if arg not in self._dict: - self._dict[arg] = len(self._dict) - return self._dict[arg] + Raises: + QiskitOptimizationError: if any duplicate names contained in the list. + """ + self._dict = {} + for i, name in enumerate(names): + if name in self._dict: + raise QiskitOptimizationError('Duplicate name: {}'.format(name)) + self._dict[name] = i + + def _convert_one(self, item: Union[str, int]) -> int: + if isinstance(item, int): + if not 0 <= item < len(self._dict): + raise QiskitOptimizationError('Invalid index: {}'.format(item)) + return item + if not isinstance(item, str): + raise QiskitOptimizationError('Invalid arg: {}'.format(item)) + if item not in self._dict: + raise QiskitOptimizationError('No associated index of name: {}'.format(item)) + return self._dict[item] def convert(self, *args) -> Union[int, List[int]]: """Convert a set of names into a set of indices. There are three types of arguments. - - `convert()` returns all indices. + - `convert()` + returns all indices. + + - `convert(Union[str, int])` + returns an index corresponding to the argument. + If the argument is already integer, this returns the same integer value. - - `convert(Union[str, int])` return an index corresponding to the argument. - If the argument is already integer, this returns the same integer value. + - `convert(List[Union[str, int]])` + returns a list of indices - - `convert(List[Union[str, int]])` returns a list of indices + - `convert(begin, end)` + returns a list of indices in a range starting from `begin` to `end`, + which includes both `begin` and `end`. + Note that it behaves similar to `range(begin, end+1)` - - `convert(begin, end)` return a list of indices in a range starting from `begin` to `end`, - which includes both `begin` and `end`. - Note that it behaves similar to `range(begin, end+1)` + Returns: + An index of a name or list of indices of names. + + Raises: + QiskitOptimizationError: if arguments are not valid. """ if len(args) == 0: return list(self._dict.values()) @@ -90,6 +109,10 @@ def convert(self, *args) -> Union[int, List[int]]: raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) -def init_list_args(*args): - """Initialize default arguments with empty lists if necessary.""" +def init_list_args(*args) -> Tuple: + """Initialize default arguments with empty lists if necessary. + + Returns: + A tuple of arguments where `None` is replaced with `[]`. + """ return tuple([] if a is None else a for a in args) diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index acc610634e..64e98e767d 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -219,7 +219,8 @@ def test_penalize_sense(self): ) self.assertEqual(op.linear_constraints.get_num(), 3) conv = PenalizeLinearEqualityConstraints() - self.assertRaises(QiskitOptimizationError, lambda: conv.encode(op)) + with self.assertRaises(QiskitOptimizationError): + conv.encode(op) def test_penalize_binary(self): """ Test PenalizeLinearEqualityConstraints with binary variables """ diff --git a/test/optimization/test_helpers.py b/test/optimization/test_helpers.py index 1854eb5ff9..90fa36d60f 100644 --- a/test/optimization/test_helpers.py +++ b/test/optimization/test_helpers.py @@ -16,8 +16,9 @@ import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase -from qiskit.optimization.utils.helpers import NameIndex, init_list_args + from qiskit.optimization import QiskitOptimizationError +from qiskit.optimization.utils.helpers import NameIndex, init_list_args class TestHelpers(QiskitOptimizationTestCase): @@ -30,30 +31,39 @@ def test_init_list_args(self): def test_name_index1(self): """ test name index 1 """ - n_i = NameIndex() - self.assertEqual(n_i.convert('1'), 0) - self.assertListEqual(n_i.convert(['2', '3']), [1, 2]) - self.assertEqual(n_i.convert('1'), 0) - self.assertListEqual(n_i.convert(), [0, 1, 2]) - self.assertListEqual(n_i.convert('1', '3'), [0, 1, 2]) - self.assertListEqual(n_i.convert('1', '2'), [0, 1]) + nidx = NameIndex() + nidx.build(['1', '2', '3']) + self.assertEqual(nidx.convert('1'), 0) + self.assertListEqual(nidx.convert(['2', '3']), [1, 2]) + self.assertEqual(nidx.convert('1'), 0) + self.assertListEqual(nidx.convert(), [0, 1, 2]) + self.assertListEqual(nidx.convert('1', '3'), [0, 1, 2]) + self.assertListEqual(nidx.convert('1', '2'), [0, 1]) def test_name_index2(self): """ test name index 2 """ - n_i = NameIndex() - n_i.build(['1', '2', '3']) - self.assertEqual(n_i.convert('1'), 0) - self.assertListEqual(n_i.convert(), [0, 1, 2]) - self.assertListEqual(n_i.convert('1', '3'), [0, 1, 2]) - self.assertListEqual(n_i.convert('1', '2'), [0, 1]) + nidx = NameIndex() + nidx.build(['1', '2', '3']) + self.assertEqual(nidx.convert('1'), 0) + self.assertListEqual(nidx.convert(), [0, 1, 2]) + self.assertListEqual(nidx.convert('1', '3'), [0, 1, 2]) + self.assertListEqual(nidx.convert('1', '2'), [0, 1]) def test_name_index3(self): """ test name index 3 """ - n_i = NameIndex() + nidx = NameIndex() + with self.assertRaises(QiskitOptimizationError): + nidx.convert({}) + with self.assertRaises(QiskitOptimizationError): + nidx.convert(1, 2, 3) + nidx.build(['x', 'y', 'z']) + self.assertEqual(nidx.convert(1), 1) with self.assertRaises(QiskitOptimizationError): - n_i.convert({}) + nidx.convert(4) + self.assertEqual(nidx.convert('z'), 2) with self.assertRaises(QiskitOptimizationError): - n_i.convert(1, 2, 3) + nidx.convert('a') + nidx.convert(1, 2, 3) if __name__ == '__main__': diff --git a/test/optimization/test_linear_constraints.py b/test/optimization/test_linear_constraints.py index 3624d5757f..ab3e14fed0 100644 --- a/test/optimization/test_linear_constraints.py +++ b/test/optimization/test_linear_constraints.py @@ -16,7 +16,9 @@ import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase + from cplex import SparsePair + from qiskit.optimization import OptimizationProblem @@ -248,6 +250,16 @@ def test_get_histogram(self): with self.assertRaises(NotImplementedError): op.linear_constraints.get_histogram() + def test_empty_names(self): + """ test empty names """ + op = OptimizationProblem() + r = op.linear_constraints.add(names=['', '', '']) + self.assertListEqual(op.linear_constraints.get_names(), ['c1', 'c2', 'c3']) + self.assertEqual(r, range(0, 3)) + r = op.linear_constraints.add(names=['a', '', 'c']) + self.assertEqual(r, range(3, 6)) + self.assertListEqual(op.linear_constraints.get_names(), + ['c1', 'c2', 'c3', 'a', 'c5', 'c']) -if __name__ == '__main__': - unittest.main() + if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_objective.py b/test/optimization/test_objective.py index 38ebfd59ef..0cc907f736 100644 --- a/test/optimization/test_objective.py +++ b/test/optimization/test_objective.py @@ -16,8 +16,10 @@ import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase + from cplex import SparsePair -from qiskit.optimization import OptimizationProblem + +from qiskit.optimization import OptimizationProblem, QiskitOptimizationError class TestObjective(QiskitOptimizationTestCase): @@ -31,39 +33,27 @@ def test_obj_sense(self): self.assertEqual(op.objective.sense[1], 'minimize') self.assertEqual(op.objective.sense[-1], 'maximize') - def test_set_linear0(self): - """ - op = OptimizationProblem() - op.variables.add(names=[str(i) for i in range(4)]) - self.assertListEqual(op.objective.get_linear(), [0.0, 0.0, 0.0, 0.0]) - op.objective.set_linear(0, 1.0) - self.assertListEqual(op.objective.get_linear(), [1.0, 0.0, 0.0, 0.0]) - op.objective.set_linear('3', -1.0) - self.assertListEqual(op.objective.get_linear(), [1.0, 0.0, 0.0, -1.0]) - op.objective.set_linear([("2", 2.0), (1, 0.5)]) - self.assertListEqual(op.objective.get_linear(), [1.0, 0.5, 2.0, -1.0]) - """ - pass - def test_set_linear(self): """ test set linear """ op = OptimizationProblem() n = 4 op.variables.add(names=[str(i) for i in range(n)]) - self.assertDictEqual(op.objective.get_linear(), {}) + self.assertListEqual(op.objective.get_linear(), [0.0, 0.0, 0.0, 0.0]) + self.assertDictEqual(op.objective.get_linear_dict(), {}) op.objective.set_linear(0, 1.0) - self.assertDictEqual(op.objective.get_linear(), {0: 1.0}) - self.assertListEqual(op.objective.get_linear(range(n)), [1.0, 0.0, 0.0, 0.0]) + self.assertListEqual(op.objective.get_linear(), [1.0, 0.0, 0.0, 0.0]) + self.assertDictEqual(op.objective.get_linear_dict(), {0: 1.0}) op.objective.set_linear('3', -1.0) - self.assertDictEqual(op.objective.get_linear(), {0: 1.0, 3: -1.0}) + self.assertListEqual(op.objective.get_linear(), [1.0, 0.0, 0.0, -1.0]) + self.assertDictEqual(op.objective.get_linear_dict(), {0: 1.0, 3: -1.0}) op.objective.set_linear([("2", 2.0), (1, 0.5)]) - self.assertDictEqual(op.objective.get_linear(), {0: 1.0, 1: 0.5, 2: 2.0, 3: -1.0}) - self.assertListEqual(op.objective.get_linear(range(n)), [1.0, 0.5, 2.0, -1.0]) + self.assertListEqual(op.objective.get_linear(), [1.0, 0.5, 2.0, -1.0]) + self.assertDictEqual(op.objective.get_linear_dict(), {0: 1.0, 1: 0.5, 2: 2.0, 3: -1.0}) def test_set_empty_quadratic(self): """ test set empty quadratic """ op = OptimizationProblem() - op.objective.set_quadratic([]) + self.assertIsNone(op.objective.set_quadratic([])) with self.assertRaises(TypeError): op.objective.set_quadratic() @@ -76,22 +66,39 @@ def test_set_quadratic(self): obj.set_quadratic([SparsePair(ind=[0, 1, 2], val=[1.0, -2.0, 0.5]), ([0, 1], [-2.0, -1.0]), SparsePair(ind=[0, 2], val=[0.5, -3.0])]) - lst = obj.get_quadratic(range(n)) + lst = obj.get_quadratic() self.assertListEqual(lst[0].ind, [0, 1, 2]) self.assertListEqual(lst[0].val, [1.0, -2.0, 0.5]) self.assertListEqual(lst[1].ind, [0, 1]) self.assertListEqual(lst[1].val, [-2.0, -1.0]) self.assertListEqual(lst[2].ind, [0, 2]) self.assertListEqual(lst[2].val, [0.5, -3.0]) + self.assertDictEqual( + obj.get_quadratic_dict(), + {(0, 0): 1.0, (0, 1): -2.0, (0, 2): 0.5, (1, 0): -2.0, (1, 1): -1.0, + (2, 0): 0.5, (2, 2): -3.0} + ) obj.set_quadratic([1.0, 2.0, 3.0]) - lst = obj.get_quadratic(range(n)) + lst = obj.get_quadratic() self.assertListEqual(lst[0].ind, [0]) self.assertListEqual(lst[0].val, [1.0]) self.assertListEqual(lst[1].ind, [1]) self.assertListEqual(lst[1].val, [2.0]) self.assertListEqual(lst[2].ind, [2]) self.assertListEqual(lst[2].val, [3.0]) + self.assertDictEqual( + obj.get_quadratic_dict(), + {(0, 0): 1.0, (1, 1): 2.0, (2, 2): 3.0} + ) + + def test_get_quadratic_dict(self): + """ test get quadratic dict """ + op = OptimizationProblem() + op.variables.add(names=['x', 'y']) + op.objective.set_quadratic_coefficients([('x', 'x', 1), ('x', 'y', 2)]) + self.assertDictEqual(op.objective.get_quadratic_dict(), + {(0, 0): 1, (0, 1): 2, (1, 0): 2}) def test_set_quadratic_coefficients(self): """ test set quadratic coefficients """ @@ -100,6 +107,8 @@ def test_set_quadratic_coefficients(self): op.variables.add(names=[str(i) for i in range(n)]) obj = op.objective obj.set_quadratic_coefficients(0, 1, 1.0) + with self.assertRaises(QiskitOptimizationError): + obj.get_quadratic_coefficients() lst = op.objective.get_quadratic(range(n)) self.assertListEqual(lst[0].ind, [1]) self.assertListEqual(lst[0].val, [1.0]) @@ -152,9 +161,16 @@ def test_get_linear(self): self.assertEqual(obj.get_linear(8), 12) self.assertListEqual(obj.get_linear('1', 3), [1.5, 3.0, 4.5]) self.assertListEqual(obj.get_linear([2, '0', 5]), [3.0, 0.0, 7.5]) - self.assertListEqual(obj.get_linear(range(n)), + self.assertListEqual(obj.get_linear(), [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5]) + def test_get_linear_dict(self): + """ test get linear dict """ + op = OptimizationProblem() + op.variables.add(names=['x', 'y']) + op.objective.set_linear([('x', 1), ('y', 2)]) + self.assertDictEqual(op.objective.get_linear_dict(), {0: 1, 1: 2}) + def test_get_quadratic(self): """ test get quadratic """ op = OptimizationProblem() @@ -182,18 +198,24 @@ def test_get_quadratic(self): self.assertListEqual(s_p[2].ind, [5]) self.assertListEqual(s_p[2].val, [7.5]) - s_p = obj.get_quadratic(range(n)) + s_p = obj.get_quadratic() for i in range(n): - self.assertListEqual(s_p[i].ind, [i]) - self.assertListEqual(s_p[i].val, [1.5 * i]) + if i == 0: + self.assertListEqual(s_p[i].ind, []) + self.assertListEqual(s_p[i].val, []) + else: + self.assertListEqual(s_p[i].ind, [i]) + self.assertListEqual(s_p[i].val, [1.5 * i]) - def test_get_quadratic2(self): - """ test get quadratic 2 """ + def test_get_quadratic_coefficients(self): + """ test get quadratic coefficients """ op = OptimizationProblem() n = 3 op.variables.add(names=[str(i) for i in range(n)]) obj = op.objective obj.set_quadratic_coefficients(0, 1, 1.0) + with self.assertRaises(QiskitOptimizationError): + obj.get_quadratic_coefficients() self.assertEqual(obj.get_quadratic_coefficients('1', 0), 1.0) obj.set_quadratic_coefficients([(1, 1, 2.0), (0, 2, 3.0), (1, 0, 5.0)]) self.assertListEqual(obj.get_quadratic_coefficients([(1, 0), (1, "1"), (2, "0")]), @@ -247,6 +269,24 @@ def test_offset(self): op.objective.set_offset(3.14) self.assertEqual(op.objective.get_offset(), 3.14) + def test_set_quadratic_coefficients2(self): + """ test set quadratic coefficients 2 """ + op = OptimizationProblem() + n = 2 + op.variables.add(names=[str(i) for i in range(n)]) + obj = op.objective + obj.set_quadratic_coefficients([(0, 1, 1.0), (1, 0, -1.0)]) + lst = op.objective.get_quadratic() + self.assertListEqual(lst[0].ind, [1]) + self.assertListEqual(lst[0].val, [-1.0]) + self.assertListEqual(lst[1].ind, [0]) + self.assertListEqual(lst[1].val, [-1.0]) + + def test_default_name(self): + """ test default name """ + op = OptimizationProblem() + self.assertEqual(op.objective.get_name(), 'obj1') + if __name__ == '__main__': unittest.main() diff --git a/test/optimization/test_optimization_problem.py b/test/optimization/test_optimization_problem.py index 95aaf0cc05..085f846d9a 100644 --- a/test/optimization/test_optimization_problem.py +++ b/test/optimization/test_optimization_problem.py @@ -14,12 +14,15 @@ """ Test OptimizationProblem """ -import unittest import os.path import tempfile +import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase -import qiskit.optimization.problems.optimization_problem -from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError + +from cplex import Cplex, SparsePair, SparseTriple, infinity + +from qiskit.optimization import OptimizationProblem, QiskitOptimizationError +from qiskit.optimization.problems.optimization_problem import SubstitutionStatus class TestOptimizationProblem(QiskitOptimizationTestCase): @@ -32,68 +35,67 @@ def setUp(self): def test_constructor1(self): """ test constructor """ - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() + self.assertEqual(op.get_problem_name(), '') op.variables.add(names=['x1', 'x2', 'x3']) self.assertEqual(op.variables.get_num(), 3) def test_constructor2(self): """ test constructor 2 """ with self.assertRaises(QiskitOptimizationError): - _ = qiskit.optimization.OptimizationProblem("unknown") + _ = OptimizationProblem("unknown") # If filename does not exist, an exception is raised. - def test_constructor3(self): - """ test constructor 3 """ - # we can pass at most one argument - with self.assertRaises(QiskitOptimizationError): - _ = qiskit.optimization.OptimizationProblem("test", "west") - def test_constructor_context(self): """ test constructor context """ - with qiskit.optimization.OptimizationProblem() as op: + with OptimizationProblem() as op: op.variables.add(names=['x1', 'x2', 'x3']) self.assertEqual(op.variables.get_num(), 3) - # def test_end(self): - # op = qiskit.optimization.OptimizationProblem() - # op.end() - # TODO: we do not need to release the object - # with self.assertRaises(QiskitOptimizationError): - # op.variables.add(names=['x1', 'x2', 'x3']) + def test_end(self): + """ test end """ + op = OptimizationProblem() + self.assertIsNone(op.end()) + + def test_solve(self): + """ test solve """ + op = OptimizationProblem() + self.assertIsNone(op.solve()) def test_read1(self): - """ test read 2""" - op = qiskit.optimization.OptimizationProblem() + """ test read 1""" + op = OptimizationProblem() op.read(self.resource_file) self.assertEqual(op.variables.get_num(), 3) def test_write1(self): """ test write 1 """ - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() op.variables.add(names=['x1', 'x2', 'x3']) file, filename = tempfile.mkstemp(suffix='.lp') os.close(file) op.write(filename) - assert os.path.exists(filename) == 1 + self.assertEqual(os.path.exists(filename), 1) def test_write2(self): """ test write 2 """ - op1 = qiskit.optimization.OptimizationProblem() + op1 = OptimizationProblem() op1.variables.add(names=['x1', 'x2', 'x3']) file, filename = tempfile.mkstemp(suffix='.lp') os.close(file) op1.write(filename) - op2 = qiskit.optimization.OptimizationProblem() + op2 = OptimizationProblem() op2.read(filename) self.assertEqual(op2.variables.get_num(), 3) def test_write3(self): """ test write 3 """ - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() op.variables.add(names=['x1', 'x2', 'x3']) class NoOpStream: """ stream """ + def __init__(self): self.was_called = False @@ -106,6 +108,7 @@ def write(self, byt): def flush(self): """ flush """ pass + stream = NoOpStream() op.write_to_stream(stream) self.assertEqual(stream.was_called, True) @@ -115,34 +118,302 @@ def flush(self): def test_write4(self): """ test write 4 """ # Writes a problem as a string in the given file format. - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() op.variables.add(names=['x1', 'x2', 'x3']) lp_str = op.write_as_string("lp") self.assertGreater(len(lp_str), 0) def test_problem_type1(self): """ test problem type 1 """ - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() op.read(self.resource_file) - self.assertEqual(op.get_problem_type(), - qiskit.optimization.problems.problem_type.CPXPROB_QP) + self.assertEqual(op.get_problem_type(), op.problem_type.QP) self.assertEqual(op.problem_type[op.get_problem_type()], 'QP') def test_problem_type2(self): """ test problem type 2""" - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() op.set_problem_type(op.problem_type.LP) - self.assertEqual(op.get_problem_type(), - qiskit.optimization.problems.problem_type.CPXPROB_LP) + self.assertEqual(op.get_problem_type(), op.problem_type.LP) self.assertEqual(op.problem_type[op.get_problem_type()], 'LP') + def test_problem_type3(self): + """ test problem type 3""" + op = OptimizationProblem() + self.assertEqual(op.get_problem_type(), op.problem_type.LP) + op.variables.add(names=['x1', 'x2', 'x3'], types='B' * 3) + op.objective.set_linear([('x1', 2.0), ('x3', 0.5)]) + self.assertEqual(op.get_problem_type(), op.problem_type.MILP) + op.objective.set_quadratic([ + SparsePair(ind=[0, 1], val=[2.0, 3.0]), + SparsePair(ind=[0], val=[3.0]), + SparsePair(ind=[], val=[]) + ]) + self.assertEqual(op.get_problem_type(), op.problem_type.MIQP) + op.linear_constraints.add( + lin_expr=[SparsePair(ind=["x1", "x3"], val=[1.0, -1.0]), + SparsePair(ind=["x1", "x2"], val=[1.0, 1.0]), + SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), + SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])], + senses=["E", "L", "G", "R"], + rhs=[0.0, 1.0, -1.0, 2.0], + range_values=[0.0, 0.0, 0.0, -10.0], + names=["c0", "c1", "c2", "c3"]) + self.assertEqual(op.get_problem_type(), op.problem_type.MIQP) + op.quadratic_constraints.add( + lin_expr=SparsePair(ind=['x1', 'x3'], val=[1.0, -1.0]), + quad_expr=SparseTriple(ind1=['x1', 'x2'], ind2=['x2', 'x3'], val=[1.0, -1.0]), + sense='E', + rhs=1.0 + ) + self.assertEqual(op.get_problem_type(), op.problem_type.MIQCP) + def test_problem_name(self): """ test problem name """ - op = qiskit.optimization.OptimizationProblem() + op = OptimizationProblem() op.set_problem_name("test") # test self.assertEqual(op.get_problem_name(), "test") + def test_from_and_to_cplex(self): + """ test from_cplex and to_cplex """ + op = Cplex() + op.variables.add(names=['x1', 'x2', 'x3'], types='B' * 3) + op.objective.set_linear([('x1', 2.0), ('x3', 0.5)]) + op.objective.set_quadratic([ + SparsePair(ind=[0, 1], val=[2.0, 3.0]), + SparsePair(ind=[0], val=[3.0]), + SparsePair(ind=[], val=[]) + ]) + op.linear_constraints.add( + lin_expr=[SparsePair(ind=["x1", "x3"], val=[1.0, -1.0]), + SparsePair(ind=["x1", "x2"], val=[1.0, 1.0]), + SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), + SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])], + senses=["E", "L", "G", "R"], + rhs=[0.0, 1.0, -1.0, 2.0], + range_values=[0.0, 0.0, 0.0, -10.0], + names=["c0", "c1", "c2", "c3"]) + op.quadratic_constraints.add( + lin_expr=SparsePair(ind=['x1', 'x3'], val=[1.0, -1.0]), + quad_expr=SparseTriple(ind1=['x1', 'x2'], ind2=['x2', 'x3'], val=[1.0, -1.0]), + sense='E', + rhs=1.0 + ) + orig = op.write_as_string() + op2 = OptimizationProblem() + op2.from_cplex(op) + self.assertEqual(op2.write_as_string(), orig) + op3 = op2.to_cplex() + self.assertEqual(op3.write_as_string(), orig) + + op.set_problem_name('test') + orig = op.write_as_string() + op2 = OptimizationProblem() + op2.from_cplex(op) + self.assertEqual(op2.write_as_string(), orig) + op3 = op2.to_cplex() + self.assertEqual(op3.write_as_string(), orig) + + def test_substitute_variables_bounds1(self): + """ test substitute variables bounds 1 """ + op = OptimizationProblem() + op.set_problem_name('before') + n = 5 + op.variables.add(names=['x' + str(i) for i in range(n)], types='I' * n, + lb=[-2] * n, ub=[4] * n) + op2, status = op.substitute_variables(constants=SparsePair(ind=['x0'], val=[100])) + self.assertEqual(status, SubstitutionStatus.infeasible) + op2, status = op.substitute_variables( + constants=SparsePair(ind=['x0'], val=[3.0]), + variables=SparseTriple(ind1=['x1', 'x3'], ind2=['x2', 'x4'], val=[2.0, -2.0]) + ) + self.assertEqual(status, SubstitutionStatus.success) + self.assertListEqual(op2.variables.get_names(), ['x2', 'x4']) + self.assertListEqual(op2.variables.get_lower_bounds(), [-1, -2]) + self.assertListEqual(op2.variables.get_upper_bounds(), [2, 1]) + + def test_substitute_variables_bounds2(self): + """ test substitute variables bounds 2 """ + op = OptimizationProblem() + op.set_problem_name('before') + n = 5 + op.variables.add(names=['x' + str(i) for i in range(n)], types='I' * n, + lb=[0] * n, ub=[infinity] * n) + op2, status = op.substitute_variables(constants=SparsePair(ind=['x0'], val=[-1])) + self.assertEqual(status, SubstitutionStatus.infeasible) + op2, status = op.substitute_variables( + constants=SparsePair(ind=['x0'], val=[1.0]), + variables=SparseTriple(ind1=['x1', 'x3'], ind2=['x2', 'x4'], val=[2.0, -2.0]) + ) + self.assertEqual(status, SubstitutionStatus.success) + self.assertListEqual(op2.variables.get_names(), ['x2', 'x4']) + self.assertListEqual(op2.variables.get_lower_bounds(), [0, 0]) + self.assertListEqual(op2.variables.get_upper_bounds(), [infinity, 0]) + + def test_substitute_variables_obj(self): + """ test substitute variables objective """ + op = OptimizationProblem() + op.set_problem_name('before') + op.variables.add(names=['x1', 'x2', 'x3'], types='I' * 3, lb=[-2] * 3, ub=[4] * 3) + op.objective.set_linear([('x1', 1.0), ('x2', 2.0)]) + op.objective.set_quadratic_coefficients([ + ('x1', 'x1', 1), + ('x2', 'x3', 2) + ]) + op2, status = op.substitute_variables( + constants=SparsePair(ind=['x1'], val=[3]), + variables=SparseTriple(ind1=['x2'], ind2=['x3'], val=[-2]) + ) + self.assertEqual(status, SubstitutionStatus.success) + self.assertListEqual(op2.variables.get_names(), ['x3']) + self.assertEqual(op2.objective.get_offset(), 7.5) + self.assertListEqual(op2.objective.get_linear(), [-4]) + self.assertEqual(op2.objective.get_quadratic_coefficients(0, 0), -8) + self.assertEqual(op.objective.get_sense(), op2.objective.get_sense()) + + def test_substitute_variables_lin_cst1(self): + """ test substitute variables linear constraints 1 """ + op = OptimizationProblem() + n = 5 + op.variables.add(names=['x' + str(i) for i in range(n)], types='I' * n, + lb=[-10] * n, ub=[14] * n) + op.linear_constraints.add( + lin_expr=[SparsePair(ind=['x0'], val=[1.0]), + SparsePair(ind=['x1'], val=[1.0]), + SparsePair(ind=['x2'], val=[1.0]), + SparsePair(ind=['x3'], val=[1.0]), + SparsePair(ind=['x4'], val=[1.0])], + senses=["L", "E", "G", "R", "R"], + rhs=[-1.0, 1.0, 1.0, 2.0, 2.0], + range_values=[0.0, 0.0, 0.0, 10.0, -10.0], + names=["c0", "c1", "c2", "c3", "c4"]) + _, status = op.substitute_variables(constants=SparsePair(ind=['x0'], val=[3])) + self.assertEqual(status, SubstitutionStatus.infeasible) + _, status = op.substitute_variables(constants=SparsePair(ind=['x1'], val=[3])) + self.assertEqual(status, SubstitutionStatus.infeasible) + _, status = op.substitute_variables(constants=SparsePair(ind=['x1'], val=[1])) + self.assertEqual(status, SubstitutionStatus.success) + _, status = op.substitute_variables(constants=SparsePair(ind=['x2'], val=[-1])) + self.assertEqual(status, SubstitutionStatus.infeasible) + _, status = op.substitute_variables(constants=SparsePair(ind=['x3'], val=[1.99])) + self.assertEqual(status, SubstitutionStatus.infeasible) + _, status = op.substitute_variables(constants=SparsePair(ind=['x3'], val=[2])) + self.assertEqual(status, SubstitutionStatus.success) + _, status = op.substitute_variables(constants=SparsePair(ind=['x3'], val=[12])) + self.assertEqual(status, SubstitutionStatus.success) + _, status = op.substitute_variables(constants=SparsePair(ind=['x3'], val=[12.01])) + self.assertEqual(status, SubstitutionStatus.infeasible) + _, status = op.substitute_variables(constants=SparsePair(ind=['x4'], val=[-8.01])) + self.assertEqual(status, SubstitutionStatus.infeasible) + _, status = op.substitute_variables(constants=SparsePair(ind=['x4'], val=[-8])) + self.assertEqual(status, SubstitutionStatus.success) + _, status = op.substitute_variables(constants=SparsePair(ind=['x4'], val=[2])) + self.assertEqual(status, SubstitutionStatus.success) + _, status = op.substitute_variables(constants=SparsePair(ind=['x4'], val=[2.01])) + self.assertEqual(status, SubstitutionStatus.infeasible) + + def test_substitute_variables_lin_cst2(self): + """ test substitute variables linear constraints 2 """ + op = OptimizationProblem() + n = 3 + op.variables.add(names=['x' + str(i) for i in range(n)], types='I' * n, + lb=[-2] * n, ub=[4] * n) + op.linear_constraints.add( + lin_expr=[SparsePair(ind=["x0", "x2"], val=[1.0, -1.0]), + SparsePair(ind=["x0", "x1"], val=[1.0, 1.0]), + SparsePair(ind=["x0", "x1", "x2"], val=[-1.0] * 3), + SparsePair(ind=["x1", "x2"], val=[10.0, -2.0])], + senses=["E", "L", "G", "R"], + rhs=[0.0, 1.0, -1.0, 2.0], + range_values=[0.0, 0.0, 0.0, -10.0], + names=["c0", "c1", "c2", "c3"]) + op2, status = op.substitute_variables( + SparsePair(ind=['x0'], val=[2.0]), + SparseTriple(ind1=['x1'], ind2=['x2'], val=[3.0]) + ) + self.assertEqual(status, SubstitutionStatus.success) + self.assertListEqual(op2.variables.get_names(), ['x2']) + rows = op2.linear_constraints.get_rows() + self.assertListEqual(rows[0].ind, [0]) + self.assertListEqual(rows[0].val, [-1]) + self.assertListEqual(rows[1].ind, [0]) + self.assertListEqual(rows[1].val, [3]) + self.assertListEqual(rows[2].ind, [0]) + self.assertListEqual(rows[2].val, [-4]) + self.assertListEqual(rows[3].ind, [0]) + self.assertListEqual(rows[3].val, [28]) + self.assertListEqual(op2.linear_constraints.get_rhs(), [-2, -1, 1, 2]) + + def test_substitute_variables_quad_cst1(self): + """ test substitute variables quadratic constraints 1 """ + op = OptimizationProblem() + n = 3 + op.variables.add(names=['x' + str(i) for i in range(n)], types='I' * n, + lb=[-2] * n, ub=[4] * n) + op.quadratic_constraints.add( + lin_expr=SparsePair(ind=['x0', 'x1'], val=[1.0, -1.0]), + quad_expr=SparseTriple(ind1=['x0', 'x1', 'x2'], ind2=['x1', 'x2', 'x2'], + val=[1.0, -2.0, 3.0]), + sense='L', + rhs=1.0 + ) + op2, status = op.substitute_variables( + constants=SparsePair(ind=['x0', 'x1', 'x2'], val=[1, 1, 1])) + self.assertEqual(status, SubstitutionStatus.infeasible) + + op2, status = op.substitute_variables(SparsePair(ind=['x0', 'x1', 'x2'], val=[-1, 1, 1])) + self.assertEqual(status, SubstitutionStatus.success) + + op2, status = op.substitute_variables(SparsePair(ind=['x0', 'x1'], val=[1, -1])) + self.assertEqual(status, SubstitutionStatus.success) + self.assertEqual(op2.quadratic_constraints.get_num(), 1) + lin = op2.quadratic_constraints.get_linear_components(0) + self.assertListEqual(lin.ind, [0]) + self.assertListEqual(lin.val, [2]) + q = op2.quadratic_constraints.get_quadratic_components(0) + self.assertListEqual(q.ind1, [0]) + self.assertListEqual(q.ind2, [0]) + self.assertListEqual(q.val, [3]) + self.assertEqual(op2.quadratic_constraints.get_senses(0), 'L') + self.assertEqual(op2.quadratic_constraints.get_rhs(0), 0) + + with self.assertRaises(QiskitOptimizationError): + op.substitute_variables( + variables=SparseTriple(ind1=['x0'], ind2=['x0'], val=[2])) + with self.assertRaises(QiskitOptimizationError): + op.substitute_variables( + variables=SparseTriple(ind1=['x1', 'x0'], ind2=['x2', 'x1'], val=[1.5, 1])) + with self.assertRaises(QiskitOptimizationError): + op.substitute_variables( + variables=SparseTriple(ind1=['x1', 'x1'], ind2=['x2', 'x0'], val=[1.5, 1])) + + op2, status = op.substitute_variables( + variables=SparseTriple(ind1=['x1'], ind2=['x2'], val=[1.5])) + self.assertEqual(status, op.substitution_status.success) + lin = op2.quadratic_constraints.get_linear_components(0) + self.assertListEqual(lin.ind, [0, 1]) + self.assertListEqual(lin.val, [1, -1.5]) + q = op2.quadratic_constraints.get_quadratic_components(0) + self.assertListEqual(q.ind1, [1]) + self.assertListEqual(q.ind2, [0]) + self.assertListEqual(q.val, [1.5]) + self.assertEqual(op2.quadratic_constraints.get_senses(0), 'L') + self.assertEqual(op2.quadratic_constraints.get_rhs(0), 1) + + op2, status = op.substitute_variables( + constants=SparsePair(ind=['x2'], val=[2])) + self.assertEqual(status, op.substitution_status.success) + lin = op2.quadratic_constraints.get_linear_components(0) + self.assertListEqual(lin.ind, [0, 1]) + self.assertListEqual(lin.val, [1, -5]) + q = op2.quadratic_constraints.get_quadratic_components(0) + self.assertListEqual(q.ind1, [1]) + self.assertListEqual(q.ind2, [0]) + self.assertListEqual(q.val, [1]) + self.assertEqual(op2.quadratic_constraints.get_senses(0), 'L') + self.assertEqual(op2.quadratic_constraints.get_rhs(0), -11) + if __name__ == '__main__': unittest.main() diff --git a/test/optimization/test_optimization_problem_to_negative_value_oracle.py b/test/optimization/test_optimization_problem_to_negative_value_oracle.py index 1a0a09b7df..284c3c7661 100644 --- a/test/optimization/test_optimization_problem_to_negative_value_oracle.py +++ b/test/optimization/test_optimization_problem_to_negative_value_oracle.py @@ -27,13 +27,13 @@ class TestOptimizationProblemToNegativeValueOracle(QiskitOptimizationTestCase): """OPtNVO Tests""" def _validate_function(self, func_dict, problem): - linear = problem.objective.get_linear() - quadratic = problem.objective.get_quadratic() + linear = problem.objective.get_linear_dict() + quadratic = problem.objective.get_quadratic_dict() for key in func_dict: if isinstance(key, int) and key >= 0: self.assertEqual(linear[key], func_dict[key]) elif isinstance(key, tuple): - self.assertEqual(quadratic[key[0]][key[1]], func_dict[key]) + self.assertEqual(quadratic[key[0], key[1]], func_dict[key]) else: self.assertEqual(problem.objective.get_offset(), func_dict[key]) diff --git a/test/optimization/test_quadratic_constraints.py b/test/optimization/test_quadratic_constraints.py index 95cdb18cd1..0750bddb54 100644 --- a/test/optimization/test_quadratic_constraints.py +++ b/test/optimization/test_quadratic_constraints.py @@ -16,6 +16,7 @@ import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase + from cplex import SparsePair, SparseTriple from qiskit.optimization import QiskitOptimizationError @@ -34,13 +35,14 @@ def test_initial1(self): self.assertEqual(op.quadratic_constraints.get_num(), 3) self.assertListEqual(op.quadratic_constraints.get_names(), ['c1', 'c2', 'c3']) self.assertListEqual([c_1, c_2, c_3], [0, 1, 2]) - self.assertRaises(QiskitOptimizationError, lambda: op.quadratic_constraints.add(name='c1')) + with self.assertRaises(QiskitOptimizationError): + op.quadratic_constraints.add(name='c1') def test_initial2(self): """ test initial 2""" op = OptimizationProblem() op.variables.add(names=['x1', 'x2', 'x3'], types='B' * 3) - _ = op.quadratic_constraints.add( + op.quadratic_constraints.add( lin_expr=SparsePair(ind=['x1', 'x3'], val=[1.0, -1.0]), quad_expr=SparseTriple(ind1=['x1', 'x2'], ind2=['x2', 'x3'], val=[1.0, -1.0]), sense='E', @@ -48,41 +50,49 @@ def test_initial2(self): ) quad = op.quadratic_constraints self.assertEqual(quad.get_num(), 1) - self.assertListEqual(quad.get_names(), ['q0']) + self.assertListEqual(quad.get_names(), ['q1']) self.assertListEqual(quad.get_rhs(), [1.0]) self.assertListEqual(quad.get_senses(), ['E']) self.assertListEqual(quad.get_linear_num_nonzeros(), [2]) self.assertListEqual(quad.get_quad_num_nonzeros(), [2]) - l_a = quad.get_linear_components() - self.assertEqual(len(l_a), 1) - self.assertListEqual(l_a[0].ind, [0, 2]) - self.assertListEqual(l_a[0].val, [1.0, -1.0]) + lin = quad.get_linear_components() + self.assertEqual(len(lin), 1) + self.assertListEqual(lin[0].ind, [0, 2]) + self.assertListEqual(lin[0].val, [1.0, -1.0]) q = quad.get_quadratic_components() self.assertEqual(len(q), 1) self.assertListEqual(q[0].ind1, [1, 2]) self.assertListEqual(q[0].ind2, [0, 1]) self.assertListEqual(q[0].val, [1.0, -1.0]) + def test_initial3(self): + """ test initial 3""" + op = OptimizationProblem() + op.variables.add(names=['x', 'y']) + with self.assertRaises(QiskitOptimizationError): + op.quadratic_constraints.add(lin_expr=([0, 0], [1, 1])) + op.quadratic_constraints.add(quad_expr=([0, 0, 1, 1], [0, 1, 0, 1], [1, 1, 1, 1])) + def test_get_num(self): """ test get num """ op = OptimizationProblem() op.variables.add(names=['x', 'y']) - l_a = SparsePair(ind=['x'], val=[1.0]) + lin = SparsePair(ind=['x'], val=[1.0]) q = SparseTriple(ind1=['x'], ind2=['y'], val=[1.0]) n = 10 for i in range(n): - self.assertEqual(op.quadratic_constraints.add(name=str(i), - lin_expr=l_a, quad_expr=q), i) + self.assertEqual( + op.quadratic_constraints.add(name=str(i), lin_expr=lin, quad_expr=q), i) self.assertEqual(op.quadratic_constraints.get_num(), n) def test_add(self): """ test add """ op = OptimizationProblem() op.variables.add(names=['x', 'y']) - l_a = SparsePair(ind=['x'], val=[1.0]) + lin = SparsePair(ind=['x'], val=[1.0]) q = SparseTriple(ind1=['x'], ind2=['y'], val=[1.0]) self.assertEqual(op.quadratic_constraints.add( - name='my quad', lin_expr=l_a, quad_expr=q, rhs=1.0, sense='G'), 0) + name='my quad', lin_expr=lin, quad_expr=q, rhs=1.0, sense='G'), 0) def test_delete(self): """ test delete """ @@ -182,39 +192,39 @@ def test_get_linear_components2(self): _ = [q.add(name=str(i), lin_expr=[range(i), [1.0 * (j + 1.0) for j in range(i)]]) for i in range(10)] - s_c = q.get_linear_components(8) - self.assertListEqual(s_c.ind, [0, 1, 2, 3, 4, 5, 6, 7]) - self.assertListEqual(s_c.val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]) - - s_c = q.get_linear_components('1', 3) - self.assertEqual(len(s_c), 3) - self.assertListEqual(s_c[0].ind, [0]) - self.assertListEqual(s_c[0].val, [1.0]) - self.assertListEqual(s_c[1].ind, [0, 1]) - self.assertListEqual(s_c[1].val, [1.0, 2.0]) - self.assertListEqual(s_c[2].ind, [0, 1, 2]) - self.assertListEqual(s_c[2].val, [1.0, 2.0, 3.0]) - - s_c = q.get_linear_components([2, '0', 5]) - self.assertEqual(len(s_c), 3) - self.assertListEqual(s_c[0].ind, [0, 1]) - self.assertListEqual(s_c[0].val, [1.0, 2.0]) - self.assertListEqual(s_c[1].ind, []) - self.assertListEqual(s_c[1].val, []) - self.assertListEqual(s_c[2].ind, [0, 1, 2, 3, 4]) - self.assertListEqual(s_c[2].val, [1.0, 2.0, 3.0, 4.0, 5.0]) + s_p = q.get_linear_components(8) + self.assertListEqual(s_p.ind, [0, 1, 2, 3, 4, 5, 6, 7]) + self.assertListEqual(s_p.val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]) + + s_p = q.get_linear_components('1', 3) + self.assertEqual(len(s_p), 3) + self.assertListEqual(s_p[0].ind, [0]) + self.assertListEqual(s_p[0].val, [1.0]) + self.assertListEqual(s_p[1].ind, [0, 1]) + self.assertListEqual(s_p[1].val, [1.0, 2.0]) + self.assertListEqual(s_p[2].ind, [0, 1, 2]) + self.assertListEqual(s_p[2].val, [1.0, 2.0, 3.0]) + + s_p = q.get_linear_components([2, '0', 5]) + self.assertEqual(len(s_p), 3) + self.assertListEqual(s_p[0].ind, [0, 1]) + self.assertListEqual(s_p[0].val, [1.0, 2.0]) + self.assertListEqual(s_p[1].ind, []) + self.assertListEqual(s_p[1].val, []) + self.assertListEqual(s_p[2].ind, [0, 1, 2, 3, 4]) + self.assertListEqual(s_p[2].val, [1.0, 2.0, 3.0, 4.0, 5.0]) q.delete(4, 9) - s_c = q.get_linear_components() - self.assertEqual(len(s_c), 4) - self.assertListEqual(s_c[0].ind, []) - self.assertListEqual(s_c[0].val, []) - self.assertListEqual(s_c[1].ind, [0]) - self.assertListEqual(s_c[1].val, [1.0]) - self.assertListEqual(s_c[2].ind, [0, 1]) - self.assertListEqual(s_c[2].val, [1.0, 2.0]) - self.assertListEqual(s_c[3].ind, [0, 1, 2]) - self.assertListEqual(s_c[3].val, [1.0, 2.0, 3.0]) + s_p = q.get_linear_components() + self.assertEqual(len(s_p), 4) + self.assertListEqual(s_p[0].ind, []) + self.assertListEqual(s_p[0].val, []) + self.assertListEqual(s_p[1].ind, [0]) + self.assertListEqual(s_p[1].val, [1.0]) + self.assertListEqual(s_p[2].ind, [0, 1]) + self.assertListEqual(s_p[2].val, [1.0, 2.0]) + self.assertListEqual(s_p[3].ind, [0, 1, 2]) + self.assertListEqual(s_p[3].val, [1.0, 2.0, 3.0]) def test_quad_num_nonzeros(self): """ test quad num non zeros """ @@ -278,53 +288,53 @@ def test_get_quadratic_components2(self): _ = [q.add(name=str(i), quad_expr=[range(i), range(i), [1.0 * (j + 1.0) for j in range(i)]]) for i in range(1, 11)] - s_c = q.get_quadratic_components(8) - self.assertListEqual(s_c.ind1, [0, 1, 2, 3, 4, 5, 6, 7, 8]) - self.assertListEqual(s_c.ind2, [0, 1, 2, 3, 4, 5, 6, 7, 8]) - self.assertListEqual(s_c.val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]) - - s_c = q.get_quadratic_components('1', 3) - self.assertEqual(len(s_c), 4) - self.assertListEqual(s_c[0].ind1, [0]) - self.assertListEqual(s_c[0].ind2, [0]) - self.assertListEqual(s_c[0].val, [1.0]) - self.assertListEqual(s_c[1].ind1, [0, 1]) - self.assertListEqual(s_c[1].ind2, [0, 1]) - self.assertListEqual(s_c[1].val, [1.0, 2.0]) - self.assertListEqual(s_c[2].ind1, [0, 1, 2]) - self.assertListEqual(s_c[2].ind2, [0, 1, 2]) - self.assertListEqual(s_c[2].val, [1.0, 2.0, 3.0]) - self.assertListEqual(s_c[3].ind1, [0, 1, 2, 3]) - self.assertListEqual(s_c[3].ind2, [0, 1, 2, 3]) - self.assertListEqual(s_c[3].val, [1.0, 2.0, 3.0, 4.0]) - - s_c = q.get_quadratic_components([2, '1', 5]) - self.assertEqual(len(s_c), 3) - self.assertListEqual(s_c[0].ind1, [0, 1, 2]) - self.assertListEqual(s_c[0].ind2, [0, 1, 2]) - self.assertListEqual(s_c[0].val, [1.0, 2.0, 3.0]) - self.assertListEqual(s_c[1].ind1, [0]) - self.assertListEqual(s_c[1].ind2, [0]) - self.assertListEqual(s_c[1].val, [1.0]) - self.assertListEqual(s_c[2].ind1, [0, 1, 2, 3, 4, 5]) - self.assertListEqual(s_c[2].ind2, [0, 1, 2, 3, 4, 5]) - self.assertListEqual(s_c[2].val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) + s_t = q.get_quadratic_components(8) + self.assertListEqual(s_t.ind1, [0, 1, 2, 3, 4, 5, 6, 7, 8]) + self.assertListEqual(s_t.ind2, [0, 1, 2, 3, 4, 5, 6, 7, 8]) + self.assertListEqual(s_t.val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]) + + s_t = q.get_quadratic_components('1', 3) + self.assertEqual(len(s_t), 4) + self.assertListEqual(s_t[0].ind1, [0]) + self.assertListEqual(s_t[0].ind2, [0]) + self.assertListEqual(s_t[0].val, [1.0]) + self.assertListEqual(s_t[1].ind1, [0, 1]) + self.assertListEqual(s_t[1].ind2, [0, 1]) + self.assertListEqual(s_t[1].val, [1.0, 2.0]) + self.assertListEqual(s_t[2].ind1, [0, 1, 2]) + self.assertListEqual(s_t[2].ind2, [0, 1, 2]) + self.assertListEqual(s_t[2].val, [1.0, 2.0, 3.0]) + self.assertListEqual(s_t[3].ind1, [0, 1, 2, 3]) + self.assertListEqual(s_t[3].ind2, [0, 1, 2, 3]) + self.assertListEqual(s_t[3].val, [1.0, 2.0, 3.0, 4.0]) + + s_t = q.get_quadratic_components([2, '1', 5]) + self.assertEqual(len(s_t), 3) + self.assertListEqual(s_t[0].ind1, [0, 1, 2]) + self.assertListEqual(s_t[0].ind2, [0, 1, 2]) + self.assertListEqual(s_t[0].val, [1.0, 2.0, 3.0]) + self.assertListEqual(s_t[1].ind1, [0]) + self.assertListEqual(s_t[1].ind2, [0]) + self.assertListEqual(s_t[1].val, [1.0]) + self.assertListEqual(s_t[2].ind1, [0, 1, 2, 3, 4, 5]) + self.assertListEqual(s_t[2].ind2, [0, 1, 2, 3, 4, 5]) + self.assertListEqual(s_t[2].val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) q.delete(4, 9) - s_c = q.get_quadratic_components() - self.assertEqual(len(s_c), 4) - self.assertListEqual(s_c[0].ind1, [0]) - self.assertListEqual(s_c[0].ind2, [0]) - self.assertListEqual(s_c[0].val, [1.0]) - self.assertListEqual(s_c[1].ind1, [0, 1]) - self.assertListEqual(s_c[1].ind2, [0, 1]) - self.assertListEqual(s_c[1].val, [1.0, 2.0]) - self.assertListEqual(s_c[2].ind1, [0, 1, 2]) - self.assertListEqual(s_c[2].ind2, [0, 1, 2]) - self.assertListEqual(s_c[2].val, [1.0, 2.0, 3.0]) - self.assertListEqual(s_c[3].ind1, [0, 1, 2, 3]) - self.assertListEqual(s_c[3].ind2, [0, 1, 2, 3]) - self.assertListEqual(s_c[3].val, [1.0, 2.0, 3.0, 4.0]) + s_t = q.get_quadratic_components() + self.assertEqual(len(s_t), 4) + self.assertListEqual(s_t[0].ind1, [0]) + self.assertListEqual(s_t[0].ind2, [0]) + self.assertListEqual(s_t[0].val, [1.0]) + self.assertListEqual(s_t[1].ind1, [0, 1]) + self.assertListEqual(s_t[1].ind2, [0, 1]) + self.assertListEqual(s_t[1].val, [1.0, 2.0]) + self.assertListEqual(s_t[2].ind1, [0, 1, 2]) + self.assertListEqual(s_t[2].ind2, [0, 1, 2]) + self.assertListEqual(s_t[2].val, [1.0, 2.0, 3.0]) + self.assertListEqual(s_t[3].ind1, [0, 1, 2, 3]) + self.assertListEqual(s_t[3].ind2, [0, 1, 2, 3]) + self.assertListEqual(s_t[3].val, [1.0, 2.0, 3.0, 4.0]) def test_get_names(self): """ test get names """ @@ -341,6 +351,15 @@ def test_get_names(self): self.assertListEqual(q.get_names(), ['q1', 'q2', 'q3', 'q4', 'q5', 'q6', 'q7', 'q8', 'q9', 'q10']) + def test_empty_names(self): + """ test empty names """ + op = OptimizationProblem() + op.variables.add(names=['x']) + op.quadratic_constraints.add(name='a') + op.quadratic_constraints.add(name='') + op.quadratic_constraints.add(name='c') + self.assertListEqual(op.quadratic_constraints.get_names(), ['a', 'q2', 'c']) + if __name__ == '__main__': unittest.main() diff --git a/test/optimization/test_variables.py b/test/optimization/test_variables.py index f62dd4482d..89cbc0667f 100644 --- a/test/optimization/test_variables.py +++ b/test/optimization/test_variables.py @@ -16,7 +16,10 @@ import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase -from qiskit.optimization.problems import OptimizationProblem + +from cplex import infinity + +from qiskit.optimization import OptimizationProblem, QiskitOptimizationError class TestVariables(QiskitOptimizationTestCase): @@ -85,7 +88,6 @@ def test_get_num_semiinteger(self): def test_add(self): """ add test """ - from cplex import infinity op = OptimizationProblem() op.linear_constraints.add(names=["c0", "c1", "c2"]) op.variables.add(types=[op.variables.type.integer] * 3) @@ -243,7 +245,6 @@ def test_add2(self): def test_default_bounds(self): """ test default bounds """ - from cplex import infinity op = OptimizationProblem() types = ['B', 'I', 'C', 'S', 'N'] op.variables.add(names=types, types=types) @@ -251,6 +252,35 @@ def test_default_bounds(self): # the upper bound of binary variable is 1. self.assertListEqual(op.variables.get_upper_bounds(), [1.0] + [infinity] * 4) + def test_empty_names(self): + """ test empty names """ + op = OptimizationProblem() + r = op.variables.add(names=['', '', '']) + self.assertEqual(r, range(0, 3)) + self.assertListEqual(op.variables.get_names(), ['x1', 'x2', 'x3']) + r = op.variables.add(names=['a', '', 'c']) + self.assertEqual(r, range(3, 6)) + self.assertListEqual(op.variables.get_names(), + ['x1', 'x2', 'x3', 'a', 'x5', 'c']) + + def test_duplicate_names(self): + """ test duplicate names """ + op = OptimizationProblem() + op.variables.add(names=['', '', '']) + with self.assertRaises(QiskitOptimizationError): + op.variables.add(names=['a', 'a']) + with self.assertRaises(QiskitOptimizationError): + op.variables.add(names=['x1']) + + def test_add3(self): + """ test add 3 """ + op = OptimizationProblem() + op.variables.add(names=['c'], types=['C'], lb=[-10], ub=[10]) + op.variables.add(names=['b'], types=['B'], lb=[-10], ub=[10]) + op.variables.add(names=['b2'], types=['B']) + self.assertListEqual(op.variables.get_lower_bounds(), [-10, -10, 0]) + self.assertListEqual(op.variables.get_upper_bounds(), [10, 10, 1]) + if __name__ == '__main__': unittest.main() From 6700f5ae69f51cd43348382e7ed8be8fa367f2f7 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Mon, 6 Apr 2020 12:46:52 -0400 Subject: [PATCH 152/323] fix style, lint, spell --- .pylintdict | 3 +++ .../recursive_minimum_eigen_optimizer.py | 3 +++ qiskit/optimization/utils/base.py | 15 ++++++++++++--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.pylintdict b/.pylintdict index 37cf30860b..1e68aa3553 100644 --- a/.pylintdict +++ b/.pylintdict @@ -196,6 +196,7 @@ fujii fullname func fval +Gambella gambetta gauopen gaussian @@ -532,6 +533,7 @@ shanno shor shor's sigmoid +Simonetto sj sklearn slsqp @@ -540,6 +542,7 @@ solutionstatus spsa sqrt srange +src statevector statevectors stdout diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index de926d2d29..fcb1286c9a 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -105,6 +105,9 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: Returns: The result of the optimizer applied to the problem. + Raises: + QiskitOptimizationError: Infeasible due to variable substitution + """ from cplex import SparseTriple # convert problem to QUBO diff --git a/qiskit/optimization/utils/base.py b/qiskit/optimization/utils/base.py index 412f53c2ba..d0d95733cb 100644 --- a/qiskit/optimization/utils/base.py +++ b/qiskit/optimization/utils/base.py @@ -12,6 +12,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""common methods for sub-interfaces within Qiskit Optimization""" from abc import ABC from typing import Callable, Sequence, Union, Any, List, Generator @@ -67,11 +68,14 @@ def _setter(setfunc: Callable[[Union[str, int], Any], None], *args) -> None: """A generic setter method Args: - setfunc(index, val): A setter function of two parameters: `index` and `val`. + setfunc: A setter function of two parameters: `index` and `val`. Since `index` can be a string, users need to convert it into an appropriate index by applying `NameIndex.convert`. *args: A pair of index and value or a list of pairs of index and value. `setfunc` is invoked with `args`. + + Raises: + QiskitOptimizationError: Invalid arguments """ # check for all elements in args whether they are types if len(args) == 1 and isinstance(args[0], (Sequence, Generator)): @@ -95,8 +99,13 @@ def _getter(getfunc: Callable[[int], Any], *args) -> Any: `index` should be already converted by `NameIndex.convert`. *args: A single index or a list of indices. `getfunc` is invoked with args. - Returns: if `args` is a single index, this returns a single value genereted by `getfunc`. - If `args` is a list of indices, this returns a list of values. + Returns: + Union[int, List[int]]: if `args` is a single index, this returns a single + value generated by `getfunc`. If `args` is a list of indices, + this returns a list of values. + + Raises: + QiskitOptimizationError: Invalid arguments """ if len(args) == 0: raise QiskitOptimizationError('Empty arguments should be handled in the caller') From d123f14e9bb3a781efe78d2b0930ff0b4e2f3917 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Mon, 6 Apr 2020 21:34:36 +0200 Subject: [PATCH 153/323] add gambella and simonetto --- .pylintdict | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.pylintdict b/.pylintdict index 37cf30860b..6d796a6fd5 100644 --- a/.pylintdict +++ b/.pylintdict @@ -196,6 +196,7 @@ fujii fullname func fval +gambella gambetta gauopen gaussian @@ -532,6 +533,7 @@ shanno shor shor's sigmoid +simonetto sj sklearn slsqp From 2f956779d41ffe745c009f6267a25ae3443b0880 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Mon, 6 Apr 2020 17:48:26 -0400 Subject: [PATCH 154/323] fix unit tests --- test/optimization/test_admm.py | 5 +++++ test/optimization/test_docplex.py | 5 +++-- test/optimization/test_linear_constraints.py | 5 +++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index cd19a09fe5..3b32c0305c 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -14,6 +14,7 @@ """Tests of the ADMM algorithm.""" +import unittest from test.optimization import QiskitOptimizationTestCase from docplex.mp.model import Model @@ -144,3 +145,7 @@ def test_admm_ex5(self): np.testing.assert_almost_equal(2., solution.fval, 3) self.assertIsNotNone(solution.state) self.assertIsInstance(solution.state, ADMMState) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_docplex.py b/test/optimization/test_docplex.py index d43d292d30..ba295d427b 100755 --- a/test/optimization/test_docplex.py +++ b/test/optimization/test_docplex.py @@ -340,5 +340,6 @@ def test_constants_in_left_side_and_variables_in_right_side(self): actual_sol = result['eigenstate'].tolist() self.assertListEqual(actual_sol, [0, 0, 0, 1]) - if __name__ == '__main__': - unittest.main() + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_linear_constraints.py b/test/optimization/test_linear_constraints.py index ab3e14fed0..9dfdf9ec73 100644 --- a/test/optimization/test_linear_constraints.py +++ b/test/optimization/test_linear_constraints.py @@ -261,5 +261,6 @@ def test_empty_names(self): self.assertListEqual(op.linear_constraints.get_names(), ['c1', 'c2', 'c3', 'a', 'c5', 'c']) - if __name__ == '__main__': - unittest.main() + +if __name__ == '__main__': + unittest.main() From 07272dde3254709e3d24d69be9d31fbc2cb1a8e0 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Mon, 6 Apr 2020 21:14:14 -0400 Subject: [PATCH 155/323] fix travis --- .travis.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 746aeb2c0d..b4817fdee4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -138,7 +138,7 @@ jobs: - pip install cvxopt - export PYTHON="coverage3 run --source qiskit/aqua,qiskit/chemistry,qiskit/finance,qiskit/ml,qiskit/optimization --omit */gauopen/* --parallel-mode" script: - - stestr --test-path test/aqua run --blacklist-file selection.txt 2>&1 | tee out.txt + - stestr --test-path test/aqua run --blacklist-file selection.txt 2> >(tee /dev/stderr out.txt > /dev/null) - coverage3 combine - mv .coverage aqua1.dat - python tools/extract_deprecation.py -file out.txt -output aqua137.dep @@ -153,7 +153,7 @@ jobs: before_script: - pip install cvxopt script: - - stestr --test-path test/aqua run --blacklist-file selection.txt 2>&1 | tee out.txt + - stestr --test-path test/aqua run --blacklist-file selection.txt 2> >(tee /dev/stderr out.txt > /dev/null) - python tools/extract_deprecation.py -file out.txt -output aqua138.dep - name: "Test Aqua 2 Python 3.7" <<: *stage_test_aqua @@ -168,7 +168,7 @@ jobs: - pip install cvxopt - export PYTHON="coverage3 run --source qiskit/aqua,qiskit/chemistry,qiskit/finance,qiskit/ml,qiskit/optimization --omit */gauopen/* --parallel-mode" script: - - stestr --test-path test/aqua run --whitelist-file selection.txt 2>&1 | tee out.txt + - stestr --test-path test/aqua run --whitelist-file selection.txt 2> >(tee /dev/stderr out.txt > /dev/null) - coverage3 combine - mv .coverage aqua2.dat - python tools/extract_deprecation.py -file out.txt -output aqua237.dep @@ -183,7 +183,7 @@ jobs: before_script: - pip install cvxopt script: - - stestr --test-path test/aqua run --whitelist-file selection.txt 2>&1 | tee out.txt + - stestr --test-path test/aqua run --whitelist-file selection.txt 2> >(tee /dev/stderr out.txt > /dev/null) - python tools/extract_deprecation.py -file out.txt -output aqua238.dep - name: "Test Chemistry Python 3.7" <<: *stage_dependencies @@ -206,7 +206,7 @@ jobs: before_script: - export PYTHON="coverage3 run --source qiskit/aqua,qiskit/chemistry,qiskit/finance,qiskit/ml,qiskit/optimization --omit */gauopen/* --parallel-mode" script: - - stestr --test-path test/chemistry run 2>&1 | tee out.txt + - stestr --test-path test/chemistry run 2> >(tee /dev/stderr out.txt > /dev/null) - coverage3 combine - mv .coverage chemistry.dat - python tools/extract_deprecation.py -file out.txt -output chemistry37.dep @@ -226,7 +226,7 @@ jobs: # Installing pyquante2 master branch... - pip install https://github.com/rpmuller/pyquante2/archive/master.zip --progress-bar off script: - - stestr --test-path test/chemistry run 2>&1 | tee out.txt + - stestr --test-path test/chemistry run 2> >(tee /dev/stderr out.txt > /dev/null) - python tools/extract_deprecation.py -file out.txt -output chemistry38.dep - name: "Test Finance Python 3.7" <<: *stage_dependencies @@ -240,7 +240,7 @@ jobs: before_script: - export PYTHON="coverage3 run --source qiskit/aqua,qiskit/chemistry,qiskit/finance,qiskit/ml,qiskit/optimization --omit */gauopen/* --parallel-mode" script: - - stestr --test-path test/finance run 2>&1 | tee out.txt + - stestr --test-path test/finance run 2> >(tee /dev/stderr out.txt > /dev/null) - coverage3 combine - mv .coverage finance.dat - python tools/extract_deprecation.py -file out.txt -output finance37.dep @@ -253,7 +253,7 @@ jobs: paths: finance38.dep python: 3.8 script: - - stestr --test-path test/finance run 2>&1 | tee out.txt + - stestr --test-path test/finance run 2> >(tee /dev/stderr out.txt > /dev/null) - python tools/extract_deprecation.py -file out.txt -output finance38.dep - name: "Test Machine Learning Python 3.7" <<: *stage_dependencies @@ -267,7 +267,7 @@ jobs: before_script: - export PYTHON="coverage3 run --source qiskit/aqua,qiskit/chemistry,qiskit/finance,qiskit/ml,qiskit/optimization --omit */gauopen/* --parallel-mode" script: - - stestr --test-path test/ml run 2>&1 | tee out.txt + - stestr --test-path test/ml run 2> >(tee /dev/stderr out.txt > /dev/null) - coverage3 combine - mv .coverage ml.dat - python tools/extract_deprecation.py -file out.txt -output ml37.dep @@ -280,7 +280,7 @@ jobs: paths: ml38.dep python: 3.8 script: - - stestr --test-path test/ml run 2>&1 | tee out.txt + - stestr --test-path test/ml run 2> >(tee /dev/stderr out.txt > /dev/null) - python tools/extract_deprecation.py -file out.txt -output ml38.dep - name: "Test Optimization Python 3.7" <<: *stage_dependencies @@ -294,7 +294,7 @@ jobs: before_script: - export PYTHON="coverage3 run --source qiskit/aqua,qiskit/chemistry,qiskit/finance,qiskit/ml,qiskit/optimization --omit */gauopen/* --parallel-mode" script: - - stestr --test-path test/optimization run 2>&1 | tee out.txt + - stestr --test-path test/optimization run 2> >(tee /dev/stderr out.txt > /dev/null) - coverage3 combine - mv .coverage optimization.dat - python tools/extract_deprecation.py -file out.txt -output optimization37.dep @@ -307,7 +307,7 @@ jobs: paths: optimization38.dep python: 3.8 script: - - stestr --test-path test/optimization run 2>&1 | tee out.txt + - stestr --test-path test/optimization run 2> >(tee /dev/stderr out.txt > /dev/null) - python tools/extract_deprecation.py -file out.txt -output optimization38.dep - name: "Run pip check" <<: *stage_dependencies From 7fde58485cb65a53e42adbbe18f3477408cf1b3d Mon Sep 17 00:00:00 2001 From: Cryoris Date: Tue, 7 Apr 2020 16:50:03 +0200 Subject: [PATCH 156/323] fix the random seeds in rec. opt. test --- .../test_recursive_optimization.py | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/test/optimization/test_recursive_optimization.py b/test/optimization/test_recursive_optimization.py index 87a931e880..66c09d7942 100755 --- a/test/optimization/test_recursive_optimization.py +++ b/test/optimization/test_recursive_optimization.py @@ -16,11 +16,12 @@ import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase -from ddt import ddt, data +import numpy as np +from ddt import ddt, data, unpack from qiskit import BasicAer -from qiskit.aqua.algorithms import NumPyMinimumEigensolver -from qiskit.aqua.algorithms import QAOA +from qiskit.aqua import QuantumInstance, aqua_globals +from qiskit.aqua.algorithms import NumPyMinimumEigensolver, QAOA from qiskit.aqua.components.optimizers import COBYLA from qiskit.optimization.algorithms import (MinimumEigenOptimizer, CplexOptimizer, @@ -35,38 +36,47 @@ class TestRecursiveMinEigenOptimizer(QiskitOptimizationTestCase): def setUp(self): super().setUp() + # fix random seed for reproducible results + np.random.seed = 109 + aqua_globals.random_seed = 89 + self.resource_path = './test/optimization/resources/' + # setup simulators + self.qinstances = {} + self.qinstances['qasm'] = QuantumInstance( + BasicAer.get_backend('qasm_simulator'), + shots=10000, + seed_simulator=51, + seed_transpiler=80 + ) + self.qinstances['statevector'] = QuantumInstance( + BasicAer.get_backend('statevector_simulator'), + seed_simulator=51, + seed_transpiler=80 + ) + # setup minimum eigen solvers self.min_eigen_solvers = {} - - # exact eigen solver self.min_eigen_solvers['exact'] = NumPyMinimumEigensolver() - - # QAOA - optimizer = COBYLA() - self.min_eigen_solvers['qaoa'] = QAOA(optimizer=optimizer) + self.min_eigen_solvers['qaoa'] = QAOA(optimizer=COBYLA()) @data( ('exact', None, 'op_ip1.lp'), - ('qaoa', 'statevector_simulator', 'op_ip1.lp'), - ('qaoa', 'qasm_simulator', 'op_ip1.lp') + ('qaoa', 'statevector', 'op_ip1.lp'), + ('qaoa', 'qasm', 'op_ip1.lp') ) - def test_recursive_min_eigen_optimizer(self, config): + @unpack + def test_recursive_min_eigen_optimizer(self, solver, simulator, filename): """ Min Eigen Optimizer Test """ - # unpack configuration - min_eigen_solver_name, backend, filename = config - # get minimum eigen solver - min_eigen_solver = self.min_eigen_solvers[min_eigen_solver_name] - if backend: - min_eigen_solver.quantum_instance = BasicAer.get_backend(backend) - if backend == 'qasm_simulator': - min_eigen_solver.quantum_instance.run_config.shots = 10000 + min_eigen_solver = self.min_eigen_solvers[solver] + if simulator: + min_eigen_solver.quantum_instance = self.qinstances[simulator] - min_eigen_optimizer = MinimumEigenOptimizer(min_eigen_solver) # construct minimum eigen optimizer + min_eigen_optimizer = MinimumEigenOptimizer(min_eigen_solver) recursive_min_eigen_optimizer = RecursiveMinimumEigenOptimizer(min_eigen_optimizer, min_num_vars=4) From 1ade848b3f0534be9ad709ade3a02832fe08bee4 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Tue, 7 Apr 2020 18:09:47 +0200 Subject: [PATCH 157/323] dont re-integrate deprecated logic --- .../optimization/applications/ising/clique.py | 61 +------------------ .../applications/ising/docplex.py | 57 ++++++----------- .../applications/ising/exact_cover.py | 44 ------------- .../applications/ising/graph_partition.py | 54 ---------------- .../applications/ising/max_cut.py | 54 ---------------- .../applications/ising/partition.py | 42 ------------- .../applications/ising/set_packing.py | 45 -------------- .../applications/ising/stable_set.py | 43 ------------- qiskit/optimization/applications/ising/tsp.py | 20 ------ .../applications/ising/vehicle_routing.py | 11 ---- .../applications/ising/vertex_cover.py | 57 ----------------- 11 files changed, 20 insertions(+), 468 deletions(-) diff --git a/qiskit/optimization/applications/ising/clique.py b/qiskit/optimization/applications/ising/clique.py index d3d48a4162..cdf160dd34 100644 --- a/qiskit/optimization/applications/ising/clique.py +++ b/qiskit/optimization/applications/ising/clique.py @@ -12,13 +12,12 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Convert clique instances into Pauli list +"""Convert clique instances into Pauli list + Deal with Gset format. See https://web.stanford.edu/~yyye/yyye/Gset/ """ import logging -import warnings import numpy as np from qiskit.quantum_info import Pauli @@ -142,59 +141,3 @@ def get_graph_solution(x): numpy.ndarray: graph solution as binary numpy array. """ return 1 - x - - -def random_graph(n, weight_range=10, edge_prob=0.3, savefile=None, seed=None): - """ random graph """ - # pylint: disable=import-outside-toplevel - from .common import random_graph as redirect_func - warnings.warn("random_graph function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(n=n, weight_range=weight_range, edge_prob=edge_prob, - savefile=savefile, seed=seed) - - -def parse_gset_format(filename): - """ parse gset format """ - # pylint: disable=import-outside-toplevel - from .common import parse_gset_format as redirect_func - warnings.warn("parse_gset_format function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(filename) - - -def sample_most_likely(n=None, state_vector=None): - """ sample most likely """ - # pylint: disable=import-outside-toplevel - from .common import sample_most_likely as redirect_func - if n is not None: - warnings.warn("n argument is not need and it will be removed after Aqua 0.7+", - DeprecationWarning) - warnings.warn("sample_most_likely function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(state_vector=state_vector) - - -def get_gset_result(x): - """ get gset result """ - # pylint: disable=import-outside-toplevel - from .common import get_gset_result as redirect_func - warnings.warn("get_gset_result function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(x) - - -def get_clique_qubitops(weight_matrix, K): # pylint: disable=invalid-name - """ get clique qubit ops """ - warnings.warn("get_clique_qubitops function has been changed to get_operator" - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return get_operator(weight_matrix, K) diff --git a/qiskit/optimization/applications/ising/docplex.py b/qiskit/optimization/applications/ising/docplex.py index 2672390095..b5807ece66 100644 --- a/qiskit/optimization/applications/ising/docplex.py +++ b/qiskit/optimization/applications/ising/docplex.py @@ -59,9 +59,9 @@ """ +from typing import Tuple import logging from math import fsum -import warnings import numpy as np from docplex.mp.constants import ComparisonType @@ -74,20 +74,19 @@ logger = logging.getLogger(__name__) -def get_operator(mdl, auto_penalty=True, default_penalty=1e5): - """ - Generate Ising Hamiltonian from a model of DOcplex. +def get_operator(mdl: Model, auto_penalty: bool = True, + default_penalty: float = 1e5) -> Tuple[WeightedPauliOperator, float]: + """Generate Ising Hamiltonian from a model of DOcplex. Args: - mdl (docplex.mp.model.Model): A model of DOcplex for a optimization problem. - auto_penalty (bool): If true, the penalty coefficient is automatically defined + mdl: A model of DOcplex for a optimization problem. + auto_penalty: If true, the penalty coefficient is automatically defined by "_auto_define_penalty()". - default_penalty (float): The default value of the penalty coefficient for the constraints. + default_penalty: The default value of the penalty coefficient for the constraints. This value is used if "auto_penalty" is False. Returns: - tuple(operators.WeightedPauliOperator, float): operator for the Hamiltonian and a - constant shift for the obj function. + Operator for the Hamiltonian and a constant shift for the obj function. """ _validate_input_model(mdl) @@ -208,15 +207,14 @@ def get_operator(mdl, auto_penalty=True, default_penalty=1e5): return qubit_op, shift -def _validate_input_model(mdl): - """ - Check whether an input model is valid. If not, raise an AquaError +def _validate_input_model(mdl: Model) -> None: + """Check whether an input model is valid. If not, raise an AquaError. Args: - mdl (docplex.mp.model.Model): A model of DOcplex for a optimization problem. + mdl: A model of DOcplex for a optimization problem. Raises: - AquaError: Unsupported input model + AquaError: Unsupported input model. """ valid = True @@ -241,18 +239,17 @@ def _validate_input_model(mdl): raise AquaError('The input model has unsupported elements.') -def _auto_define_penalty(mdl, default_penalty=1e5): - """ - Automatically define the penalty coefficient. - This returns object function's (upper bound - lower bound + 1). +def _auto_define_penalty(mdl: Model, default_penalty: float = 1e5) -> float: + """Automatically define the penalty coefficient. + This returns object function's (upper bound - lower bound + 1). Args: - mdl (docplex.mp.model.Model): A model of DOcplex for a optimization problem. - default_penalty (float): The default value of the penalty coefficient for the constraints. + mdl: A model of DOcplex for a optimization problem. + default_penalty: The default value of the penalty coefficient for the constraints. Returns: - float: The penalty coefficient for the Hamiltonian. + The penalty coefficient for the Hamiltonian. """ # if a constraint has float coefficient, return 1e5 for the penalty coefficient. @@ -275,21 +272,3 @@ def _auto_define_penalty(mdl, default_penalty=1e5): penalties.extend(abs(i[1]) for i in mdl.get_objective_expr().iter_quads()) return fsum(penalties) - - -def sample_most_likely(state_vector): - """ sample most likely """ - # pylint: disable=import-outside-toplevel - from .common import sample_most_likely as redirect_func - warnings.warn("sample_most_likely function has been moved to qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(state_vector=state_vector) - - -def get_qubitops(mdl, auto_penalty=True, default_penalty=1e5): - """ get qubit ops """ - warnings.warn("get_qubitops function has been changed to get_operator." - " The method here will be removed after Aqua 0.7+", - DeprecationWarning) - return get_operator(mdl, auto_penalty, default_penalty) diff --git a/qiskit/optimization/applications/ising/exact_cover.py b/qiskit/optimization/applications/ising/exact_cover.py index e69794c538..87583e3f69 100644 --- a/qiskit/optimization/applications/ising/exact_cover.py +++ b/qiskit/optimization/applications/ising/exact_cover.py @@ -15,7 +15,6 @@ """ exact cover """ import logging -import warnings import numpy as np @@ -122,46 +121,3 @@ def check_solution_satisfiability(sol, list_of_subsets): return False return True - - -def random_number_list(n, weight_range=100, savefile=None): - """ random number list """ - # pylint: disable=import-outside-toplevel - from .common import random_number_list as redirect_func - warnings.warn("random_number_list function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(n=n, weight_range=weight_range, savefile=savefile) - - -def read_numbers_from_file(filename): - """ read numbers from file """ - # pylint: disable=import-outside-toplevel - from .common import read_numbers_from_file as redirect_func - warnings.warn("read_numbers_from_file function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(filename) - - -def sample_most_likely(n=None, state_vector=None): - """ sample most likely """ - # pylint: disable=import-outside-toplevel - from .common import sample_most_likely as redirect_func - if n is not None: - warnings.warn("n argument is not need and it will be removed after Aqua 0.7+", - DeprecationWarning) - warnings.warn("sample_most_likely function has been moved to qiskit.aqua.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(state_vector=state_vector) - - -def get_exact_cover_qubitops(list_of_subsets): - """ get exact cover qubit ops """ - warnings.warn("get_exact_cover_qubitops function has been changed to get_operator" - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return get_operator(list_of_subsets) diff --git a/qiskit/optimization/applications/ising/graph_partition.py b/qiskit/optimization/applications/ising/graph_partition.py index 33bd62e4d0..9b556d9c51 100644 --- a/qiskit/optimization/applications/ising/graph_partition.py +++ b/qiskit/optimization/applications/ising/graph_partition.py @@ -18,7 +18,6 @@ """ import logging -import warnings import numpy as np @@ -101,56 +100,3 @@ def get_graph_solution(x): numpy.ndarray: graph solution as binary numpy array. """ return 1 - x - - -def random_graph(n, weight_range=10, edge_prob=0.3, savefile=None, seed=None): - """ random graph """ - # pylint: disable=import-outside-toplevel - from .common import random_graph as redirect_func - warnings.warn("random_graph function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(n=n, weight_range=weight_range, edge_prob=edge_prob, - savefile=savefile, seed=seed) - - -def parse_gset_format(filename): - """ parse gset format """ - # pylint: disable=import-outside-toplevel - from .common import parse_gset_format as redirect_func - warnings.warn("parse_gset_format function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(filename) - - -def sample_most_likely(state_vector): - """ sample most likely """ - # pylint: disable=import-outside-toplevel - from .common import sample_most_likely as redirect_func - warnings.warn("sample_most_likely function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(state_vector=state_vector) - - -def get_gset_result(x): - """ get gset result """ - # pylint: disable=import-outside-toplevel - from .common import get_gset_result as redirect_func - warnings.warn("get_gset_result function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(x) - - -def get_graph_partition_qubitops(weight_matrix): - """ get graph partition qubit ops """ - warnings.warn("get_graph_partition_qubitops function has been changed to get_operator" - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return get_operator(weight_matrix) diff --git a/qiskit/optimization/applications/ising/max_cut.py b/qiskit/optimization/applications/ising/max_cut.py index bf276dfa45..4bef9da2e8 100644 --- a/qiskit/optimization/applications/ising/max_cut.py +++ b/qiskit/optimization/applications/ising/max_cut.py @@ -21,7 +21,6 @@ """ import logging -import warnings import numpy as np @@ -82,56 +81,3 @@ def get_graph_solution(x): numpy.ndarray: graph solution as binary numpy array. """ return 1 - x - - -def random_graph(n, weight_range=10, edge_prob=0.3, savefile=None, seed=None): - """ random graph """ - # pylint: disable=import-outside-toplevel - from .common import random_graph as redirect_func - warnings.warn("random_graph function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(n=n, weight_range=weight_range, edge_prob=edge_prob, - savefile=savefile, seed=seed) - - -def parse_gset_format(filename): - """ parse gset format """ - # pylint: disable=import-outside-toplevel - from .common import parse_gset_format as redirect_func - warnings.warn("parse_gset_format function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(filename) - - -def sample_most_likely(state_vector): - """ sample most likely """ - # pylint: disable=import-outside-toplevel - from .common import sample_most_likely as redirect_func - warnings.warn("sample_most_likely function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(state_vector=state_vector) - - -def get_gset_result(x): - """ returns gset result """ - # pylint: disable=import-outside-toplevel - from .common import get_gset_result as redirect_func - warnings.warn("get_gset_result function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(x) - - -def get_max_cut_qubitops(weight_matrix): - """ returns max cut qubit ops """ - warnings.warn("get_max_cut_qubitops function has been changed to get_operator" - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return get_operator(weight_matrix) diff --git a/qiskit/optimization/applications/ising/partition.py b/qiskit/optimization/applications/ising/partition.py index cca588dde4..c305b02fc1 100644 --- a/qiskit/optimization/applications/ising/partition.py +++ b/qiskit/optimization/applications/ising/partition.py @@ -18,7 +18,6 @@ """ import logging -import warnings import numpy as np from qiskit.quantum_info import Pauli @@ -69,44 +68,3 @@ def partition_value(x, number_list): """ diff = np.sum(number_list[x == 0]) - np.sum(number_list[x == 1]) return diff * diff - - -def random_number_list(n, weight_range=100, savefile=None): - """ random number list """ - # pylint: disable=import-outside-toplevel - from .common import random_number_list as redirect_func - warnings.warn("random_number_list function has been moved to " - "qiskit.optimization.ising.common,, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(n=n, weight_range=weight_range, savefile=savefile) - - -def read_numbers_from_file(filename): - """ read numbers from file """ - # pylint: disable=import-outside-toplevel - from .common import read_numbers_from_file as redirect_func - warnings.warn("read_numbers_from_file function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(filename) - - -def sample_most_likely(state_vector): - """ sample most likely """ - # pylint: disable=import-outside-toplevel - from .common import sample_most_likely as redirect_func - warnings.warn("sample_most_likely function has been moved " - "to qiskit.optimization.ising.common,, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(state_vector=state_vector) - - -def get_partition_qubitops(values): - """ get partition qubit ops """ - warnings.warn("get_partition_qubitops function has been changed to get_operator" - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return get_operator(values) diff --git a/qiskit/optimization/applications/ising/set_packing.py b/qiskit/optimization/applications/ising/set_packing.py index f12b96c43e..aafc666c49 100644 --- a/qiskit/optimization/applications/ising/set_packing.py +++ b/qiskit/optimization/applications/ising/set_packing.py @@ -15,7 +15,6 @@ """ set packing module """ import logging -import warnings import numpy as np from qiskit.quantum_info import Pauli @@ -112,47 +111,3 @@ def check_disjoint(sol, list_of_subsets): return False return True - - -def random_number_list(n, weight_range=100, savefile=None): - """ random number list """ - # pylint: disable=import-outside-toplevel - from .common import random_number_list as redirect_func - warnings.warn("random_number_list function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(n=n, weight_range=weight_range, savefile=savefile) - - -def read_numbers_from_file(filename): - """ read numbers from file """ - # pylint: disable=import-outside-toplevel - from .common import read_numbers_from_file as redirect_func - warnings.warn("read_numbers_from_file function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(filename) - - -def sample_most_likely(n=None, state_vector=None): - """ sample most likely """ - # pylint: disable=import-outside-toplevel - from .common import sample_most_likely as redirect_func - if n is not None: - warnings.warn("n argument is not need and it will be removed after Aqua 0.7+", - DeprecationWarning) - warnings.warn("sample_most_likely function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(state_vector=state_vector) - - -def get_set_packing_qubitops(list_of_subsets): - """ get set packing qubit ops """ - warnings.warn("get_set_packing_qubitops function has been changed to get_operator" - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return get_operator(list_of_subsets) diff --git a/qiskit/optimization/applications/ising/stable_set.py b/qiskit/optimization/applications/ising/stable_set.py index be06b4b846..fd36adc497 100644 --- a/qiskit/optimization/applications/ising/stable_set.py +++ b/qiskit/optimization/applications/ising/stable_set.py @@ -21,7 +21,6 @@ """ import logging -import warnings import numpy as np from qiskit.quantum_info import Pauli @@ -96,45 +95,3 @@ def get_graph_solution(x): numpy.ndarray: graph solution as binary numpy array. """ return 1 - x - - -def random_graph(n, edge_prob=0.5, savefile=None, seed=None): - """ random graph """ - # pylint: disable=import-outside-toplevel - from .common import random_graph as redirect_func - warnings.warn("random_graph function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(n=n, weight_range=2, edge_prob=edge_prob, - negative_weight=False, savefile=savefile, seed=seed) - - -def parse_gset_format(filename): - """ parse gset format """ - # pylint: disable=import-outside-toplevel - from .common import parse_gset_format as redirect_func - warnings.warn("parse_gset_format function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(filename) - - -def sample_most_likely(state_vector): - """ sample most likely """ - # pylint: disable=import-outside-toplevel - from .common import sample_most_likely as redirect_func - warnings.warn("sample_most_likely function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(state_vector=state_vector) - - -def get_stable_set_qubitops(w): - """ get stable set qubit ops """ - warnings.warn("get_stable_set_qubitops function has been changed to get_operator" - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return get_operator(w) diff --git a/qiskit/optimization/applications/ising/tsp.py b/qiskit/optimization/applications/ising/tsp.py index b740b83de5..32a2886f54 100644 --- a/qiskit/optimization/applications/ising/tsp.py +++ b/qiskit/optimization/applications/ising/tsp.py @@ -22,7 +22,6 @@ """ import logging -import warnings from collections import namedtuple import numpy as np @@ -267,22 +266,3 @@ def get_tsp_solution(x): assert len(z) == p__ z.append(i) return z - - -def sample_most_likely(state_vector): - """ sample most likely """ - # pylint: disable=import-outside-toplevel - from .common import sample_most_likely as redirect_func - warnings.warn("sample_most_likely function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(state_vector=state_vector) - - -def get_tsp_qubitops(ins, penalty=1e5): - """ get tsp qubit ops """ - warnings.warn("get_tsp_qubitops function has been changed to get_operator" - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return get_operator(ins, penalty) diff --git a/qiskit/optimization/applications/ising/vehicle_routing.py b/qiskit/optimization/applications/ising/vehicle_routing.py index d360459e7a..3f7b58e966 100644 --- a/qiskit/optimization/applications/ising/vehicle_routing.py +++ b/qiskit/optimization/applications/ising/vehicle_routing.py @@ -18,8 +18,6 @@ checking its objective function value). """ -import warnings - import numpy as np from qiskit.quantum_info import Pauli @@ -193,12 +191,3 @@ def get_vehiclerouting_solution(instance, n, K, result): # pylint: disable=inva x_sol = np.flip(x_sol, axis=0) return x_sol - - -def get_vehiclerouting_qubitops(instance, n, K): - """ get vehicle routing qubit ops """ - # pylint: disable=invalid-name - warnings.warn("get_vehiclerouting_qubitops function has been changed to get_operator" - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return get_operator(instance, n, K) diff --git a/qiskit/optimization/applications/ising/vertex_cover.py b/qiskit/optimization/applications/ising/vertex_cover.py index 0f61aedc97..9710300e75 100644 --- a/qiskit/optimization/applications/ising/vertex_cover.py +++ b/qiskit/optimization/applications/ising/vertex_cover.py @@ -18,7 +18,6 @@ """ import logging -import warnings import numpy as np from qiskit.quantum_info import Pauli @@ -114,59 +113,3 @@ def get_graph_solution(x): numpy.ndarray: graph solution as binary numpy array. """ return 1 - x - - -def random_graph(n, weight_range=10, edge_prob=0.3, savefile=None, seed=None): - """ random graph """ - # pylint: disable=import-outside-toplevel - from .common import random_graph as redirect_func - warnings.warn("random_graph function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(n=n, weight_range=weight_range, edge_prob=edge_prob, - savefile=savefile, seed=seed) - - -def parse_gset_format(filename): - """ parse gset format """ - # pylint: disable=import-outside-toplevel - from .common import parse_gset_format as redirect_func - warnings.warn("parse_gset_format function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(filename) - - -def sample_most_likely(n=None, state_vector=None): - """ sample most likely """ - # pylint: disable=import-outside-toplevel - from .common import sample_most_likely as redirect_func - if n is not None: - warnings.warn("n argument is not need and it will be removed after Aqua 0.7+", - DeprecationWarning) - warnings.warn("sample_most_likely function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(state_vector=state_vector) - - -def get_gset_result(x): - """ get gset result """ - # pylint: disable=import-outside-toplevel - from .common import get_gset_result as redirect_func - warnings.warn("get_gset_result function has been moved to " - "qiskit.optimization.ising.common, " - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return redirect_func(x) - - -def get_vertex_cover_qubitops(weight_matrix): - """ get vertex cover qubit ops """ - warnings.warn("get_vertex_cover_qubitops function has been changed to get_operator" - "the method here will be removed after Aqua 0.7+", - DeprecationWarning) - return get_operator(weight_matrix) From d72127a406642cd0981a039ea9927af2f1821747 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Tue, 7 Apr 2020 19:00:27 +0200 Subject: [PATCH 158/323] change to derive from AquaError --- .../optimization/utils/qiskit_optimization_error.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/qiskit/optimization/utils/qiskit_optimization_error.py b/qiskit/optimization/utils/qiskit_optimization_error.py index 9d5b471707..aa14a50fed 100644 --- a/qiskit/optimization/utils/qiskit_optimization_error.py +++ b/qiskit/optimization/utils/qiskit_optimization_error.py @@ -14,14 +14,9 @@ """ Optimization Exception """ +from qiskit.aqua.aqua_error import AquaError -class QiskitOptimizationError(Exception): - """Class for errors returned by Qiskit Optimization libraries functions. - self.args[0] : A string describing the error. - """ - - def __str__(self): - # Note: this is actually ok. Exception does have subscriptable args. - # pylint: disable=unsubscriptable-object - return self.args[0] +class QiskitOptimizationError(AquaError): + """Class for errors returned by Qiskit Optimization libraries functions.""" + pass From 5435e809c3a2885f09550c36deb94e2da77965fc Mon Sep 17 00:00:00 2001 From: Cryoris Date: Tue, 7 Apr 2020 19:15:23 +0200 Subject: [PATCH 159/323] implement first part of steve's comments --- .../eigen_solvers/numpy_eigen_solver.py | 20 ++++------ .../minimum_eigen_solvers/min_eigen_solver.py | 37 ------------------- .../algorithms/cobyla_optimizer.py | 17 ++++----- .../algorithms/minimum_eigen_optimizer.py | 19 +++++----- .../recursive_minimum_eigen_optimizer.py | 5 ++- .../problems/quadratic_constraint.py | 2 +- 6 files changed, 28 insertions(+), 72 deletions(-) delete mode 100644 qiskit/aqua/algorithms/minimum_eigen_solvers/min_eigen_solver.py diff --git a/qiskit/aqua/algorithms/eigen_solvers/numpy_eigen_solver.py b/qiskit/aqua/algorithms/eigen_solvers/numpy_eigen_solver.py index e8cef9d06e..f4395d0d51 100755 --- a/qiskit/aqua/algorithms/eigen_solvers/numpy_eigen_solver.py +++ b/qiskit/aqua/algorithms/eigen_solvers/numpy_eigen_solver.py @@ -23,20 +23,15 @@ from qiskit.aqua import AquaError from qiskit.aqua.algorithms import ClassicalAlgorithm -from qiskit.aqua.operators import MatrixOperator, op_converter # pylint: disable=unused-import -from qiskit.aqua.operators import BaseOperator +from qiskit.aqua.operators import BaseOperator, op_converter from qiskit.aqua.utils.validation import validate_min from .eigen_solver_result import EigensolverResult logger = logging.getLogger(__name__) -# pylint: disable=invalid-name - - class NumPyEigensolver(ClassicalAlgorithm): - r""" - The NumPy Eigensolver algorithm. + r"""The NumPy Eigensolver algorithm. NumPy Eigensolver computes up to the first :math:`k` eigenvalues of a complex-valued square matrix of dimension :math:`n \times n`, with :math:`k \leq n`. @@ -66,7 +61,7 @@ def __init__(self, operator: Optional[BaseOperator] = None, k: int = 1, self._operator = None self._aux_operators = None self._in_k = k - self._k = k + self._k = k # pylint: disable=invalid-name self.operator = operator self.aux_operators = aux_operators @@ -186,10 +181,11 @@ def _eval_aux_operators(self, wavefn, threshold=1e-12): return np.asarray(values) def _run(self): - """ - Run the algorithm to compute up to the requested k number of eigenvalues. + """Run the algorithm to compute up to the requested k number of eigenvalues. + Returns: dict: Dictionary of results + Raises: AquaError: if no operator has been provided """ @@ -217,9 +213,7 @@ def _run(self): class ExactEigensolver(NumPyEigensolver): - """ - The deprecated Eigensolver algorithm. - """ + """The deprecated Eigensolver algorithm.""" def __init__(self, operator: BaseOperator, k: int = 1, aux_operators: Optional[List[BaseOperator]] = None) -> None: diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/min_eigen_solver.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/min_eigen_solver.py deleted file mode 100644 index e246278559..0000000000 --- a/qiskit/aqua/algorithms/minimum_eigen_solvers/min_eigen_solver.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""TODO""" - -from abc import abstractmethod - - -class MinEigenSolver: - """Base class for algorithms that search the minimal eigenvalue of an operator.""" - - @abstractmethod - def __init__(self, operator=None): - self._operator = operator - - @abstractmethod - def compute_min_eigenvalue(self, operator=None): - """ computes minimum eigen value """ - raise NotImplementedError() - - # Cannot implement this, since ExactEigenSolver and VQE both inherit from - # QuantumAlgorithm which has the signature `run(self, quantum_instance, kwargs)` - # and not `run(self, operator)`. - # @abstractmethod - # def run(self, operator): - # raise NotImplementedError() diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index c84c50a407..bcc288de17 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -13,14 +13,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""The COBYLA optimizer wrapped to be used within Qiskit Optimization. - - Examples: - >>> problem = OptimizationProblem() - >>> # specify problem here - >>> optimizer = CobylaOptimizer() - >>> result = optimizer.solve(problem) -""" +"""The COBYLA optimizer wrapped to be used within Qiskit Optimization.""" from typing import Optional @@ -41,6 +34,12 @@ class CobylaOptimizer(OptimizationAlgorithm): (https://docs.scipy.org/doc/scipy-0.14.0/reference/generated/scipy.optimize.fmin_cobyla.html) to be used within Qiskit Optimization. The arguments for ``fmin_cobyla`` are passed via the constructor. + + Examples: + >>> problem = OptimizationProblem() + >>> # specify problem here + >>> optimizer = CobylaOptimizer() + >>> result = optimizer.solve(problem) """ def __init__(self, rhobeg: float = 1.0, rhoend: float = 1e-4, maxfun: int = 1000, @@ -48,7 +47,7 @@ def __init__(self, rhobeg: float = 1.0, rhoend: float = 1e-4, maxfun: int = 1000 """Initializes the CobylaOptimizer. This initializer takes the algorithmic parameters of COBYLA and stores them for later use - of ``fmin_cobyla`` when ``solve()`` is invoked. + of ``fmin_cobyla`` when :meth:`solve` is invoked. This optimizer can be applied to find a (local) optimum for problems consisting of only continuous variables. diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 344777ac70..9deb024a14 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -13,16 +13,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""A wrapper for minimum eigen solvers from Qiskit Aqua to be used within Qiskit Optimization. - -Examples: - >>> problem = OptimizationProblem() - >>> # specify problem here - >>> # specify minimum eigen solver to be used, e.g., QAOA - >>> qaoa = QAOA(...) - >>> optimizer = MinEigenOptimizer(qaoa) - >>> result = optimizer.solve(problem) -""" +"""A wrapper for minimum eigen solvers from Qiskit Aqua to be used within Qiskit Optimization.""" from typing import Optional, Any import numpy as np @@ -86,6 +77,14 @@ class MinimumEigenOptimizer(OptimizationAlgorithm): corresponding eigenstate correspond to the optimal solution of the original optimization problem. The provided minimum eigen solver is then used to approximate the ground state of the Hamiltonian to find a good solution for the optimization problem. + + Examples: + >>> problem = OptimizationProblem() + >>> # specify problem here + >>> # specify minimum eigen solver to be used, e.g., QAOA + >>> qaoa = QAOA(...) + >>> optimizer = MinEigenOptimizer(qaoa) + >>> result = optimizer.solve(problem) """ def __init__(self, min_eigen_solver: MinimumEigensolver, penalty: Optional[float] = None diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index fcb1286c9a..b2fd8c09de 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -28,6 +28,7 @@ import numpy as np from qiskit.aqua.algorithms import NumPyMinimumEigensolver +from qiskit.aqua.utils.validation import validate_min from .optimization_algorithm import OptimizationAlgorithm from .minimum_eigen_optimizer import MinimumEigenOptimizer @@ -70,9 +71,9 @@ def __init__(self, min_eigen_optimizer: MinimumEigenOptimizer, min_num_vars: int # --> would support efficient classical implementation for QAOA with depth p=1 # --> add results class for MinimumEigenSolver that contains enough info to do so. + validate_min('min_num_vars', min_num_vars, 1) + self._min_eigen_optimizer = min_eigen_optimizer - if min_num_vars < 1: - raise QiskitOptimizationError('Minimal problem size needs to be >= 1!') self._min_num_vars = min_num_vars if min_num_vars_optimizer: self._min_num_vars_optimizer = min_num_vars_optimizer diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index 9748f4cda2..9290f71fdd 100644 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -39,7 +39,7 @@ def __init__(self, varindex: Callable): `OptimizationProblem` class as `OptimizationProblem.quadratic_constraints`. This constructor is not meant to be used externally. """ - super(QuadraticConstraintInterface, self).__init__() + super().__init__() self._rhs = [] self._senses = [] self._names = [] From 3d88930b8f89a80f86bccff71ac2bfda0d8e6bdc Mon Sep 17 00:00:00 2001 From: Cryoris Date: Tue, 7 Apr 2020 19:15:44 +0200 Subject: [PATCH 160/323] skip some unnecessary varform checks --- qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py | 9 ++++----- qiskit/aqua/algorithms/vq_algorithm.py | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py index fc09d8ab25..03a50f8fb2 100755 --- a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py @@ -204,11 +204,10 @@ def aux_operators(self, aux_operators: List[BaseOperator]) -> None: def var_form(self, var_form: VariationalForm): """ Sets variational form """ VQAlgorithm.var_form.fset(self, var_form) - if var_form: - self._var_form_params = ParameterVector('θ', var_form.num_parameters) - if self.initial_point is None: - self.initial_point = var_form.preferred_init_points - self._check_operator_varform() + self._var_form_params = ParameterVector('θ', var_form.num_parameters) + if self.initial_point is None: + self.initial_point = var_form.preferred_init_points + self._check_operator_varform() def _check_operator_varform(self): if self.operator is not None and self.var_form is not None: diff --git a/qiskit/aqua/algorithms/vq_algorithm.py b/qiskit/aqua/algorithms/vq_algorithm.py index ce4c1f7334..b925dab832 100644 --- a/qiskit/aqua/algorithms/vq_algorithm.py +++ b/qiskit/aqua/algorithms/vq_algorithm.py @@ -86,8 +86,7 @@ def var_form(self) -> Optional[VariationalForm]: def var_form(self, var_form: VariationalForm): """ Sets variational form """ self._var_form = var_form - if var_form: - self._var_form_params = ParameterVector('θ', var_form.num_parameters) + self._var_form_params = ParameterVector('θ', var_form.num_parameters) @property def optimizer(self) -> Optional[Optimizer]: From 2db121534432438c397cf625b31266cb6b34484e Mon Sep 17 00:00:00 2001 From: Cryoris Date: Tue, 7 Apr 2020 20:15:08 +0200 Subject: [PATCH 161/323] Revert "skip some unnecessary varform checks" This reverts commit 3d88930b8f89a80f86bccff71ac2bfda0d8e6bdc. --- qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py | 9 +++++---- qiskit/aqua/algorithms/vq_algorithm.py | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py index 03a50f8fb2..fc09d8ab25 100755 --- a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py @@ -204,10 +204,11 @@ def aux_operators(self, aux_operators: List[BaseOperator]) -> None: def var_form(self, var_form: VariationalForm): """ Sets variational form """ VQAlgorithm.var_form.fset(self, var_form) - self._var_form_params = ParameterVector('θ', var_form.num_parameters) - if self.initial_point is None: - self.initial_point = var_form.preferred_init_points - self._check_operator_varform() + if var_form: + self._var_form_params = ParameterVector('θ', var_form.num_parameters) + if self.initial_point is None: + self.initial_point = var_form.preferred_init_points + self._check_operator_varform() def _check_operator_varform(self): if self.operator is not None and self.var_form is not None: diff --git a/qiskit/aqua/algorithms/vq_algorithm.py b/qiskit/aqua/algorithms/vq_algorithm.py index b925dab832..ce4c1f7334 100644 --- a/qiskit/aqua/algorithms/vq_algorithm.py +++ b/qiskit/aqua/algorithms/vq_algorithm.py @@ -86,7 +86,8 @@ def var_form(self) -> Optional[VariationalForm]: def var_form(self, var_form: VariationalForm): """ Sets variational form """ self._var_form = var_form - self._var_form_params = ParameterVector('θ', var_form.num_parameters) + if var_form: + self._var_form_params = ParameterVector('θ', var_form.num_parameters) @property def optimizer(self) -> Optional[Optimizer]: From 6274995acf858655651705ca4dec1618a6cc8b03 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Wed, 8 Apr 2020 15:31:16 +0900 Subject: [PATCH 162/323] rename OptimizationProblem with QuadraticProgram --- .pylintdict | 2 +- qiskit/optimization/__init__.py | 4 +- .../optimization/algorithms/admm_optimizer.py | 40 +++++----- .../algorithms/cobyla_optimizer.py | 8 +- .../algorithms/cplex_optimizer.py | 10 +-- .../algorithms/grover_minimum_finder.py | 18 ++--- .../algorithms/minimum_eigen_optimizer.py | 18 ++--- .../algorithms/optimization_algorithm.py | 6 +- .../recursive_minimum_eigen_optimizer.py | 14 ++-- qiskit/optimization/converters/__init__.py | 12 +-- .../inequality_to_equality_converter.py | 10 +-- .../converters/integer_to_binary_converter.py | 10 +-- .../penalize_linear_equality_constraints.py | 10 +-- ...ratic_program_to_negative_value_oracle.py} | 8 +- ...or.py => quadratic_program_to_operator.py} | 8 +- ...o_qubo.py => quadratic_program_to_qubo.py} | 12 +-- qiskit/optimization/problems/__init__.py | 8 +- .../problems/linear_constraint.py | 66 ++++++++-------- qiskit/optimization/problems/objective.py | 70 ++++++++--------- qiskit/optimization/problems/problem_type.py | 6 +- .../problems/quadratic_constraint.py | 42 +++++----- ...zation_problem.py => quadratic_program.py} | 68 ++++++++--------- qiskit/optimization/problems/variables.py | 76 +++++++++---------- qiskit/optimization/utils/base.py | 4 +- test/optimization/test_admm.py | 8 +- test/optimization/test_cobyla_optimizer.py | 6 +- test/optimization/test_converters.py | 58 +++++++------- test/optimization/test_cplex_optimizer.py | 4 +- .../test_grover_minimum_finder.py | 8 +- test/optimization/test_linear_constraints.py | 38 +++++----- test/optimization/test_min_eigen_optimizer.py | 4 +- test/optimization/test_objective.py | 40 +++++----- .../test_quadratic_constraints.py | 34 ++++----- ...n_problem.py => test_quadratic_program.py} | 56 +++++++------- ...ratic_program_to_negative_value_oracle.py} | 18 ++--- .../test_recursive_optimization.py | 4 +- test/optimization/test_variables.py | 54 ++++++------- 37 files changed, 431 insertions(+), 431 deletions(-) rename qiskit/optimization/converters/{optimization_problem_to_negative_value_oracle.py => quadratic_program_to_negative_value_oracle.py} (97%) rename qiskit/optimization/converters/{optimization_problem_to_operator.py => quadratic_program_to_operator.py} (94%) rename qiskit/optimization/converters/{optimization_problem_to_qubo.py => quadratic_program_to_qubo.py} (93%) rename qiskit/optimization/problems/{optimization_problem.py => quadratic_program.py} (94%) rename test/optimization/{test_optimization_problem.py => test_quadratic_program.py} (93%) rename test/optimization/{test_optimization_problem_to_negative_value_oracle.py => test_quadratic_program_to_negative_value_oracle.py} (90%) diff --git a/.pylintdict b/.pylintdict index 48daf9622d..22b13ccc91 100644 --- a/.pylintdict +++ b/.pylintdict @@ -401,7 +401,7 @@ online onwards oplus optim -OptimizationProblem +QuadraticProgram optimizer's optimizers orbsym diff --git a/qiskit/optimization/__init__.py b/qiskit/optimization/__init__.py index c5d0c44a50..b4e6e9fe18 100755 --- a/qiskit/optimization/__init__.py +++ b/qiskit/optimization/__init__.py @@ -29,12 +29,12 @@ from .infinity import infinity # must be at the top of the file from .utils import QiskitOptimizationError -from .problems import OptimizationProblem +from .problems import QuadraticProgram from ._logging import (get_qiskit_optimization_logging, set_qiskit_optimization_logging) from .util import get_qubo_solutions -__all__ = ['OptimizationProblem', +__all__ = ['QuadraticProgram', 'QiskitOptimizationError', 'get_qiskit_optimization_logging', 'set_qiskit_optimization_logging', diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index b2fbed0365..52bb82ac4a 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -21,7 +21,7 @@ from cplex import SparsePair from qiskit.optimization.algorithms.cplex_optimizer import CplexOptimizer from qiskit.optimization.algorithms.optimization_algorithm import OptimizationAlgorithm -from qiskit.optimization.problems.optimization_problem import OptimizationProblem +from qiskit.optimization.problems.quadratic_program import QuadraticProgram from qiskit.optimization.problems.variables import CPX_BINARY, CPX_CONTINUOUS from qiskit.optimization.results.optimization_result import OptimizationResult @@ -84,7 +84,7 @@ class ADMMState: """ def __init__(self, - op: OptimizationProblem, + op: QuadraticProgram, binary_indices: List[int], continuous_indices: List[int], rho_initial: float) -> None: @@ -197,7 +197,7 @@ def __init__(self, qubo_optimizer: Optional[OptimizationAlgorithm] = None, # the solve method. self._state: Optional[ADMMState] = None - def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + def is_compatible(self, problem: QuadraticProgram) -> Optional[str]: """Checks whether a given problem can be solved with the optimizer implementing this method. Args: @@ -232,7 +232,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: return None - def solve(self, problem: OptimizationProblem) -> ADMMOptimizerResult: + def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: """Tries to solves the given problem using ADMM algorithm. Args: @@ -324,7 +324,7 @@ def solve(self, problem: OptimizationProblem) -> ADMMOptimizerResult: return result @staticmethod - def _get_variable_indices(op: OptimizationProblem, var_type: str) -> List[int]: + def _get_variable_indices(op: QuadraticProgram, var_type: str) -> List[int]: """Returns a list of indices of the variables of the specified type. Args: @@ -584,13 +584,13 @@ def _get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): a_3 = matrix[:, len(self._state.binary_indices):] return a_2, a_3, b_2 - def _create_step1_problem(self) -> OptimizationProblem: + def _create_step1_problem(self) -> QuadraticProgram: """Creates a step 1 sub-problem. Returns: A newly created optimization problem. """ - op1 = OptimizationProblem() + op1 = QuadraticProgram() binary_size = len(self._state.binary_indices) # create the same binary variables. @@ -621,13 +621,13 @@ def _create_step1_problem(self) -> OptimizationProblem: op1.objective.set_linear(i, linear_objective[i]) return op1 - def _create_step2_problem(self) -> OptimizationProblem: + def _create_step2_problem(self) -> QuadraticProgram: """Creates a step 2 sub-problem. Returns: A newly created optimization problem. """ - op2 = OptimizationProblem() + op2 = QuadraticProgram() continuous_size = len(self._state.continuous_indices) binary_size = len(self._state.binary_indices) @@ -707,13 +707,13 @@ def _create_step2_problem(self) -> OptimizationProblem: return op2 - def _create_step3_problem(self) -> OptimizationProblem: + def _create_step3_problem(self) -> QuadraticProgram: """Creates a step 3 sub-problem. Returns: A newly created optimization problem. """ - op3 = OptimizationProblem() + op3 = QuadraticProgram() # add y variables. binary_size = len(self._state.binary_indices) op3.variables.add(names=["y_" + str(i + 1) for i in range(binary_size)], @@ -735,22 +735,22 @@ def _create_step3_problem(self) -> OptimizationProblem: return op3 - def _update_x0(self, op1: OptimizationProblem) -> np.ndarray: - """Solves the Step1 OptimizationProblem via the qubo optimizer. + def _update_x0(self, op1: QuadraticProgram) -> np.ndarray: + """Solves the Step1 QuadraticProgram via the qubo optimizer. Args: - op1: the Step1 OptimizationProblem. + op1: the Step1 QuadraticProgram. Returns: A solution of the Step1, as a numpy array. """ return np.asarray(self._qubo_optimizer.solve(op1).x) - def _update_x1(self, op2: OptimizationProblem) -> (np.ndarray, np.ndarray): - """Solves the Step2 OptimizationProblem via the continuous optimizer. + def _update_x1(self, op2: QuadraticProgram) -> (np.ndarray, np.ndarray): + """Solves the Step2 QuadraticProgram via the continuous optimizer. Args: - op2: the Step2 OptimizationProblem + op2: the Step2 QuadraticProgram Returns: A solution of the Step2, as a pair of numpy arrays. @@ -763,11 +763,11 @@ def _update_x1(self, op2: OptimizationProblem) -> (np.ndarray, np.ndarray): vars_z = np.asarray(vars_op2[len(self._state.continuous_indices):]) return vars_u, vars_z - def _update_y(self, op3: OptimizationProblem) -> np.ndarray: - """Solves the Step3 OptimizationProblem via the continuous optimizer. + def _update_y(self, op3: QuadraticProgram) -> np.ndarray: + """Solves the Step3 QuadraticProgram via the continuous optimizer. Args: - op3: the Step3 OptimizationProblem + op3: the Step3 QuadraticProgram Returns: A solution of the Step3, as a numpy array. diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index c84c50a407..e227747fb4 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -16,7 +16,7 @@ """The COBYLA optimizer wrapped to be used within Qiskit Optimization. Examples: - >>> problem = OptimizationProblem() + >>> problem = QuadraticProgram() >>> # specify problem here >>> optimizer = CobylaOptimizer() >>> result = optimizer.solve(problem) @@ -29,7 +29,7 @@ from qiskit.optimization.algorithms import OptimizationAlgorithm from qiskit.optimization.results import OptimizationResult -from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.problems import QuadraticProgram from qiskit.optimization import QiskitOptimizationError from qiskit.optimization import infinity @@ -68,7 +68,7 @@ def __init__(self, rhobeg: float = 1.0, rhoend: float = 1e-4, maxfun: int = 1000 self._disp = disp self._catol = catol - def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + def is_compatible(self, problem: QuadraticProgram) -> Optional[str]: """Checks whether a given problem can be solved with this optimizer. Checks whether the given problem is compatible, i.e., whether the problem contains only @@ -94,7 +94,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: else: return None - def solve(self, problem: OptimizationProblem) -> OptimizationResult: + def solve(self, problem: QuadraticProgram) -> OptimizationResult: """Tries to solves the given problem using the optimizer. Runs the optimizer to try to solve the optimization problem. diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index e84e59c4aa..b814ac1a11 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -16,7 +16,7 @@ """The CPLEX optimizer wrapped to be used within Qiskit Optimization. Examples: - >>> problem = OptimizationProblem() + >>> problem = QuadraticProgram() >>> # specify problem here >>> optimizer = CplexOptimizer() >>> result = optimizer.solve(problem) @@ -29,7 +29,7 @@ from .optimization_algorithm import OptimizationAlgorithm from ..utils.qiskit_optimization_error import QiskitOptimizationError from ..results.optimization_result import OptimizationResult -from ..problems.optimization_problem import OptimizationProblem +from ..problems.quadratic_program import QuadraticProgram class CplexOptimizer(OptimizationAlgorithm): @@ -69,11 +69,11 @@ def parameter_set(self, parameter_set: Optional[ParameterSet]): """ self._parameter_set = parameter_set - def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + def is_compatible(self, problem: QuadraticProgram) -> Optional[str]: """Checks whether a given problem can be solved with this optimizer. Returns ``True`` since CPLEX accepts all problems that can be modeled using the - ``OptimizationProblem``. CPLEX may throw an exception in case the problem is determined + ``QuadraticProgram``. CPLEX may throw an exception in case the problem is determined to be non-convex. This case could be addressed by setting CPLEX parameters accordingly. Args: @@ -84,7 +84,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: """ return None - def solve(self, problem: OptimizationProblem) -> OptimizationResult: + def solve(self, problem: QuadraticProgram) -> OptimizationResult: """Tries to solves the given problem using the optimizer. Runs the optimizer to try to solve the optimization problem. If problem is not convex, diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index bb36f43682..3f69b4fc8a 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -21,9 +21,9 @@ import numpy as np from qiskit.aqua import QuantumInstance from qiskit.optimization.algorithms import OptimizationAlgorithm -from qiskit.optimization.problems import OptimizationProblem -from qiskit.optimization.converters import (OptimizationProblemToQubo, - OptimizationProblemToNegativeValueOracle) +from qiskit.optimization.problems import QuadraticProgram +from qiskit.optimization.converters import (QuadraticProgramToQubo, + QuadraticProgramToNegativeValueOracle) from qiskit.optimization.results import GroverOptimizationResults from qiskit.optimization.results import OptimizationResult from qiskit.optimization.util import get_qubo_solutions @@ -52,7 +52,7 @@ def __init__(self, num_iterations: int = 3, quantum_instance = QuantumInstance(backend) self._quantum_instance = quantum_instance - def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + def is_compatible(self, problem: QuadraticProgram) -> Optional[str]: """Checks whether a given problem can be solved with this optimizer. Checks whether the given problem is compatible, i.e., whether the problem can be converted @@ -64,9 +64,9 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: Returns: Returns ``None`` if the problem is compatible and else a string with the error message. """ - return OptimizationProblemToQubo.is_compatible(problem) + return QuadraticProgramToQubo.is_compatible(problem) - def solve(self, problem: OptimizationProblem) -> OptimizationResult: + def solve(self, problem: QuadraticProgram) -> OptimizationResult: """Tries to solves the given problem using the optimizer. Runs the optimizer to try to solve the optimization problem. If problem is not convex, @@ -83,7 +83,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: """ # convert problem to QUBO - qubo_converter = OptimizationProblemToQubo() + qubo_converter = QuadraticProgramToQubo() problem_ = qubo_converter.encode(problem) # TODO: How to get from Optimization Problem? @@ -113,8 +113,8 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: # Initialize oracle helper object. orig_constant = problem_.objective.get_offset() measurement = not self._quantum_instance.is_statevector - opt_prob_converter = OptimizationProblemToNegativeValueOracle(n_value, - measurement) + opt_prob_converter = QuadraticProgramToNegativeValueOracle(n_value, + measurement) while not optimum_found: m = 1 diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 344777ac70..1bf8199fca 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -16,7 +16,7 @@ """A wrapper for minimum eigen solvers from Qiskit Aqua to be used within Qiskit Optimization. Examples: - >>> problem = OptimizationProblem() + >>> problem = QuadraticProgram() >>> # specify problem here >>> # specify minimum eigen solver to be used, e.g., QAOA >>> qaoa = QAOA(...) @@ -30,10 +30,10 @@ from qiskit.aqua.algorithms import MinimumEigensolver from .optimization_algorithm import OptimizationAlgorithm -from ..problems.optimization_problem import OptimizationProblem +from ..problems.quadratic_program import QuadraticProgram from ..utils.eigenvector_to_solutions import eigenvector_to_solutions -from ..converters.optimization_problem_to_operator import OptimizationProblemToOperator -from ..converters.optimization_problem_to_qubo import OptimizationProblemToQubo +from ..converters.quadratic_program_to_operator import QuadraticProgramToOperator +from ..converters.quadratic_program_to_qubo import QuadraticProgramToQubo from ..results.optimization_result import OptimizationResult @@ -104,7 +104,7 @@ def __init__(self, min_eigen_solver: MinimumEigensolver, penalty: Optional[float self._min_eigen_solver = min_eigen_solver self._penalty = penalty - def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + def is_compatible(self, problem: QuadraticProgram) -> Optional[str]: """Checks whether a given problem can be solved with this optimizer. Checks whether the given problem is compatible, i.e., whether the problem can be converted @@ -116,9 +116,9 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: Returns: Returns ``None`` if the problem is compatible and else a string with the error message. """ - return OptimizationProblemToQubo.is_compatible(problem) + return QuadraticProgramToQubo.is_compatible(problem) - def solve(self, problem: OptimizationProblem) -> MinimumEigenOptimizerResult: + def solve(self, problem: QuadraticProgram) -> MinimumEigenOptimizerResult: """Tries to solves the given problem using the optimizer. Runs the optimizer to try to solve the optimization problem. @@ -132,11 +132,11 @@ def solve(self, problem: OptimizationProblem) -> MinimumEigenOptimizerResult: """ # convert problem to QUBO - qubo_converter = OptimizationProblemToQubo() + qubo_converter = QuadraticProgramToQubo() problem_ = qubo_converter.encode(problem) # construct operator and offset - operator_converter = OptimizationProblemToOperator() + operator_converter = QuadraticProgramToOperator() operator, offset = operator_converter.encode(problem_) # approximate ground state of operator using min eigen solver diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index 49f6a167b6..8e1521d5d3 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -19,7 +19,7 @@ from typing import Optional -from ..problems.optimization_problem import OptimizationProblem +from ..problems.quadratic_program import QuadraticProgram from ..results.optimization_result import OptimizationResult @@ -27,7 +27,7 @@ class OptimizationAlgorithm(ABC): """An abstract class for optimization algorithms in Qiskit Optimization.""" @abstractmethod - def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + def is_compatible(self, problem: QuadraticProgram) -> Optional[str]: """Checks whether a given problem can be solved with the optimizer implementing this method. Args: @@ -39,7 +39,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: raise NotImplementedError @abstractmethod - def solve(self, problem: OptimizationProblem) -> OptimizationResult: + def solve(self, problem: QuadraticProgram) -> OptimizationResult: """Tries to solves the given problem using the optimizer. Runs the optimizer to try to solve the optimization problem. diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index fcb1286c9a..b2a8ed18df 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -15,7 +15,7 @@ """A recursive minimal eigen optimizer in Qiskit Optimization. Examples: - >>> problem = OptimizationProblem() + >>> problem = QuadraticProgram() >>> # specify problem here >>> # specify minimum eigen solver to be used, e.g., QAOA >>> qaoa = QAOA(...) @@ -32,9 +32,9 @@ from .optimization_algorithm import OptimizationAlgorithm from .minimum_eigen_optimizer import MinimumEigenOptimizer from ..utils.qiskit_optimization_error import QiskitOptimizationError -from ..problems.optimization_problem import OptimizationProblem +from ..problems.quadratic_program import QuadraticProgram from ..results.optimization_result import OptimizationResult -from ..converters.optimization_problem_to_qubo import OptimizationProblemToQubo +from ..converters.quadratic_program_to_qubo import QuadraticProgramToQubo class RecursiveMinimumEigenOptimizer(OptimizationAlgorithm): @@ -80,7 +80,7 @@ def __init__(self, min_eigen_optimizer: MinimumEigenOptimizer, min_num_vars: int self._min_num_vars_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) self._penalty = penalty - def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + def is_compatible(self, problem: QuadraticProgram) -> Optional[str]: """Checks whether a given problem can be solved with this optimizer. Checks whether the given problem is compatible, i.e., whether the problem can be converted @@ -92,9 +92,9 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: Returns: Returns ``None`` if the problem is compatible and else a string with the error message. """ - return OptimizationProblemToQubo.is_compatible(problem) + return QuadraticProgramToQubo.is_compatible(problem) - def solve(self, problem: OptimizationProblem) -> OptimizationResult: + def solve(self, problem: QuadraticProgram) -> OptimizationResult: """Tries to solves the given problem using the recursive optimizer. Runs the optimizer to try to solve the optimization problem. @@ -111,7 +111,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: """ from cplex import SparseTriple # convert problem to QUBO - qubo_converter = OptimizationProblemToQubo() + qubo_converter = QuadraticProgramToQubo() problem_ = qubo_converter.encode(problem) problem_ref = deepcopy(problem_) diff --git a/qiskit/optimization/converters/__init__.py b/qiskit/optimization/converters/__init__.py index c8bd087b54..8693f7d28d 100644 --- a/qiskit/optimization/converters/__init__.py +++ b/qiskit/optimization/converters/__init__.py @@ -24,18 +24,18 @@ """ -from .optimization_problem_to_negative_value_oracle import OptimizationProblemToNegativeValueOracle +from .quadratic_program_to_negative_value_oracle import QuadraticProgramToNegativeValueOracle from .inequality_to_equality_converter import InequalityToEqualityConverter from .integer_to_binary_converter import IntegerToBinaryConverter -from .optimization_problem_to_operator import OptimizationProblemToOperator +from .quadratic_program_to_operator import QuadraticProgramToOperator from .penalize_linear_equality_constraints import PenalizeLinearEqualityConstraints -from .optimization_problem_to_qubo import OptimizationProblemToQubo +from .quadratic_program_to_qubo import QuadraticProgramToQubo __all__ = [ "InequalityToEqualityConverter", "IntegerToBinaryConverter", - "OptimizationProblemToNegativeValueOracle", - "OptimizationProblemToOperator", - "OptimizationProblemToQubo", + "QuadraticProgramToNegativeValueOracle", + "QuadraticProgramToOperator", + "QuadraticProgramToQubo", "PenalizeLinearEqualityConstraints", ] diff --git a/qiskit/optimization/converters/inequality_to_equality_converter.py b/qiskit/optimization/converters/inequality_to_equality_converter.py index 37dec752e0..1bdad53291 100644 --- a/qiskit/optimization/converters/inequality_to_equality_converter.py +++ b/qiskit/optimization/converters/inequality_to_equality_converter.py @@ -20,7 +20,7 @@ from cplex import SparsePair -from ..problems.optimization_problem import OptimizationProblem +from ..problems.quadratic_program import QuadraticProgram from ..results.optimization_result import OptimizationResult from ..utils.qiskit_optimization_error import QiskitOptimizationError @@ -29,7 +29,7 @@ class InequalityToEqualityConverter: """Convert inequality constraints into equality constraints by introducing slack variables. Examples: - >>> problem = OptimizationProblem() + >>> problem = QuadraticProgram() >>> # define a problem >>> conv = InequalityToEqualityConverter() >>> problem2 = conv.encode(problem) @@ -45,8 +45,8 @@ def __init__(self) -> None: self._conv: Dict[str, List[Tuple[str, int]]] = {} # e.g., self._conv = {'c1': [c1@slack_var]} - def encode(self, op: OptimizationProblem, name: Optional[str] = None, - mode: str = 'auto') -> OptimizationProblem: + def encode(self, op: QuadraticProgram, name: Optional[str] = None, + mode: str = 'auto') -> QuadraticProgram: """Convert a problem with inequality constraints into one with only equality constraints. Args: @@ -67,7 +67,7 @@ def encode(self, op: OptimizationProblem, name: Optional[str] = None, QiskitOptimizationError: If an unsupported sense is specified. """ self._src = copy.deepcopy(op) - self._dst = OptimizationProblem() + self._dst = QuadraticProgram() # declare variables names = self._src.variables.get_names() diff --git a/qiskit/optimization/converters/integer_to_binary_converter.py b/qiskit/optimization/converters/integer_to_binary_converter.py index 923776c2d8..423f7f773b 100644 --- a/qiskit/optimization/converters/integer_to_binary_converter.py +++ b/qiskit/optimization/converters/integer_to_binary_converter.py @@ -20,16 +20,16 @@ import numpy as np from cplex import SparsePair -from ..problems.optimization_problem import OptimizationProblem +from ..problems.quadratic_program import QuadraticProgram from ..results.optimization_result import OptimizationResult from ..utils.qiskit_optimization_error import QiskitOptimizationError class IntegerToBinaryConverter: - """Convert an `OptimizationProblem` into new one by encoding integer with binary variables. + """Convert an `QuadraticProgram` into new one by encoding integer with binary variables. Examples: - >>> problem = OptimizationProblem() + >>> problem = QuadraticProgram() >>> problem.variables.add(names=['x'], types=['I'], lb=[0], ub=[10]) >>> conv = IntegerToBinaryConverter() >>> problem2 = conv.encode(problem) @@ -44,7 +44,7 @@ def __init__(self) -> None: self._conv: Dict[str, List[Tuple[str, int]]] = {} # e.g., self._conv = {'x': [('x@1', 1), ('x@2', 2)]} - def encode(self, op: OptimizationProblem, name: Optional[str] = None) -> OptimizationProblem: + def encode(self, op: QuadraticProgram, name: Optional[str] = None) -> QuadraticProgram: """Convert an integer problem into a new problem with binary variables. Args: @@ -57,7 +57,7 @@ def encode(self, op: OptimizationProblem, name: Optional[str] = None) -> Optimiz """ self._src = copy.deepcopy(op) - self._dst = OptimizationProblem() + self._dst = QuadraticProgram() if name: self._dst.set_problem_name(name) else: diff --git a/qiskit/optimization/converters/penalize_linear_equality_constraints.py b/qiskit/optimization/converters/penalize_linear_equality_constraints.py index e9b17a16e0..8113a6074c 100644 --- a/qiskit/optimization/converters/penalize_linear_equality_constraints.py +++ b/qiskit/optimization/converters/penalize_linear_equality_constraints.py @@ -19,7 +19,7 @@ import copy from collections import defaultdict -from ..problems.optimization_problem import OptimizationProblem +from ..problems.quadratic_program import QuadraticProgram from ..utils.qiskit_optimization_error import QiskitOptimizationError @@ -31,8 +31,8 @@ def __init__(self): self._src = None self._dst = None - def encode(self, op: OptimizationProblem, penalty_factor: float = 1e5, - name: Optional[str] = None) -> OptimizationProblem: + def encode(self, op: QuadraticProgram, penalty_factor: float = 1e5, + name: Optional[str] = None) -> QuadraticProgram: """Convert a problem with equality constraints into an unconstrained problem. Args: @@ -49,9 +49,9 @@ def encode(self, op: OptimizationProblem, penalty_factor: float = 1e5, # TODO: test compatibility, how to react in case of incompatibility? - # create empty OptimizationProblem model + # create empty QuadraticProgram model self._src = copy.deepcopy(op) # deep copy - self._dst = OptimizationProblem() + self._dst = QuadraticProgram() # set variables (obj is set via objective interface) var_names = self._src.variables.get_names() diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py similarity index 97% rename from qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py rename to qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py index 63a9f1d768..9b984db579 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py @@ -12,7 +12,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""OptimizationProblemToNegativeValueOracle module""" +"""QuadraticProgramToNegativeValueOracle module""" import logging from typing import Tuple, Dict, Union @@ -23,12 +23,12 @@ from qiskit.aqua.components.initial_states import Custom from qiskit.aqua.components.iqfts import Standard as IQFT from qiskit.aqua.components.oracles import CustomCircuitOracle -from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.problems import QuadraticProgram logger = logging.getLogger(__name__) -class OptimizationProblemToNegativeValueOracle: +class QuadraticProgramToNegativeValueOracle: """Converts an optimization problem (QUBO) to a negative value oracle. In addition, a state preparation operator is generated from the coefficients and constant of a @@ -46,7 +46,7 @@ def __init__(self, num_output_qubits: int, measurement: bool = False) -> None: self._num_value = num_output_qubits self._measurement = measurement - def encode(self, problem: OptimizationProblem) -> \ + def encode(self, problem: QuadraticProgram) -> \ Tuple[Custom, CustomCircuitOracle, Dict[Union[int, Tuple[int, int]], int]]: """A helper function that converts a QUBO into an oracle that recognizes negative numbers. diff --git a/qiskit/optimization/converters/optimization_problem_to_operator.py b/qiskit/optimization/converters/quadratic_program_to_operator.py similarity index 94% rename from qiskit/optimization/converters/optimization_problem_to_operator.py rename to qiskit/optimization/converters/quadratic_program_to_operator.py index d34e00c672..772d3980ba 100644 --- a/qiskit/optimization/converters/optimization_problem_to_operator.py +++ b/qiskit/optimization/converters/quadratic_program_to_operator.py @@ -13,7 +13,7 @@ # that they have been altered from the originals. -"""The converter from an ```OptimizationProblem``` to ``Operator``.""" +"""The converter from an ```QuadraticProgram``` to ``Operator``.""" from typing import Dict, Tuple @@ -22,11 +22,11 @@ from qiskit.aqua.operators import WeightedPauliOperator -from ..problems.optimization_problem import OptimizationProblem +from ..problems.quadratic_program import QuadraticProgram from ..utils.qiskit_optimization_error import QiskitOptimizationError -class OptimizationProblemToOperator: +class QuadraticProgramToOperator: """Convert an optimization problem into a qubit operator.""" def __init__(self) -> None: @@ -35,7 +35,7 @@ def __init__(self) -> None: self._q_d: Dict[int, int] = {} # e.g., self._q_d = {0: 0} - def encode(self, op: OptimizationProblem) -> Tuple[WeightedPauliOperator, float]: + def encode(self, op: QuadraticProgram) -> Tuple[WeightedPauliOperator, float]: """Convert a problem into a qubit operator Args: diff --git a/qiskit/optimization/converters/optimization_problem_to_qubo.py b/qiskit/optimization/converters/quadratic_program_to_qubo.py similarity index 93% rename from qiskit/optimization/converters/optimization_problem_to_qubo.py rename to qiskit/optimization/converters/quadratic_program_to_qubo.py index e4c9c9bbda..1353d444c1 100644 --- a/qiskit/optimization/converters/optimization_problem_to_qubo.py +++ b/qiskit/optimization/converters/quadratic_program_to_qubo.py @@ -17,20 +17,20 @@ from typing import Optional -from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.problems import QuadraticProgram from qiskit.optimization.results import OptimizationResult from qiskit.optimization.converters import (PenalizeLinearEqualityConstraints, IntegerToBinaryConverter) from qiskit.optimization.utils import QiskitOptimizationError -class OptimizationProblemToQubo: +class QuadraticProgramToQubo: """ Convert a given optimization problem to a new problem that is a QUBO. Examples: - >>> problem = OptimizationProblem() + >>> problem = QuadraticProgram() >>> # define a problem - >>> conv = OptimizationProblemToQubo() + >>> conv = QuadraticProgramToQubo() >>> problem2 = conv.encode(problem) """ @@ -44,7 +44,7 @@ def __init__(self, penalty: Optional[float] = 1e5) -> None: self._penalize_lin_eq_constraints = PenalizeLinearEqualityConstraints() self._penalty = penalty - def encode(self, problem: OptimizationProblem) -> OptimizationProblem: + def encode(self, problem: QuadraticProgram) -> QuadraticProgram: """ Convert a problem with linear equality constraints into new one with a QUBO form. Args: @@ -89,7 +89,7 @@ def decode(self, result: OptimizationResult) -> OptimizationResult: return self._int_to_bin.decode(result) @staticmethod - def is_compatible(problem: OptimizationProblem) -> Optional[str]: + def is_compatible(problem: QuadraticProgram) -> Optional[str]: """Checks whether a given problem can be cast to a Quadratic Unconstrained Binary Optimization (QUBO) problem, i.e., whether the problem contains only binary and integer variables as well as linear equality constraints, and otherwise, returns a message diff --git a/qiskit/optimization/problems/__init__.py b/qiskit/optimization/problems/__init__.py index b26caa97ab..8f9207d6a6 100644 --- a/qiskit/optimization/problems/__init__.py +++ b/qiskit/optimization/problems/__init__.py @@ -29,27 +29,27 @@ LinearConstraintInterface ObjSense ObjectiveInterface - OptimizationProblem + QuadraticProgram QuadraticConstraintInterface VariablesInterface N.B. Additional classes LinearConstraintInterface, QuadraticConstraintInterface, ObjectiveInterface, and VariablesInterface are not to be instantiated directly. Objects of those types are available within -an instantiated OptimizationProblem. +an instantiated QuadraticProgram. """ from .linear_constraint import LinearConstraintInterface from .objective import ObjSense, ObjectiveInterface -from .optimization_problem import OptimizationProblem +from .quadratic_program import QuadraticProgram from .quadratic_constraint import QuadraticConstraintInterface from .variables import VariablesInterface __all__ = ['LinearConstraintInterface', 'ObjSense', 'ObjectiveInterface', - 'OptimizationProblem', + 'QuadraticProgram', 'QuadraticConstraintInterface', 'VariablesInterface' ] diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index fd4c46e193..5c4ea7c8a4 100644 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -32,7 +32,7 @@ def __init__(self, varindex: Callable): """Creates a new LinearConstraintInterface. The linear constraints interface is exposed by the top-level - `OptimizationProblem` class as `OptimizationProblem.linear_constraints`. + `QuadraticProgram` class as `QuadraticProgram.linear_constraints`. This constructor is not meant to be used externally. """ super(LinearConstraintInterface, self).__init__() @@ -49,8 +49,8 @@ def get_num(self) -> int: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.linear_constraints.add(names = ["c1", "c2", "c3"]) >>> op.linear_constraints.get_num() 3 @@ -98,8 +98,8 @@ def add(self, lin_expr: Optional[List[SparsePair]] = None, senses: str = "", Returns an iterator containing the indices of the added linear constraints. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = ["x1", "x2", "x3"]) >>> indices = op.linear_constraints.add(\ lin_expr = [SparsePair(ind = ["x1", "x3"], val = [1.0, -1.0]),\ @@ -188,8 +188,8 @@ def delete(self, *args): Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.linear_constraints.add(names=[str(i) for i in range(10)]) >>> op.linear_constraints.get_num() 10 @@ -244,8 +244,8 @@ def set_rhs(self, *args): the corresponding values. Equivalent to [linear_constraints.set_rhs(pair[0], pair[1]) for pair in seq_of_pairs]. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) >>> op.linear_constraints.get_rhs() [0.0, 0.0, 0.0, 0.0] @@ -279,8 +279,8 @@ def set_names(self, *args): corresponding strings. Equivalent to [linear_constraints.set_names(pair[0], pair[1]) for pair in seq_of_pairs]. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) >>> op.linear_constraints.set_names("c1", "second") >>> op.linear_constraints.get_names(1) @@ -316,8 +316,8 @@ def set_senses(self, *args): and 'R', indicating greater-than, less-than, equality, and ranged constraints, respectively. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) >>> op.linear_constraints.get_senses() ['E', 'E', 'E', 'E'] @@ -356,8 +356,8 @@ def set_linear_components(self, *args): to the corresponding vector. Equivalent to [linear_constraints.set_linear_components(pair[0], pair[1]) for pair in seq_of_pairs]. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) >>> indices = op.variables.add(names = ["x0", "x1"]) >>> op.linear_constraints.set_linear_components("c0", [["x0"], [1.0]]) @@ -426,8 +426,8 @@ def set_range_values(self, *args): the corresponding values. Equivalent to [linear_constraints.set_range_values(pair[0], pair[1]) for pair in seq_of_pairs]. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) >>> op.linear_constraints.set_range_values("c1", 1.0) >>> op.linear_constraints.get_range_values() @@ -457,8 +457,8 @@ def set_coefficients(self, *args): coefficients must be a list of (row, col, val) triples as described above. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) >>> indices = op.variables.add(names = ["x0", "x1"]) >>> op.linear_constraints.set_coefficients("c0", "x1", 1.0) @@ -504,8 +504,8 @@ def get_rhs(self, *args) -> Union[float, List[float]]: indices the members of s. Equivalent to [linear_constraints.get_rhs(i) for i in s] - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.linear_constraints.add(rhs = [1.5 * i for i in range(10)],\ names = [str(i) for i in range(10)]) >>> op.linear_constraints.get_num() @@ -545,8 +545,8 @@ def get_senses(self, *args) -> Union[str, List[str]]: the members of s. Equivalent to [linear_constraints.get_senses(i) for i in s] - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.linear_constraints.add( ... senses=["E", "G", "L", "R"], ... names=[str(i) for i in range(4)]) @@ -601,8 +601,8 @@ def get_range_values(self, *args) -> Union[float, List[float]]: indices the members of s. Equivalent to [linear_constraints.get_range_values(i) for i in s] - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.linear_constraints.add(\ range_values = [1.5 * i for i in range(10)],\ senses = ["R"] * 10,\ @@ -644,8 +644,8 @@ def get_coefficients(self, *args) -> Union[float, List[float]]: linear_constraints.get_coefficients(sequence_of_pairs) returns a list of coefficients. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = ["x0", "x1"]) >>> indices = op.linear_constraints.add(\ names = ["c0", "c1"],\ @@ -697,8 +697,8 @@ def get_rows(self, *args) -> Union[SparsePair, List[SparsePair]]: of s. Equivalent to [linear_constraints.get_rows(i) for i in s] - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = ["x1", "x2", "x3"]) >>> indices = op.linear_constraints.add(\ names = ["c0", "c1", "c2", "c3"],\ @@ -733,8 +733,8 @@ def get_num_nonzeros(self) -> int: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = ["x1", "x2", "x3"]) >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"],\ lin_expr = [SparsePair(ind = ["x1", "x3"], val = [1.0, -1.0]),\ @@ -767,8 +767,8 @@ def get_names(self, *args) -> Union[str, List[str]]: the linear constraints with indices the members of s. Equivalent to [linear_constraints.get_names(i) for i in s] - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.linear_constraints.add(names = ["c" + str(i) for i in range(10)]) >>> op.linear_constraints.get_num() 10 diff --git a/qiskit/optimization/problems/objective.py b/qiskit/optimization/problems/objective.py index d0ea30e121..2fae02d464 100644 --- a/qiskit/optimization/problems/objective.py +++ b/qiskit/optimization/problems/objective.py @@ -47,8 +47,8 @@ def __getitem__(self, item: int) -> str: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> op.objective.sense.minimize 1 >>> op.objective.sense[1] @@ -70,7 +70,7 @@ def __init__(self, varindex: Callable): """Creates a new ObjectiveInterface. The objective function interface is exposed by the top-level - `OptimizationProblem` class as `OptimizationProblem.objective`. + `QuadraticProgram` class as `QuadraticProgram.objective`. This constructor is not meant to be used externally. """ super(ObjectiveInterface, self).__init__() @@ -96,8 +96,8 @@ def set_linear(self, *args): above. Changes the coefficients for the specified variables to the given values. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = [str(i) for i in range(4)]) >>> op.objective.get_linear() [0.0, 0.0, 0.0, 0.0] @@ -142,8 +142,8 @@ def set_quadratic(self, coef: Union[List[float], List[SparsePair], List[Sequence quadratic objective function, use the method set_quadratic_coefficients. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = [str(i) for i in range(3)]) >>> op.objective.set_quadratic([SparsePair(ind = [0, 1, 2], val = [1.0, -2.0, 0.5]),\ SparsePair(ind = [0, 1], val = [-2.0, -1.0]),\ @@ -215,8 +215,8 @@ def set_quadratic_coefficients(self, *args): can be time consuming. Instead, use the method set_quadratic to set the quadratic part of the objective efficiently. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = [str(i) for i in range(3)]) >>> op.objective.set_quadratic_coefficients(0, 1, 1.0) >>> op.objective.get_quadratic() @@ -259,8 +259,8 @@ def set_sense(self, sense: int): The argument to this method must be either objective.sense.minimize or objective.sense.maximize. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> op.objective.sense[op.objective.get_sense()] 'minimize' >>> op.objective.set_sense(op.objective.sense.maximize) @@ -282,8 +282,8 @@ def set_name(self, name: str): Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> op.objective.set_name("cost") >>> op.objective.get_name() 'cost' @@ -310,8 +310,8 @@ def get_linear(self, *args) -> Union[float, List[float]]: indices the members of s. Equivalent to [objective.get_linear(i) for i in s] - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(obj = [1.5 * i for i in range(10)],\ names = [str(i) for i in range(10)]) >>> op.variables.get_num() @@ -335,8 +335,8 @@ def get_linear_dict(self) -> Dict[int, float]: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> op.variables.add(names=['x', 'y']) >>> op.objective.set_linear([('x', 1), ('y', 2)]) >>> print(op.objective.get_linear_dict()) @@ -365,8 +365,8 @@ def get_quadratic(self, *args) -> Union[SparsePair, List[SparsePair]]: with the variables with indices the members of s. Equivalent to [objective.get_quadratic(i) for i in s] - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = [str(i) for i in range(10)]) >>> op.variables.get_num() 10 @@ -401,8 +401,8 @@ def get_quadratic_dict(self) -> Dict[Tuple[int, int], float]: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> op.variables.add(names=['x', 'y']) >>> op.objective.set_quadratic_coefficients([('x', 'x', 1), ('x', 'y', 2)]) >>> print(op.objective.get_quadratic_dict()) @@ -420,8 +420,8 @@ def get_quadratic_coefficients(self, *args) -> Union[float, List[float]]: query multiple coefficients where sequence is a list or tuple of pairs (v1, v2) as described above. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = [str(i) for i in range(3)]) >>> op.objective.set_quadratic_coefficients(0, 1, 1.0) >>> op.objective.get_quadratic_coefficients("1", 0) @@ -458,8 +458,8 @@ def get_sense(self) -> int: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> op.objective.sense[op.objective.get_sense()] 'minimize' >>> op.objective.set_sense(op.objective.sense.maximize) @@ -476,8 +476,8 @@ def get_name(self) -> str: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> op.objective.set_name("cost") >>> op.objective.get_name() 'cost' @@ -489,8 +489,8 @@ def get_num_quadratic_variables(self) -> int: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = [str(i) for i in range(3)]) >>> op.objective.set_quadratic_coefficients(0, 1, 1.0) >>> op.objective.get_num_quadratic_variables() @@ -513,8 +513,8 @@ def get_num_quadratic_nonzeros(self) -> int: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = [str(i) for i in range(3)]) >>> op.objective.set_quadratic_coefficients(0, 1, 1.0) >>> op.objective.get_num_quadratic_nonzeros() @@ -533,8 +533,8 @@ def get_offset(self) -> float: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> offset = op.objective.get_offset() >>> abs(offset - 0.0) < 1e-6 True @@ -546,8 +546,8 @@ def set_offset(self, offset: float): Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> op.objective.set_offset(3.14) >>> offset = op.objective.get_offset() >>> abs(offset - 3.14) < 1e-6 diff --git a/qiskit/optimization/problems/problem_type.py b/qiskit/optimization/problems/problem_type.py index bbc38d18a7..a12855b9d8 100644 --- a/qiskit/optimization/problems/problem_type.py +++ b/qiskit/optimization/problems/problem_type.py @@ -33,7 +33,7 @@ class ProblemType: """ - Types of problems the OptimizationProblem class can encapsulate. + Types of problems the QuadraticProgram class can encapsulate. These types are compatible with those of IBM ILOG CPLEX. For explanations of the problem types, see those topics in the CPLEX User's Manual in the topic titled Continuous Optimization @@ -64,8 +64,8 @@ def __getitem__(self, item: int) -> str: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> op.problem_type.LP 0 >>> op.problem_type[0] diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index 9748f4cda2..966801b19d 100644 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -36,7 +36,7 @@ def __init__(self, varindex: Callable): """Creates a new QuadraticConstraintInterface. The quadratic constraints interface is exposed by the top-level - `OptimizationProblem` class as `OptimizationProblem.quadratic_constraints`. + `QuadraticProgram` class as `QuadraticProgram.quadratic_constraints`. This constructor is not meant to be used externally. """ super(QuadraticConstraintInterface, self).__init__() @@ -53,8 +53,8 @@ def get_num(self) -> int: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = ['x','y']) >>> l = SparsePair(ind = ['x'], val = [1.0]) >>> q = SparseTriple(ind1 = ['x'], ind2 = ['y'], val = [1.0]) @@ -103,8 +103,8 @@ def add(self, lin_expr: Optional[SparsePair] = None, quad_expr: Optional[SparseT Raises: QiskitOptimizationError: if arguments are not valid. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = ['x','y']) >>> l = SparsePair(ind = ['x'], val = [1.0]) >>> q = SparseTriple(ind1 = ['x'], ind2 = ['y'], val = [1.0]) @@ -208,8 +208,8 @@ def delete(self, *args): Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names=['x', 'y']) >>> l = SparsePair(ind=['x'], val=[1.0]) >>> q = SparseTriple(ind1=['x'], ind2=['y'], val=[1.0]) @@ -280,8 +280,8 @@ def get_rhs(self, *args) -> Union[float, List[float]]: end. Equivalent to quadratic_constraints.get_rhs(range(begin, end + 1)). - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = [str(i) for i in range(10)]) >>> [op.quadratic_constraints.add(rhs=1.5 * i, name=str(i)) ... for i in range(10)] @@ -332,8 +332,8 @@ def get_senses(self, *args) -> Union[str, List[str]]: end. Equivalent to quadratic_constraints.get_senses(range(begin, end + 1)). - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = ["x0"]) >>> [op.quadratic_constraints.add(name=str(i), sense=j) ... for i, j in enumerate("GGLL")] @@ -385,8 +385,8 @@ def get_linear_num_nonzeros(self, *args) -> Union[int, List[int]]: inclusive of end. Equivalent to quadratic_constraints.get_linear_num_nonzeros(range(begin, end + 1)). - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = [str(i) for i in range(11)], types = "B" * 11) >>> [op.quadratic_constraints.add( ... name = str(i), @@ -443,8 +443,8 @@ def get_linear_components(self, *args) -> Union[SparsePair, List[SparsePair]]: Examples: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add( ... names=[str(i) for i in range(4)], ... types="B" * 4 @@ -507,8 +507,8 @@ def get_quad_num_nonzeros(self, *args) -> Union[int, List[int]]: inclusive of end. Equivalent to quadratic_constraints.get_quad_num_nonzeros(range(begin, end + 1)). - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = [str(i) for i in range(11)]) >>> [op.quadratic_constraints.add( ... name = str(i), @@ -560,8 +560,8 @@ def get_quadratic_components(self, *args) -> Union[SparseTriple, List[SparseTrip inclusive of end. Equivalent to quadratic_constraints.get_quadratic_components(range(begin, end + 1)). - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add( ... names=[str(i) for i in range(4)] ... ) @@ -628,8 +628,8 @@ def get_names(self, *args) -> Union[str, List[str]]: begin and end, inclusive of end. Equivalent to quadratic_constraints.get_names(range(begin, end + 1)). - >>> from qiskit.optimization.problems import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization.problems import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = [str(i) for i in range(11)]) >>> [op.quadratic_constraints.add( ... name = "q" + str(i), diff --git a/qiskit/optimization/problems/optimization_problem.py b/qiskit/optimization/problems/quadratic_program.py similarity index 94% rename from qiskit/optimization/problems/optimization_problem.py rename to qiskit/optimization/problems/quadratic_program.py index a637e6ae5a..132d95f599 100644 --- a/qiskit/optimization/problems/optimization_problem.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -35,18 +35,18 @@ logger = getLogger(__name__) -class OptimizationProblem: +class QuadraticProgram: """A class encapsulating an optimization problem, modeled after Python CPLEX API. - An instance of the OptimizationProblem class provides methods for creating, + An instance of the QuadraticProgram class provides methods for creating, modifying, and querying an optimization problem, solving it, and querying aspects of the solution. """ def __init__(self, file_name: Optional[str] = None): - """Constructor of the OptimizationProblem class. + """Constructor of the QuadraticProgram class. - The OptimizationProblem constructor accepts four types of argument lists. + The QuadraticProgram constructor accepts four types of argument lists. Args: file_name: read a model from a file. @@ -56,19 +56,19 @@ def __init__(self, file_name: Optional[str] = None): Examples: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() op is a new problem with no data - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem("filename") + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram("filename") op is a new problem containing the data in filename. If filename does not exist, an exception is raised. - The OptimizationProblem object is a context manager and can be used, like so: + The QuadraticProgram object is a context manager and can be used, like so: - >>> from qiskit.optimization import OptimizationProblem - >>> with OptimizationProblem() as op: + >>> from qiskit.optimization import QuadraticProgram + >>> with QuadraticProgram() as op: >>> # do stuff >>> op.solve() @@ -217,7 +217,7 @@ def to_cplex(self) -> Cplex: return op def end(self): - """Releases the OptimizationProblem object.""" + """Releases the QuadraticProgram object.""" self._name = '' self.variables = VariablesInterface() varindex = self.variables.get_indices @@ -227,7 +227,7 @@ def end(self): self.solution = SolutionInterface() self._problem_type = None - def __enter__(self) -> 'OptimizationProblem': + def __enter__(self) -> 'QuadraticProgram': """To implement a ContextManager, as in Cplex.""" return self @@ -242,8 +242,8 @@ def read(self, filename: str, filetype: str = ""): The first argument is a string specifying the filename from which the problem will be read. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> op.read("lpex.mps") """ cplex = Cplex() @@ -258,8 +258,8 @@ def write(self, filename: str, filetype: str = ""): Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names=['x1', 'x2', 'x3']) >>> op.write("example.lp") """ @@ -270,7 +270,7 @@ def write_to_stream(self, stream: object, filetype: str = 'LP', comptype: str = """Writes a problem to a file-like object in the given file format. The filetype argument can be any of "sav" (a binary format), "lp" - (the default), "mps", "rew", "rlp", or "alp" (see `OptimizationProblem.write` + (the default), "mps", "rew", "rlp", or "alp" (see `QuadraticProgram.write` for an explanation of these). If comptype is "bz2" (for BZip2) or "gz" (for GNU Zip), a @@ -281,8 +281,8 @@ def write_to_stream(self, stream: object, filetype: str = 'LP', comptype: str = Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names=['x1', 'x2', 'x3']) >>> class NoOpStream(object): ... def __init__(self): @@ -307,8 +307,8 @@ def write_to_stream(self, stream: object, filetype: str = 'LP', comptype: str = def write_as_string(self, filetype: str = 'LP', comptype: str = '') -> str: """Writes a problem as a string in the given file format. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names=['x1', 'x2', 'x3']) >>> lp_str = op.write_as_string("lp") >>> len(lp_str) > 0 @@ -322,8 +322,8 @@ def get_problem_type(self) -> int: The return value is an attribute of self.problem_type. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> op.read("lpex.mps") >>> op.get_problem_type() 0 @@ -365,7 +365,7 @@ def solve(self): """Prints out a message to ask users to use `OptimizationAlgorithm`. Users need to apply one of `OptimiztionAlgorithm`s instead of this method. """ - logger.warning('`OptimizationProblem.solve` is intentionally empty.' + logger.warning('`QuadraticProgram.solve` is intentionally empty.' 'You can solve it by applying `OptimizationAlgorithm.solve`.') def set_problem_name(self, name: str): @@ -378,7 +378,7 @@ def get_problem_name(self) -> str: def substitute_variables(self, constants: Optional[SparsePair] = None, variables: Optional[SparseTriple] = None) \ - -> Tuple['OptimizationProblem', 'SubstitutionStatus']: + -> Tuple['QuadraticProgram', 'SubstitutionStatus']: """Substitutes variables with constants or other variables. Args: @@ -403,9 +403,9 @@ def substitute_variables(self, constants: Optional[SparsePair] = None, Example usage: - >>> from qiskit.optimization import OptimizationProblem + >>> from qiskit.optimization import QuadraticProgram >>> from cplex import SparsePair, SparseTriple - >>> op = OptimizationProblem() + >>> op = QuadraticProgram() >>> op.variables.add(names=['x', 'y'], types='I'*2, lb=[-1]*2, ub=[2]*2) >>> op.objective.set_sense(op.objective.sense.minimize) >>> op.objective.set_linear([('x', 1), ('y', 2)]) @@ -465,7 +465,7 @@ def substitute_variables(self, constants: Optional[SparsePair] = None, class SubstitutionStatus(Enum): - """Status of `OptimizationProblem.substitute_variables`""" + """Status of `QuadraticProgram.substitute_variables`""" success = 1 infeasible = 2 @@ -477,14 +477,14 @@ class SubstituteVariables: CONST = -1 def __init__(self): - self._src: OptimizationProblem = None - self._dst: OptimizationProblem = None + self._src: QuadraticProgram = None + self._dst: QuadraticProgram = None self._subs = {} - def substitute_variables(self, src: OptimizationProblem, + def substitute_variables(self, src: QuadraticProgram, constants: Optional[SparsePair] = None, variables: Optional[SparseTriple] = None) \ - -> Tuple[OptimizationProblem, SubstitutionStatus]: + -> Tuple[QuadraticProgram, SubstitutionStatus]: """Substitutes variables with constants or other variables. Args: @@ -510,7 +510,7 @@ def substitute_variables(self, src: OptimizationProblem, """ self._src = src - self._dst = OptimizationProblem() + self._dst = QuadraticProgram() self._dst.set_problem_name(src.get_problem_name()) # do not set problem type, then it detects its type automatically diff --git a/qiskit/optimization/problems/variables.py b/qiskit/optimization/problems/variables.py index d0a75c6c9d..ce64bae462 100644 --- a/qiskit/optimization/problems/variables.py +++ b/qiskit/optimization/problems/variables.py @@ -53,8 +53,8 @@ def __getitem__(self, item: str) -> str: Example usage: - >>> from qiskit.optimization.problems import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization.problems import QuadraticProgram + >>> op = QuadraticProgram() >>> op.variables.type.binary 'B' >>> op.variables.type['B'] @@ -78,8 +78,8 @@ class VariablesInterface(BaseInterface): Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = ["x0", "x1", "x2"]) >>> # default values for lower_bounds are 0.0 >>> op.variables.get_lower_bounds() @@ -106,8 +106,8 @@ class VariablesInterface(BaseInterface): def __init__(self): """Creates a new VariablesInterface. - The variables interface is exposed by the top-level `OptimizationProblem` class - as `OptimizationProblem.variables`. This constructor is not meant to be used + The variables interface is exposed by the top-level `QuadraticProgram` class + as `QuadraticProgram.variables`. This constructor is not meant to be used externally. """ super(VariablesInterface, self).__init__() @@ -124,8 +124,8 @@ def get_num(self) -> int: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) >>> op.variables.get_num() @@ -138,8 +138,8 @@ def get_num_continuous(self) -> int: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) >>> op.variables.get_num_continuous() @@ -152,8 +152,8 @@ def get_num_integer(self) -> int: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) >>> op.variables.get_num_integer() @@ -166,8 +166,8 @@ def get_num_binary(self) -> int: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.semi_continuous, t.binary, t.integer]) >>> op.variables.get_num_binary() @@ -180,8 +180,8 @@ def get_num_semicontinuous(self) -> int: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.semi_continuous, t.semi_integer, t.semi_integer]) >>> op.variables.get_num_semicontinuous() @@ -194,8 +194,8 @@ def get_num_semiinteger(self) -> int: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.semi_continuous, t.semi_integer, t.semi_integer]) >>> op.variables.get_num_semiinteger() @@ -242,9 +242,9 @@ def add(self, obj: None = None, lb: Optional[List[float]] = None, Example usage: - >>> from qiskit.optimization import OptimizationProblem + >>> from qiskit.optimization import QuadraticProgram >>> from cplex import SparsePair, infinity - >>> op = OptimizationProblem() + >>> op = QuadraticProgram() >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2"]) >>> indices = op.variables.add(obj = [1.0, 2.0, 3.0],\ types = [op.variables.type.integer] * 3) @@ -325,8 +325,8 @@ def delete(self, *args): Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names=[str(i) for i in range(10)]) >>> op.variables.get_num() 10 @@ -384,8 +384,8 @@ def set_lower_bounds(self, *args): the corresponding values. Equivalent to [variables.set_lower_bounds(pair[0], pair[1]) for pair in seq_of_pairs]. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = ["x0", "x1", "x2"]) >>> op.variables.set_lower_bounds(0, 1.0) >>> op.variables.get_lower_bounds() @@ -418,8 +418,8 @@ def set_upper_bounds(self, *args): the corresponding values. Equivalent to [variables.set_upper_bounds(pair[0], pair[1]) for pair in seq_of_pairs]. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = ["x0", "x1", "x2"]) >>> op.variables.set_upper_bounds(0, 1.0) >>> op.variables.set_upper_bounds([("x1", 10.0), (2, 3.0)]) @@ -449,8 +449,8 @@ def set_names(self, *args): corresponding strings. Equivalent to [variables.set_names(pair[0], pair[1]) for pair in seq_of_pairs]. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> t = op.variables.type >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) >>> op.variables.set_names(0, "first") @@ -486,8 +486,8 @@ def set_types(self, *args): If the types are set, the problem will be treated as a MIP, even if all variable types are continuous. - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = [str(i) for i in range(5)]) >>> op.variables.set_types(0, op.variables.type.continuous) >>> op.variables.set_types([("1", op.variables.type.integer),\ @@ -526,8 +526,8 @@ def get_lower_bounds(self, *args) -> Union[float, List[float]]: of s. Equivalent to [variables.get_lower_bounds(i) for i in s] - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(lb = [1.5 * i for i in range(10)],\ names = [str(i) for i in range(10)]) >>> op.variables.get_num() @@ -574,8 +574,8 @@ def get_upper_bounds(self, *args) -> Union[float, List[float]]: begin and end, inclusive of end. Equivalent to variables.get_upper_bounds(range(begin, end + 1)). - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(ub = [(1.5 * i) + 1.0 for i in range(10)],\ names = [str(i) for i in range(10)]) >>> op.variables.get_num() @@ -614,8 +614,8 @@ def get_names(self, *args) -> Union[str, List[str]]: names of the variables with indices the members of s. Equivalent to [variables.get_names(i) for i in s] - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names = ['x' + str(i) for i in range(10)]) >>> op.variables.get_num() 10 @@ -654,8 +654,8 @@ def get_types(self, *args) -> Union[str, List[str]]: the types of the variables with indices the members of s. Equivalent to [variables.get_types(i) for i in s] - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> t = op.variables.type >>> indices = op.variables.add(names = [str(i) for i in range(5)],\ types = [t.continuous, t.integer,\ diff --git a/qiskit/optimization/utils/base.py b/qiskit/optimization/utils/base.py index d0d95733cb..8a7a630d1b 100644 --- a/qiskit/optimization/utils/base.py +++ b/qiskit/optimization/utils/base.py @@ -53,8 +53,8 @@ def get_indices(self, *name) -> Union[int, List[int]]: Example usage: - >>> from qiskit.optimization import OptimizationProblem - >>> op = OptimizationProblem() + >>> from qiskit.optimization import QuadraticProgram + >>> op = QuadraticProgram() >>> indices = op.variables.add(names=["a", "b"]) >>> op.variables.get_indices("a") 0 diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index 3b32c0305c..a0300222f9 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -24,7 +24,7 @@ from qiskit.optimization.algorithms import CplexOptimizer, MinimumEigenOptimizer from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters, \ ADMMOptimizerResult, ADMMState -from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.problems import QuadraticProgram class TestADMMOptimizer(QiskitOptimizationTestCase): @@ -36,7 +36,7 @@ def test_admm_maximization(self): c = mdl.continuous_var(lb=0, ub=10, name='c') x = mdl.binary_var(name='x') mdl.maximize(c + x * x) - op = OptimizationProblem() + op = QuadraticProgram() op.from_docplex(mdl) self.assertIsNotNone(op) @@ -78,7 +78,7 @@ def test_admm_ex6(self): mdl.add_constraint(v + w + t >= 1, "cons2") mdl.add_constraint(v + w == 1, "cons3") - op = OptimizationProblem() + op = QuadraticProgram() op.from_docplex(mdl) qubo_optimizer = CplexOptimizer() @@ -120,7 +120,7 @@ def test_admm_ex5(self): mdl.add_constraint(v + w + t >= 1, "cons2") mdl.add_constraint(v + w == 1, "cons3") - op = OptimizationProblem() + op = QuadraticProgram() op.from_docplex(mdl) qubo_optimizer = CplexOptimizer() diff --git a/test/optimization/test_cobyla_optimizer.py b/test/optimization/test_cobyla_optimizer.py index a1e607bb0a..c9b27abb86 100644 --- a/test/optimization/test_cobyla_optimizer.py +++ b/test/optimization/test_cobyla_optimizer.py @@ -20,7 +20,7 @@ from ddt import ddt, data from qiskit.optimization.algorithms import CobylaOptimizer -from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.problems import QuadraticProgram @ddt @@ -43,7 +43,7 @@ def test_cobyla_optimizer(self, config): filename, fval = config # load optimization problem - problem = OptimizationProblem() + problem = QuadraticProgram() problem.read(self.resource_path + filename) # solve problem with cobyla @@ -56,7 +56,7 @@ def test_cobyla_optimizer_with_quadratic_constraint(self): """ Cobyla Optimizer Test """ from cplex import SparsePair, SparseTriple # load optimization problem - problem = OptimizationProblem() + problem = QuadraticProgram() problem.variables.add(lb=[0, 0], ub=[1, 1], types='CC') problem.objective.set_linear([(0, 1), (1, 1)]) diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index 64e98e767d..9a04c648ce 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -18,10 +18,10 @@ from test.optimization.optimization_test_case import QiskitOptimizationTestCase from cplex import SparsePair -from qiskit.optimization import OptimizationProblem, QiskitOptimizationError +from qiskit.optimization import QuadraticProgram, QiskitOptimizationError from qiskit.optimization.results import OptimizationResult from qiskit.optimization.converters import InequalityToEqualityConverter, \ - OptimizationProblemToOperator, IntegerToBinaryConverter, PenalizeLinearEqualityConstraints + QuadraticProgramToOperator, IntegerToBinaryConverter, PenalizeLinearEqualityConstraints from qiskit.aqua.operators import WeightedPauliOperator from qiskit.quantum_info import Pauli @@ -54,60 +54,60 @@ class TestConverters(QiskitOptimizationTestCase): def test_empty_problem(self): """ Test empty problem """ - op = OptimizationProblem() + op = QuadraticProgram() conv = InequalityToEqualityConverter() op = conv.encode(op) conv = IntegerToBinaryConverter() op = conv.encode(op) conv = PenalizeLinearEqualityConstraints() op = conv.encode(op) - conv = OptimizationProblemToOperator() + conv = QuadraticProgramToOperator() _, shift = conv.encode(op) self.assertEqual(shift, 0.0) def test_valid_variable_type(self): - """Validate the types of the variables for OptimizationProblemToOperator.""" + """Validate the types of the variables for QuadraticProgramToOperator.""" # Integer variable with self.assertRaises(QiskitOptimizationError): - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x'], types='I') - conv = OptimizationProblemToOperator() + conv = QuadraticProgramToOperator() _ = conv.encode(op) # Continuous variable with self.assertRaises(QiskitOptimizationError): - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x'], types='C') - conv = OptimizationProblemToOperator() + conv = QuadraticProgramToOperator() _ = conv.encode(op) # Semi-Continuous variable with self.assertRaises(QiskitOptimizationError): - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x'], types='S') - conv = OptimizationProblemToOperator() + conv = QuadraticProgramToOperator() _ = conv.encode(op) # Semi-Integer variable with self.assertRaises(QiskitOptimizationError): - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x'], types='N') - conv = OptimizationProblemToOperator() + conv = QuadraticProgramToOperator() _ = conv.encode(op) # validate the types of the variables for InequalityToEqualityConverter # Semi-Continuous variable with self.assertRaises(QiskitOptimizationError): - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x'], types='S') conv = InequalityToEqualityConverter() _ = conv.encode(op) # Semi-Integer variable with self.assertRaises(QiskitOptimizationError): - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x'], types='N') conv = InequalityToEqualityConverter() _ = conv.encode(op) def test_inequality_binary(self): """ Test InequalityToEqualityConverter with binary variables """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), @@ -131,7 +131,7 @@ def test_inequality_binary(self): def test_inequality_integer(self): """ Test InequalityToEqualityConverter with integer variables """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='I' * 3, lb=[-3] * 3, ub=[3] * 3) op.linear_constraints.add( @@ -156,7 +156,7 @@ def test_inequality_integer(self): def test_inequality_mode_integer(self): """ Test integer mode of InequalityToEqualityConverter() """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), @@ -173,7 +173,7 @@ def test_inequality_mode_integer(self): def test_inequality_mode_continuous(self): """ Test continuous mode of InequalityToEqualityConverter() """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), @@ -190,7 +190,7 @@ def test_inequality_mode_continuous(self): def test_inequality_mode_auto(self): """ Test auto mode of InequalityToEqualityConverter() """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), @@ -207,7 +207,7 @@ def test_inequality_mode_auto(self): def test_penalize_sense(self): """ Test PenalizeLinearEqualityConstraints with senses """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), @@ -224,7 +224,7 @@ def test_penalize_sense(self): def test_penalize_binary(self): """ Test PenalizeLinearEqualityConstraints with binary variables """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), @@ -240,7 +240,7 @@ def test_penalize_binary(self): def test_penalize_integer(self): """ Test PenalizeLinearEqualityConstraints with integer variables """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='I' * 3, lb=[-3] * 3, ub=[3] * 3) op.linear_constraints.add( @@ -257,7 +257,7 @@ def test_penalize_integer(self): def test_integer_to_binary(self): """ Test integer to binary """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='BIC', lb=[0, 0, 0], ub=[1, 6, 10]) op.objective.set_linear([('x', 1), ('y', 2), ('z', 1)]) @@ -285,7 +285,7 @@ def test_integer_to_binary(self): def test_binary_to_integer(self): """ Test binary to integer """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='BIB', lb=[ 0, 0, 0], ub=[1, 7, 1]) op.objective.set_linear([('x', 2), ('y', 1), ('z', 1)]) @@ -305,7 +305,7 @@ def test_binary_to_integer(self): def test_optimizationproblem_to_operator(self): """ Test optimization problem to operators""" - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['a', 'b', 'c', 'd'], types='B'*4) op.objective.set_linear([('a', 1), ('b', 1), ('c', 1), ('d', 1)]) op.linear_constraints.add( @@ -316,7 +316,7 @@ def test_optimizationproblem_to_operator(self): ) op.objective.set_sense(-1) penalize = PenalizeLinearEqualityConstraints() - op2ope = OptimizationProblemToOperator() + op2ope = QuadraticProgramToOperator() op2 = penalize.encode(op) qubitop, offset = op2ope.encode(op2) self.assertListEqual(qubitop.paulis, QUBIT_OP_MAXIMIZE_SAMPLE.paulis) @@ -326,7 +326,7 @@ def test_quadratic_constraints(self): """ Test quadratic constraints""" # IntegerToBinaryConverter with self.assertRaises(QiskitOptimizationError): - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y']) l_expr = SparsePair(ind=['x'], val=[1.0]) q_expr = [['x'], ['y'], [1]] @@ -335,7 +335,7 @@ def test_quadratic_constraints(self): _ = conv.encode(op) # InequalityToEqualityConverter with self.assertRaises(QiskitOptimizationError): - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y']) l_expr = SparsePair(ind=['x'], val=[1.0]) q_expr = [['x'], ['y'], [1]] diff --git a/test/optimization/test_cplex_optimizer.py b/test/optimization/test_cplex_optimizer.py index 3444b8b170..89c5e13844 100644 --- a/test/optimization/test_cplex_optimizer.py +++ b/test/optimization/test_cplex_optimizer.py @@ -18,7 +18,7 @@ from test.optimization.optimization_test_case import QiskitOptimizationTestCase from ddt import ddt, data from qiskit.optimization.algorithms import CplexOptimizer -from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.problems import QuadraticProgram @ddt @@ -43,7 +43,7 @@ def test_cplex_optimizer(self, config): filename, x, fval = config # load optimization problem - problem = OptimizationProblem() + problem = QuadraticProgram() problem.read(self.resource_path + filename) # solve problem with cplex diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index e434db4fbb..558b37173f 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -19,7 +19,7 @@ import numpy as np from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.optimization.algorithms import GroverMinimumFinder, MinimumEigenOptimizer -from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.problems import QuadraticProgram class TestGroverMinimumFinder(QiskitOptimizationTestCase): @@ -47,7 +47,7 @@ def test_qubo_gas_int_zero(self): """Test for when the answer is zero.""" # Input. - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x0', 'x1'], types='BB') linear = [('x0', 0), ('x1', 0)] op.objective.set_linear(linear) @@ -62,7 +62,7 @@ def test_qubo_gas_int_simple(self): """Test for simple case, with 2 linear coeffs and no quadratic coeffs or constants.""" # Input. - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x0', 'x1'], types='BB') linear = [('x0', -1), ('x1', 2)] op.objective.set_linear(linear) @@ -77,7 +77,7 @@ def test_qubo_gas_int_paper_example(self): """Test the example from https://arxiv.org/abs/1912.04088.""" # Input. - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x0', 'x1', 'x2'], types='BBB') linear = [('x0', -1), ('x1', 2), ('x2', -3)] diff --git a/test/optimization/test_linear_constraints.py b/test/optimization/test_linear_constraints.py index 9dfdf9ec73..96f4ba3cfd 100644 --- a/test/optimization/test_linear_constraints.py +++ b/test/optimization/test_linear_constraints.py @@ -19,7 +19,7 @@ from cplex import SparsePair -from qiskit.optimization import OptimizationProblem +from qiskit.optimization import QuadraticProgram class TestLinearConstraints(QiskitOptimizationTestCase): @@ -27,13 +27,13 @@ class TestLinearConstraints(QiskitOptimizationTestCase): def test_get_num(self): """ test get num """ - op = OptimizationProblem() + op = QuadraticProgram() op.linear_constraints.add(names=["c1", "c2", "c3"]) self.assertEqual(op.linear_constraints.get_num(), 3) def test_add(self): """ test add """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=["x1", "x2", "x3"]) op.linear_constraints.add( lin_expr=[SparsePair(ind=["x1", "x3"], val=[1.0, -1.0]), @@ -48,7 +48,7 @@ def test_add(self): def test_delete(self): """ test delete """ - op = OptimizationProblem() + op = QuadraticProgram() op.linear_constraints.add(names=[str(i) for i in range(10)]) self.assertEqual(op.linear_constraints.get_num(), 10) op.linear_constraints.delete(8) @@ -63,7 +63,7 @@ def test_delete(self): def test_rhs(self): """ test rhs """ - op = OptimizationProblem() + op = QuadraticProgram() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) self.assertListEqual(op.linear_constraints.get_rhs(), [0.0, 0.0, 0.0, 0.0]) op.linear_constraints.set_rhs("c1", 1.0) @@ -73,7 +73,7 @@ def test_rhs(self): def test_set_names(self): """ test set names """ - op = OptimizationProblem() + op = QuadraticProgram() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) op.linear_constraints.set_names("c1", "second") self.assertEqual(op.linear_constraints.get_names(1), 'second') @@ -82,7 +82,7 @@ def test_set_names(self): def test_set_senses(self): """ test set senses """ - op = OptimizationProblem() + op = QuadraticProgram() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) self.assertListEqual(op.linear_constraints.get_senses(), ['E', 'E', 'E', 'E']) op.linear_constraints.set_senses("c1", "G") @@ -92,7 +92,7 @@ def test_set_senses(self): def test_set_linear_components(self): """ test set linear components """ - op = OptimizationProblem() + op = QuadraticProgram() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) op.variables.add(names=["x0", "x1"]) op.linear_constraints.set_linear_components("c0", [["x0"], [1.0]]) @@ -112,7 +112,7 @@ def test_set_linear_components(self): def test_set_range_values(self): """ test set range values """ - op = OptimizationProblem() + op = QuadraticProgram() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) op.linear_constraints.set_range_values("c1", 1.0) self.assertListEqual(op.linear_constraints.get_range_values(), [0.0, 1.0, 0.0, 0.0]) @@ -121,7 +121,7 @@ def test_set_range_values(self): def test_set_coeffients(self): """ test set coefficients """ - op = OptimizationProblem() + op = QuadraticProgram() op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) op.variables.add(names=["x0", "x1"]) op.linear_constraints.set_coefficients("c0", "x1", 1.0) @@ -136,7 +136,7 @@ def test_set_coeffients(self): def test_get_rhs(self): """ test get rhs """ - op = OptimizationProblem() + op = QuadraticProgram() op.linear_constraints.add(rhs=[1.5 * i for i in range(10)], names=[str(i) for i in range(10)]) self.assertEqual(op.linear_constraints.get_num(), 10) @@ -147,7 +147,7 @@ def test_get_rhs(self): def test_get_senses(self): """ test get senses """ - op = OptimizationProblem() + op = QuadraticProgram() op.linear_constraints.add( senses=["E", "G", "L", "R"], names=[str(i) for i in range(4)]) @@ -159,7 +159,7 @@ def test_get_senses(self): def test_get_range_values(self): """ test get range values """ - op = OptimizationProblem() + op = QuadraticProgram() op.linear_constraints.add( range_values=[1.5 * i for i in range(10)], senses=["R"] * 10, @@ -173,7 +173,7 @@ def test_get_range_values(self): def test_get_coefficients(self): """ test get coefficients """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=["x0", "x1"]) op.linear_constraints.add( names=["c0", "c1"], @@ -184,7 +184,7 @@ def test_get_coefficients(self): def test_get_rows(self): """ test get rows """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=["x1", "x2", "x3"]) op.linear_constraints.add( names=["c0", "c1", "c2", "c3"], @@ -222,7 +222,7 @@ def test_get_rows(self): def test_get_num_nonzeros(self): """ test get num non zeros """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=["x1", "x2", "x3"]) op.linear_constraints.add( names=["c0", "c1", "c2", "c3"], @@ -236,7 +236,7 @@ def test_get_num_nonzeros(self): def test_get_names(self): """ test get names """ - op = OptimizationProblem() + op = QuadraticProgram() op.linear_constraints.add(names=["c" + str(i) for i in range(10)]) self.assertEqual(op.linear_constraints.get_num(), 10) self.assertEqual(op.linear_constraints.get_names(8), 'c8') @@ -246,13 +246,13 @@ def test_get_names(self): def test_get_histogram(self): """ test get histogram """ - op = OptimizationProblem() + op = QuadraticProgram() with self.assertRaises(NotImplementedError): op.linear_constraints.get_histogram() def test_empty_names(self): """ test empty names """ - op = OptimizationProblem() + op = QuadraticProgram() r = op.linear_constraints.add(names=['', '', '']) self.assertListEqual(op.linear_constraints.get_names(), ['c1', 'c2', 'c3']) self.assertEqual(r, range(0, 3)) diff --git a/test/optimization/test_min_eigen_optimizer.py b/test/optimization/test_min_eigen_optimizer.py index d2d74ed31e..562332f18a 100644 --- a/test/optimization/test_min_eigen_optimizer.py +++ b/test/optimization/test_min_eigen_optimizer.py @@ -25,7 +25,7 @@ from qiskit.aqua.components.optimizers import COBYLA from qiskit.optimization.algorithms import MinimumEigenOptimizer, CplexOptimizer -from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.problems import QuadraticProgram @ddt @@ -67,7 +67,7 @@ def test_min_eigen_optimizer(self, config): min_eigen_optimizer = MinimumEigenOptimizer(min_eigen_solver) # load optimization problem - problem = OptimizationProblem() + problem = QuadraticProgram() problem.read(self.resource_path + filename) # solve problem with cplex diff --git a/test/optimization/test_objective.py b/test/optimization/test_objective.py index 0cc907f736..24764471cf 100644 --- a/test/optimization/test_objective.py +++ b/test/optimization/test_objective.py @@ -19,7 +19,7 @@ from cplex import SparsePair -from qiskit.optimization import OptimizationProblem, QiskitOptimizationError +from qiskit.optimization import QuadraticProgram, QiskitOptimizationError class TestObjective(QiskitOptimizationTestCase): @@ -27,7 +27,7 @@ class TestObjective(QiskitOptimizationTestCase): def test_obj_sense(self): """ test obj sense """ - op = OptimizationProblem() + op = QuadraticProgram() self.assertEqual(op.objective.sense.minimize, 1) self.assertEqual(op.objective.sense.maximize, -1) self.assertEqual(op.objective.sense[1], 'minimize') @@ -35,7 +35,7 @@ def test_obj_sense(self): def test_set_linear(self): """ test set linear """ - op = OptimizationProblem() + op = QuadraticProgram() n = 4 op.variables.add(names=[str(i) for i in range(n)]) self.assertListEqual(op.objective.get_linear(), [0.0, 0.0, 0.0, 0.0]) @@ -52,14 +52,14 @@ def test_set_linear(self): def test_set_empty_quadratic(self): """ test set empty quadratic """ - op = OptimizationProblem() + op = QuadraticProgram() self.assertIsNone(op.objective.set_quadratic([])) with self.assertRaises(TypeError): op.objective.set_quadratic() def test_set_quadratic(self): """ test set quadratic """ - op = OptimizationProblem() + op = QuadraticProgram() n = 3 op.variables.add(names=[str(i) for i in range(n)]) obj = op.objective @@ -94,7 +94,7 @@ def test_set_quadratic(self): def test_get_quadratic_dict(self): """ test get quadratic dict """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y']) op.objective.set_quadratic_coefficients([('x', 'x', 1), ('x', 'y', 2)]) self.assertDictEqual(op.objective.get_quadratic_dict(), @@ -102,7 +102,7 @@ def test_get_quadratic_dict(self): def test_set_quadratic_coefficients(self): """ test set quadratic coefficients """ - op = OptimizationProblem() + op = QuadraticProgram() n = 3 op.variables.add(names=[str(i) for i in range(n)]) obj = op.objective @@ -137,7 +137,7 @@ def test_set_quadratic_coefficients(self): def test_set_senses(self): """ test set senses """ - op = OptimizationProblem() + op = QuadraticProgram() self.assertEqual(op.objective.sense[op.objective.get_sense()], 'minimize') op.objective.set_sense(op.objective.sense.maximize) self.assertEqual(op.objective.sense[op.objective.get_sense()], 'maximize') @@ -146,13 +146,13 @@ def test_set_senses(self): def test_set_name(self): """ test set name """ - op = OptimizationProblem() + op = QuadraticProgram() op.objective.set_name('cost') self.assertEqual(op.objective.get_name(), 'cost') def test_get_linear(self): """ test get linear """ - op = OptimizationProblem() + op = QuadraticProgram() n = 10 op.variables.add(names=[str(i) for i in range(n)]) obj = op.objective @@ -166,14 +166,14 @@ def test_get_linear(self): def test_get_linear_dict(self): """ test get linear dict """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y']) op.objective.set_linear([('x', 1), ('y', 2)]) self.assertDictEqual(op.objective.get_linear_dict(), {0: 1, 1: 2}) def test_get_quadratic(self): """ test get quadratic """ - op = OptimizationProblem() + op = QuadraticProgram() n = 10 op.variables.add(names=[str(i) for i in range(n)]) obj = op.objective @@ -209,7 +209,7 @@ def test_get_quadratic(self): def test_get_quadratic_coefficients(self): """ test get quadratic coefficients """ - op = OptimizationProblem() + op = QuadraticProgram() n = 3 op.variables.add(names=[str(i) for i in range(n)]) obj = op.objective @@ -223,7 +223,7 @@ def test_get_quadratic_coefficients(self): def test_get_sense(self): """ test get sense """ - op = OptimizationProblem() + op = QuadraticProgram() self.assertEqual(op.objective.sense[op.objective.get_sense()], 'minimize') op.objective.set_sense(op.objective.sense.maximize) self.assertEqual(op.objective.sense[op.objective.get_sense()], 'maximize') @@ -232,13 +232,13 @@ def test_get_sense(self): def test_get_name(self): """ test get name """ - op = OptimizationProblem() + op = QuadraticProgram() op.objective.set_name('cost') self.assertEqual(op.objective.get_name(), 'cost') def test_get_num_quadratic_variables(self): """ test get num quadratic variables """ - op = OptimizationProblem() + op = QuadraticProgram() n = 3 op.variables.add(names=[str(i) for i in range(n)]) obj = op.objective @@ -251,7 +251,7 @@ def test_get_num_quadratic_variables(self): def test_get_num_quadratic_nonzeros(self): """ test get num quadratic non zeros """ - op = OptimizationProblem() + op = QuadraticProgram() n = 3 op.variables.add(names=[str(i) for i in range(n)]) obj = op.objective @@ -264,14 +264,14 @@ def test_get_num_quadratic_nonzeros(self): def test_offset(self): """ test offset """ - op = OptimizationProblem() + op = QuadraticProgram() self.assertEqual(op.objective.get_offset(), 0.0) op.objective.set_offset(3.14) self.assertEqual(op.objective.get_offset(), 3.14) def test_set_quadratic_coefficients2(self): """ test set quadratic coefficients 2 """ - op = OptimizationProblem() + op = QuadraticProgram() n = 2 op.variables.add(names=[str(i) for i in range(n)]) obj = op.objective @@ -284,7 +284,7 @@ def test_set_quadratic_coefficients2(self): def test_default_name(self): """ test default name """ - op = OptimizationProblem() + op = QuadraticProgram() self.assertEqual(op.objective.get_name(), 'obj1') diff --git a/test/optimization/test_quadratic_constraints.py b/test/optimization/test_quadratic_constraints.py index 0750bddb54..b6e3fe7097 100644 --- a/test/optimization/test_quadratic_constraints.py +++ b/test/optimization/test_quadratic_constraints.py @@ -20,7 +20,7 @@ from cplex import SparsePair, SparseTriple from qiskit.optimization import QiskitOptimizationError -from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.problems import QuadraticProgram class TestQuadraticConstraints(QiskitOptimizationTestCase): @@ -28,7 +28,7 @@ class TestQuadraticConstraints(QiskitOptimizationTestCase): def test_initial1(self): """ test initial 1""" - op = OptimizationProblem() + op = QuadraticProgram() c_1 = op.quadratic_constraints.add(name='c1') c_2 = op.quadratic_constraints.add(name='c2') c_3 = op.quadratic_constraints.add(name='c3') @@ -40,7 +40,7 @@ def test_initial1(self): def test_initial2(self): """ test initial 2""" - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x1', 'x2', 'x3'], types='B' * 3) op.quadratic_constraints.add( lin_expr=SparsePair(ind=['x1', 'x3'], val=[1.0, -1.0]), @@ -67,7 +67,7 @@ def test_initial2(self): def test_initial3(self): """ test initial 3""" - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y']) with self.assertRaises(QiskitOptimizationError): op.quadratic_constraints.add(lin_expr=([0, 0], [1, 1])) @@ -75,7 +75,7 @@ def test_initial3(self): def test_get_num(self): """ test get num """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y']) lin = SparsePair(ind=['x'], val=[1.0]) q = SparseTriple(ind1=['x'], ind2=['y'], val=[1.0]) @@ -87,7 +87,7 @@ def test_get_num(self): def test_add(self): """ test add """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y']) lin = SparsePair(ind=['x'], val=[1.0]) q = SparseTriple(ind1=['x'], ind2=['y'], val=[1.0]) @@ -96,7 +96,7 @@ def test_add(self): def test_delete(self): """ test delete """ - op = OptimizationProblem() + op = QuadraticProgram() q_0 = [op.quadratic_constraints.add(name=str(i)) for i in range(10)] self.assertListEqual(q_0, list(range(10))) q = op.quadratic_constraints @@ -112,7 +112,7 @@ def test_delete(self): def test_get_rhs(self): """ test get rhs """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=[str(i) for i in range(10)]) q_0 = [op.quadratic_constraints.add(rhs=1.5 * i, name=str(i)) for i in range(10)] self.assertListEqual(q_0, list(range(10))) @@ -125,7 +125,7 @@ def test_get_rhs(self): def test_get_senses(self): """ test get senses """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=["x0"]) q = op.quadratic_constraints q_0 = [q.add(name=str(i), sense=j) for i, j in enumerate('GGLL')] @@ -137,7 +137,7 @@ def test_get_senses(self): def test_get_linear_num_nonzeros(self): """ test get linear num non zeros """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=[str(i) for i in range(11)], types="B" * 11) q = op.quadratic_constraints n = 10 @@ -152,7 +152,7 @@ def test_get_linear_num_nonzeros(self): def test_get_linear_components(self): """ test get linear components """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=[str(i) for i in range(4)], types="B" * 4) q = op.quadratic_constraints z = [q.add(name=str(i), @@ -186,7 +186,7 @@ def test_get_linear_components(self): def test_get_linear_components2(self): """ test get linear components 2 """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=[str(i) for i in range(11)], types="B" * 11) q = op.quadratic_constraints _ = [q.add(name=str(i), @@ -228,7 +228,7 @@ def test_get_linear_components2(self): def test_quad_num_nonzeros(self): """ test quad num non zeros """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=[str(i) for i in range(11)]) q = op.quadratic_constraints _ = [q.add(name=str(i), @@ -242,7 +242,7 @@ def test_quad_num_nonzeros(self): def test_get_quadratic_components(self): """ test get quadratic components """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=[str(i) for i in range(4)]) q = op.quadratic_constraints z = [q.add(name="q{0}".format(i), @@ -282,7 +282,7 @@ def test_get_quadratic_components(self): def test_get_quadratic_components2(self): """ test get quadratic components 2 """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=[str(i) for i in range(11)]) q = op.quadratic_constraints _ = [q.add(name=str(i), @@ -338,7 +338,7 @@ def test_get_quadratic_components2(self): def test_get_names(self): """ test get names """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=[str(i) for i in range(11)]) q = op.quadratic_constraints _ = [q.add(name="q" + str(i), @@ -353,7 +353,7 @@ def test_get_names(self): def test_empty_names(self): """ test empty names """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x']) op.quadratic_constraints.add(name='a') op.quadratic_constraints.add(name='') diff --git a/test/optimization/test_optimization_problem.py b/test/optimization/test_quadratic_program.py similarity index 93% rename from test/optimization/test_optimization_problem.py rename to test/optimization/test_quadratic_program.py index 085f846d9a..19a973944d 100644 --- a/test/optimization/test_optimization_problem.py +++ b/test/optimization/test_quadratic_program.py @@ -12,7 +12,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" Test OptimizationProblem """ +""" Test QuadraticProgram """ import os.path import tempfile @@ -21,12 +21,12 @@ from cplex import Cplex, SparsePair, SparseTriple, infinity -from qiskit.optimization import OptimizationProblem, QiskitOptimizationError -from qiskit.optimization.problems.optimization_problem import SubstitutionStatus +from qiskit.optimization import QuadraticProgram, QiskitOptimizationError +from qiskit.optimization.problems.quadratic_program import SubstitutionStatus -class TestOptimizationProblem(QiskitOptimizationTestCase): - """Test OptimizationProblem without the members that have separate test classes +class TestQuadraticProgram(QiskitOptimizationTestCase): + """Test QuadraticProgram without the members that have separate test classes (VariablesInterface, etc).""" def setUp(self): @@ -35,7 +35,7 @@ def setUp(self): def test_constructor1(self): """ test constructor """ - op = OptimizationProblem() + op = QuadraticProgram() self.assertEqual(op.get_problem_name(), '') op.variables.add(names=['x1', 'x2', 'x3']) self.assertEqual(op.variables.get_num(), 3) @@ -43,34 +43,34 @@ def test_constructor1(self): def test_constructor2(self): """ test constructor 2 """ with self.assertRaises(QiskitOptimizationError): - _ = OptimizationProblem("unknown") + _ = QuadraticProgram("unknown") # If filename does not exist, an exception is raised. def test_constructor_context(self): """ test constructor context """ - with OptimizationProblem() as op: + with QuadraticProgram() as op: op.variables.add(names=['x1', 'x2', 'x3']) self.assertEqual(op.variables.get_num(), 3) def test_end(self): """ test end """ - op = OptimizationProblem() + op = QuadraticProgram() self.assertIsNone(op.end()) def test_solve(self): """ test solve """ - op = OptimizationProblem() + op = QuadraticProgram() self.assertIsNone(op.solve()) def test_read1(self): """ test read 1""" - op = OptimizationProblem() + op = QuadraticProgram() op.read(self.resource_file) self.assertEqual(op.variables.get_num(), 3) def test_write1(self): """ test write 1 """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x1', 'x2', 'x3']) file, filename = tempfile.mkstemp(suffix='.lp') os.close(file) @@ -79,18 +79,18 @@ def test_write1(self): def test_write2(self): """ test write 2 """ - op1 = OptimizationProblem() + op1 = QuadraticProgram() op1.variables.add(names=['x1', 'x2', 'x3']) file, filename = tempfile.mkstemp(suffix='.lp') os.close(file) op1.write(filename) - op2 = OptimizationProblem() + op2 = QuadraticProgram() op2.read(filename) self.assertEqual(op2.variables.get_num(), 3) def test_write3(self): """ test write 3 """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x1', 'x2', 'x3']) class NoOpStream: @@ -118,28 +118,28 @@ def flush(self): def test_write4(self): """ test write 4 """ # Writes a problem as a string in the given file format. - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x1', 'x2', 'x3']) lp_str = op.write_as_string("lp") self.assertGreater(len(lp_str), 0) def test_problem_type1(self): """ test problem type 1 """ - op = OptimizationProblem() + op = QuadraticProgram() op.read(self.resource_file) self.assertEqual(op.get_problem_type(), op.problem_type.QP) self.assertEqual(op.problem_type[op.get_problem_type()], 'QP') def test_problem_type2(self): """ test problem type 2""" - op = OptimizationProblem() + op = QuadraticProgram() op.set_problem_type(op.problem_type.LP) self.assertEqual(op.get_problem_type(), op.problem_type.LP) self.assertEqual(op.problem_type[op.get_problem_type()], 'LP') def test_problem_type3(self): """ test problem type 3""" - op = OptimizationProblem() + op = QuadraticProgram() self.assertEqual(op.get_problem_type(), op.problem_type.LP) op.variables.add(names=['x1', 'x2', 'x3'], types='B' * 3) op.objective.set_linear([('x1', 2.0), ('x3', 0.5)]) @@ -170,7 +170,7 @@ def test_problem_type3(self): def test_problem_name(self): """ test problem name """ - op = OptimizationProblem() + op = QuadraticProgram() op.set_problem_name("test") # test self.assertEqual(op.get_problem_name(), "test") @@ -201,7 +201,7 @@ def test_from_and_to_cplex(self): rhs=1.0 ) orig = op.write_as_string() - op2 = OptimizationProblem() + op2 = QuadraticProgram() op2.from_cplex(op) self.assertEqual(op2.write_as_string(), orig) op3 = op2.to_cplex() @@ -209,7 +209,7 @@ def test_from_and_to_cplex(self): op.set_problem_name('test') orig = op.write_as_string() - op2 = OptimizationProblem() + op2 = QuadraticProgram() op2.from_cplex(op) self.assertEqual(op2.write_as_string(), orig) op3 = op2.to_cplex() @@ -217,7 +217,7 @@ def test_from_and_to_cplex(self): def test_substitute_variables_bounds1(self): """ test substitute variables bounds 1 """ - op = OptimizationProblem() + op = QuadraticProgram() op.set_problem_name('before') n = 5 op.variables.add(names=['x' + str(i) for i in range(n)], types='I' * n, @@ -235,7 +235,7 @@ def test_substitute_variables_bounds1(self): def test_substitute_variables_bounds2(self): """ test substitute variables bounds 2 """ - op = OptimizationProblem() + op = QuadraticProgram() op.set_problem_name('before') n = 5 op.variables.add(names=['x' + str(i) for i in range(n)], types='I' * n, @@ -253,7 +253,7 @@ def test_substitute_variables_bounds2(self): def test_substitute_variables_obj(self): """ test substitute variables objective """ - op = OptimizationProblem() + op = QuadraticProgram() op.set_problem_name('before') op.variables.add(names=['x1', 'x2', 'x3'], types='I' * 3, lb=[-2] * 3, ub=[4] * 3) op.objective.set_linear([('x1', 1.0), ('x2', 2.0)]) @@ -274,7 +274,7 @@ def test_substitute_variables_obj(self): def test_substitute_variables_lin_cst1(self): """ test substitute variables linear constraints 1 """ - op = OptimizationProblem() + op = QuadraticProgram() n = 5 op.variables.add(names=['x' + str(i) for i in range(n)], types='I' * n, lb=[-10] * n, ub=[14] * n) @@ -315,7 +315,7 @@ def test_substitute_variables_lin_cst1(self): def test_substitute_variables_lin_cst2(self): """ test substitute variables linear constraints 2 """ - op = OptimizationProblem() + op = QuadraticProgram() n = 3 op.variables.add(names=['x' + str(i) for i in range(n)], types='I' * n, lb=[-2] * n, ub=[4] * n) @@ -347,7 +347,7 @@ def test_substitute_variables_lin_cst2(self): def test_substitute_variables_quad_cst1(self): """ test substitute variables quadratic constraints 1 """ - op = OptimizationProblem() + op = QuadraticProgram() n = 3 op.variables.add(names=['x' + str(i) for i in range(n)], types='I' * n, lb=[-2] * n, ub=[4] * n) diff --git a/test/optimization/test_optimization_problem_to_negative_value_oracle.py b/test/optimization/test_quadratic_program_to_negative_value_oracle.py similarity index 90% rename from test/optimization/test_optimization_problem_to_negative_value_oracle.py rename to test/optimization/test_quadratic_program_to_negative_value_oracle.py index 284c3c7661..4dd240a660 100644 --- a/test/optimization/test_optimization_problem_to_negative_value_oracle.py +++ b/test/optimization/test_quadratic_program_to_negative_value_oracle.py @@ -17,13 +17,13 @@ import unittest from test.optimization import QiskitOptimizationTestCase import numpy as np -from qiskit.optimization.converters import OptimizationProblemToNegativeValueOracle +from qiskit.optimization.converters import QuadraticProgramToNegativeValueOracle from qiskit.optimization.util import get_qubo_solutions from qiskit import QuantumCircuit, Aer, execute -from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.problems import QuadraticProgram -class TestOptimizationProblemToNegativeValueOracle(QiskitOptimizationTestCase): +class TestQuadraticProgramToNegativeValueOracle(QiskitOptimizationTestCase): """OPtNVO Tests""" def _validate_function(self, func_dict, problem): @@ -84,7 +84,7 @@ def test_optnvo_3_linear_2_quadratic_no_constant(self): num_value = 4 # Input. - problem = OptimizationProblem() + problem = QuadraticProgram() problem.variables.add(names=['x0', 'x1', 'x2'], types='BBB') linear = [('x0', -1), ('x1', 2), ('x2', -3)] problem.objective.set_linear(linear) @@ -92,7 +92,7 @@ def test_optnvo_3_linear_2_quadratic_no_constant(self): problem.objective.set_quadratic_coefficients('x1', 'x2', -1) # Convert to dictionary format with operator/oracle. - converter = OptimizationProblemToNegativeValueOracle(num_value) + converter = QuadraticProgramToNegativeValueOracle(num_value) a_operator, _, func_dict = converter.encode(problem) self._validate_function(func_dict, problem) @@ -104,7 +104,7 @@ def test_optnvo_4_key_all_negative(self): num_value = 5 # Input. - problem = OptimizationProblem() + problem = QuadraticProgram() problem.variables.add(names=['x0', 'x1', 'x2'], types='BBB') linear = [('x0', -1), ('x1', -2), ('x2', -1)] problem.objective.set_linear(linear) @@ -114,7 +114,7 @@ def test_optnvo_4_key_all_negative(self): problem.objective.set_offset(-1) # Convert to dictionary format with operator/oracle. - converter = OptimizationProblemToNegativeValueOracle(num_value) + converter = QuadraticProgramToNegativeValueOracle(num_value) a_operator, _, func_dict = converter.encode(problem) self._validate_function(func_dict, problem) @@ -126,7 +126,7 @@ def test_optnvo_6_key(self): num_value = 4 # Input. - problem = OptimizationProblem() + problem = QuadraticProgram() problem.variables.add(names=['x0', 'x1', 'x2', 'x3', 'x4', 'x5'], types='BBBBBB') linear = [('x0', -1), ('x1', -2), ('x2', -1), ('x3', 0), ('x4', 1), ('x5', 2)] problem.objective.set_linear(linear) @@ -134,7 +134,7 @@ def test_optnvo_6_key(self): problem.objective.set_quadratic_coefficients('x1', 'x5', -2) # Convert to dictionary format with operator/oracle. - converter = OptimizationProblemToNegativeValueOracle(num_value) + converter = QuadraticProgramToNegativeValueOracle(num_value) a_operator, _, func_dict = converter.encode(problem) self._validate_function(func_dict, problem) diff --git a/test/optimization/test_recursive_optimization.py b/test/optimization/test_recursive_optimization.py index 66c09d7942..c3c0f43cc7 100755 --- a/test/optimization/test_recursive_optimization.py +++ b/test/optimization/test_recursive_optimization.py @@ -26,7 +26,7 @@ from qiskit.optimization.algorithms import (MinimumEigenOptimizer, CplexOptimizer, RecursiveMinimumEigenOptimizer) -from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.problems import QuadraticProgram @ddt @@ -81,7 +81,7 @@ def test_recursive_min_eigen_optimizer(self, solver, simulator, filename): min_num_vars=4) # load optimization problem - problem = OptimizationProblem() + problem = QuadraticProgram() problem.read(self.resource_path + filename) # solve problem with cplex diff --git a/test/optimization/test_variables.py b/test/optimization/test_variables.py index 89cbc0667f..2481b30eb0 100644 --- a/test/optimization/test_variables.py +++ b/test/optimization/test_variables.py @@ -19,7 +19,7 @@ from cplex import infinity -from qiskit.optimization import OptimizationProblem, QiskitOptimizationError +from qiskit.optimization import QuadraticProgram, QiskitOptimizationError class TestVariables(QiskitOptimizationTestCase): @@ -27,13 +27,13 @@ class TestVariables(QiskitOptimizationTestCase): def test_type(self): """ test type """ - op = OptimizationProblem() + op = QuadraticProgram() self.assertEqual(op.variables.type.binary, 'B') self.assertEqual(op.variables.type['B'], 'binary') def test_initial(self): """ test initial """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=["x0", "x1", "x2"]) self.assertListEqual(op.variables.get_lower_bounds(), [0.0, 0.0, 0.0]) op.variables.set_lower_bounds(0, 1.0) @@ -46,49 +46,49 @@ def test_initial(self): def test_get_num(self): """ test get num """ - op = OptimizationProblem() + op = QuadraticProgram() typ = op.variables.type op.variables.add(types=[typ.continuous, typ.binary, typ.integer]) self.assertEqual(op.variables.get_num(), 3) def test_get_num_continuous(self): """ test get num continuous """ - op = OptimizationProblem() + op = QuadraticProgram() typ = op.variables.type op.variables.add(types=[typ.continuous, typ.binary, typ.integer]) self.assertEqual(op.variables.get_num_continuous(), 1) def test_get_num_integer(self): """ test get num integer """ - op = OptimizationProblem() + op = QuadraticProgram() typ = op.variables.type op.variables.add(types=[typ.continuous, typ.binary, typ.integer]) self.assertEqual(op.variables.get_num_integer(), 1) def test_get_num_binary(self): """ test get num binary """ - op = OptimizationProblem() + op = QuadraticProgram() typ = op.variables.type op.variables.add(types=[typ.semi_continuous, typ.binary, typ.integer]) self.assertEqual(op.variables.get_num_binary(), 1) def test_get_num_semicontinuous(self): """ test get num semi continuous """ - op = OptimizationProblem() + op = QuadraticProgram() typ = op.variables.type op.variables.add(types=[typ.semi_continuous, typ.semi_integer, typ.semi_integer]) self.assertEqual(op.variables.get_num_semicontinuous(), 1) def test_get_num_semiinteger(self): """ test get num semi integer """ - op = OptimizationProblem() + op = QuadraticProgram() typ = op.variables.type op.variables.add(types=[typ.semi_continuous, typ.semi_integer, typ.semi_integer]) self.assertEqual(op.variables.get_num_semiinteger(), 2) def test_add(self): """ add test """ - op = OptimizationProblem() + op = QuadraticProgram() op.linear_constraints.add(names=["c0", "c1", "c2"]) op.variables.add(types=[op.variables.type.integer] * 3) op.variables.add( @@ -106,7 +106,7 @@ def test_add(self): def test_delete(self): """ test delete """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=[str(i) for i in range(10)]) self.assertEqual(op.variables.get_num(), 10) self.assertListEqual(op.variables.get_names(), @@ -123,7 +123,7 @@ def test_delete(self): def test_set_lower_bounds(self): """ test set lower bounds """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=["x0", "x1", "x2"]) op.variables.set_lower_bounds(0, 1.0) self.assertListEqual(op.variables.get_lower_bounds(), [1.0, 0.0, 0.0]) @@ -132,7 +132,7 @@ def test_set_lower_bounds(self): def test_set_upper_bounds(self): """ test set upper bounds """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=["x0", "x1", "x2"]) op.variables.set_upper_bounds(0, 1.0) op.variables.set_upper_bounds([("x1", 10.0), (2, 3.0)]) @@ -140,7 +140,7 @@ def test_set_upper_bounds(self): def test_set_names(self): """ test set names """ - op = OptimizationProblem() + op = QuadraticProgram() typ = op.variables.type op.variables.add(types=[typ.continuous, typ.binary, typ.integer]) op.variables.set_names(0, "first") @@ -149,7 +149,7 @@ def test_set_names(self): def test_set_types(self): """ test set types """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=[str(i) for i in range(5)]) op.variables.set_types(0, op.variables.type.continuous) op.variables.set_types([("1", op.variables.type.integer), @@ -161,7 +161,7 @@ def test_set_types(self): def test_get_lower_bounds(self): """ test get lower bounds """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(lb=[1.5 * i for i in range(10)], names=[str(i) for i in range(10)]) self.assertEqual(op.variables.get_num(), 10) @@ -173,7 +173,7 @@ def test_get_lower_bounds(self): def test_get_upper_bounds(self): """ test get upper bounds """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(ub=[(1.5 * i) + 1.0 for i in range(10)], names=[str(i) for i in range(10)]) self.assertEqual(op.variables.get_num(), 10) @@ -185,7 +185,7 @@ def test_get_upper_bounds(self): def test_get_names(self): """ test get names """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x' + str(i) for i in range(10)]) self.assertEqual(op.variables.get_num(), 10) self.assertEqual(op.variables.get_names(8), 'x8') @@ -196,7 +196,7 @@ def test_get_names(self): def test_set_types2(self): """ test set types 2 """ - op = OptimizationProblem() + op = QuadraticProgram() typ = op.variables.type op.variables.add(names=[str(i) for i in range(5)], types=[typ.continuous, typ.integer, @@ -215,26 +215,26 @@ def test_set_types2(self): def test_get_cols(self): """ test get cols """ - op = OptimizationProblem() + op = QuadraticProgram() with self.assertRaises(NotImplementedError): op.variables.get_cols() def test_get_obj(self): """ test get obj """ - op = OptimizationProblem() + op = QuadraticProgram() with self.assertRaises(NotImplementedError): op.variables.get_obj() def test_get_indices(self): """ test get indices """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['a', 'b']) self.assertEqual(op.variables.get_indices('a'), 0) self.assertListEqual(op.variables.get_indices(['a', 'b']), [0, 1]) def test_add2(self): """ test add2 """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x']) self.assertEqual(op.variables.get_indices('x'), 0) self.assertListEqual(op.variables.get_indices(), [0]) @@ -245,7 +245,7 @@ def test_add2(self): def test_default_bounds(self): """ test default bounds """ - op = OptimizationProblem() + op = QuadraticProgram() types = ['B', 'I', 'C', 'S', 'N'] op.variables.add(names=types, types=types) self.assertListEqual(op.variables.get_lower_bounds(), [0.0] * 5) @@ -254,7 +254,7 @@ def test_default_bounds(self): def test_empty_names(self): """ test empty names """ - op = OptimizationProblem() + op = QuadraticProgram() r = op.variables.add(names=['', '', '']) self.assertEqual(r, range(0, 3)) self.assertListEqual(op.variables.get_names(), ['x1', 'x2', 'x3']) @@ -265,7 +265,7 @@ def test_empty_names(self): def test_duplicate_names(self): """ test duplicate names """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['', '', '']) with self.assertRaises(QiskitOptimizationError): op.variables.add(names=['a', 'a']) @@ -274,7 +274,7 @@ def test_duplicate_names(self): def test_add3(self): """ test add 3 """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['c'], types=['C'], lb=[-10], ub=[10]) op.variables.add(names=['b'], types=['B'], lb=[-10], ub=[10]) op.variables.add(names=['b2'], types=['B']) From e5b2cec81fecfd563afe4f543bcff8c58b6f1346 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Wed, 8 Apr 2020 17:21:01 -0400 Subject: [PATCH 163/323] test cplex installation --- .../optimization/algorithms/admm_optimizer.py | 17 +- .../algorithms/cplex_optimizer.py | 24 +- .../recursive_minimum_eigen_optimizer.py | 14 +- .../inequality_to_equality_converter.py | 14 +- .../converters/integer_to_binary_converter.py | 15 +- .../problems/linear_constraint.py | 19 +- qiskit/optimization/problems/objective.py | 19 +- .../problems/optimization_problem.py | 31 ++- .../problems/quadratic_constraint.py | 23 +- .../finance/test_portfolio_diversification.py | 27 ++- test/optimization/test_admm.py | 207 +++++++++--------- test/optimization/test_cobyla_optimizer.py | 14 +- test/optimization/test_converters.py | 16 +- test/optimization/test_cplex_optimizer.py | 9 +- .../test_grover_minimum_finder.py | 84 +++---- test/optimization/test_linear_constraints.py | 17 +- test/optimization/test_min_eigen_optimizer.py | 40 ++-- test/optimization/test_objective.py | 17 +- .../optimization/test_optimization_problem.py | 15 +- ...zation_problem_to_negative_value_oracle.py | 111 +++++----- .../test_quadratic_constraints.py | 17 +- .../test_recursive_optimization.py | 50 +++-- test/optimization/test_variables.py | 17 +- 23 files changed, 519 insertions(+), 298 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index b2fbed0365..8c4cfcb1b0 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -16,9 +16,8 @@ import logging import time from typing import List, Optional, Any - import numpy as np -from cplex import SparsePair + from qiskit.optimization.algorithms.cplex_optimizer import CplexOptimizer from qiskit.optimization.algorithms.optimization_algorithm import OptimizationAlgorithm from qiskit.optimization.problems.optimization_problem import OptimizationProblem @@ -28,6 +27,15 @@ UPDATE_RHO_BY_TEN_PERCENT = 0 UPDATE_RHO_BY_RESIDUALS = 1 +logger = logging.getLogger(__name__) + +_HAS_CPLEX = False +try: + from cplex import SparsePair + _HAS_CPLEX = True +except ImportError: + logger.info('CPLEX is not installed.') + class ADMMParameters: """Defines a set of parameters for ADMM optimizer.""" @@ -180,7 +188,12 @@ def __init__(self, qubo_optimizer: Optional[OptimizationAlgorithm] = None, continuous_optimizer: An instance of OptimizationAlgorithm that can solve continuous problems. params: An instance of ADMMParameters. + + Raises: + NameError: CPLEX is not installed. """ + if not _HAS_CPLEX: + raise NameError('CPLEX is not installed.') super().__init__() self._log = logging.getLogger(__name__) diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index e84e59c4aa..cb10dcfcdc 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -23,14 +23,22 @@ """ from typing import Optional -from cplex import ParameterSet -from cplex.exceptions import CplexSolverError +import logging from .optimization_algorithm import OptimizationAlgorithm from ..utils.qiskit_optimization_error import QiskitOptimizationError from ..results.optimization_result import OptimizationResult from ..problems.optimization_problem import OptimizationProblem +logger = logging.getLogger(__name__) + +_HAS_CPLEX = False +try: + from cplex.exceptions import CplexSolverError + _HAS_CPLEX = True +except ImportError: + logger.info('CPLEX is not installed.') + class CplexOptimizer(OptimizationAlgorithm): """The CPLEX optimizer wrapped to be used within the Qiskit Optimization. @@ -41,7 +49,7 @@ class CplexOptimizer(OptimizationAlgorithm): TODO: The arguments for ``Cplex`` are passed via the constructor. """ - def __init__(self, parameter_set: Optional[ParameterSet] = None) -> None: + def __init__(self, parameter_set: Optional['ParameterSet'] = None) -> None: """Initializes the CplexOptimizer. TODO: This initializer takes the algorithmic parameters of CPLEX and stores them for later @@ -49,11 +57,17 @@ def __init__(self, parameter_set: Optional[ParameterSet] = None) -> None: Args: parameter_set: The CPLEX parameter set + + Raises: + NameError: CPLEX is not installed. """ + if not _HAS_CPLEX: + raise NameError('CPLEX is not installed.') + self._parameter_set = parameter_set @property - def parameter_set(self) -> Optional[ParameterSet]: + def parameter_set(self) -> Optional['ParameterSet']: """Returns the parameter set. Returns the algorithmic parameters for CPLEX. Returns: @@ -62,7 +76,7 @@ def parameter_set(self) -> Optional[ParameterSet]: return self._parameter_set @parameter_set.setter - def parameter_set(self, parameter_set: Optional[ParameterSet]): + def parameter_set(self, parameter_set: Optional['ParameterSet']): """Set the parameter set. Args: parameter_set: The new parameter set. diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index b2fd8c09de..9be5d4db65 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -25,6 +25,7 @@ from copy import deepcopy from typing import Optional +import logging import numpy as np from qiskit.aqua.algorithms import NumPyMinimumEigensolver @@ -37,6 +38,15 @@ from ..results.optimization_result import OptimizationResult from ..converters.optimization_problem_to_qubo import OptimizationProblemToQubo +logger = logging.getLogger(__name__) + +_HAS_CPLEX = False +try: + from cplex import SparseTriple + _HAS_CPLEX = True +except ImportError: + logger.info('CPLEX is not installed.') + class RecursiveMinimumEigenOptimizer(OptimizationAlgorithm): """ A meta-algorithm that applies the recursive optimization scheme introduce in @@ -65,11 +75,14 @@ def __init__(self, min_eigen_optimizer: MinimumEigenOptimizer, min_num_vars: int Raises: QiskitOptimizationError: In case of invalid parameters (num_min_vars < 1). + NameError: CPLEX is not installed. """ # TODO: should also allow function that maps problem to -correlators? # --> would support efficient classical implementation for QAOA with depth p=1 # --> add results class for MinimumEigenSolver that contains enough info to do so. + if not _HAS_CPLEX: + raise NameError('CPLEX is not installed.') validate_min('min_num_vars', min_num_vars, 1) @@ -110,7 +123,6 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: QiskitOptimizationError: Infeasible due to variable substitution """ - from cplex import SparseTriple # convert problem to QUBO qubo_converter = OptimizationProblemToQubo() problem_ = qubo_converter.encode(problem) diff --git a/qiskit/optimization/converters/inequality_to_equality_converter.py b/qiskit/optimization/converters/inequality_to_equality_converter.py index 37dec752e0..668da26504 100644 --- a/qiskit/optimization/converters/inequality_to_equality_converter.py +++ b/qiskit/optimization/converters/inequality_to_equality_converter.py @@ -17,13 +17,21 @@ import copy import math from typing import List, Tuple, Dict, Optional - -from cplex import SparsePair +import logging from ..problems.optimization_problem import OptimizationProblem from ..results.optimization_result import OptimizationResult from ..utils.qiskit_optimization_error import QiskitOptimizationError +logger = logging.getLogger(__name__) + +_HAS_CPLEX = False +try: + from cplex import SparsePair + _HAS_CPLEX = True +except ImportError: + logger.info('CPLEX is not installed.') + class InequalityToEqualityConverter: """Convert inequality constraints into equality constraints by introducing slack variables. @@ -39,6 +47,8 @@ class InequalityToEqualityConverter: def __init__(self) -> None: """Initialize the integral variables.""" + if not _HAS_CPLEX: + raise NameError('CPLEX is not installed.') self._src = None self._dst = None diff --git a/qiskit/optimization/converters/integer_to_binary_converter.py b/qiskit/optimization/converters/integer_to_binary_converter.py index 923776c2d8..875a0b7b9d 100644 --- a/qiskit/optimization/converters/integer_to_binary_converter.py +++ b/qiskit/optimization/converters/integer_to_binary_converter.py @@ -16,14 +16,22 @@ import copy from typing import Dict, List, Optional, Tuple - +import logging import numpy as np -from cplex import SparsePair from ..problems.optimization_problem import OptimizationProblem from ..results.optimization_result import OptimizationResult from ..utils.qiskit_optimization_error import QiskitOptimizationError +logger = logging.getLogger(__name__) + +_HAS_CPLEX = False +try: + from cplex import SparsePair + _HAS_CPLEX = True +except ImportError: + logger.info('CPLEX is not installed.') + class IntegerToBinaryConverter: """Convert an `OptimizationProblem` into new one by encoding integer with binary variables. @@ -39,6 +47,9 @@ class IntegerToBinaryConverter: def __init__(self) -> None: """Initializes the internal data structure.""" + if not _HAS_CPLEX: + raise NameError('CPLEX is not installed.') + self._src = None self._dst = None self._conv: Dict[str, List[Tuple[str, int]]] = {} diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index fd4c46e193..49971eef8d 100644 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -17,13 +17,21 @@ import copy from collections.abc import Sequence from typing import Callable, Optional, List, Union - -from cplex import SparsePair +import logging from qiskit.optimization.utils.base import BaseInterface from qiskit.optimization.utils.helpers import init_list_args, NameIndex from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError +logger = logging.getLogger(__name__) + +_HAS_CPLEX = False +try: + from cplex import SparsePair + _HAS_CPLEX = True +except ImportError: + logger.info('CPLEX is not installed.') + class LinearConstraintInterface(BaseInterface): """Methods for adding, modifying, and querying linear constraints.""" @@ -35,6 +43,9 @@ def __init__(self, varindex: Callable): `OptimizationProblem` class as `OptimizationProblem.linear_constraints`. This constructor is not meant to be used externally. """ + if not _HAS_CPLEX: + raise NameError('CPLEX is not installed.') + super(LinearConstraintInterface, self).__init__() self._rhs = [] self._senses = [] @@ -57,7 +68,7 @@ def get_num(self) -> int: """ return len(self._names) - def add(self, lin_expr: Optional[List[SparsePair]] = None, senses: str = "", + def add(self, lin_expr: Optional[List['SparsePair']] = None, senses: str = "", rhs: Optional[List[float]] = None, range_values: Optional[List[float]] = None, names: Optional[List[str]] = None) -> range: """Adds linear constraints to the problem. @@ -676,7 +687,7 @@ def _get(args): raise QiskitOptimizationError( "Wrong number of arguments. Please use 2 or one list of pairs: {}".format(args)) - def get_rows(self, *args) -> Union[SparsePair, List[SparsePair]]: + def get_rows(self, *args) -> Union['SparsePair', List['SparsePair']]: """Returns a set of rows of the linear constraint matrix. Returns a list of SparsePair instances or a single SparsePair diff --git a/qiskit/optimization/problems/objective.py b/qiskit/optimization/problems/objective.py index d0ea30e121..c70d091978 100644 --- a/qiskit/optimization/problems/objective.py +++ b/qiskit/optimization/problems/objective.py @@ -17,15 +17,21 @@ import copy import numbers from collections.abc import Sequence -from logging import getLogger from typing import Callable, List, Union, Dict, Tuple +import logging -from cplex import SparsePair from scipy.sparse import dok_matrix from qiskit.optimization.utils import BaseInterface, QiskitOptimizationError -logger = getLogger(__name__) +logger = logging.getLogger(__name__) + +_HAS_CPLEX = False +try: + from cplex import SparsePair + _HAS_CPLEX = True +except ImportError: + logger.info('CPLEX is not installed.') CPX_MAX = -1 CPX_MIN = 1 @@ -73,6 +79,9 @@ def __init__(self, varindex: Callable): `OptimizationProblem` class as `OptimizationProblem.objective`. This constructor is not meant to be used externally. """ + if not _HAS_CPLEX: + raise NameError('CPLEX is not installed.') + super(ObjectiveInterface, self).__init__() self._linear = {} self._quadratic = dok_matrix((0, 0)) @@ -121,7 +130,7 @@ def _set(i, v): self._setter(_set, *args) - def set_quadratic(self, coef: Union[List[float], List[SparsePair], List[Sequence]]): + def set_quadratic(self, coef: Union[List[float], List['SparsePair'], List[Sequence]]): """Sets the quadratic part of the objective function. Call this method with a list with length equal to the number @@ -344,7 +353,7 @@ def get_linear_dict(self) -> Dict[int, float]: """ return copy.deepcopy(self._linear) - def get_quadratic(self, *args) -> Union[SparsePair, List[SparsePair]]: + def get_quadratic(self, *args) -> Union['SparsePair', List['SparsePair']]: """Returns a set of columns of the quadratic component of the objective function. Returns a SparsePair instance or a list of SparsePair instances. diff --git a/qiskit/optimization/problems/optimization_problem.py b/qiskit/optimization/problems/optimization_problem.py index a637e6ae5a..239d2e6f01 100644 --- a/qiskit/optimization/problems/optimization_problem.py +++ b/qiskit/optimization/problems/optimization_problem.py @@ -15,13 +15,10 @@ """Mixed integer quadratically constrained quadratic program""" from enum import Enum -from logging import getLogger from math import fsum from typing import Optional, Tuple +import logging -from cplex import Cplex, SparsePair -from cplex import SparseTriple, infinity -from cplex.exceptions import CplexSolverError from docplex.mp.model import Model as DocplexModel from qiskit.optimization.problems.linear_constraint import LinearConstraintInterface @@ -32,7 +29,16 @@ from qiskit.optimization.results.solution import SolutionInterface from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError -logger = getLogger(__name__) +logger = logging.getLogger(__name__) + +_HAS_CPLEX = False +try: + from cplex import Cplex, SparsePair + from cplex import SparseTriple, infinity + from cplex.exceptions import CplexSolverError + _HAS_CPLEX = True +except ImportError: + logger.info('CPLEX is not installed.') class OptimizationProblem: @@ -53,6 +59,7 @@ def __init__(self, file_name: Optional[str] = None): Raises: QiskitOptimizationError: if it cannot load a file. + NameError: CPLEX is not installed. Examples: @@ -74,6 +81,8 @@ def __init__(self, file_name: Optional[str] = None): When the with block is finished, the end() method will be called automatically. """ + if not _HAS_CPLEX: + raise NameError('CPLEX is not installed.') self._name = '' @@ -100,7 +109,7 @@ def __init__(self, file_name: Optional[str] = None): except CplexSolverError: raise QiskitOptimizationError('Could not load file: {}'.format(file_name)) - def from_cplex(self, op: Cplex): + def from_cplex(self, op: 'Cplex'): """Loads an optimization problem from a Cplex object Args: @@ -168,7 +177,7 @@ def from_docplex(self, model: DocplexModel): # Docplex does not copy the model name. We need to do it manually. self.set_problem_name(model.get_name()) - def to_cplex(self) -> Cplex: + def to_cplex(self) -> 'Cplex': """Converts the optimization problem into a Cplex object. Returns: Cplex object @@ -376,8 +385,8 @@ def get_problem_name(self) -> str: """Returns the problem name""" return self._name - def substitute_variables(self, constants: Optional[SparsePair] = None, - variables: Optional[SparseTriple] = None) \ + def substitute_variables(self, constants: Optional['SparsePair'] = None, + variables: Optional['SparseTriple'] = None) \ -> Tuple['OptimizationProblem', 'SubstitutionStatus']: """Substitutes variables with constants or other variables. @@ -482,8 +491,8 @@ def __init__(self): self._subs = {} def substitute_variables(self, src: OptimizationProblem, - constants: Optional[SparsePair] = None, - variables: Optional[SparseTriple] = None) \ + constants: Optional['SparsePair'] = None, + variables: Optional['SparseTriple'] = None) \ -> Tuple[OptimizationProblem, SubstitutionStatus]: """Substitutes variables with constants or other variables. diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index 9290f71fdd..34344c0bed 100644 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -16,17 +16,23 @@ import copy from collections.abc import Sequence -from logging import getLogger +import logging from typing import List, Dict, Callable, Union, Optional -from cplex import SparsePair, SparseTriple from scipy.sparse import dok_matrix from qiskit.optimization.utils.base import BaseInterface from qiskit.optimization.utils.helpers import NameIndex from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError -logger = getLogger(__name__) +logger = logging.getLogger(__name__) + +_HAS_CPLEX = False +try: + from cplex import SparsePair, SparseTriple + _HAS_CPLEX = True +except ImportError: + logger.info('CPLEX is not installed.') class QuadraticConstraintInterface(BaseInterface): @@ -39,6 +45,9 @@ def __init__(self, varindex: Callable): `OptimizationProblem` class as `OptimizationProblem.quadratic_constraints`. This constructor is not meant to be used externally. """ + if not _HAS_CPLEX: + raise NameError('CPLEX is not installed.') + super().__init__() self._rhs = [] self._senses = [] @@ -66,7 +75,9 @@ def get_num(self) -> int: """ return len(self._names) - def add(self, lin_expr: Optional[SparsePair] = None, quad_expr: Optional[SparseTriple] = None, + def add(self, + lin_expr: Optional['SparsePair'] = None, + quad_expr: Optional['SparseTriple'] = None, sense: str = "L", rhs: float = 0.0, name: str = "") -> int: """Adds a quadratic constraint to the problem. @@ -412,7 +423,7 @@ def _nonzero(i) -> int: keys = self._index.convert(*args) return self._getter(_nonzero, keys) - def get_linear_components(self, *args) -> Union[SparsePair, List[SparsePair]]: + def get_linear_components(self, *args) -> Union['SparsePair', List['SparsePair']]: """Returns the linear part of a set of quadratic constraints. Returns a list of SparsePair instances or one SparsePair instance. @@ -533,7 +544,7 @@ def _nonzero(i) -> int: keys = self._index.convert(*args) return self._getter(_nonzero, keys) - def get_quadratic_components(self, *args) -> Union[SparseTriple, List[SparseTriple]]: + def get_quadratic_components(self, *args) -> Union['SparseTriple', List['SparseTriple']]: """Returns the quadratic part of a set of quadratic constraints. Can be called by four forms. diff --git a/test/finance/test_portfolio_diversification.py b/test/finance/test_portfolio_diversification.py index dfd5d4c694..5c093f53c9 100644 --- a/test/finance/test_portfolio_diversification.py +++ b/test/finance/test_portfolio_diversification.py @@ -17,8 +17,9 @@ import unittest import math from test.finance import QiskitFinanceTestCase - +import logging import numpy as np + from qiskit.quantum_info import Pauli from qiskit.aqua import aqua_globals @@ -28,6 +29,15 @@ get_operator, get_portfoliodiversification_value) +logger = logging.getLogger(__name__) + +_HAS_CPLEX = False +try: + import cplex + _HAS_CPLEX = True +except ImportError: + logger.info('CPLEX is not installed.') + class ClassicalOptimizer: """ Classical Optimizer """ @@ -43,10 +53,6 @@ def _compute_allowed_combinations(self): def cplex_solution(self): """ cplex solution """ - try: - import cplex # pylint: disable=import-outside-toplevel - except ImportError as ex: - print(str(ex)) # refactoring rho = self.rho @@ -226,13 +232,12 @@ def test_portfolio_diversification(self): # Something of an integration test # Solve the problem in a classical fashion via CPLEX and compare the solution # Note that CPLEX uses a completely different integer linear programming formulation. + if not _HAS_CPLEX: + self.skipTest('CPLEX is not installed.') x = None - try: - classical_optimizer = ClassicalOptimizer(self.instance, self.n, self.q) - x, classical_cost = classical_optimizer.cplex_solution() - except Exception: # pylint: disable=broad-except - # This test should not focus on the availability of CPLEX, so we just eat the exception. - self.skipTest("CPLEX may be missing.") + classical_optimizer = ClassicalOptimizer(self.instance, self.n, self.q) + x, classical_cost = classical_optimizer.cplex_solution() + # Solve the problem using the exact eigensolver result = NumPyMinimumEigensolver(self.qubit_op).run() quantum_solution = get_portfoliodiversification_solution(self.instance, diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index 3b32c0305c..f6f1435b91 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -32,32 +32,35 @@ class TestADMMOptimizer(QiskitOptimizationTestCase): def test_admm_maximization(self): """Tests a simple maximization problem using ADMM optimizer""" - mdl = Model('test') - c = mdl.continuous_var(lb=0, ub=10, name='c') - x = mdl.binary_var(name='x') - mdl.maximize(c + x * x) - op = OptimizationProblem() - op.from_docplex(mdl) - self.assertIsNotNone(op) - - admm_params = ADMMParameters() - - qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) - continuous_optimizer = CplexOptimizer() - - solver = ADMMOptimizer(qubo_optimizer=qubo_optimizer, - continuous_optimizer=continuous_optimizer, - params=admm_params) - solution: ADMMOptimizerResult = solver.solve(op) - self.assertIsNotNone(solution) - self.assertIsInstance(solution, ADMMOptimizerResult) - - self.assertIsNotNone(solution.x) - np.testing.assert_almost_equal([10, 0], solution.x, 3) - self.assertIsNotNone(solution.fval) - np.testing.assert_almost_equal(10, solution.fval, 3) - self.assertIsNotNone(solution.state) - self.assertIsInstance(solution.state, ADMMState) + try: + mdl = Model('test') + c = mdl.continuous_var(lb=0, ub=10, name='c') + x = mdl.binary_var(name='x') + mdl.maximize(c + x * x) + op = OptimizationProblem() + op.from_docplex(mdl) + self.assertIsNotNone(op) + + admm_params = ADMMParameters() + + qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) + continuous_optimizer = CplexOptimizer() + + solver = ADMMOptimizer(qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer, + params=admm_params) + solution: ADMMOptimizerResult = solver.solve(op) + self.assertIsNotNone(solution) + self.assertIsInstance(solution, ADMMOptimizerResult) + + self.assertIsNotNone(solution.x) + np.testing.assert_almost_equal([10, 0], solution.x, 3) + self.assertIsNotNone(solution.fval) + np.testing.assert_almost_equal(10, solution.fval, 3) + self.assertIsNotNone(solution.state) + self.assertIsInstance(solution.state, ADMMState) + except NameError as ex: + self.skipTest(str(ex)) def test_admm_ex6(self): """Example 6 as a unit test. Example 6 is reported in: @@ -65,42 +68,45 @@ def test_admm_ex6(self): Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical and Quantum Computers. arXiv preprint arXiv:2001.02069.""" - mdl = Model('ex6') - - # pylint:disable=invalid-name - v = mdl.binary_var(name='v') - w = mdl.binary_var(name='w') - t = mdl.binary_var(name='t') - u = mdl.continuous_var(name='u') - - mdl.minimize(v + w + t + 5 * (u - 2) ** 2) - mdl.add_constraint(v + 2 * w + t + u <= 3, "cons1") - mdl.add_constraint(v + w + t >= 1, "cons2") - mdl.add_constraint(v + w == 1, "cons3") - - op = OptimizationProblem() - op.from_docplex(mdl) - - qubo_optimizer = CplexOptimizer() - continuous_optimizer = CplexOptimizer() - - admm_params = ADMMParameters( - rho_initial=1001, beta=1000, factor_c=900, - max_iter=100, three_block=True, tol=1.e-6 - ) - - solver = ADMMOptimizer(params=admm_params, qubo_optimizer=qubo_optimizer, - continuous_optimizer=continuous_optimizer) - solution = solver.solve(op) - self.assertIsNotNone(solution) - self.assertIsInstance(solution, ADMMOptimizerResult) - - self.assertIsNotNone(solution.x) - np.testing.assert_almost_equal([1., 0., 0., 2.], solution.x, 3) - self.assertIsNotNone(solution.fval) - np.testing.assert_almost_equal(1., solution.fval, 3) - self.assertIsNotNone(solution.state) - self.assertIsInstance(solution.state, ADMMState) + try: + mdl = Model('ex6') + + # pylint:disable=invalid-name + v = mdl.binary_var(name='v') + w = mdl.binary_var(name='w') + t = mdl.binary_var(name='t') + u = mdl.continuous_var(name='u') + + mdl.minimize(v + w + t + 5 * (u - 2) ** 2) + mdl.add_constraint(v + 2 * w + t + u <= 3, "cons1") + mdl.add_constraint(v + w + t >= 1, "cons2") + mdl.add_constraint(v + w == 1, "cons3") + + op = OptimizationProblem() + op.from_docplex(mdl) + + qubo_optimizer = CplexOptimizer() + continuous_optimizer = CplexOptimizer() + + admm_params = ADMMParameters( + rho_initial=1001, beta=1000, factor_c=900, + max_iter=100, three_block=True, tol=1.e-6 + ) + + solver = ADMMOptimizer(params=admm_params, qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer) + solution = solver.solve(op) + self.assertIsNotNone(solution) + self.assertIsInstance(solution, ADMMOptimizerResult) + + self.assertIsNotNone(solution.x) + np.testing.assert_almost_equal([1., 0., 0., 2.], solution.x, 3) + self.assertIsNotNone(solution.fval) + np.testing.assert_almost_equal(1., solution.fval, 3) + self.assertIsNotNone(solution.state) + self.assertIsInstance(solution.state, ADMMState) + except NameError as ex: + self.skipTest(str(ex)) def test_admm_ex5(self): """Example 5 as a unit test. Example 5 is reported in: @@ -108,43 +114,46 @@ def test_admm_ex5(self): Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical and Quantum Computers. arXiv preprint arXiv:2001.02069.""" - mdl = Model('ex5') - - # pylint:disable=invalid-name - v = mdl.binary_var(name='v') - w = mdl.binary_var(name='w') - t = mdl.binary_var(name='t') - - mdl.minimize(v + w + t) - mdl.add_constraint(2 * v + 2 * w + t <= 3, "cons1") - mdl.add_constraint(v + w + t >= 1, "cons2") - mdl.add_constraint(v + w == 1, "cons3") - - op = OptimizationProblem() - op.from_docplex(mdl) - - qubo_optimizer = CplexOptimizer() - - continuous_optimizer = CplexOptimizer() - - admm_params = ADMMParameters( - rho_initial=1001, beta=1000, factor_c=900, - max_iter=100, three_block=False - ) - - solver = ADMMOptimizer(params=admm_params, qubo_optimizer=qubo_optimizer, - continuous_optimizer=continuous_optimizer, ) - solution = solver.solve(op) - - self.assertIsNotNone(solution) - self.assertIsInstance(solution, ADMMOptimizerResult) - - self.assertIsNotNone(solution.x) - np.testing.assert_almost_equal([1., 0., 1.], solution.x, 3) - self.assertIsNotNone(solution.fval) - np.testing.assert_almost_equal(2., solution.fval, 3) - self.assertIsNotNone(solution.state) - self.assertIsInstance(solution.state, ADMMState) + try: + mdl = Model('ex5') + + # pylint:disable=invalid-name + v = mdl.binary_var(name='v') + w = mdl.binary_var(name='w') + t = mdl.binary_var(name='t') + + mdl.minimize(v + w + t) + mdl.add_constraint(2 * v + 2 * w + t <= 3, "cons1") + mdl.add_constraint(v + w + t >= 1, "cons2") + mdl.add_constraint(v + w == 1, "cons3") + + op = OptimizationProblem() + op.from_docplex(mdl) + + qubo_optimizer = CplexOptimizer() + + continuous_optimizer = CplexOptimizer() + + admm_params = ADMMParameters( + rho_initial=1001, beta=1000, factor_c=900, + max_iter=100, three_block=False + ) + + solver = ADMMOptimizer(params=admm_params, qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer, ) + solution = solver.solve(op) + + self.assertIsNotNone(solution) + self.assertIsInstance(solution, ADMMOptimizerResult) + + self.assertIsNotNone(solution.x) + np.testing.assert_almost_equal([1., 0., 1.], solution.x, 3) + self.assertIsNotNone(solution.fval) + np.testing.assert_almost_equal(2., solution.fval, 3) + self.assertIsNotNone(solution.state) + self.assertIsInstance(solution.state, ADMMState) + except NameError as ex: + self.skipTest(str(ex)) if __name__ == '__main__': diff --git a/test/optimization/test_cobyla_optimizer.py b/test/optimization/test_cobyla_optimizer.py index a1e607bb0a..b7ccfb4af7 100644 --- a/test/optimization/test_cobyla_optimizer.py +++ b/test/optimization/test_cobyla_optimizer.py @@ -16,12 +16,21 @@ import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase - +import logging from ddt import ddt, data from qiskit.optimization.algorithms import CobylaOptimizer from qiskit.optimization.problems import OptimizationProblem +logger = logging.getLogger(__name__) + +_HAS_CPLEX = False +try: + from cplex import SparsePair, SparseTriple + _HAS_CPLEX = True +except ImportError: + logger.info('CPLEX is not installed.') + @ddt class TestCobylaOptimizer(QiskitOptimizationTestCase): @@ -29,6 +38,8 @@ class TestCobylaOptimizer(QiskitOptimizationTestCase): def setUp(self): super().setUp() + if not _HAS_CPLEX: + self.skipTest('CPLEX is not installed.') self.resource_path = './test/optimization/resources/' self.cobyla_optimizer = CobylaOptimizer() @@ -54,7 +65,6 @@ def test_cobyla_optimizer(self, config): def test_cobyla_optimizer_with_quadratic_constraint(self): """ Cobyla Optimizer Test """ - from cplex import SparsePair, SparseTriple # load optimization problem problem = OptimizationProblem() problem.variables.add(lb=[0, 0], ub=[1, 1], types='CC') diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index 64e98e767d..a7477e2953 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -16,7 +16,7 @@ import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase -from cplex import SparsePair +import logging from qiskit.optimization import OptimizationProblem, QiskitOptimizationError from qiskit.optimization.results import OptimizationResult @@ -25,6 +25,15 @@ from qiskit.aqua.operators import WeightedPauliOperator from qiskit.quantum_info import Pauli +logger = logging.getLogger(__name__) + +_HAS_CPLEX = False +try: + from cplex import SparsePair + _HAS_CPLEX = True +except ImportError: + logger.info('CPLEX is not installed.') + QUBIT_OP_MAXIMIZE_SAMPLE = WeightedPauliOperator( paulis=[[(-199999.5+0j), Pauli(z=[True, False, False, False], x=[False, False, False, False])], @@ -52,6 +61,11 @@ class TestConverters(QiskitOptimizationTestCase): """Test Converters""" + def setUp(self) -> None: + super().setUp() + if not _HAS_CPLEX: + self.skipTest('CPLEX is not installed.') + def test_empty_problem(self): """ Test empty problem """ op = OptimizationProblem() diff --git a/test/optimization/test_cplex_optimizer.py b/test/optimization/test_cplex_optimizer.py index 3444b8b170..26437f4b5d 100644 --- a/test/optimization/test_cplex_optimizer.py +++ b/test/optimization/test_cplex_optimizer.py @@ -27,9 +27,11 @@ class TestCplexOptimizer(QiskitOptimizationTestCase): def setUp(self): super().setUp() - - self.resource_path = './test/optimization/resources/' - self.cplex_optimizer = CplexOptimizer() + try: + self.resource_path = './test/optimization/resources/' + self.cplex_optimizer = CplexOptimizer() + except NameError as ex: + self.skipTest(str(ex)) @data( ('op_ip1.lp', [0, 2], 6), @@ -38,7 +40,6 @@ def setUp(self): ) def test_cplex_optimizer(self, config): """ Cplex Optimizer Test """ - # unpack configuration filename, x, fval = config diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index e434db4fbb..2c473f86cb 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -45,51 +45,57 @@ def validate_results(self, problem, results, max_iterations): def test_qubo_gas_int_zero(self): """Test for when the answer is zero.""" - - # Input. - op = OptimizationProblem() - op.variables.add(names=['x0', 'x1'], types='BB') - linear = [('x0', 0), ('x1', 0)] - op.objective.set_linear(linear) - - # Will not find a negative, should return 0. - gmf = GroverMinimumFinder(num_iterations=1) - results = gmf.solve(op) - self.assertEqual(results.x, [0, 0]) - self.assertEqual(results.fval, 0.0) + try: + # Input. + op = OptimizationProblem() + op.variables.add(names=['x0', 'x1'], types='BB') + linear = [('x0', 0), ('x1', 0)] + op.objective.set_linear(linear) + + # Will not find a negative, should return 0. + gmf = GroverMinimumFinder(num_iterations=1) + results = gmf.solve(op) + self.assertEqual(results.x, [0, 0]) + self.assertEqual(results.fval, 0.0) + except NameError as ex: + self.skipTest(str(ex)) def test_qubo_gas_int_simple(self): """Test for simple case, with 2 linear coeffs and no quadratic coeffs or constants.""" - - # Input. - op = OptimizationProblem() - op.variables.add(names=['x0', 'x1'], types='BB') - linear = [('x0', -1), ('x1', 2)] - op.objective.set_linear(linear) - - # Get the optimum key and value. - n_iter = 8 - gmf = GroverMinimumFinder(num_iterations=n_iter) - results = gmf.solve(op) - self.validate_results(op, results, n_iter) + try: + # Input. + op = OptimizationProblem() + op.variables.add(names=['x0', 'x1'], types='BB') + linear = [('x0', -1), ('x1', 2)] + op.objective.set_linear(linear) + + # Get the optimum key and value. + n_iter = 8 + gmf = GroverMinimumFinder(num_iterations=n_iter) + results = gmf.solve(op) + self.validate_results(op, results, n_iter) + except NameError as ex: + self.skipTest(str(ex)) def test_qubo_gas_int_paper_example(self): """Test the example from https://arxiv.org/abs/1912.04088.""" - - # Input. - op = OptimizationProblem() - op.variables.add(names=['x0', 'x1', 'x2'], types='BBB') - - linear = [('x0', -1), ('x1', 2), ('x2', -3)] - op.objective.set_linear(linear) - op.objective.set_quadratic_coefficients('x0', 'x2', -2) - op.objective.set_quadratic_coefficients('x1', 'x2', -1) - - # Get the optimum key and value. - n_iter = 10 - gmf = GroverMinimumFinder(num_iterations=n_iter) - results = gmf.solve(op) - self.validate_results(op, results, 10) + try: + # Input. + op = OptimizationProblem() + op.variables.add(names=['x0', 'x1', 'x2'], types='BBB') + + linear = [('x0', -1), ('x1', 2), ('x2', -3)] + op.objective.set_linear(linear) + op.objective.set_quadratic_coefficients('x0', 'x2', -2) + op.objective.set_quadratic_coefficients('x1', 'x2', -1) + + # Get the optimum key and value. + n_iter = 10 + gmf = GroverMinimumFinder(num_iterations=n_iter) + results = gmf.solve(op) + self.validate_results(op, results, 10) + except NameError as ex: + self.skipTest(str(ex)) if __name__ == '__main__': diff --git a/test/optimization/test_linear_constraints.py b/test/optimization/test_linear_constraints.py index 9dfdf9ec73..c5759215be 100644 --- a/test/optimization/test_linear_constraints.py +++ b/test/optimization/test_linear_constraints.py @@ -16,15 +16,28 @@ import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase - -from cplex import SparsePair +import logging from qiskit.optimization import OptimizationProblem +logger = logging.getLogger(__name__) + +_HAS_CPLEX = False +try: + from cplex import SparsePair + _HAS_CPLEX = True +except ImportError: + logger.info('CPLEX is not installed.') + class TestLinearConstraints(QiskitOptimizationTestCase): """Test LinearConstraintInterface.""" + def setUp(self) -> None: + super().setUp() + if not _HAS_CPLEX: + self.skipTest('CPLEX is not installed.') + def test_get_num(self): """ test get num """ op = OptimizationProblem() diff --git a/test/optimization/test_min_eigen_optimizer.py b/test/optimization/test_min_eigen_optimizer.py index d2d74ed31e..805b462064 100644 --- a/test/optimization/test_min_eigen_optimizer.py +++ b/test/optimization/test_min_eigen_optimizer.py @@ -54,31 +54,33 @@ def setUp(self): ) def test_min_eigen_optimizer(self, config): """ Min Eigen Optimizer Test """ + try: + # unpack configuration + min_eigen_solver_name, backend, filename = config - # unpack configuration - min_eigen_solver_name, backend, filename = config + # get minimum eigen solver + min_eigen_solver = self.min_eigen_solvers[min_eigen_solver_name] + if backend: + min_eigen_solver.quantum_instance = BasicAer.get_backend(backend) - # get minimum eigen solver - min_eigen_solver = self.min_eigen_solvers[min_eigen_solver_name] - if backend: - min_eigen_solver.quantum_instance = BasicAer.get_backend(backend) + # construct minimum eigen optimizer + min_eigen_optimizer = MinimumEigenOptimizer(min_eigen_solver) - # construct minimum eigen optimizer - min_eigen_optimizer = MinimumEigenOptimizer(min_eigen_solver) + # load optimization problem + problem = OptimizationProblem() + problem.read(self.resource_path + filename) - # load optimization problem - problem = OptimizationProblem() - problem.read(self.resource_path + filename) + # solve problem with cplex + cplex = CplexOptimizer() + cplex_result = cplex.solve(problem) - # solve problem with cplex - cplex = CplexOptimizer() - cplex_result = cplex.solve(problem) + # solve problem + result = min_eigen_optimizer.solve(problem) - # solve problem - result = min_eigen_optimizer.solve(problem) - - # analyze results - self.assertAlmostEqual(cplex_result.fval, result.fval) + # analyze results + self.assertAlmostEqual(cplex_result.fval, result.fval) + except NameError as ex: + self.skipTest(str(ex)) if __name__ == '__main__': diff --git a/test/optimization/test_objective.py b/test/optimization/test_objective.py index 0cc907f736..d2cc53354e 100644 --- a/test/optimization/test_objective.py +++ b/test/optimization/test_objective.py @@ -16,15 +16,28 @@ import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase - -from cplex import SparsePair +import logging from qiskit.optimization import OptimizationProblem, QiskitOptimizationError +logger = logging.getLogger(__name__) + +_HAS_CPLEX = False +try: + from cplex import SparsePair + _HAS_CPLEX = True +except ImportError: + logger.info('CPLEX is not installed.') + class TestObjective(QiskitOptimizationTestCase): """Test ObjectiveInterface""" + def setUp(self) -> None: + super().setUp() + if not _HAS_CPLEX: + self.skipTest('CPLEX is not installed.') + def test_obj_sense(self): """ test obj sense """ op = OptimizationProblem() diff --git a/test/optimization/test_optimization_problem.py b/test/optimization/test_optimization_problem.py index 085f846d9a..216c7520e9 100644 --- a/test/optimization/test_optimization_problem.py +++ b/test/optimization/test_optimization_problem.py @@ -18,12 +18,20 @@ import tempfile import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase - -from cplex import Cplex, SparsePair, SparseTriple, infinity +import logging from qiskit.optimization import OptimizationProblem, QiskitOptimizationError from qiskit.optimization.problems.optimization_problem import SubstitutionStatus +logger = logging.getLogger(__name__) + +_HAS_CPLEX = False +try: + from cplex import Cplex, SparsePair, SparseTriple, infinity + _HAS_CPLEX = True +except ImportError: + logger.info('CPLEX is not installed.') + class TestOptimizationProblem(QiskitOptimizationTestCase): """Test OptimizationProblem without the members that have separate test classes @@ -31,6 +39,9 @@ class TestOptimizationProblem(QiskitOptimizationTestCase): def setUp(self): super().setUp() + if not _HAS_CPLEX: + self.skipTest('CPLEX is not installed.') + self.resource_file = './test/optimization/resources/op_ip2.lp' def test_constructor1(self): diff --git a/test/optimization/test_optimization_problem_to_negative_value_oracle.py b/test/optimization/test_optimization_problem_to_negative_value_oracle.py index 284c3c7661..aa09d8cd8c 100644 --- a/test/optimization/test_optimization_problem_to_negative_value_oracle.py +++ b/test/optimization/test_optimization_problem_to_negative_value_oracle.py @@ -80,65 +80,74 @@ def _bin_to_int(v, num_value_bits): def test_optnvo_3_linear_2_quadratic_no_constant(self): """Test with 3 linear coefficients, 2 quadratic, and no constant.""" - # Circuit parameters. - num_value = 4 - - # Input. - problem = OptimizationProblem() - problem.variables.add(names=['x0', 'x1', 'x2'], types='BBB') - linear = [('x0', -1), ('x1', 2), ('x2', -3)] - problem.objective.set_linear(linear) - problem.objective.set_quadratic_coefficients('x0', 'x2', -2) - problem.objective.set_quadratic_coefficients('x1', 'x2', -1) - - # Convert to dictionary format with operator/oracle. - converter = OptimizationProblemToNegativeValueOracle(num_value) - a_operator, _, func_dict = converter.encode(problem) - - self._validate_function(func_dict, problem) - self._validate_operator(func_dict, len(linear), num_value, a_operator) + try: + # Circuit parameters. + num_value = 4 + + # Input. + problem = OptimizationProblem() + problem.variables.add(names=['x0', 'x1', 'x2'], types='BBB') + linear = [('x0', -1), ('x1', 2), ('x2', -3)] + problem.objective.set_linear(linear) + problem.objective.set_quadratic_coefficients('x0', 'x2', -2) + problem.objective.set_quadratic_coefficients('x1', 'x2', -1) + + # Convert to dictionary format with operator/oracle. + converter = OptimizationProblemToNegativeValueOracle(num_value) + a_operator, _, func_dict = converter.encode(problem) + + self._validate_function(func_dict, problem) + self._validate_operator(func_dict, len(linear), num_value, a_operator) + except NameError as ex: + self.skipTest(str(ex)) def test_optnvo_4_key_all_negative(self): """Test with all negative values.""" # Circuit parameters. - num_value = 5 - - # Input. - problem = OptimizationProblem() - problem.variables.add(names=['x0', 'x1', 'x2'], types='BBB') - linear = [('x0', -1), ('x1', -2), ('x2', -1)] - problem.objective.set_linear(linear) - problem.objective.set_quadratic_coefficients('x0', 'x1', -1) - problem.objective.set_quadratic_coefficients('x0', 'x2', -2) - problem.objective.set_quadratic_coefficients('x1', 'x2', -1) - problem.objective.set_offset(-1) - - # Convert to dictionary format with operator/oracle. - converter = OptimizationProblemToNegativeValueOracle(num_value) - a_operator, _, func_dict = converter.encode(problem) - - self._validate_function(func_dict, problem) - self._validate_operator(func_dict, len(linear), num_value, a_operator) + try: + num_value = 5 + + # Input. + problem = OptimizationProblem() + problem.variables.add(names=['x0', 'x1', 'x2'], types='BBB') + linear = [('x0', -1), ('x1', -2), ('x2', -1)] + problem.objective.set_linear(linear) + problem.objective.set_quadratic_coefficients('x0', 'x1', -1) + problem.objective.set_quadratic_coefficients('x0', 'x2', -2) + problem.objective.set_quadratic_coefficients('x1', 'x2', -1) + problem.objective.set_offset(-1) + + # Convert to dictionary format with operator/oracle. + converter = OptimizationProblemToNegativeValueOracle(num_value) + a_operator, _, func_dict = converter.encode(problem) + + self._validate_function(func_dict, problem) + self._validate_operator(func_dict, len(linear), num_value, a_operator) + except NameError as ex: + self.skipTest(str(ex)) def test_optnvo_6_key(self): """Test with 6 linear coefficients, negative quadratics, no constant.""" # Circuit parameters. - num_value = 4 - - # Input. - problem = OptimizationProblem() - problem.variables.add(names=['x0', 'x1', 'x2', 'x3', 'x4', 'x5'], types='BBBBBB') - linear = [('x0', -1), ('x1', -2), ('x2', -1), ('x3', 0), ('x4', 1), ('x5', 2)] - problem.objective.set_linear(linear) - problem.objective.set_quadratic_coefficients('x0', 'x3', -1) - problem.objective.set_quadratic_coefficients('x1', 'x5', -2) - - # Convert to dictionary format with operator/oracle. - converter = OptimizationProblemToNegativeValueOracle(num_value) - a_operator, _, func_dict = converter.encode(problem) - - self._validate_function(func_dict, problem) - self._validate_operator(func_dict, len(linear), num_value, a_operator) + try: + num_value = 4 + + # Input. + problem = OptimizationProblem() + problem.variables.add(names=['x0', 'x1', 'x2', 'x3', 'x4', 'x5'], types='BBBBBB') + linear = [('x0', -1), ('x1', -2), ('x2', -1), ('x3', 0), ('x4', 1), ('x5', 2)] + problem.objective.set_linear(linear) + problem.objective.set_quadratic_coefficients('x0', 'x3', -1) + problem.objective.set_quadratic_coefficients('x1', 'x5', -2) + + # Convert to dictionary format with operator/oracle. + converter = OptimizationProblemToNegativeValueOracle(num_value) + a_operator, _, func_dict = converter.encode(problem) + + self._validate_function(func_dict, problem) + self._validate_operator(func_dict, len(linear), num_value, a_operator) + except NameError as ex: + self.skipTest(str(ex)) if __name__ == '__main__': diff --git a/test/optimization/test_quadratic_constraints.py b/test/optimization/test_quadratic_constraints.py index 0750bddb54..912c47a0af 100644 --- a/test/optimization/test_quadratic_constraints.py +++ b/test/optimization/test_quadratic_constraints.py @@ -16,16 +16,29 @@ import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase - -from cplex import SparsePair, SparseTriple +import logging from qiskit.optimization import QiskitOptimizationError from qiskit.optimization.problems import OptimizationProblem +logger = logging.getLogger(__name__) + +_HAS_CPLEX = False +try: + from cplex import SparsePair, SparseTriple + _HAS_CPLEX = True +except ImportError: + logger.info('CPLEX is not installed.') + class TestQuadraticConstraints(QiskitOptimizationTestCase): """Test QuadraticConstraintInterface.""" + def setUp(self) -> None: + super().setUp() + if not _HAS_CPLEX: + self.skipTest('CPLEX is not installed.') + def test_initial1(self): """ test initial 1""" op = OptimizationProblem() diff --git a/test/optimization/test_recursive_optimization.py b/test/optimization/test_recursive_optimization.py index 66c09d7942..df12447513 100755 --- a/test/optimization/test_recursive_optimization.py +++ b/test/optimization/test_recursive_optimization.py @@ -69,30 +69,32 @@ def setUp(self): @unpack def test_recursive_min_eigen_optimizer(self, solver, simulator, filename): """ Min Eigen Optimizer Test """ - - # get minimum eigen solver - min_eigen_solver = self.min_eigen_solvers[solver] - if simulator: - min_eigen_solver.quantum_instance = self.qinstances[simulator] - - # construct minimum eigen optimizer - min_eigen_optimizer = MinimumEigenOptimizer(min_eigen_solver) - recursive_min_eigen_optimizer = RecursiveMinimumEigenOptimizer(min_eigen_optimizer, - min_num_vars=4) - - # load optimization problem - problem = OptimizationProblem() - problem.read(self.resource_path + filename) - - # solve problem with cplex - cplex = CplexOptimizer() - cplex_result = cplex.solve(problem) - - # solve problem - result = recursive_min_eigen_optimizer.solve(problem) - - # analyze results - self.assertAlmostEqual(cplex_result.fval, result.fval) + try: + # get minimum eigen solver + min_eigen_solver = self.min_eigen_solvers[solver] + if simulator: + min_eigen_solver.quantum_instance = self.qinstances[simulator] + + # construct minimum eigen optimizer + min_eigen_optimizer = MinimumEigenOptimizer(min_eigen_solver) + recursive_min_eigen_optimizer = RecursiveMinimumEigenOptimizer(min_eigen_optimizer, + min_num_vars=4) + + # load optimization problem + problem = OptimizationProblem() + problem.read(self.resource_path + filename) + + # solve problem with cplex + cplex = CplexOptimizer() + cplex_result = cplex.solve(problem) + + # solve problem + result = recursive_min_eigen_optimizer.solve(problem) + + # analyze results + self.assertAlmostEqual(cplex_result.fval, result.fval) + except NameError as ex: + self.skipTest(str(ex)) if __name__ == '__main__': diff --git a/test/optimization/test_variables.py b/test/optimization/test_variables.py index 89cbc0667f..4b8723a6df 100644 --- a/test/optimization/test_variables.py +++ b/test/optimization/test_variables.py @@ -16,15 +16,28 @@ import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase - -from cplex import infinity +import logging from qiskit.optimization import OptimizationProblem, QiskitOptimizationError +logger = logging.getLogger(__name__) + +_HAS_CPLEX = False +try: + from cplex import infinity + _HAS_CPLEX = True +except ImportError: + logger.info('CPLEX is not installed.') + class TestVariables(QiskitOptimizationTestCase): """Test VariablesInterface.""" + def setUp(self) -> None: + super().setUp() + if not _HAS_CPLEX: + self.skipTest('CPLEX is not installed.') + def test_type(self): """ test type """ op = OptimizationProblem() From f929bfbf496dc461215e1e3b8b1f32482ceb1a47 Mon Sep 17 00:00:00 2001 From: Atsushi Matsuo Date: Thu, 9 Apr 2020 13:48:23 +0900 Subject: [PATCH 164/323] fixed decode func for continuous variables --- .../converters/integer_to_binary_converter.py | 34 ++- test/optimization/test_converters.py | 282 ++++++++++-------- 2 files changed, 178 insertions(+), 138 deletions(-) diff --git a/qiskit/optimization/converters/integer_to_binary_converter.py b/qiskit/optimization/converters/integer_to_binary_converter.py index 875a0b7b9d..a6b5067770 100644 --- a/qiskit/optimization/converters/integer_to_binary_converter.py +++ b/qiskit/optimization/converters/integer_to_binary_converter.py @@ -28,9 +28,10 @@ _HAS_CPLEX = False try: from cplex import SparsePair + _HAS_CPLEX = True except ImportError: - logger.info('CPLEX is not installed.') + logger.info("CPLEX is not installed.") class IntegerToBinaryConverter: @@ -43,12 +44,12 @@ class IntegerToBinaryConverter: >>> problem2 = conv.encode(problem) """ - _delimiter = '@' # users are supposed not to use this character in variable names + _delimiter = "@" # users are supposed not to use this character in variable names def __init__(self) -> None: """Initializes the internal data structure.""" if not _HAS_CPLEX: - raise NameError('CPLEX is not installed.') + raise NameError("CPLEX is not installed.") self._src = None self._dst = None @@ -81,16 +82,18 @@ def encode(self, op: OptimizationProblem, name: Optional[str] = None) -> Optimiz upper_bounds = self._src.variables.get_upper_bounds() for i, variable in enumerate(names): typ = types[i] - if typ == 'I': - new_vars: List[Tuple[str, int]] = self._encode_var(name=variable, - lower_bound=lower_bounds[i], - upper_bound=upper_bounds[i]) + if typ == "I": + new_vars: List[Tuple[str, int]] = self._encode_var( + name=variable, lower_bound=lower_bounds[i], upper_bound=upper_bounds[i] + ) self._conv[variable] = new_vars - self._dst.variables.add(names=[new_name for new_name, _ in new_vars], - types='B' * len(new_vars)) + self._dst.variables.add( + names=[new_name for new_name, _ in new_vars], types="B" * len(new_vars) + ) else: - self._dst.variables.add(names=[variable], types=typ, - lb=[lower_bounds[i]], ub=[upper_bounds[i]]) + self._dst.variables.add( + names=[variable], types=typ, lb=[lower_bounds[i]], ub=[upper_bounds[i]] + ) self._substitute_int_var() @@ -194,12 +197,13 @@ def _substitute_int_var(self): lin_expr.append(sparse_pair) - self._dst.linear_constraints.add(lin_expr, linear_sense, linear_rhs, linear_ranges, - linear_names) + self._dst.linear_constraints.add( + lin_expr, linear_sense, linear_rhs, linear_ranges, linear_names + ) # TODO: add quadratic constraints if self._src.quadratic_constraints.get_num() > 0: - raise QiskitOptimizationError('Quadratic constraints are not yet supported.') + raise QiskitOptimizationError("Quadratic constraints are not yet supported.") def decode(self, result: OptimizationResult) -> OptimizationResult: """Convert the encoded problem (binary variables) back to the original (integer variables). @@ -218,7 +222,7 @@ def decode(self, result: OptimizationResult) -> OptimizationResult: def _decode_var(self, names, vals) -> List[int]: # decode integer values - sol = {name: int(vals[i]) for i, name in enumerate(names)} + sol = {name: float(vals[i]) for i, name in enumerate(names)} new_vals = [] for name in self._src.variables.get_names(): if name in self._conv: diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index a7477e2953..a858fcfd89 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -18,11 +18,19 @@ from test.optimization.optimization_test_case import QiskitOptimizationTestCase import logging +from qiskit.aqua.operators import WeightedPauliOperator +from qiskit.aqua.algorithms import NumPyMinimumEigensolver +from docplex.mp.model import Model from qiskit.optimization import OptimizationProblem, QiskitOptimizationError from qiskit.optimization.results import OptimizationResult -from qiskit.optimization.converters import InequalityToEqualityConverter, \ - OptimizationProblemToOperator, IntegerToBinaryConverter, PenalizeLinearEqualityConstraints -from qiskit.aqua.operators import WeightedPauliOperator +from qiskit.optimization.converters import ( + InequalityToEqualityConverter, + OptimizationProblemToOperator, + IntegerToBinaryConverter, + PenalizeLinearEqualityConstraints, +) +from qiskit.optimization.algorithms import MinimumEigenOptimizer, CplexOptimizer, ADMMOptimizer +from qiskit.optimization.algorithms.admm_optimizer import ADMMParameters from qiskit.quantum_info import Pauli logger = logging.getLogger(__name__) @@ -30,31 +38,25 @@ _HAS_CPLEX = False try: from cplex import SparsePair + _HAS_CPLEX = True except ImportError: - logger.info('CPLEX is not installed.') + logger.info("CPLEX is not installed.") QUBIT_OP_MAXIMIZE_SAMPLE = WeightedPauliOperator( - paulis=[[(-199999.5+0j), Pauli(z=[True, False, False, False], - x=[False, False, False, False])], - [(-399999.5+0j), Pauli(z=[False, True, False, False], - x=[False, False, False, False])], - [(-599999.5+0j), Pauli(z=[False, False, True, False], - x=[False, False, False, False])], - [(-799999.5+0j), Pauli(z=[False, False, False, True], - x=[False, False, False, False])], - [(100000+0j), Pauli(z=[True, True, False, False], - x=[False, False, False, False])], - [(150000+0j), Pauli(z=[True, False, True, False], - x=[False, False, False, False])], - [(200000+0j), Pauli(z=[True, False, False, True], - x=[False, False, False, False])], - [(300000+0j), Pauli(z=[False, True, True, False], - x=[False, False, False, False])], - [(400000+0j), Pauli(z=[False, True, False, True], - x=[False, False, False, False])], - [(600000+0j), Pauli(z=[False, False, True, True], - x=[False, False, False, False])]]) + paulis=[ + [(-199999.5 + 0j), Pauli(z=[True, False, False, False], x=[False, False, False, False])], + [(-399999.5 + 0j), Pauli(z=[False, True, False, False], x=[False, False, False, False])], + [(-599999.5 + 0j), Pauli(z=[False, False, True, False], x=[False, False, False, False])], + [(-799999.5 + 0j), Pauli(z=[False, False, False, True], x=[False, False, False, False])], + [(100000 + 0j), Pauli(z=[True, True, False, False], x=[False, False, False, False])], + [(150000 + 0j), Pauli(z=[True, False, True, False], x=[False, False, False, False])], + [(200000 + 0j), Pauli(z=[True, False, False, True], x=[False, False, False, False])], + [(300000 + 0j), Pauli(z=[False, True, True, False], x=[False, False, False, False])], + [(400000 + 0j), Pauli(z=[False, True, False, True], x=[False, False, False, False])], + [(600000 + 0j), Pauli(z=[False, False, True, True], x=[False, False, False, False])], + ] +) OFFSET_MAXIMIZE_SAMPLE = 1149998 @@ -64,7 +66,7 @@ class TestConverters(QiskitOptimizationTestCase): def setUp(self) -> None: super().setUp() if not _HAS_CPLEX: - self.skipTest('CPLEX is not installed.') + self.skipTest("CPLEX is not installed.") def test_empty_problem(self): """ Test empty problem """ @@ -84,60 +86,62 @@ def test_valid_variable_type(self): # Integer variable with self.assertRaises(QiskitOptimizationError): op = OptimizationProblem() - op.variables.add(names=['x'], types='I') + op.variables.add(names=["x"], types="I") conv = OptimizationProblemToOperator() _ = conv.encode(op) # Continuous variable with self.assertRaises(QiskitOptimizationError): op = OptimizationProblem() - op.variables.add(names=['x'], types='C') + op.variables.add(names=["x"], types="C") conv = OptimizationProblemToOperator() _ = conv.encode(op) # Semi-Continuous variable with self.assertRaises(QiskitOptimizationError): op = OptimizationProblem() - op.variables.add(names=['x'], types='S') + op.variables.add(names=["x"], types="S") conv = OptimizationProblemToOperator() _ = conv.encode(op) # Semi-Integer variable with self.assertRaises(QiskitOptimizationError): op = OptimizationProblem() - op.variables.add(names=['x'], types='N') + op.variables.add(names=["x"], types="N") conv = OptimizationProblemToOperator() _ = conv.encode(op) # validate the types of the variables for InequalityToEqualityConverter # Semi-Continuous variable with self.assertRaises(QiskitOptimizationError): op = OptimizationProblem() - op.variables.add(names=['x'], types='S') + op.variables.add(names=["x"], types="S") conv = InequalityToEqualityConverter() _ = conv.encode(op) # Semi-Integer variable with self.assertRaises(QiskitOptimizationError): op = OptimizationProblem() - op.variables.add(names=['x'], types='N') + op.variables.add(names=["x"], types="N") conv = InequalityToEqualityConverter() _ = conv.encode(op) def test_inequality_binary(self): """ Test InequalityToEqualityConverter with binary variables """ op = OptimizationProblem() - op.variables.add(names=['x', 'y', 'z'], types='B' * 3) + op.variables.add(names=["x", "y", "z"], types="B" * 3) op.linear_constraints.add( - lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), - SparsePair(ind=['y', 'z'], val=[1, -1]), - SparsePair(ind=['z', 'x'], val=[1, 2])], - senses=['E', 'L', 'G'], + lin_expr=[ + SparsePair(ind=["x", "y"], val=[1, 1]), + SparsePair(ind=["y", "z"], val=[1, -1]), + SparsePair(ind=["z", "x"], val=[1, 2]), + ], + senses=["E", "L", "G"], rhs=[1, 2, 3], - names=['xy', 'yz', 'zx'] + names=["xy", "yz", "zx"], ) conv = InequalityToEqualityConverter() op2 = conv.encode(op) self.assertEqual(op.get_problem_name(), op2.get_problem_name()) self.assertEqual(op.get_problem_type(), op2.get_problem_type()) cst = op2.linear_constraints - self.assertListEqual(cst.get_names(), ['xy', 'yz', 'zx']) - self.assertListEqual(cst.get_senses(), ['E', 'E', 'E']) + self.assertListEqual(cst.get_names(), ["xy", "yz", "zx"]) + self.assertListEqual(cst.get_senses(), ["E", "E", "E"]) self.assertListEqual(cst.get_rhs(), [1, 2, 3]) var = op2.variables self.assertListEqual(var.get_lower_bounds(3, 4), [0, 0]) @@ -146,23 +150,24 @@ def test_inequality_binary(self): def test_inequality_integer(self): """ Test InequalityToEqualityConverter with integer variables """ op = OptimizationProblem() - op.variables.add(names=['x', 'y', 'z'], - types='I' * 3, lb=[-3] * 3, ub=[3] * 3) + op.variables.add(names=["x", "y", "z"], types="I" * 3, lb=[-3] * 3, ub=[3] * 3) op.linear_constraints.add( - lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), - SparsePair(ind=['y', 'z'], val=[1, -1]), - SparsePair(ind=['z', 'x'], val=[1, 2])], - senses=['E', 'L', 'G'], + lin_expr=[ + SparsePair(ind=["x", "y"], val=[1, 1]), + SparsePair(ind=["y", "z"], val=[1, -1]), + SparsePair(ind=["z", "x"], val=[1, 2]), + ], + senses=["E", "L", "G"], rhs=[1, 2, 3], - names=['xy', 'yz', 'zx'] + names=["xy", "yz", "zx"], ) conv = InequalityToEqualityConverter() op2 = conv.encode(op) self.assertEqual(op.get_problem_name(), op2.get_problem_name()) self.assertEqual(op.get_problem_type(), op2.get_problem_type()) cst = op2.linear_constraints - self.assertListEqual(cst.get_names(), ['xy', 'yz', 'zx']) - self.assertListEqual(cst.get_senses(), ['E', 'E', 'E']) + self.assertListEqual(cst.get_names(), ["xy", "yz", "zx"]) + self.assertListEqual(cst.get_senses(), ["E", "E", "E"]) self.assertListEqual(cst.get_rhs(), [1, 2, 3]) var = op2.variables self.assertListEqual(var.get_lower_bounds(3, 4), [0, 0]) @@ -171,65 +176,73 @@ def test_inequality_integer(self): def test_inequality_mode_integer(self): """ Test integer mode of InequalityToEqualityConverter() """ op = OptimizationProblem() - op.variables.add(names=['x', 'y', 'z'], types='B' * 3) + op.variables.add(names=["x", "y", "z"], types="B" * 3) op.linear_constraints.add( - lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), - SparsePair(ind=['y', 'z'], val=[1, -1]), - SparsePair(ind=['z', 'x'], val=[1, 2])], - senses=['E', 'L', 'G'], + lin_expr=[ + SparsePair(ind=["x", "y"], val=[1, 1]), + SparsePair(ind=["y", "z"], val=[1, -1]), + SparsePair(ind=["z", "x"], val=[1, 2]), + ], + senses=["E", "L", "G"], rhs=[1, 2, 3], - names=['xy', 'yz', 'zx'] + names=["xy", "yz", "zx"], ) conv = InequalityToEqualityConverter() - op2 = conv.encode(op, mode='integer') + op2 = conv.encode(op, mode="integer") var = op2.variables - self.assertListEqual(var.get_types(3, 4), ['I', 'I']) + self.assertListEqual(var.get_types(3, 4), ["I", "I"]) def test_inequality_mode_continuous(self): """ Test continuous mode of InequalityToEqualityConverter() """ op = OptimizationProblem() - op.variables.add(names=['x', 'y', 'z'], types='B' * 3) + op.variables.add(names=["x", "y", "z"], types="B" * 3) op.linear_constraints.add( - lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), - SparsePair(ind=['y', 'z'], val=[1, -1]), - SparsePair(ind=['z', 'x'], val=[1, 2])], - senses=['E', 'L', 'G'], + lin_expr=[ + SparsePair(ind=["x", "y"], val=[1, 1]), + SparsePair(ind=["y", "z"], val=[1, -1]), + SparsePair(ind=["z", "x"], val=[1, 2]), + ], + senses=["E", "L", "G"], rhs=[1, 2, 3], - names=['xy', 'yz', 'zx'] + names=["xy", "yz", "zx"], ) conv = InequalityToEqualityConverter() - op2 = conv.encode(op, mode='continuous') + op2 = conv.encode(op, mode="continuous") var = op2.variables - self.assertListEqual(var.get_types(3, 4), ['C', 'C']) + self.assertListEqual(var.get_types(3, 4), ["C", "C"]) def test_inequality_mode_auto(self): """ Test auto mode of InequalityToEqualityConverter() """ op = OptimizationProblem() - op.variables.add(names=['x', 'y', 'z'], types='B' * 3) + op.variables.add(names=["x", "y", "z"], types="B" * 3) op.linear_constraints.add( - lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), - SparsePair(ind=['y', 'z'], val=[1, -1]), - SparsePair(ind=['z', 'x'], val=[1.1, 2.2])], - senses=['E', 'L', 'G'], + lin_expr=[ + SparsePair(ind=["x", "y"], val=[1, 1]), + SparsePair(ind=["y", "z"], val=[1, -1]), + SparsePair(ind=["z", "x"], val=[1.1, 2.2]), + ], + senses=["E", "L", "G"], rhs=[1, 2, 3.3], - names=['xy', 'yz', 'zx'] + names=["xy", "yz", "zx"], ) conv = InequalityToEqualityConverter() - op2 = conv.encode(op, mode='auto') + op2 = conv.encode(op, mode="auto") var = op2.variables - self.assertListEqual(var.get_types(3, 4), ['I', 'C']) + self.assertListEqual(var.get_types(3, 4), ["I", "C"]) def test_penalize_sense(self): """ Test PenalizeLinearEqualityConstraints with senses """ op = OptimizationProblem() - op.variables.add(names=['x', 'y', 'z'], types='B' * 3) + op.variables.add(names=["x", "y", "z"], types="B" * 3) op.linear_constraints.add( - lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), - SparsePair(ind=['y', 'z'], val=[1, -1]), - SparsePair(ind=['z', 'x'], val=[1, 2])], - senses=['E', 'L', 'G'], + lin_expr=[ + SparsePair(ind=["x", "y"], val=[1, 1]), + SparsePair(ind=["y", "z"], val=[1, -1]), + SparsePair(ind=["z", "x"], val=[1, 2]), + ], + senses=["E", "L", "G"], rhs=[1, 2, 3], - names=['xy', 'yz', 'zx'] + names=["xy", "yz", "zx"], ) self.assertEqual(op.linear_constraints.get_num(), 3) conv = PenalizeLinearEqualityConstraints() @@ -239,13 +252,15 @@ def test_penalize_sense(self): def test_penalize_binary(self): """ Test PenalizeLinearEqualityConstraints with binary variables """ op = OptimizationProblem() - op.variables.add(names=['x', 'y', 'z'], types='B' * 3) + op.variables.add(names=["x", "y", "z"], types="B" * 3) op.linear_constraints.add( - lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), - SparsePair(ind=['y', 'z'], val=[1, -1])], - senses=['E', 'E'], + lin_expr=[ + SparsePair(ind=["x", "y"], val=[1, 1]), + SparsePair(ind=["y", "z"], val=[1, -1]), + ], + senses=["E", "E"], rhs=[1, 2], - names=['xy', 'yz'] + names=["xy", "yz"], ) self.assertEqual(op.linear_constraints.get_num(), 2) conv = PenalizeLinearEqualityConstraints() @@ -255,14 +270,15 @@ def test_penalize_binary(self): def test_penalize_integer(self): """ Test PenalizeLinearEqualityConstraints with integer variables """ op = OptimizationProblem() - op.variables.add(names=['x', 'y', 'z'], - types='I' * 3, lb=[-3] * 3, ub=[3] * 3) + op.variables.add(names=["x", "y", "z"], types="I" * 3, lb=[-3] * 3, ub=[3] * 3) op.linear_constraints.add( - lin_expr=[SparsePair(ind=['x', 'y'], val=[1, 1]), - SparsePair(ind=['y', 'z'], val=[1, -1])], - senses=['E', 'E'], + lin_expr=[ + SparsePair(ind=["x", "y"], val=[1, 1]), + SparsePair(ind=["y", "z"], val=[1, -1]), + ], + senses=["E", "E"], rhs=[1, 2], - names=['xy', 'yz'] + names=["xy", "yz"], ) self.assertEqual(op.linear_constraints.get_num(), 2) conv = PenalizeLinearEqualityConstraints() @@ -272,47 +288,46 @@ def test_penalize_integer(self): def test_integer_to_binary(self): """ Test integer to binary """ op = OptimizationProblem() - op.variables.add(names=['x', 'y', 'z'], types='BIC', - lb=[0, 0, 0], ub=[1, 6, 10]) - op.objective.set_linear([('x', 1), ('y', 2), ('z', 1)]) + op.variables.add(names=["x", "y", "z"], types="BIC", lb=[0, 0, 0], ub=[1, 6, 10]) + op.objective.set_linear([("x", 1), ("y", 2), ("z", 1)]) op.linear_constraints.add( - lin_expr=[SparsePair(ind=['x', 'y', 'z'], val=[1, 3, 1])], - senses=['L'], + lin_expr=[SparsePair(ind=["x", "y", "z"], val=[1, 3, 1])], + senses=["L"], rhs=[10], - names=['xyz'] + names=["xyz"], ) self.assertEqual(op.variables.get_num(), 3) conv = IntegerToBinaryConverter() op2 = conv.encode(op) names = op2.variables.get_names() - self.assertIn('x', names) - self.assertIn('z', names) + self.assertIn("x", names) + self.assertIn("z", names) variables = op2.variables - self.assertEqual(variables.get_lower_bounds('x'), 0.0) - self.assertEqual(variables.get_lower_bounds('z'), 0.0) - self.assertEqual(variables.get_upper_bounds('x'), 1.0) - self.assertEqual(variables.get_upper_bounds('z'), 10.0) - self.assertListEqual(variables.get_types(['x', 'y@0', 'y@1', 'y@2', 'z']), - ['B', 'B', 'B', 'B', 'C']) - self.assertListEqual(op2.objective.get_linear(['y@0', 'y@1', 'y@2']), [2, 4, 6]) + self.assertEqual(variables.get_lower_bounds("x"), 0.0) + self.assertEqual(variables.get_lower_bounds("z"), 0.0) + self.assertEqual(variables.get_upper_bounds("x"), 1.0) + self.assertEqual(variables.get_upper_bounds("z"), 10.0) + self.assertListEqual( + variables.get_types(["x", "y@0", "y@1", "y@2", "z"]), ["B", "B", "B", "B", "C"] + ) + self.assertListEqual(op2.objective.get_linear(["y@0", "y@1", "y@2"]), [2, 4, 6]) self.assertListEqual(op2.linear_constraints.get_rows()[0].val, [1, 3, 6, 9, 1]) def test_binary_to_integer(self): """ Test binary to integer """ op = OptimizationProblem() - op.variables.add(names=['x', 'y', 'z'], types='BIB', lb=[ - 0, 0, 0], ub=[1, 7, 1]) - op.objective.set_linear([('x', 2), ('y', 1), ('z', 1)]) + op.variables.add(names=["x", "y", "z"], types="BIB", lb=[0, 0, 0], ub=[1, 7, 1]) + op.objective.set_linear([("x", 2), ("y", 1), ("z", 1)]) op.linear_constraints.add( - lin_expr=[SparsePair(ind=['x', 'y', 'z'], val=[1, 1, 1])], - senses=['L'], + lin_expr=[SparsePair(ind=["x", "y", "z"], val=[1, 1, 1])], + senses=["L"], rhs=[7], - names=['xyz'] + names=["xyz"], ) op.objective.set_sense(-1) conv = IntegerToBinaryConverter() _ = conv.encode(op) - result = OptimizationResult(x=[1, 0., 1, 1, 0], fval=8) + result = OptimizationResult(x=[1, 0.0, 1, 1, 0], fval=8) new_result = conv.decode(result) self.assertListEqual(new_result.x, [1, 6, 0]) self.assertEqual(new_result.fval, 8) @@ -320,13 +335,13 @@ def test_binary_to_integer(self): def test_optimizationproblem_to_operator(self): """ Test optimization problem to operators""" op = OptimizationProblem() - op.variables.add(names=['a', 'b', 'c', 'd'], types='B'*4) - op.objective.set_linear([('a', 1), ('b', 1), ('c', 1), ('d', 1)]) + op.variables.add(names=["a", "b", "c", "d"], types="B" * 4) + op.objective.set_linear([("a", 1), ("b", 1), ("c", 1), ("d", 1)]) op.linear_constraints.add( - lin_expr=[SparsePair(ind=['a', 'b', 'c', 'd'], val=[1, 2, 3, 4])], - senses=['E'], + lin_expr=[SparsePair(ind=["a", "b", "c", "d"], val=[1, 2, 3, 4])], + senses=["E"], rhs=[3], - names=['abcd'] + names=["abcd"], ) op.objective.set_sense(-1) penalize = PenalizeLinearEqualityConstraints() @@ -341,22 +356,43 @@ def test_quadratic_constraints(self): # IntegerToBinaryConverter with self.assertRaises(QiskitOptimizationError): op = OptimizationProblem() - op.variables.add(names=['x', 'y']) - l_expr = SparsePair(ind=['x'], val=[1.0]) - q_expr = [['x'], ['y'], [1]] + op.variables.add(names=["x", "y"]) + l_expr = SparsePair(ind=["x"], val=[1.0]) + q_expr = [["x"], ["y"], [1]] op.quadratic_constraints.add(name=str(1), lin_expr=l_expr, quad_expr=q_expr) conv = IntegerToBinaryConverter() _ = conv.encode(op) # InequalityToEqualityConverter with self.assertRaises(QiskitOptimizationError): op = OptimizationProblem() - op.variables.add(names=['x', 'y']) - l_expr = SparsePair(ind=['x'], val=[1.0]) - q_expr = [['x'], ['y'], [1]] + op.variables.add(names=["x", "y"]) + l_expr = SparsePair(ind=["x"], val=[1.0]) + q_expr = [["x"], ["y"], [1]] op.quadratic_constraints.add(name=str(1), lin_expr=l_expr, quad_expr=q_expr) conv = InequalityToEqualityConverter() _ = conv.encode(op) + def test_continuous_variable_decode(self): + mdl = Model("test_continuous_varable_decode") + c = mdl.continuous_var(lb=0, ub=10.9, name="c") + x = mdl.binary_var(name="x") + mdl.maximize(c + x * x) + op = OptimizationProblem() + op.from_docplex(mdl) + converter = IntegerToBinaryConverter() + op = converter.encode(op) + admm_params = ADMMParameters() + qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) + continuous_optimizer = CplexOptimizer() + solver = ADMMOptimizer( + qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer, + params=admm_params, + ) + solution = solver.solve(op) + solution = converter.decode(solution) + self.assertEqual(solution.x[0], 10.9) + -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() From 06b35aa2322f05d34b7e8de37e8c3161475d2429 Mon Sep 17 00:00:00 2001 From: Atsushi Matsuo Date: Thu, 9 Apr 2020 14:01:52 +0900 Subject: [PATCH 165/323] fixed format and added docstrings for the test --- .../converters/integer_to_binary_converter.py | 9 +- test/optimization/test_converters.py | 194 +++++++++--------- 2 files changed, 101 insertions(+), 102 deletions(-) diff --git a/qiskit/optimization/converters/integer_to_binary_converter.py b/qiskit/optimization/converters/integer_to_binary_converter.py index a6b5067770..7512ad66b7 100644 --- a/qiskit/optimization/converters/integer_to_binary_converter.py +++ b/qiskit/optimization/converters/integer_to_binary_converter.py @@ -28,10 +28,9 @@ _HAS_CPLEX = False try: from cplex import SparsePair - _HAS_CPLEX = True except ImportError: - logger.info("CPLEX is not installed.") + logger.info('CPLEX is not installed.') class IntegerToBinaryConverter: @@ -44,12 +43,12 @@ class IntegerToBinaryConverter: >>> problem2 = conv.encode(problem) """ - _delimiter = "@" # users are supposed not to use this character in variable names + _delimiter = '@' # users are supposed not to use this character in variable names def __init__(self) -> None: """Initializes the internal data structure.""" if not _HAS_CPLEX: - raise NameError("CPLEX is not installed.") + raise NameError('CPLEX is not installed.') self._src = None self._dst = None @@ -203,7 +202,7 @@ def _substitute_int_var(self): # TODO: add quadratic constraints if self._src.quadratic_constraints.get_num() > 0: - raise QiskitOptimizationError("Quadratic constraints are not yet supported.") + raise QiskitOptimizationError('Quadratic constraints are not yet supported.') def decode(self, result: OptimizationResult) -> OptimizationResult: """Convert the encoded problem (binary variables) back to the original (integer variables). diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index a858fcfd89..0cc9f50711 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -38,7 +38,6 @@ _HAS_CPLEX = False try: from cplex import SparsePair - _HAS_CPLEX = True except ImportError: logger.info("CPLEX is not installed.") @@ -66,7 +65,7 @@ class TestConverters(QiskitOptimizationTestCase): def setUp(self) -> None: super().setUp() if not _HAS_CPLEX: - self.skipTest("CPLEX is not installed.") + self.skipTest('CPLEX is not installed.') def test_empty_problem(self): """ Test empty problem """ @@ -86,62 +85,62 @@ def test_valid_variable_type(self): # Integer variable with self.assertRaises(QiskitOptimizationError): op = OptimizationProblem() - op.variables.add(names=["x"], types="I") + op.variables.add(names=['x'], types='I') conv = OptimizationProblemToOperator() _ = conv.encode(op) # Continuous variable with self.assertRaises(QiskitOptimizationError): op = OptimizationProblem() - op.variables.add(names=["x"], types="C") + op.variables.add(names=['x'], types='C') conv = OptimizationProblemToOperator() _ = conv.encode(op) # Semi-Continuous variable with self.assertRaises(QiskitOptimizationError): op = OptimizationProblem() - op.variables.add(names=["x"], types="S") + op.variables.add(names=['x'], types='S') conv = OptimizationProblemToOperator() _ = conv.encode(op) # Semi-Integer variable with self.assertRaises(QiskitOptimizationError): op = OptimizationProblem() - op.variables.add(names=["x"], types="N") + op.variables.add(names=['x'], types='N') conv = OptimizationProblemToOperator() _ = conv.encode(op) # validate the types of the variables for InequalityToEqualityConverter # Semi-Continuous variable with self.assertRaises(QiskitOptimizationError): op = OptimizationProblem() - op.variables.add(names=["x"], types="S") + op.variables.add(names=['x'], types='S') conv = InequalityToEqualityConverter() _ = conv.encode(op) # Semi-Integer variable with self.assertRaises(QiskitOptimizationError): op = OptimizationProblem() - op.variables.add(names=["x"], types="N") + op.variables.add(names=['x'], types='N') conv = InequalityToEqualityConverter() _ = conv.encode(op) def test_inequality_binary(self): """ Test InequalityToEqualityConverter with binary variables """ op = OptimizationProblem() - op.variables.add(names=["x", "y", "z"], types="B" * 3) + op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( lin_expr=[ - SparsePair(ind=["x", "y"], val=[1, 1]), - SparsePair(ind=["y", "z"], val=[1, -1]), - SparsePair(ind=["z", "x"], val=[1, 2]), + SparsePair(ind=['x', 'y'], val=[1, 1]), + SparsePair(ind=['y', 'z'], val=[1, -1]), + SparsePair(ind=['z', 'x'], val=[1, 2]), ], - senses=["E", "L", "G"], + senses=['E', 'L', 'G'], rhs=[1, 2, 3], - names=["xy", "yz", "zx"], + names=['xy', 'yz', 'zx'], ) conv = InequalityToEqualityConverter() op2 = conv.encode(op) self.assertEqual(op.get_problem_name(), op2.get_problem_name()) self.assertEqual(op.get_problem_type(), op2.get_problem_type()) cst = op2.linear_constraints - self.assertListEqual(cst.get_names(), ["xy", "yz", "zx"]) - self.assertListEqual(cst.get_senses(), ["E", "E", "E"]) + self.assertListEqual(cst.get_names(), ['xy', 'yz', 'zx']) + self.assertListEqual(cst.get_senses(), ['E', 'E', 'E']) self.assertListEqual(cst.get_rhs(), [1, 2, 3]) var = op2.variables self.assertListEqual(var.get_lower_bounds(3, 4), [0, 0]) @@ -150,24 +149,24 @@ def test_inequality_binary(self): def test_inequality_integer(self): """ Test InequalityToEqualityConverter with integer variables """ op = OptimizationProblem() - op.variables.add(names=["x", "y", "z"], types="I" * 3, lb=[-3] * 3, ub=[3] * 3) + op.variables.add(names=['x', 'y', 'z'], types='I' * 3, lb=[-3] * 3, ub=[3] * 3) op.linear_constraints.add( lin_expr=[ - SparsePair(ind=["x", "y"], val=[1, 1]), - SparsePair(ind=["y", "z"], val=[1, -1]), - SparsePair(ind=["z", "x"], val=[1, 2]), + SparsePair(ind=['x', 'y'], val=[1, 1]), + SparsePair(ind=['y', 'z'], val=[1, -1]), + SparsePair(ind=['z', 'x'], val=[1, 2]), ], - senses=["E", "L", "G"], + senses=['E', 'L', 'G'], rhs=[1, 2, 3], - names=["xy", "yz", "zx"], + names=['xy', 'yz', 'zx'], ) conv = InequalityToEqualityConverter() op2 = conv.encode(op) self.assertEqual(op.get_problem_name(), op2.get_problem_name()) self.assertEqual(op.get_problem_type(), op2.get_problem_type()) cst = op2.linear_constraints - self.assertListEqual(cst.get_names(), ["xy", "yz", "zx"]) - self.assertListEqual(cst.get_senses(), ["E", "E", "E"]) + self.assertListEqual(cst.get_names(), ['xy', 'yz', 'zx']) + self.assertListEqual(cst.get_senses(), ['E', 'E', 'E']) self.assertListEqual(cst.get_rhs(), [1, 2, 3]) var = op2.variables self.assertListEqual(var.get_lower_bounds(3, 4), [0, 0]) @@ -176,73 +175,73 @@ def test_inequality_integer(self): def test_inequality_mode_integer(self): """ Test integer mode of InequalityToEqualityConverter() """ op = OptimizationProblem() - op.variables.add(names=["x", "y", "z"], types="B" * 3) + op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( lin_expr=[ - SparsePair(ind=["x", "y"], val=[1, 1]), - SparsePair(ind=["y", "z"], val=[1, -1]), - SparsePair(ind=["z", "x"], val=[1, 2]), + SparsePair(ind=['x', 'y'], val=[1, 1]), + SparsePair(ind=['y', 'z'], val=[1, -1]), + SparsePair(ind=['z', 'x'], val=[1, 2]), ], - senses=["E", "L", "G"], + senses=['E', 'L', 'G'], rhs=[1, 2, 3], - names=["xy", "yz", "zx"], + names=['xy', 'yz', 'zx'], ) conv = InequalityToEqualityConverter() - op2 = conv.encode(op, mode="integer") + op2 = conv.encode(op, mode='integer') var = op2.variables - self.assertListEqual(var.get_types(3, 4), ["I", "I"]) + self.assertListEqual(var.get_types(3, 4), ['I', 'I']) def test_inequality_mode_continuous(self): """ Test continuous mode of InequalityToEqualityConverter() """ op = OptimizationProblem() - op.variables.add(names=["x", "y", "z"], types="B" * 3) + op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( lin_expr=[ - SparsePair(ind=["x", "y"], val=[1, 1]), - SparsePair(ind=["y", "z"], val=[1, -1]), - SparsePair(ind=["z", "x"], val=[1, 2]), + SparsePair(ind=['x', 'y'], val=[1, 1]), + SparsePair(ind=['y', 'z'], val=[1, -1]), + SparsePair(ind=['z', 'x'], val=[1, 2]), ], - senses=["E", "L", "G"], + senses=['E', 'L', 'G'], rhs=[1, 2, 3], - names=["xy", "yz", "zx"], + names=['xy', 'yz', 'zx'], ) conv = InequalityToEqualityConverter() - op2 = conv.encode(op, mode="continuous") + op2 = conv.encode(op, mode='continuous') var = op2.variables - self.assertListEqual(var.get_types(3, 4), ["C", "C"]) + self.assertListEqual(var.get_types(3, 4), ['C', 'C']) def test_inequality_mode_auto(self): """ Test auto mode of InequalityToEqualityConverter() """ op = OptimizationProblem() - op.variables.add(names=["x", "y", "z"], types="B" * 3) + op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( lin_expr=[ - SparsePair(ind=["x", "y"], val=[1, 1]), - SparsePair(ind=["y", "z"], val=[1, -1]), - SparsePair(ind=["z", "x"], val=[1.1, 2.2]), + SparsePair(ind=['x', 'y'], val=[1, 1]), + SparsePair(ind=['y', 'z'], val=[1, -1]), + SparsePair(ind=['z', 'x'], val=[1.1, 2.2]), ], - senses=["E", "L", "G"], + senses=['E', 'L', 'G'], rhs=[1, 2, 3.3], - names=["xy", "yz", "zx"], + names=['xy', 'yz', 'zx'], ) conv = InequalityToEqualityConverter() - op2 = conv.encode(op, mode="auto") + op2 = conv.encode(op, mode='auto') var = op2.variables - self.assertListEqual(var.get_types(3, 4), ["I", "C"]) + self.assertListEqual(var.get_types(3, 4), ['I', 'C']) def test_penalize_sense(self): """ Test PenalizeLinearEqualityConstraints with senses """ op = OptimizationProblem() - op.variables.add(names=["x", "y", "z"], types="B" * 3) + op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( lin_expr=[ - SparsePair(ind=["x", "y"], val=[1, 1]), - SparsePair(ind=["y", "z"], val=[1, -1]), - SparsePair(ind=["z", "x"], val=[1, 2]), + SparsePair(ind=['x', 'y'], val=[1, 1]), + SparsePair(ind=['y', 'z'], val=[1, -1]), + SparsePair(ind=['z', 'x'], val=[1, 2]), ], - senses=["E", "L", "G"], + senses=['E', 'L', 'G'], rhs=[1, 2, 3], - names=["xy", "yz", "zx"], + names=['xy', 'yz', 'zx'], ) self.assertEqual(op.linear_constraints.get_num(), 3) conv = PenalizeLinearEqualityConstraints() @@ -252,15 +251,15 @@ def test_penalize_sense(self): def test_penalize_binary(self): """ Test PenalizeLinearEqualityConstraints with binary variables """ op = OptimizationProblem() - op.variables.add(names=["x", "y", "z"], types="B" * 3) + op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( lin_expr=[ - SparsePair(ind=["x", "y"], val=[1, 1]), - SparsePair(ind=["y", "z"], val=[1, -1]), + SparsePair(ind=['x', 'y'], val=[1, 1]), + SparsePair(ind=['y', 'z'], val=[1, -1]), ], - senses=["E", "E"], + senses=['E', 'E'], rhs=[1, 2], - names=["xy", "yz"], + names=['xy', 'yz'], ) self.assertEqual(op.linear_constraints.get_num(), 2) conv = PenalizeLinearEqualityConstraints() @@ -270,15 +269,15 @@ def test_penalize_binary(self): def test_penalize_integer(self): """ Test PenalizeLinearEqualityConstraints with integer variables """ op = OptimizationProblem() - op.variables.add(names=["x", "y", "z"], types="I" * 3, lb=[-3] * 3, ub=[3] * 3) + op.variables.add(names=['x', 'y', 'z'], types='I' * 3, lb=[-3] * 3, ub=[3] * 3) op.linear_constraints.add( lin_expr=[ - SparsePair(ind=["x", "y"], val=[1, 1]), - SparsePair(ind=["y", "z"], val=[1, -1]), + SparsePair(ind=['x', 'y'], val=[1, 1]), + SparsePair(ind=['y', 'z'], val=[1, -1]), ], - senses=["E", "E"], + senses=['E', 'E'], rhs=[1, 2], - names=["xy", "yz"], + names=['xy', 'yz'], ) self.assertEqual(op.linear_constraints.get_num(), 2) conv = PenalizeLinearEqualityConstraints() @@ -288,41 +287,41 @@ def test_penalize_integer(self): def test_integer_to_binary(self): """ Test integer to binary """ op = OptimizationProblem() - op.variables.add(names=["x", "y", "z"], types="BIC", lb=[0, 0, 0], ub=[1, 6, 10]) - op.objective.set_linear([("x", 1), ("y", 2), ("z", 1)]) + op.variables.add(names=['x', 'y', 'z'], types='BIC', lb=[0, 0, 0], ub=[1, 6, 10]) + op.objective.set_linear([('x', 1), ('y', 2), ('z', 1)]) op.linear_constraints.add( - lin_expr=[SparsePair(ind=["x", "y", "z"], val=[1, 3, 1])], - senses=["L"], + lin_expr=[SparsePair(ind=['x', 'y', 'z'], val=[1, 3, 1])], + senses=['L'], rhs=[10], - names=["xyz"], + names=['xyz'], ) self.assertEqual(op.variables.get_num(), 3) conv = IntegerToBinaryConverter() op2 = conv.encode(op) names = op2.variables.get_names() - self.assertIn("x", names) - self.assertIn("z", names) + self.assertIn('x', names) + self.assertIn('z', names) variables = op2.variables - self.assertEqual(variables.get_lower_bounds("x"), 0.0) - self.assertEqual(variables.get_lower_bounds("z"), 0.0) - self.assertEqual(variables.get_upper_bounds("x"), 1.0) - self.assertEqual(variables.get_upper_bounds("z"), 10.0) + self.assertEqual(variables.get_lower_bounds('x'), 0.0) + self.assertEqual(variables.get_lower_bounds('z'), 0.0) + self.assertEqual(variables.get_upper_bounds('x'), 1.0) + self.assertEqual(variables.get_upper_bounds('z'), 10.0) self.assertListEqual( - variables.get_types(["x", "y@0", "y@1", "y@2", "z"]), ["B", "B", "B", "B", "C"] + variables.get_types(['x', 'y@0', 'y@1', 'y@2', 'z']), ['B', 'B', 'B', 'B', 'C'] ) - self.assertListEqual(op2.objective.get_linear(["y@0", "y@1", "y@2"]), [2, 4, 6]) + self.assertListEqual(op2.objective.get_linear(['y@0', 'y@1', 'y@2']), [2, 4, 6]) self.assertListEqual(op2.linear_constraints.get_rows()[0].val, [1, 3, 6, 9, 1]) def test_binary_to_integer(self): """ Test binary to integer """ op = OptimizationProblem() - op.variables.add(names=["x", "y", "z"], types="BIB", lb=[0, 0, 0], ub=[1, 7, 1]) - op.objective.set_linear([("x", 2), ("y", 1), ("z", 1)]) + op.variables.add(names=['x', 'y', 'z'], types='BIB', lb=[0, 0, 0], ub=[1, 7, 1]) + op.objective.set_linear([('x', 2), ('y', 1), ('z', 1)]) op.linear_constraints.add( - lin_expr=[SparsePair(ind=["x", "y", "z"], val=[1, 1, 1])], - senses=["L"], + lin_expr=[SparsePair(ind=['x', 'y', 'z'], val=[1, 1, 1])], + senses=['L'], rhs=[7], - names=["xyz"], + names=['xyz'], ) op.objective.set_sense(-1) conv = IntegerToBinaryConverter() @@ -335,13 +334,13 @@ def test_binary_to_integer(self): def test_optimizationproblem_to_operator(self): """ Test optimization problem to operators""" op = OptimizationProblem() - op.variables.add(names=["a", "b", "c", "d"], types="B" * 4) - op.objective.set_linear([("a", 1), ("b", 1), ("c", 1), ("d", 1)]) + op.variables.add(names=['a', 'b', 'c', 'd'], types='B' * 4) + op.objective.set_linear([('a', 1), ('b', 1), ('c', 1), ('d', 1)]) op.linear_constraints.add( - lin_expr=[SparsePair(ind=["a", "b", "c", "d"], val=[1, 2, 3, 4])], - senses=["E"], + lin_expr=[SparsePair(ind=['a', 'b', 'c', 'd'], val=[1, 2, 3, 4])], + senses=['E'], rhs=[3], - names=["abcd"], + names=['abcd'], ) op.objective.set_sense(-1) penalize = PenalizeLinearEqualityConstraints() @@ -356,26 +355,27 @@ def test_quadratic_constraints(self): # IntegerToBinaryConverter with self.assertRaises(QiskitOptimizationError): op = OptimizationProblem() - op.variables.add(names=["x", "y"]) - l_expr = SparsePair(ind=["x"], val=[1.0]) - q_expr = [["x"], ["y"], [1]] + op.variables.add(names=['x', 'y']) + l_expr = SparsePair(ind=['x'], val=[1.0]) + q_expr = [['x'], ['y'], [1]] op.quadratic_constraints.add(name=str(1), lin_expr=l_expr, quad_expr=q_expr) conv = IntegerToBinaryConverter() _ = conv.encode(op) # InequalityToEqualityConverter with self.assertRaises(QiskitOptimizationError): op = OptimizationProblem() - op.variables.add(names=["x", "y"]) - l_expr = SparsePair(ind=["x"], val=[1.0]) - q_expr = [["x"], ["y"], [1]] + op.variables.add(names=['x', 'y']) + l_expr = SparsePair(ind=['x'], val=[1.0]) + q_expr = [['x'], ['y'], [1]] op.quadratic_constraints.add(name=str(1), lin_expr=l_expr, quad_expr=q_expr) conv = InequalityToEqualityConverter() _ = conv.encode(op) def test_continuous_variable_decode(self): - mdl = Model("test_continuous_varable_decode") - c = mdl.continuous_var(lb=0, ub=10.9, name="c") - x = mdl.binary_var(name="x") + """ Test decode func of IntegerToBinaryConverter for continuous variables""" + mdl = Model('test_continuous_varable_decode') + c = mdl.continuous_var(lb=0, ub=10.9, name='c') + x = mdl.binary_var(name='x') mdl.maximize(c + x * x) op = OptimizationProblem() op.from_docplex(mdl) @@ -394,5 +394,5 @@ def test_continuous_variable_decode(self): self.assertEqual(solution.x[0], 10.9) -if __name__ == "__main__": +if __name__ == '__main__': unittest.main() From e746772fffd27ef2f8abcafe7ee87359890d02c0 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 9 Apr 2020 14:23:57 +0900 Subject: [PATCH 166/323] fix some codes after merge of upstream --- .../algorithms/cobyla_optimizer.py | 2 +- .../algorithms/minimum_eigen_optimizer.py | 2 +- test/optimization/test_admm.py | 8 ++++---- .../optimization/test_grover_minimum_finder.py | 8 ++++---- test/optimization/test_min_eigen_optimizer.py | 4 ++-- ...adratic_program_to_negative_value_oracle.py | 18 +++++++++--------- .../test_recursive_optimization.py | 4 ++-- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index d25c766ca0..06691e6902 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -36,7 +36,7 @@ class CobylaOptimizer(OptimizationAlgorithm): The arguments for ``fmin_cobyla`` are passed via the constructor. Examples: - >>> problem = OptimizationProblem() + >>> problem = QuadraticProgram() >>> # specify problem here >>> optimizer = CobylaOptimizer() >>> result = optimizer.solve(problem) diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 22d210e4f6..ba4d556c00 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -79,7 +79,7 @@ class MinimumEigenOptimizer(OptimizationAlgorithm): Hamiltonian to find a good solution for the optimization problem. Examples: - >>> problem = OptimizationProblem() + >>> problem = QuadraticProgram() >>> # specify problem here >>> # specify minimum eigen solver to be used, e.g., QAOA >>> qaoa = QAOA(...) diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index f6f1435b91..b9c5bb16fa 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -24,7 +24,7 @@ from qiskit.optimization.algorithms import CplexOptimizer, MinimumEigenOptimizer from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters, \ ADMMOptimizerResult, ADMMState -from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.problems import QuadraticProgram class TestADMMOptimizer(QiskitOptimizationTestCase): @@ -37,7 +37,7 @@ def test_admm_maximization(self): c = mdl.continuous_var(lb=0, ub=10, name='c') x = mdl.binary_var(name='x') mdl.maximize(c + x * x) - op = OptimizationProblem() + op = QuadraticProgram() op.from_docplex(mdl) self.assertIsNotNone(op) @@ -82,7 +82,7 @@ def test_admm_ex6(self): mdl.add_constraint(v + w + t >= 1, "cons2") mdl.add_constraint(v + w == 1, "cons3") - op = OptimizationProblem() + op = QuadraticProgram() op.from_docplex(mdl) qubo_optimizer = CplexOptimizer() @@ -127,7 +127,7 @@ def test_admm_ex5(self): mdl.add_constraint(v + w + t >= 1, "cons2") mdl.add_constraint(v + w == 1, "cons3") - op = OptimizationProblem() + op = QuadraticProgram() op.from_docplex(mdl) qubo_optimizer = CplexOptimizer() diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index 2c473f86cb..500b6aa903 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -19,7 +19,7 @@ import numpy as np from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.optimization.algorithms import GroverMinimumFinder, MinimumEigenOptimizer -from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.problems import QuadraticProgram class TestGroverMinimumFinder(QiskitOptimizationTestCase): @@ -47,7 +47,7 @@ def test_qubo_gas_int_zero(self): """Test for when the answer is zero.""" try: # Input. - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x0', 'x1'], types='BB') linear = [('x0', 0), ('x1', 0)] op.objective.set_linear(linear) @@ -64,7 +64,7 @@ def test_qubo_gas_int_simple(self): """Test for simple case, with 2 linear coeffs and no quadratic coeffs or constants.""" try: # Input. - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x0', 'x1'], types='BB') linear = [('x0', -1), ('x1', 2)] op.objective.set_linear(linear) @@ -81,7 +81,7 @@ def test_qubo_gas_int_paper_example(self): """Test the example from https://arxiv.org/abs/1912.04088.""" try: # Input. - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x0', 'x1', 'x2'], types='BBB') linear = [('x0', -1), ('x1', 2), ('x2', -3)] diff --git a/test/optimization/test_min_eigen_optimizer.py b/test/optimization/test_min_eigen_optimizer.py index 805b462064..d163278692 100644 --- a/test/optimization/test_min_eigen_optimizer.py +++ b/test/optimization/test_min_eigen_optimizer.py @@ -25,7 +25,7 @@ from qiskit.aqua.components.optimizers import COBYLA from qiskit.optimization.algorithms import MinimumEigenOptimizer, CplexOptimizer -from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.problems import QuadraticProgram @ddt @@ -67,7 +67,7 @@ def test_min_eigen_optimizer(self, config): min_eigen_optimizer = MinimumEigenOptimizer(min_eigen_solver) # load optimization problem - problem = OptimizationProblem() + problem = QuadraticProgram() problem.read(self.resource_path + filename) # solve problem with cplex diff --git a/test/optimization/test_quadratic_program_to_negative_value_oracle.py b/test/optimization/test_quadratic_program_to_negative_value_oracle.py index aa09d8cd8c..00ed28419d 100644 --- a/test/optimization/test_quadratic_program_to_negative_value_oracle.py +++ b/test/optimization/test_quadratic_program_to_negative_value_oracle.py @@ -17,13 +17,13 @@ import unittest from test.optimization import QiskitOptimizationTestCase import numpy as np -from qiskit.optimization.converters import OptimizationProblemToNegativeValueOracle +from qiskit.optimization.converters import QuadraticProgramToNegativeValueOracle from qiskit.optimization.util import get_qubo_solutions from qiskit import QuantumCircuit, Aer, execute -from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.problems import QuadraticProgram -class TestOptimizationProblemToNegativeValueOracle(QiskitOptimizationTestCase): +class TestQuadraticProgramToNegativeValueOracle(QiskitOptimizationTestCase): """OPtNVO Tests""" def _validate_function(self, func_dict, problem): @@ -85,7 +85,7 @@ def test_optnvo_3_linear_2_quadratic_no_constant(self): num_value = 4 # Input. - problem = OptimizationProblem() + problem = QuadraticProgram() problem.variables.add(names=['x0', 'x1', 'x2'], types='BBB') linear = [('x0', -1), ('x1', 2), ('x2', -3)] problem.objective.set_linear(linear) @@ -93,7 +93,7 @@ def test_optnvo_3_linear_2_quadratic_no_constant(self): problem.objective.set_quadratic_coefficients('x1', 'x2', -1) # Convert to dictionary format with operator/oracle. - converter = OptimizationProblemToNegativeValueOracle(num_value) + converter = QuadraticProgramToNegativeValueOracle(num_value) a_operator, _, func_dict = converter.encode(problem) self._validate_function(func_dict, problem) @@ -108,7 +108,7 @@ def test_optnvo_4_key_all_negative(self): num_value = 5 # Input. - problem = OptimizationProblem() + problem = QuadraticProgram() problem.variables.add(names=['x0', 'x1', 'x2'], types='BBB') linear = [('x0', -1), ('x1', -2), ('x2', -1)] problem.objective.set_linear(linear) @@ -118,7 +118,7 @@ def test_optnvo_4_key_all_negative(self): problem.objective.set_offset(-1) # Convert to dictionary format with operator/oracle. - converter = OptimizationProblemToNegativeValueOracle(num_value) + converter = QuadraticProgramToNegativeValueOracle(num_value) a_operator, _, func_dict = converter.encode(problem) self._validate_function(func_dict, problem) @@ -133,7 +133,7 @@ def test_optnvo_6_key(self): num_value = 4 # Input. - problem = OptimizationProblem() + problem = QuadraticProgram() problem.variables.add(names=['x0', 'x1', 'x2', 'x3', 'x4', 'x5'], types='BBBBBB') linear = [('x0', -1), ('x1', -2), ('x2', -1), ('x3', 0), ('x4', 1), ('x5', 2)] problem.objective.set_linear(linear) @@ -141,7 +141,7 @@ def test_optnvo_6_key(self): problem.objective.set_quadratic_coefficients('x1', 'x5', -2) # Convert to dictionary format with operator/oracle. - converter = OptimizationProblemToNegativeValueOracle(num_value) + converter = QuadraticProgramToNegativeValueOracle(num_value) a_operator, _, func_dict = converter.encode(problem) self._validate_function(func_dict, problem) diff --git a/test/optimization/test_recursive_optimization.py b/test/optimization/test_recursive_optimization.py index df12447513..3114e0054a 100755 --- a/test/optimization/test_recursive_optimization.py +++ b/test/optimization/test_recursive_optimization.py @@ -26,7 +26,7 @@ from qiskit.optimization.algorithms import (MinimumEigenOptimizer, CplexOptimizer, RecursiveMinimumEigenOptimizer) -from qiskit.optimization.problems import OptimizationProblem +from qiskit.optimization.problems import QuadraticProgram @ddt @@ -81,7 +81,7 @@ def test_recursive_min_eigen_optimizer(self, solver, simulator, filename): min_num_vars=4) # load optimization problem - problem = OptimizationProblem() + problem = QuadraticProgram() problem.read(self.resource_path + filename) # solve problem with cplex From c9fd8a43497952b5966646ce4cea19d04516d003 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Thu, 9 Apr 2020 10:40:22 +0200 Subject: [PATCH 167/323] change None for not impl error --- qiskit/optimization/results/solution.py | 34 +++++++++++++------------ 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/qiskit/optimization/results/solution.py b/qiskit/optimization/results/solution.py index b08b33e20d..5f85b70d90 100755 --- a/qiskit/optimization/results/solution.py +++ b/qiskit/optimization/results/solution.py @@ -63,7 +63,7 @@ def get_status(self): >>> c.solution.get_status() 1 """ - return None + raise NotImplementedError def get_method(self): """Returns the method used to solve the problem. @@ -79,7 +79,7 @@ def get_method(self): >>> c.solution.get_method() 2 """ - return None + raise NotImplementedError def get_status_string(self, status_code=None): """Returns a string describing the status of the solution. @@ -98,7 +98,7 @@ def get_status_string(self, status_code=None): # pylint: disable=unused-argument # if status_code is None: # status_code = self.get_status() - return None + raise NotImplementedError def get_objective_value(self): """Returns the value of the objective function. @@ -114,7 +114,7 @@ def get_objective_value(self): >>> c.solution.get_objective_value() -202.5 """ - return None + raise NotImplementedError def get_values(self, *args): """Returns the values of a set of variables at the solution. @@ -149,7 +149,7 @@ def get_values(self, *args): [25.5, 0.0, 80.0] """ # pylint: disable=unused-argument - return None + raise NotImplementedError def get_integer_quality(self, which): """Returns a measure of the quality of the solution. @@ -168,10 +168,11 @@ def get_integer_quality(self, which): >>> c.solution.get_integer_quality([m.max_x, m.max_dual_infeasibility]) [18, -1] """ - if isinstance(which, int): - return None - else: - return [None for a in which] + # if isinstance(which, int): + # return None + # else: + # return [None for a in which] + raise NotImplementedError def get_float_quality(self, which): """Returns a measure of the quality of the solution. @@ -193,10 +194,11 @@ def get_float_quality(self, which): >>> c.solution.get_float_quality([m.max_x, m.max_dual_infeasibility]) [500.0, 0.0] """ - if isinstance(which, int): - return None - else: - return [None for a in which] + # if isinstance(which, int): + # return None + # else: + # return [None for a in which] + raise NotImplementedError def get_solution_type(self): """Returns the type of the solution. @@ -212,7 +214,7 @@ def get_solution_type(self): >>> c.solution.get_solution_type() 1 """ - return None + raise NotImplementedError def is_primal_feasible(self): """Returns whether or not the solution is known to be primal feasible. @@ -231,7 +233,7 @@ def is_primal_feasible(self): >>> c.solution.is_primal_feasible() True """ - return None + raise NotImplementedError def get_quality_metrics(self): """Returns an object containing measures of the solution quality. @@ -254,4 +256,4 @@ def write(self, filename): >>> c.solution.write("lpex.sol") """ # pylint: disable=unused-argument - pass + raise NotImplementedError From a09000bd52346044fba48a88d7e5708034040288 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Thu, 9 Apr 2020 11:14:57 +0200 Subject: [PATCH 168/323] is_compatible returns bool and raises --- .../optimization/algorithms/admm_optimizer.py | 24 ++++++++++++----- .../algorithms/cobyla_optimizer.py | 24 +++++++---------- .../algorithms/cplex_optimizer.py | 6 ++--- .../algorithms/grover_minimum_finder.py | 7 +++-- .../algorithms/minimum_eigen_optimizer.py | 6 ++--- .../algorithms/optimization_algorithm.py | 6 ++--- .../recursive_minimum_eigen_optimizer.py | 7 +++-- .../optimization_problem_to_qubo.py | 27 ++++++++++--------- 8 files changed, 54 insertions(+), 53 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 8c4cfcb1b0..3dd3001eb1 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -23,6 +23,7 @@ from qiskit.optimization.problems.optimization_problem import OptimizationProblem from qiskit.optimization.problems.variables import CPX_BINARY, CPX_CONTINUOUS from qiskit.optimization.results.optimization_result import OptimizationResult +from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError UPDATE_RHO_BY_TEN_PERCENT = 0 UPDATE_RHO_BY_RESIDUALS = 1 @@ -210,21 +211,26 @@ def __init__(self, qubo_optimizer: Optional[OptimizationAlgorithm] = None, # the solve method. self._state: Optional[ADMMState] = None - def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + def is_compatible(self, problem: OptimizationProblem) -> bool: """Checks whether a given problem can be solved with the optimizer implementing this method. Args: problem: The optimization problem to check compatibility. Returns: - Returns ``None`` if the problem is compatible and else a string with the error message. + Returns True if the problem is compatible, otherwise raises an error. + + Raises: + QiskitOptimizationError: If the problem is not compatible with the ADMM optimizer. """ + msg = '' + # 1. only binary and continuous variables are supported for var_type in problem.variables.get_types(): if var_type not in (CPX_BINARY, CPX_CONTINUOUS): # variable is not binary and not continuous. - return "Only binary and continuous variables are supported" + msg += 'Only binary and continuous variables are supported. ' binary_indices = self._get_variable_indices(problem, CPX_BINARY) continuous_indices = self._get_variable_indices(problem, CPX_CONTINUOUS) @@ -235,15 +241,19 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: coeff = problem.objective.get_quadratic_coefficients(binary_index, continuous_index) if coeff != 0: # binary and continuous vars are mixed. - return "Binary and continuous variables are not separable in the objective" + msg += 'Binary and continuous variables are not separable in the objective. ' # 3. no quadratic constraints are supported. quad_constraints = problem.quadratic_constraints.get_num() if quad_constraints is not None and quad_constraints > 0: # quadratic constraints are not supported. - return "Quadratic constraints are not supported" + msg += 'Quadratic constraints are not supported. ' + + # if an error occurred, return error message, otherwise, return None + if len(msg) > 0: + raise QiskitOptimizationError('The problem is not compatible with ADMM: %s' % msg) - return None + return True def solve(self, problem: OptimizationProblem) -> ADMMOptimizerResult: """Tries to solves the given problem using ADMM algorithm. @@ -619,7 +629,7 @@ def _create_step1_problem(self) -> OptimizationProblem: 2 * ( self._params.factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) + self._state.rho / 2 * np.eye(binary_size) - ) + ) for i in range(binary_size): for j in range(i, binary_size): op1.objective.set_quadratic_coefficients(i, j, quadratic_objective[i, j]) diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index bcc288de17..280e9d75d7 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -67,7 +67,7 @@ def __init__(self, rhobeg: float = 1.0, rhoend: float = 1e-4, maxfun: int = 1000 self._disp = disp self._catol = catol - def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + def is_compatible(self, problem: OptimizationProblem) -> bool: """Checks whether a given problem can be solved with this optimizer. Checks whether the given problem is compatible, i.e., whether the problem contains only @@ -77,21 +77,16 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: problem: The optimization problem to check compatibility. Returns: - Returns ``None`` if the problem is compatible and else a string with the error message. - """ - - # initialize message - msg = '' + Returns True if the problem is compatible, otherwise raises an error. + Raises: + QiskitOptimizationError: If the problem contains non-continuous variables. + """ # check whether there are variables of type other than continuous if problem.variables.get_num() > problem.variables.get_num_continuous(): - msg += 'This optimizer supports only continuous variables! ' + raise QiskitOptimizationError('The COBYLA optimizer supports only continuous variables') - # if an error occurred, return error message, otherwise, return None - if len(msg) > 0: - return msg.strip() - else: - return None + return True def solve(self, problem: OptimizationProblem) -> OptimizationResult: """Tries to solves the given problem using the optimizer. @@ -109,9 +104,8 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: """ # check compatibility and raise exception if incompatible - msg = self.is_compatible(problem) - if msg: - raise QiskitOptimizationError('Incompatible problem: {}'.format(msg)) + if not self.is_compatible(problem): + raise QiskitOptimizationError('Incompatible problem.') # get number of variables num_vars = problem.variables.get_num() diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index cb10dcfcdc..6e736c006d 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -83,7 +83,7 @@ def parameter_set(self, parameter_set: Optional['ParameterSet']): """ self._parameter_set = parameter_set - def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + def is_compatible(self, problem: OptimizationProblem) -> bool: """Checks whether a given problem can be solved with this optimizer. Returns ``True`` since CPLEX accepts all problems that can be modeled using the @@ -94,9 +94,9 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: problem: The optimization problem to check compatibility. Returns: - Returns ``None`` if the problem is compatible and else a string with the error message. + True. """ - return None + return True def solve(self, problem: OptimizationProblem) -> OptimizationResult: """Tries to solves the given problem using the optimizer. diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index bb36f43682..9fefbaa583 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -52,7 +52,7 @@ def __init__(self, num_iterations: int = 3, quantum_instance = QuantumInstance(backend) self._quantum_instance = quantum_instance - def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + def is_compatible(self, problem: OptimizationProblem) -> bool: """Checks whether a given problem can be solved with this optimizer. Checks whether the given problem is compatible, i.e., whether the problem can be converted @@ -62,7 +62,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: problem: The optimization problem to check compatibility. Returns: - Returns ``None`` if the problem is compatible and else a string with the error message. + True, if the problem is compatible and else raise an error. """ return OptimizationProblemToQubo.is_compatible(problem) @@ -81,8 +81,7 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: Raises: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ - - # convert problem to QUBO + # convert problem to QUBO, this implicitly checks if the problem is compatible qubo_converter = OptimizationProblemToQubo() problem_ = qubo_converter.encode(problem) diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 9deb024a14..558d77e41a 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -113,7 +113,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: problem: The optimization problem to check compatibility. Returns: - Returns ``None`` if the problem is compatible and else a string with the error message. + True, if the problem is compatible and else raise an error. """ return OptimizationProblemToQubo.is_compatible(problem) @@ -127,10 +127,8 @@ def solve(self, problem: OptimizationProblem) -> MinimumEigenOptimizerResult: Returns: The result of the optimizer applied to the problem. - """ - - # convert problem to QUBO + # convert problem to QUBO, this implicitly checks if the problem is compatible qubo_converter = OptimizationProblemToQubo() problem_ = qubo_converter.encode(problem) diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index 49f6a167b6..27d9e79d46 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -17,8 +17,6 @@ from abc import ABC, abstractmethod -from typing import Optional - from ..problems.optimization_problem import OptimizationProblem from ..results.optimization_result import OptimizationResult @@ -27,14 +25,14 @@ class OptimizationAlgorithm(ABC): """An abstract class for optimization algorithms in Qiskit Optimization.""" @abstractmethod - def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + def is_compatible(self, problem: OptimizationProblem) -> bool: """Checks whether a given problem can be solved with the optimizer implementing this method. Args: problem: The optimization problem to check compatibility. Returns: - Returns ``None`` if the problem is compatible and else a string with the error message. + Returns True if the problem is compatible, otherwise raises an error. """ raise NotImplementedError diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 9be5d4db65..99ebf0449f 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -94,7 +94,7 @@ def __init__(self, min_eigen_optimizer: MinimumEigenOptimizer, min_num_vars: int self._min_num_vars_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) self._penalty = penalty - def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + def is_compatible(self, problem: OptimizationProblem) -> bool: """Checks whether a given problem can be solved with this optimizer. Checks whether the given problem is compatible, i.e., whether the problem can be converted @@ -104,7 +104,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: problem: The optimization problem to check compatibility. Returns: - Returns ``None`` if the problem is compatible and else a string with the error message. + True, if the problem is compatible and else raise an error. """ return OptimizationProblemToQubo.is_compatible(problem) @@ -121,9 +121,8 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: Raises: QiskitOptimizationError: Infeasible due to variable substitution - """ - # convert problem to QUBO + # convert problem to QUBO, this implicitly checks if the problem is compatible qubo_converter = OptimizationProblemToQubo() problem_ = qubo_converter.encode(problem) problem_ref = deepcopy(problem_) diff --git a/qiskit/optimization/converters/optimization_problem_to_qubo.py b/qiskit/optimization/converters/optimization_problem_to_qubo.py index e4c9c9bbda..94d15d8ada 100644 --- a/qiskit/optimization/converters/optimization_problem_to_qubo.py +++ b/qiskit/optimization/converters/optimization_problem_to_qubo.py @@ -59,9 +59,8 @@ def encode(self, problem: OptimizationProblem) -> OptimizationProblem: """ # analyze compatibility of problem - msg = self.is_compatible(problem) - if msg is not None: - raise QiskitOptimizationError('Incompatible problem: %s' % msg) + if not self.is_compatible(problem): + raise QiskitOptimizationError('Incompatible problem.') # map integer variables to binary variables problem_ = self._int_to_bin.encode(problem) @@ -89,18 +88,22 @@ def decode(self, result: OptimizationResult) -> OptimizationResult: return self._int_to_bin.decode(result) @staticmethod - def is_compatible(problem: OptimizationProblem) -> Optional[str]: - """Checks whether a given problem can be cast to a Quadratic Unconstrained Binary - Optimization (QUBO) problem, i.e., whether the problem contains only binary and integer - variables as well as linear equality constraints, and otherwise, returns a message - explaining the incompatibility. + def is_compatible(problem: OptimizationProblem) -> bool: + """Checks whether a given problem can be cast to a QUBO. + + An optimization problem can be converted to a QUBO (Quadratic Unconstrained Binary + Optimization) problem, if the problem contains only binary and integer variables as well + as linear equality constraints. Args: problem: The optimization problem to check compatibility. Returns: - Returns ``None`` if the problem is compatible and else a string with the error message. + True, if the problem can be converted to a QUBO, otherwise a QiskitOptimizationError + is raised. + Raises: + QiskitOptimizationError: If the conversion to QUBO is not possible. """ # initialize message @@ -124,6 +127,6 @@ def is_compatible(problem: OptimizationProblem) -> Optional[str]: # if an error occurred, return error message, otherwise, return None if len(msg) > 0: - return msg.strip() - else: - return None + raise QiskitOptimizationError('Cannot convert the problem to QUBO: %s' % msg) + + return True From d0325ed02cc35be2ff1302b4ca16960f195e7073 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Thu, 9 Apr 2020 11:43:25 +0100 Subject: [PATCH 169/323] adding support for quadratic constraints --- .../optimization/algorithms/admm_optimizer.py | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index b2fbed0365..e87130d739 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -18,7 +18,7 @@ from typing import List, Optional, Any import numpy as np -from cplex import SparsePair +from cplex import SparsePair, SparseTriple from qiskit.optimization.algorithms.cplex_optimizer import CplexOptimizer from qiskit.optimization.algorithms.optimization_algorithm import OptimizationAlgorithm from qiskit.optimization.problems.optimization_problem import OptimizationProblem @@ -705,8 +705,40 @@ def _create_step2_problem(self) -> OptimizationProblem: senses=["L"] * constraint_count, rhs=self._state.b3.tolist()) + # add quadratic constraints for + # In the step 2, we basically need to copy all quadratic constraints of the original + # problem, and substitute binary variables with variables 0<=z<=1. + # The quadratic constraint can have any equality/inequality sign. + # + # For example: + # Original problem: + # Quadratic_constraint: x**2 + u**2 <= 1 + # Vars: x binary, L <= u <= U + # + # Step 2: + # Quadratic_constraint: z**2 + u**2 <= 1 + # Vars: 0<=z<=1, L <= u <= U + for linear, quad, sense, rhs \ + in zip(self._state.op.quadratic_constraints.get_linear_components(), + self._state.quadratic_constraints.get_quadratic_components(), + self._state.quadratic_constraints.get_senses(), + self._state.quadratic_constraints.get_rhs()): + # types in the loop: SparsePair, SparseTriple, character, float + print(linear, quad, sense, rhs) + new_linear = SparsePair(ind=self._binary_indices_to_continuous(linear.ins), + val=linear.val) + new_quadratic = SparseTriple(ind1=self._binary_indices_to_continuous(quad.ind1), + ind2=self._binary_indices_to_continuous(quad.ind2), + val=quad.val) + op2.quadratic_constraints.add(lin_expr=new_linear, quad_expr=new_quadratic, + sense=sense, rhs=rhs) + return op2 + def _binary_indices_to_continuous(self, binary_indices: List[int]) -> List[int]: + # todo: implement + return binary_indices + def _create_step3_problem(self) -> OptimizationProblem: """Creates a step 3 sub-problem. From 8fcf270a1916502b0a1680318e2f7c3fc4a4e078 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Thu, 9 Apr 2020 11:46:53 +0100 Subject: [PATCH 170/323] fixing imports --- 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 a6df305096..1906a1b6b9 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -31,7 +31,7 @@ _HAS_CPLEX = False try: - from cplex import SparsePair + from cplex import SparsePair, SparseTriple _HAS_CPLEX = True except ImportError: logger.info('CPLEX is not installed.') From fbc4581298e167b513b7d73b9a979f27ec50778e Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 9 Apr 2020 20:17:22 +0900 Subject: [PATCH 171/323] rename OptimizationProblem with QuadraticProgram in test_converters --- test/optimization/test_converters.py | 60 ++++++++++++++-------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index 0cc9f50711..52b64712e4 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -21,11 +21,11 @@ from qiskit.aqua.operators import WeightedPauliOperator from qiskit.aqua.algorithms import NumPyMinimumEigensolver from docplex.mp.model import Model -from qiskit.optimization import OptimizationProblem, QiskitOptimizationError +from qiskit.optimization import QuadraticProgram, QiskitOptimizationError from qiskit.optimization.results import OptimizationResult from qiskit.optimization.converters import ( InequalityToEqualityConverter, - OptimizationProblemToOperator, + QuadraticProgramToOperator, IntegerToBinaryConverter, PenalizeLinearEqualityConstraints, ) @@ -69,60 +69,60 @@ def setUp(self) -> None: def test_empty_problem(self): """ Test empty problem """ - op = OptimizationProblem() + op = QuadraticProgram() conv = InequalityToEqualityConverter() op = conv.encode(op) conv = IntegerToBinaryConverter() op = conv.encode(op) conv = PenalizeLinearEqualityConstraints() op = conv.encode(op) - conv = OptimizationProblemToOperator() + conv = QuadraticProgramToOperator() _, shift = conv.encode(op) self.assertEqual(shift, 0.0) def test_valid_variable_type(self): - """Validate the types of the variables for OptimizationProblemToOperator.""" + """Validate the types of the variables for QuadraticProgramToOperator.""" # Integer variable with self.assertRaises(QiskitOptimizationError): - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x'], types='I') - conv = OptimizationProblemToOperator() + conv = QuadraticProgramToOperator() _ = conv.encode(op) # Continuous variable with self.assertRaises(QiskitOptimizationError): - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x'], types='C') - conv = OptimizationProblemToOperator() + conv = QuadraticProgramToOperator() _ = conv.encode(op) # Semi-Continuous variable with self.assertRaises(QiskitOptimizationError): - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x'], types='S') - conv = OptimizationProblemToOperator() + conv = QuadraticProgramToOperator() _ = conv.encode(op) # Semi-Integer variable with self.assertRaises(QiskitOptimizationError): - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x'], types='N') - conv = OptimizationProblemToOperator() + conv = QuadraticProgramToOperator() _ = conv.encode(op) # validate the types of the variables for InequalityToEqualityConverter # Semi-Continuous variable with self.assertRaises(QiskitOptimizationError): - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x'], types='S') conv = InequalityToEqualityConverter() _ = conv.encode(op) # Semi-Integer variable with self.assertRaises(QiskitOptimizationError): - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x'], types='N') conv = InequalityToEqualityConverter() _ = conv.encode(op) def test_inequality_binary(self): """ Test InequalityToEqualityConverter with binary variables """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( lin_expr=[ @@ -148,7 +148,7 @@ def test_inequality_binary(self): def test_inequality_integer(self): """ Test InequalityToEqualityConverter with integer variables """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='I' * 3, lb=[-3] * 3, ub=[3] * 3) op.linear_constraints.add( lin_expr=[ @@ -174,7 +174,7 @@ def test_inequality_integer(self): def test_inequality_mode_integer(self): """ Test integer mode of InequalityToEqualityConverter() """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( lin_expr=[ @@ -193,7 +193,7 @@ def test_inequality_mode_integer(self): def test_inequality_mode_continuous(self): """ Test continuous mode of InequalityToEqualityConverter() """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( lin_expr=[ @@ -212,7 +212,7 @@ def test_inequality_mode_continuous(self): def test_inequality_mode_auto(self): """ Test auto mode of InequalityToEqualityConverter() """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( lin_expr=[ @@ -231,7 +231,7 @@ def test_inequality_mode_auto(self): def test_penalize_sense(self): """ Test PenalizeLinearEqualityConstraints with senses """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( lin_expr=[ @@ -250,7 +250,7 @@ def test_penalize_sense(self): def test_penalize_binary(self): """ Test PenalizeLinearEqualityConstraints with binary variables """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='B' * 3) op.linear_constraints.add( lin_expr=[ @@ -268,7 +268,7 @@ def test_penalize_binary(self): def test_penalize_integer(self): """ Test PenalizeLinearEqualityConstraints with integer variables """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='I' * 3, lb=[-3] * 3, ub=[3] * 3) op.linear_constraints.add( lin_expr=[ @@ -286,7 +286,7 @@ def test_penalize_integer(self): def test_integer_to_binary(self): """ Test integer to binary """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='BIC', lb=[0, 0, 0], ub=[1, 6, 10]) op.objective.set_linear([('x', 1), ('y', 2), ('z', 1)]) op.linear_constraints.add( @@ -314,7 +314,7 @@ def test_integer_to_binary(self): def test_binary_to_integer(self): """ Test binary to integer """ - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y', 'z'], types='BIB', lb=[0, 0, 0], ub=[1, 7, 1]) op.objective.set_linear([('x', 2), ('y', 1), ('z', 1)]) op.linear_constraints.add( @@ -333,7 +333,7 @@ def test_binary_to_integer(self): def test_optimizationproblem_to_operator(self): """ Test optimization problem to operators""" - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['a', 'b', 'c', 'd'], types='B' * 4) op.objective.set_linear([('a', 1), ('b', 1), ('c', 1), ('d', 1)]) op.linear_constraints.add( @@ -344,7 +344,7 @@ def test_optimizationproblem_to_operator(self): ) op.objective.set_sense(-1) penalize = PenalizeLinearEqualityConstraints() - op2ope = OptimizationProblemToOperator() + op2ope = QuadraticProgramToOperator() op2 = penalize.encode(op) qubitop, offset = op2ope.encode(op2) self.assertListEqual(qubitop.paulis, QUBIT_OP_MAXIMIZE_SAMPLE.paulis) @@ -354,7 +354,7 @@ def test_quadratic_constraints(self): """ Test quadratic constraints""" # IntegerToBinaryConverter with self.assertRaises(QiskitOptimizationError): - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y']) l_expr = SparsePair(ind=['x'], val=[1.0]) q_expr = [['x'], ['y'], [1]] @@ -363,7 +363,7 @@ def test_quadratic_constraints(self): _ = conv.encode(op) # InequalityToEqualityConverter with self.assertRaises(QiskitOptimizationError): - op = OptimizationProblem() + op = QuadraticProgram() op.variables.add(names=['x', 'y']) l_expr = SparsePair(ind=['x'], val=[1.0]) q_expr = [['x'], ['y'], [1]] @@ -377,7 +377,7 @@ def test_continuous_variable_decode(self): c = mdl.continuous_var(lb=0, ub=10.9, name='c') x = mdl.binary_var(name='x') mdl.maximize(c + x * x) - op = OptimizationProblem() + op = QuadraticProgram() op.from_docplex(mdl) converter = IntegerToBinaryConverter() op = converter.encode(op) From 5ded0f69c5e7c48ee962e674c9453ad18c18c04a Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Thu, 9 Apr 2020 09:26:17 -0400 Subject: [PATCH 172/323] fix lint --- test/optimization/test_converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index 0cc9f50711..bb16ecda52 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -17,10 +17,10 @@ import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase import logging +from docplex.mp.model import Model from qiskit.aqua.operators import WeightedPauliOperator from qiskit.aqua.algorithms import NumPyMinimumEigensolver -from docplex.mp.model import Model from qiskit.optimization import OptimizationProblem, QiskitOptimizationError from qiskit.optimization.results import OptimizationResult from qiskit.optimization.converters import ( From 4495760737e7a8388832f7a7ea002389db5be5d4 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Thu, 9 Apr 2020 14:58:01 +0100 Subject: [PATCH 173/323] commenting out quad constraints support --- .../optimization/algorithms/admm_optimizer.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 1906a1b6b9..4e6408e3f6 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -731,20 +731,20 @@ def _create_step2_problem(self) -> OptimizationProblem: # Step 2: # Quadratic_constraint: z**2 + u**2 <= 1 # Vars: 0<=z<=1, L <= u <= U - for linear, quad, sense, rhs \ - in zip(self._state.op.quadratic_constraints.get_linear_components(), - self._state.quadratic_constraints.get_quadratic_components(), - self._state.quadratic_constraints.get_senses(), - self._state.quadratic_constraints.get_rhs()): - # types in the loop: SparsePair, SparseTriple, character, float - print(linear, quad, sense, rhs) - new_linear = SparsePair(ind=self._binary_indices_to_continuous(linear.ins), - val=linear.val) - new_quadratic = SparseTriple(ind1=self._binary_indices_to_continuous(quad.ind1), - ind2=self._binary_indices_to_continuous(quad.ind2), - val=quad.val) - op2.quadratic_constraints.add(lin_expr=new_linear, quad_expr=new_quadratic, - sense=sense, rhs=rhs) + # for linear, quad, sense, rhs \ + # in zip(self._state.op.quadratic_constraints.get_linear_components(), + # self._state.op.quadratic_constraints.get_quadratic_components(), + # self._state.op.quadratic_constraints.get_senses(), + # self._state.op.quadratic_constraints.get_rhs()): + # # types in the loop: SparsePair, SparseTriple, character, float + # print(linear, quad, sense, rhs) + # new_linear = SparsePair(ind=self._binary_indices_to_continuous(linear.ins), + # val=linear.val) + # new_quadratic = SparseTriple(ind1=self._binary_indices_to_continuous(quad.ind1), + # ind2=self._binary_indices_to_continuous(quad.ind2), + # val=quad.val) + # op2.quadratic_constraints.add(lin_expr=new_linear, quad_expr=new_quadratic, + # sense=sense, rhs=rhs) return op2 From 110bfff43df199454c28c88e9ff13999868a6f4f Mon Sep 17 00:00:00 2001 From: Cryoris Date: Thu, 9 Apr 2020 16:00:06 +0200 Subject: [PATCH 174/323] fix typos, add references --- .../algorithms/recursive_minimum_eigen_optimizer.py | 2 +- .../converters/inequality_to_equality_converter.py | 2 +- .../optimization/converters/integer_to_binary_converter.py | 7 ++++++- .../optimization_problem_to_negative_value_oracle.py | 6 ++++++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 9be5d4db65..0590bf1630 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -109,7 +109,7 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: return OptimizationProblemToQubo.is_compatible(problem) def solve(self, problem: OptimizationProblem) -> OptimizationResult: - """Tries to solves the given problem using the recursive optimizer. + """Tries to solve the given problem using the recursive optimizer. Runs the optimizer to try to solve the optimization problem. diff --git a/qiskit/optimization/converters/inequality_to_equality_converter.py b/qiskit/optimization/converters/inequality_to_equality_converter.py index 668da26504..485ff48b70 100644 --- a/qiskit/optimization/converters/inequality_to_equality_converter.py +++ b/qiskit/optimization/converters/inequality_to_equality_converter.py @@ -46,7 +46,7 @@ class InequalityToEqualityConverter: _delimiter = '@' # users are supposed not to use this character in variable names def __init__(self) -> None: - """Initialize the integral variables.""" + """Initialize the inequality to equality variable converter.""" if not _HAS_CPLEX: raise NameError('CPLEX is not installed.') diff --git a/qiskit/optimization/converters/integer_to_binary_converter.py b/qiskit/optimization/converters/integer_to_binary_converter.py index 7512ad66b7..0f87234579 100644 --- a/qiskit/optimization/converters/integer_to_binary_converter.py +++ b/qiskit/optimization/converters/integer_to_binary_converter.py @@ -36,11 +36,17 @@ class IntegerToBinaryConverter: """Convert an `OptimizationProblem` into new one by encoding integer with binary variables. + This bounded-coefficient encoding used in this converted is proposed in [1], Eq. (5). + Examples: >>> problem = OptimizationProblem() >>> problem.variables.add(names=['x'], types=['I'], lb=[0], ub=[10]) >>> conv = IntegerToBinaryConverter() >>> problem2 = conv.encode(problem) + + References: + [1]: Sahar Karimi, Pooya Ronagh (2017), Practical Integer-to-Binary Mapping for Quantum + Annealers. arxiv.org:1706.01945. """ _delimiter = '@' # users are supposed not to use this character in variable names @@ -99,7 +105,6 @@ def encode(self, op: OptimizationProblem, name: Optional[str] = None) -> Optimiz return self._dst def _encode_var(self, name: str, lower_bound: int, upper_bound: int) -> List[Tuple[str, int]]: - # bounded-coefficient encoding proposed in arxiv:1706.01945 (Eq. (5)) var_range = upper_bound - lower_bound power = int(np.log2(var_range)) bounded_coef = var_range - (2 ** power - 1) diff --git a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py index 63a9f1d768..870c9d9b2b 100644 --- a/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/optimization_problem_to_negative_value_oracle.py @@ -34,6 +34,12 @@ class OptimizationProblemToNegativeValueOracle: In addition, a state preparation operator is generated from the coefficients and constant of a QUBO, which can be used to encode the function into a quantum state. In conjunction, this oracle and operator can be used to flag the negative values of a QUBO encoded in a quantum state. + + The construction of the oracle is discussed in [1]. + + References: + [1]: Gilliam et al., Grover Adaptive Search for Constrained Polynomial Binary Optimization. + arxiv:1912.04088. """ def __init__(self, num_output_qubits: int, measurement: bool = False) -> None: From 1bfabb46e907b42c2f68dbc89dfa10500f51dae7 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Thu, 9 Apr 2020 16:12:58 +0200 Subject: [PATCH 175/323] is_compatible for bool, get_incomptability for msg --- .../optimization/algorithms/cobyla_optimizer.py | 17 +++++++---------- .../optimization/algorithms/cplex_optimizer.py | 8 ++++---- .../algorithms/grover_minimum_finder.py | 6 +++--- .../algorithms/minimum_eigen_optimizer.py | 6 +++--- .../algorithms/optimization_algorithm.py | 14 ++++++++++++-- .../recursive_minimum_eigen_optimizer.py | 6 +++--- .../converters/optimization_problem_to_qubo.py | 16 ++++++++++++---- 7 files changed, 44 insertions(+), 29 deletions(-) diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index 280e9d75d7..70177c1b03 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -67,7 +67,7 @@ def __init__(self, rhobeg: float = 1.0, rhoend: float = 1e-4, maxfun: int = 1000 self._disp = disp self._catol = catol - def is_compatible(self, problem: OptimizationProblem) -> bool: + def get_incompatibility(self, problem: OptimizationProblem) -> str: """Checks whether a given problem can be solved with this optimizer. Checks whether the given problem is compatible, i.e., whether the problem contains only @@ -77,16 +77,13 @@ def is_compatible(self, problem: OptimizationProblem) -> bool: problem: The optimization problem to check compatibility. Returns: - Returns True if the problem is compatible, otherwise raises an error. - - Raises: - QiskitOptimizationError: If the problem contains non-continuous variables. + Returns a string describing the incompatibility. """ # check whether there are variables of type other than continuous if problem.variables.get_num() > problem.variables.get_num_continuous(): - raise QiskitOptimizationError('The COBYLA optimizer supports only continuous variables') + return 'The COBYLA optimizer supports only continuous variables' - return True + return '' def solve(self, problem: OptimizationProblem) -> OptimizationResult: """Tries to solves the given problem using the optimizer. @@ -102,10 +99,10 @@ def solve(self, problem: OptimizationProblem) -> OptimizationResult: Raises: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ - # check compatibility and raise exception if incompatible - if not self.is_compatible(problem): - raise QiskitOptimizationError('Incompatible problem.') + msg = self.get_incompatibility(problem) + if len(msg) > 0: + raise QiskitOptimizationError('Incompatible problem: {}'.format(msg)) # get number of variables num_vars = problem.variables.get_num() diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index 6e736c006d..19e6a16c3d 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -83,10 +83,10 @@ def parameter_set(self, parameter_set: Optional['ParameterSet']): """ self._parameter_set = parameter_set - def is_compatible(self, problem: OptimizationProblem) -> bool: + def get_incompatibility(self, problem: OptimizationProblem) -> str: """Checks whether a given problem can be solved with this optimizer. - Returns ``True`` since CPLEX accepts all problems that can be modeled using the + Returns ``''`` since CPLEX accepts all problems that can be modeled using the ``OptimizationProblem``. CPLEX may throw an exception in case the problem is determined to be non-convex. This case could be addressed by setting CPLEX parameters accordingly. @@ -94,9 +94,9 @@ def is_compatible(self, problem: OptimizationProblem) -> bool: problem: The optimization problem to check compatibility. Returns: - True. + An empty string. """ - return True + return '' def solve(self, problem: OptimizationProblem) -> OptimizationResult: """Tries to solves the given problem using the optimizer. diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index 9fefbaa583..8bed15cf56 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -52,7 +52,7 @@ def __init__(self, num_iterations: int = 3, quantum_instance = QuantumInstance(backend) self._quantum_instance = quantum_instance - def is_compatible(self, problem: OptimizationProblem) -> bool: + def get_incompatibility(self, problem: OptimizationProblem) -> str: """Checks whether a given problem can be solved with this optimizer. Checks whether the given problem is compatible, i.e., whether the problem can be converted @@ -62,9 +62,9 @@ def is_compatible(self, problem: OptimizationProblem) -> bool: problem: The optimization problem to check compatibility. Returns: - True, if the problem is compatible and else raise an error. + A message describing the incompatibility. """ - return OptimizationProblemToQubo.is_compatible(problem) + return OptimizationProblemToQubo.get_incompatibility(problem) def solve(self, problem: OptimizationProblem) -> OptimizationResult: """Tries to solves the given problem using the optimizer. diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 558d77e41a..62f6e33588 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -103,7 +103,7 @@ def __init__(self, min_eigen_solver: MinimumEigensolver, penalty: Optional[float self._min_eigen_solver = min_eigen_solver self._penalty = penalty - def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: + def get_incompatibility(self, problem: OptimizationProblem) -> str: """Checks whether a given problem can be solved with this optimizer. Checks whether the given problem is compatible, i.e., whether the problem can be converted @@ -113,9 +113,9 @@ def is_compatible(self, problem: OptimizationProblem) -> Optional[str]: problem: The optimization problem to check compatibility. Returns: - True, if the problem is compatible and else raise an error. + A message describing the incompatibility. """ - return OptimizationProblemToQubo.is_compatible(problem) + return OptimizationProblemToQubo.get_incompatibility(problem) def solve(self, problem: OptimizationProblem) -> MinimumEigenOptimizerResult: """Tries to solves the given problem using the optimizer. diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index 27d9e79d46..1b209ed781 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -25,6 +25,16 @@ class OptimizationAlgorithm(ABC): """An abstract class for optimization algorithms in Qiskit Optimization.""" @abstractmethod + def get_incompatibility(self, problem: OptimizationProblem) -> str: + """Checks whether a given problem can be solved with the optimizer implementing this method. + + Args: + problem: The optimization problem to check compatibility. + + Returns: + Returns the incompatibility message. If the message is empty no issues were found. + """ + def is_compatible(self, problem: OptimizationProblem) -> bool: """Checks whether a given problem can be solved with the optimizer implementing this method. @@ -32,9 +42,9 @@ def is_compatible(self, problem: OptimizationProblem) -> bool: problem: The optimization problem to check compatibility. Returns: - Returns True if the problem is compatible, otherwise raises an error. + Returns True if the problem is compatible, False otherwise. """ - raise NotImplementedError + return len(self.get_incompatibility(problem)) > 0 @abstractmethod def solve(self, problem: OptimizationProblem) -> OptimizationResult: diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 99ebf0449f..b9338f34b1 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -94,7 +94,7 @@ def __init__(self, min_eigen_optimizer: MinimumEigenOptimizer, min_num_vars: int self._min_num_vars_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) self._penalty = penalty - def is_compatible(self, problem: OptimizationProblem) -> bool: + def get_incompatibility(self, problem: OptimizationProblem) -> str: """Checks whether a given problem can be solved with this optimizer. Checks whether the given problem is compatible, i.e., whether the problem can be converted @@ -104,9 +104,9 @@ def is_compatible(self, problem: OptimizationProblem) -> bool: problem: The optimization problem to check compatibility. Returns: - True, if the problem is compatible and else raise an error. + A message describing the incompatibility. """ - return OptimizationProblemToQubo.is_compatible(problem) + return OptimizationProblemToQubo.get_incompatibility(problem) def solve(self, problem: OptimizationProblem) -> OptimizationResult: """Tries to solves the given problem using the recursive optimizer. diff --git a/qiskit/optimization/converters/optimization_problem_to_qubo.py b/qiskit/optimization/converters/optimization_problem_to_qubo.py index 94d15d8ada..b1e3f2adbc 100644 --- a/qiskit/optimization/converters/optimization_problem_to_qubo.py +++ b/qiskit/optimization/converters/optimization_problem_to_qubo.py @@ -88,7 +88,7 @@ def decode(self, result: OptimizationResult) -> OptimizationResult: return self._int_to_bin.decode(result) @staticmethod - def is_compatible(problem: OptimizationProblem) -> bool: + def get_incompatibility(problem: OptimizationProblem) -> bool: """Checks whether a given problem can be cast to a QUBO. An optimization problem can be converted to a QUBO (Quadratic Unconstrained Binary @@ -126,7 +126,15 @@ def is_compatible(problem: OptimizationProblem) -> bool: msg += 'Quadratic constraints are not supported. ' # if an error occurred, return error message, otherwise, return None - if len(msg) > 0: - raise QiskitOptimizationError('Cannot convert the problem to QUBO: %s' % msg) + return msg - return True + def is_compatible(self, problem: OptimizationProblem) -> bool: + """Checks whether a given problem can be solved with the optimizer implementing this method. + + Args: + problem: The optimization problem to check compatibility. + + Returns: + Returns True if the problem is compatible, False otherwise. + """ + return len(self.get_incompatibility(problem)) > 0 From 1a488bb26ac11c47b916af614a51c6541728b98f Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Thu, 9 Apr 2020 10:26:52 -0400 Subject: [PATCH 176/323] fix lint --- test/optimization/test_converters.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index 33e1ec1c68..490898d8f7 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -21,7 +21,6 @@ from qiskit.aqua.operators import WeightedPauliOperator from qiskit.aqua.algorithms import NumPyMinimumEigensolver -from docplex.mp.model import Model from qiskit.optimization import QuadraticProgram, QiskitOptimizationError from qiskit.optimization.results import OptimizationResult from qiskit.optimization.converters import ( From b66602dc6a35b125aa3b79508880138223bf0348 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Thu, 9 Apr 2020 16:32:42 +0200 Subject: [PATCH 177/323] incompatibility in admm / cplex optimizer --- qiskit/optimization/algorithms/admm_optimizer.py | 8 ++------ qiskit/optimization/algorithms/cplex_optimizer.py | 1 + 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index f123032667..19dad74959 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -23,7 +23,6 @@ from qiskit.optimization.problems.quadratic_program import QuadraticProgram from qiskit.optimization.problems.variables import CPX_BINARY, CPX_CONTINUOUS from qiskit.optimization.results.optimization_result import OptimizationResult -from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError UPDATE_RHO_BY_TEN_PERCENT = 0 UPDATE_RHO_BY_RESIDUALS = 1 @@ -211,7 +210,7 @@ def __init__(self, qubo_optimizer: Optional[OptimizationAlgorithm] = None, # the solve method. self._state: Optional[ADMMState] = None - def is_compatible(self, problem: QuadraticProgram) -> Optional[str]: + def get_incompatibility(self, problem: QuadraticProgram) -> Optional[str]: """Checks whether a given problem can be solved with the optimizer implementing this method. Args: @@ -250,10 +249,7 @@ def is_compatible(self, problem: QuadraticProgram) -> Optional[str]: msg += 'Quadratic constraints are not supported. ' # if an error occurred, return error message, otherwise, return None - if len(msg) > 0: - raise QiskitOptimizationError('The problem is not compatible with ADMM: %s' % msg) - - return None + return msg def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: """Tries to solves the given problem using ADMM algorithm. diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index ec287de0d3..bfcc95e910 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -83,6 +83,7 @@ def parameter_set(self, parameter_set: Optional['ParameterSet']): """ self._parameter_set = parameter_set + # pylint:disable=unused-argument def get_incompatibility(self, problem: QuadraticProgram) -> str: """Checks whether a given problem can be solved with this optimizer. From 7686592cff3a973830136528e9d9dfe48371b4d4 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Thu, 9 Apr 2020 16:33:37 +0200 Subject: [PATCH 178/323] rename to compatibility_msg --- qiskit/optimization/algorithms/admm_optimizer.py | 2 +- qiskit/optimization/algorithms/cobyla_optimizer.py | 4 ++-- qiskit/optimization/algorithms/cplex_optimizer.py | 2 +- qiskit/optimization/algorithms/grover_minimum_finder.py | 4 ++-- qiskit/optimization/algorithms/minimum_eigen_optimizer.py | 4 ++-- qiskit/optimization/algorithms/optimization_algorithm.py | 4 ++-- .../algorithms/recursive_minimum_eigen_optimizer.py | 4 ++-- qiskit/optimization/converters/quadratic_program_to_qubo.py | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 19dad74959..d5ea066149 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -210,7 +210,7 @@ def __init__(self, qubo_optimizer: Optional[OptimizationAlgorithm] = None, # the solve method. self._state: Optional[ADMMState] = None - def get_incompatibility(self, problem: QuadraticProgram) -> Optional[str]: + def get_compatibility_msg(self, problem: QuadraticProgram) -> Optional[str]: """Checks whether a given problem can be solved with the optimizer implementing this method. Args: diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index 2de9bfa218..c9bcf347e4 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -67,7 +67,7 @@ def __init__(self, rhobeg: float = 1.0, rhoend: float = 1e-4, maxfun: int = 1000 self._disp = disp self._catol = catol - def get_incompatibility(self, problem: QuadraticProgram) -> str: + def get_compatibility_msg(self, problem: QuadraticProgram) -> str: """Checks whether a given problem can be solved with this optimizer. Checks whether the given problem is compatible, i.e., whether the problem contains only @@ -100,7 +100,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ # check compatibility and raise exception if incompatible - msg = self.get_incompatibility(problem) + msg = self.get_compatibility_msg(problem) if len(msg) > 0: raise QiskitOptimizationError('Incompatible problem: {}'.format(msg)) diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index bfcc95e910..b04a18b7fa 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -84,7 +84,7 @@ def parameter_set(self, parameter_set: Optional['ParameterSet']): self._parameter_set = parameter_set # pylint:disable=unused-argument - def get_incompatibility(self, problem: QuadraticProgram) -> str: + def get_compatibility_msg(self, problem: QuadraticProgram) -> str: """Checks whether a given problem can be solved with this optimizer. Returns ``''`` since CPLEX accepts all problems that can be modeled using the diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index f7531bfc31..38b28f4f5f 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -52,7 +52,7 @@ def __init__(self, num_iterations: int = 3, quantum_instance = QuantumInstance(backend) self._quantum_instance = quantum_instance - def get_incompatibility(self, problem: QuadraticProgram) -> str: + def get_compatibility_msg(self, problem: QuadraticProgram) -> str: """Checks whether a given problem can be solved with this optimizer. Checks whether the given problem is compatible, i.e., whether the problem can be converted @@ -64,7 +64,7 @@ def get_incompatibility(self, problem: QuadraticProgram) -> str: Returns: A message describing the incompatibility. """ - return QuadraticProgramToQubo.get_incompatibility(problem) + return QuadraticProgramToQubo.get_compatibility_msg(problem) def solve(self, problem: QuadraticProgram) -> OptimizationResult: """Tries to solves the given problem using the optimizer. diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 05971e0165..430040ee07 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -103,7 +103,7 @@ def __init__(self, min_eigen_solver: MinimumEigensolver, penalty: Optional[float self._min_eigen_solver = min_eigen_solver self._penalty = penalty - def get_incompatibility(self, problem: QuadraticProgram) -> str: + def get_compatibility_msg(self, problem: QuadraticProgram) -> str: """Checks whether a given problem can be solved with this optimizer. Checks whether the given problem is compatible, i.e., whether the problem can be converted @@ -115,7 +115,7 @@ def get_incompatibility(self, problem: QuadraticProgram) -> str: Returns: A message describing the incompatibility. """ - return QuadraticProgramToQubo.get_incompatibility(problem) + return QuadraticProgramToQubo.get_compatibility_msg(problem) def solve(self, problem: QuadraticProgram) -> MinimumEigenOptimizerResult: """Tries to solves the given problem using the optimizer. diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index c4c8f3d5c8..202503dc90 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -25,7 +25,7 @@ class OptimizationAlgorithm(ABC): """An abstract class for optimization algorithms in Qiskit Optimization.""" @abstractmethod - def get_incompatibility(self, problem: QuadraticProgram) -> str: + def get_compatibility_msg(self, problem: QuadraticProgram) -> str: """Checks whether a given problem can be solved with the optimizer implementing this method. Args: @@ -44,7 +44,7 @@ def is_compatible(self, problem: QuadraticProgram) -> bool: Returns: Returns True if the problem is compatible, False otherwise. """ - return len(self.get_incompatibility(problem)) > 0 + return len(self.get_compatibility_msg(problem)) > 0 @abstractmethod def solve(self, problem: QuadraticProgram) -> OptimizationResult: diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 3d2f75ce4e..63e71efe85 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -94,7 +94,7 @@ def __init__(self, min_eigen_optimizer: MinimumEigenOptimizer, min_num_vars: int self._min_num_vars_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) self._penalty = penalty - def get_incompatibility(self, problem: QuadraticProgram) -> str: + def get_compatibility_msg(self, problem: QuadraticProgram) -> str: """Checks whether a given problem can be solved with this optimizer. Checks whether the given problem is compatible, i.e., whether the problem can be converted @@ -106,7 +106,7 @@ def get_incompatibility(self, problem: QuadraticProgram) -> str: Returns: A message describing the incompatibility. """ - return QuadraticProgramToQubo.get_incompatibility(problem) + return QuadraticProgramToQubo.get_compatibility_msg(problem) def solve(self, problem: QuadraticProgram) -> OptimizationResult: """Tries to solves the given problem using the recursive optimizer. diff --git a/qiskit/optimization/converters/quadratic_program_to_qubo.py b/qiskit/optimization/converters/quadratic_program_to_qubo.py index 3913ac4cba..16183688ec 100644 --- a/qiskit/optimization/converters/quadratic_program_to_qubo.py +++ b/qiskit/optimization/converters/quadratic_program_to_qubo.py @@ -85,7 +85,7 @@ def decode(self, result: OptimizationResult) -> OptimizationResult: return self._int_to_bin.decode(result) @staticmethod - def get_incompatibility(problem: QuadraticProgram) -> bool: + def get_compatibility_msg(problem: QuadraticProgram) -> bool: """Checks whether a given problem can be cast to a QUBO. A quadratic program can be converted to a QUBO (Quadratic Unconstrained Binary @@ -134,4 +134,4 @@ def is_compatible(self, problem: QuadraticProgram) -> bool: Returns: Returns True if the problem is compatible, False otherwise. """ - return len(self.get_incompatibility(problem)) > 0 + return len(self.get_compatibility_msg(problem)) > 0 From f6f0aed4f2b074537172529ee1167bfbb0569716 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Thu, 9 Apr 2020 17:03:24 +0200 Subject: [PATCH 179/323] == not > --- qiskit/optimization/algorithms/optimization_algorithm.py | 2 +- .../optimization/converters/quadratic_program_to_qubo.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index 202503dc90..f5aa2ea0c7 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -44,7 +44,7 @@ def is_compatible(self, problem: QuadraticProgram) -> bool: Returns: Returns True if the problem is compatible, False otherwise. """ - return len(self.get_compatibility_msg(problem)) > 0 + return len(self.get_compatibility_msg(problem)) == 0 @abstractmethod def solve(self, problem: QuadraticProgram) -> OptimizationResult: diff --git a/qiskit/optimization/converters/quadratic_program_to_qubo.py b/qiskit/optimization/converters/quadratic_program_to_qubo.py index 16183688ec..79180ffeb1 100644 --- a/qiskit/optimization/converters/quadratic_program_to_qubo.py +++ b/qiskit/optimization/converters/quadratic_program_to_qubo.py @@ -56,8 +56,9 @@ def encode(self, problem: QuadraticProgram) -> QuadraticProgram: """ # analyze compatibility of problem - if not self.is_compatible(problem): - raise QiskitOptimizationError('Incompatible problem.') + msg = self.get_compatibility_msg(problem) + if len(msg) > 0: + raise QiskitOptimizationError('Incompatible problem: {}'.format(msg)) # map integer variables to binary variables problem_ = self._int_to_bin.encode(problem) @@ -134,4 +135,4 @@ def is_compatible(self, problem: QuadraticProgram) -> bool: Returns: Returns True if the problem is compatible, False otherwise. """ - return len(self.get_compatibility_msg(problem)) > 0 + return len(self.get_compatibility_msg(problem)) == 0 From 7f2d1c58ebbb7e8d79025510c71eb3737a40248c Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Thu, 9 Apr 2020 17:45:27 +0200 Subject: [PATCH 180/323] update cplex optimizer --- .../algorithms/cplex_optimizer.py | 29 +++++++++---------- test/optimization/test_converters.py | 1 - 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index 5ae288db69..a4e1a37bf4 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -49,14 +49,11 @@ class CplexOptimizer(OptimizationAlgorithm): TODO: The arguments for ``Cplex`` are passed via the constructor. """ - def __init__(self, parameter_set: Optional['ParameterSet'] = None) -> None: + def __init__(self, disp: Optional[bool] = None) -> None: """Initializes the CplexOptimizer. - TODO: This initializer takes the algorithmic parameters of CPLEX and stores them for later - use when ``solve()`` is invoked. - Args: - parameter_set: The CPLEX parameter set + disp: Whether to print CPLEX output or not. Raises: NameError: CPLEX is not installed. @@ -64,24 +61,24 @@ def __init__(self, parameter_set: Optional['ParameterSet'] = None) -> None: if not _HAS_CPLEX: raise NameError('CPLEX is not installed.') - self._parameter_set = parameter_set + self._disp = disp @property - def parameter_set(self) -> Optional['ParameterSet']: - """Returns the parameter set. - Returns the algorithmic parameters for CPLEX. + def disp(self) -> Optional[bool]: + """Returns the display setting. + Returns: - The CPLEX parameter set. + Whether to print CPLEX information or not. """ - return self._parameter_set + return self._disp - @parameter_set.setter - def parameter_set(self, parameter_set: Optional['ParameterSet']): - """Set the parameter set. + @disp.setter + def disp(self, disp: Optional[bool]): + """Set the display setting. Args: - parameter_set: The new parameter set. + disp: The display setting. """ - self._parameter_set = parameter_set + self._disp = disp def is_compatible(self, problem: QuadraticProgram) -> Optional[str]: """Checks whether a given problem can be solved with this optimizer. diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index 33e1ec1c68..490898d8f7 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -21,7 +21,6 @@ from qiskit.aqua.operators import WeightedPauliOperator from qiskit.aqua.algorithms import NumPyMinimumEigensolver -from docplex.mp.model import Model from qiskit.optimization import QuadraticProgram, QiskitOptimizationError from qiskit.optimization.results import OptimizationResult from qiskit.optimization.converters import ( From ad4bbd95629caa7ecc26249811580b660c638940 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Thu, 9 Apr 2020 17:59:58 +0200 Subject: [PATCH 181/323] update cplex display settings --- .../algorithms/cplex_optimizer.py | 31 ++++++++++--------- test/optimization/test_cplex_optimizer.py | 2 +- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index 443be726ab..a9b879b6fc 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -13,14 +13,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""The CPLEX optimizer wrapped to be used within Qiskit Optimization. - -Examples: - >>> problem = QuadraticProgram() - >>> # specify problem here - >>> optimizer = CplexOptimizer() - >>> result = optimizer.solve(problem) -""" +"""The CPLEX optimizer wrapped to be used within Qiskit Optimization.""" from typing import Optional import logging @@ -46,10 +39,14 @@ class CplexOptimizer(OptimizationAlgorithm): This class provides a wrapper for ``cplex.Cplex`` (https://pypi.org/project/cplex/) to be used within Qiskit Optimization. - TODO: The arguments for ``Cplex`` are passed via the constructor. + Examples: + >>> problem = QuadraticProgram() + >>> # specify problem here + >>> optimizer = CplexOptimizer() + >>> result = optimizer.solve(problem) """ - def __init__(self, disp: Optional[bool] = None) -> None: + def __init__(self, disp: Optional[bool] = False) -> None: """Initializes the CplexOptimizer. Args: @@ -64,7 +61,7 @@ def __init__(self, disp: Optional[bool] = None) -> None: self._disp = disp @property - def disp(self) -> Optional[bool]: + def disp(self) -> bool: """Returns the display setting. Returns: @@ -73,7 +70,7 @@ def disp(self) -> Optional[bool]: return self._disp @disp.setter - def disp(self, disp: Optional[bool]): + def disp(self, disp: bool): """Set the display setting. Args: disp: The display setting. @@ -86,7 +83,7 @@ def get_compatibility_msg(self, problem: QuadraticProgram) -> str: Returns ``''`` since CPLEX accepts all problems that can be modeled using the ``QuadraticProgram``. CPLEX may throw an exception in case the problem is determined - to be non-convex. This case could be addressed by setting CPLEX parameters accordingly. + to be non-convex. Args: problem: The optimization problem to check compatibility. @@ -115,7 +112,13 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: # convert to CPLEX problem cplex = problem.to_cplex() - # set parameters + # set display setting + if not self.disp: + cplex.set_log_stream(None) + cplex.set_error_stream(None) + cplex.set_warning_stream(None) + cplex.set_results_stream(None) + # TODO: need to find a good way to set the parameters # solve problem diff --git a/test/optimization/test_cplex_optimizer.py b/test/optimization/test_cplex_optimizer.py index e118759751..e86f6c3e55 100644 --- a/test/optimization/test_cplex_optimizer.py +++ b/test/optimization/test_cplex_optimizer.py @@ -29,7 +29,7 @@ def setUp(self): super().setUp() try: self.resource_path = './test/optimization/resources/' - self.cplex_optimizer = CplexOptimizer() + self.cplex_optimizer = CplexOptimizer(disp=False) except NameError as ex: self.skipTest(str(ex)) From 6ed122c33f302c6fb54041575a8f01986a9408a7 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Thu, 9 Apr 2020 12:34:26 -0400 Subject: [PATCH 182/323] fix spell --- .pylintdict | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.pylintdict b/.pylintdict index 22b13ccc91..ee33b98a53 100644 --- a/.pylintdict +++ b/.pylintdict @@ -280,6 +280,7 @@ jt jth jw kaicher +Karimi ket killoran kingma @@ -424,6 +425,7 @@ piecewise plesset pmatrix polynomially +Pooya postoracle powell pre @@ -502,6 +504,7 @@ rightarrow righthand rlp rohf +Ronagh rosen rsgtu rtype @@ -509,6 +512,7 @@ runarsson RunConfig ry rz +Sahar sanjiv sashank satyen From b5111cf75fcf8b21fbbffd1c7c55518f23df4e9c Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 10 Apr 2020 11:49:46 +0200 Subject: [PATCH 183/323] update recursive min optimizer docstring examples should be in class level, not module level fix class level docstring in general --- .../recursive_minimum_eigen_optimizer.py | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 24fdcd079c..ded3328c1c 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -12,16 +12,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""A recursive minimal eigen optimizer in Qiskit Optimization. - - Examples: - >>> problem = QuadraticProgram() - >>> # specify problem here - >>> # specify minimum eigen solver to be used, e.g., QAOA - >>> qaoa = QAOA(...) - >>> optimizer = RecursiveMinEigenOptimizer(qaoa) - >>> result = optimizer.solve(problem) -""" +"""A recursive minimal eigen optimizer in Qiskit Optimization.""" from copy import deepcopy from typing import Optional @@ -49,8 +40,23 @@ class RecursiveMinimumEigenOptimizer(OptimizationAlgorithm): - """ A meta-algorithm that applies the recursive optimization scheme introduce in - [http://arxiv.org/abs/1910.08980] on top of ``MinimumEigenOptimizer``. + """A meta-algorithm that applies a recursive optimization. + + The recursive minimum eigen optimizer applies a recursive optimization on top of + :class:`~qiskit.optimization.algorithms.MinimumEigenOptimizer`. + The algorithm is introduced in [1]. + + Examples: + >>> problem = QuadraticProgram() + >>> # specify problem here + >>> # specify minimum eigen solver to be used, e.g., QAOA + >>> qaoa = QAOA(...) + >>> optimizer = RecursiveMinEigenOptimizer(qaoa) + >>> result = optimizer.solve(problem) + + References: + [1]: Bravyi et al. (2019), Obstacles to State Preparation and Variational Optimization + from Symmetry Protection. http://arxiv.org/abs/1910.08980. """ def __init__(self, min_eigen_optimizer: MinimumEigenOptimizer, min_num_vars: int = 1, From 27349fb1bf092b3356621597b5793d631ca3b9be Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Fri, 10 Apr 2020 11:51:03 +0200 Subject: [PATCH 184/323] (Re)move utils (#67) * reorganize utils * add missing init * fix import in test --- qiskit/optimization/__init__.py | 2 +- .../algorithms/cplex_optimizer.py | 2 +- .../algorithms/minimum_eigen_optimizer.py | 93 +++++++++++++++- .../recursive_minimum_eigen_optimizer.py | 2 +- .../inequality_to_equality_converter.py | 2 +- .../converters/integer_to_binary_converter.py | 2 +- .../penalize_linear_equality_constraints.py | 2 +- .../quadratic_program_to_operator.py | 2 +- .../converters/quadratic_program_to_qubo.py | 2 +- qiskit/optimization/exceptions/__init__.py | 19 ++++ .../qiskit_optimization_error.py | 0 qiskit/optimization/problems/__init__.py | 5 +- .../optimization/{utils => problems}/base.py | 4 +- .../problems/linear_constraint.py | 7 +- .../helpers.py => problems/name_index.py} | 2 +- qiskit/optimization/problems/objective.py | 4 +- .../problems/quadratic_constraint.py | 7 +- .../problems/quadratic_program.py | 2 +- qiskit/optimization/problems/variables.py | 6 +- qiskit/optimization/results/solution.py | 2 +- .../optimization/results/solution_status.py | 2 +- qiskit/optimization/utils/__init__.py | 39 ------- .../utils/eigenvector_to_solutions.py | 100 ------------------ test/optimization/test_helpers.py | 2 +- 24 files changed, 142 insertions(+), 168 deletions(-) create mode 100644 qiskit/optimization/exceptions/__init__.py rename qiskit/optimization/{utils => exceptions}/qiskit_optimization_error.py (100%) rename qiskit/optimization/{utils => problems}/base.py (96%) rename qiskit/optimization/{utils/helpers.py => problems/name_index.py} (98%) delete mode 100644 qiskit/optimization/utils/__init__.py delete mode 100644 qiskit/optimization/utils/eigenvector_to_solutions.py diff --git a/qiskit/optimization/__init__.py b/qiskit/optimization/__init__.py index b4e6e9fe18..eaf91685c6 100755 --- a/qiskit/optimization/__init__.py +++ b/qiskit/optimization/__init__.py @@ -28,7 +28,7 @@ """ from .infinity import infinity # must be at the top of the file -from .utils import QiskitOptimizationError +from .exceptions import QiskitOptimizationError from .problems import QuadraticProgram from ._logging import (get_qiskit_optimization_logging, set_qiskit_optimization_logging) diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index a9b879b6fc..b37bf39ba0 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -19,7 +19,7 @@ import logging from .optimization_algorithm import OptimizationAlgorithm -from ..utils.qiskit_optimization_error import QiskitOptimizationError +from ..exceptions.qiskit_optimization_error import QiskitOptimizationError from ..results.optimization_result import OptimizationResult from ..problems.quadratic_program import QuadraticProgram diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 430040ee07..9533f5a6c6 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -15,14 +15,15 @@ """A wrapper for minimum eigen solvers from Qiskit Aqua to be used within Qiskit Optimization.""" -from typing import Optional, Any +from typing import Optional, Any, Union, Tuple, List import numpy as np +from qiskit import QuantumCircuit, BasicAer, execute from qiskit.aqua.algorithms import MinimumEigensolver +from qiskit.aqua.operators import WeightedPauliOperator, MatrixOperator from .optimization_algorithm import OptimizationAlgorithm from ..problems.quadratic_program import QuadraticProgram -from ..utils.eigenvector_to_solutions import eigenvector_to_solutions from ..converters.quadratic_program_to_operator import QuadraticProgramToOperator from ..converters.quadratic_program_to_qubo import QuadraticProgramToQubo from ..results.optimization_result import OptimizationResult @@ -151,3 +152,91 @@ def solve(self, problem: QuadraticProgram) -> MinimumEigenOptimizerResult: # translate results back to original problem return opt_res + + +def eigenvector_to_solutions(eigenvector: Union[dict, np.ndarray], + operator: Union[WeightedPauliOperator, MatrixOperator], + min_probability: float = 1e-6) -> List[Tuple[str, float, float]]: + """Convert the eigenvector to the bitstrings and corresponding eigenvalues. + + Examples: + >>> op = MatrixOperator(numpy.array([[1, 1], [1, -1]]) / numpy.sqrt(2)) + >>> eigenvectors = {'0': 12, '1': 1} + >>> print(eigenvector_to_solutions(eigenvectors, op)) + [('0', 0.7071067811865475, 0.9230769230769231), ('1', -0.7071067811865475, 0.07692307692307693)] + + >>> op = MatrixOperator(numpy.array([[1, 1], [1, -1]]) / numpy.sqrt(2)) + >>> eigenvectors = numpy.array([1, 1] / numpy.sqrt(2), dtype=complex) + >>> print(eigenvector_to_solutions(eigenvectors, op)) + [('0', 0.7071067811865475, 0.4999999999999999), ('1', -0.7071067811865475, 0.4999999999999999)] + + Returns: + For each computational basis state contained in the eigenvector, return the basis + state as bitstring along with the operator evaluated at that bitstring and the + probability of sampling this bitstring from the eigenvector. + + Raises: + TypeError: Invalid Argument + + """ + solutions = [] + if isinstance(eigenvector, dict): + all_counts = sum(eigenvector.values()) + # iterate over all samples + for bitstr, count in eigenvector.items(): + sampling_probability = count / all_counts + # add the bitstring, if the sampling probability exceeds the threshold + if sampling_probability > 0: + if sampling_probability >= min_probability: + value = eval_operator_at_bitstring(operator, bitstr) + solutions += [(bitstr, value, sampling_probability)] + + elif isinstance(eigenvector, np.ndarray): + num_qubits = int(np.log2(eigenvector.size)) + probabilities = np.abs(eigenvector * eigenvector.conj()) + + # iterate over all states and their sampling probabilities + for i, sampling_probability in enumerate(probabilities): + + # add the i-th state if the sampling probability exceeds the threshold + if sampling_probability > 0: + if sampling_probability >= min_probability: + bitstr = '{:b}'.format(i).rjust(num_qubits, '0')[::-1] + value = eval_operator_at_bitstring(operator, bitstr) + solutions += [(bitstr, value, sampling_probability)] + + else: + raise TypeError('Unsupported format of eigenvector. Provide a dict or numpy.ndarray.') + + return solutions + + +def eval_operator_at_bitstring(operator: Union[WeightedPauliOperator, MatrixOperator], + bitstr: str) -> float: + """Evaluate an Aqua operator at a given bitstring. + + This simulates a circuit representing the bitstring. Note that this will not be needed + with the Operator logic introduced in 0.7.0. + + Args: + operator: The operator which is evaluated. + bitstr: The bitstring at which the operator is evaluated. + + Returns: + The operator evaluated with the quantum state the bitstring describes. + """ + + # TODO check that operator size and bitstr are compatible + circuit = QuantumCircuit(len(bitstr)) + for i, bit in enumerate(bitstr): + # TODO in which order to iterate over the bitstring??? + if bit == '1': + circuit.x(i) + + # simulate the circuit + result = execute(circuit, BasicAer.get_backend('statevector_simulator')).result() + + # evaluate the operator + value = np.real(operator.evaluate_with_statevector(result.get_statevector())[0]) + + return value diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index ded3328c1c..240ffc7119 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -24,7 +24,7 @@ from .optimization_algorithm import OptimizationAlgorithm from .minimum_eigen_optimizer import MinimumEigenOptimizer -from ..utils.qiskit_optimization_error import QiskitOptimizationError +from ..exceptions.qiskit_optimization_error import QiskitOptimizationError from ..problems.quadratic_program import QuadraticProgram from ..results.optimization_result import OptimizationResult from ..converters.quadratic_program_to_qubo import QuadraticProgramToQubo diff --git a/qiskit/optimization/converters/inequality_to_equality_converter.py b/qiskit/optimization/converters/inequality_to_equality_converter.py index baf39c233d..98052a48a1 100644 --- a/qiskit/optimization/converters/inequality_to_equality_converter.py +++ b/qiskit/optimization/converters/inequality_to_equality_converter.py @@ -21,7 +21,7 @@ from ..problems.quadratic_program import QuadraticProgram from ..results.optimization_result import OptimizationResult -from ..utils.qiskit_optimization_error import QiskitOptimizationError +from ..exceptions.qiskit_optimization_error import QiskitOptimizationError logger = logging.getLogger(__name__) diff --git a/qiskit/optimization/converters/integer_to_binary_converter.py b/qiskit/optimization/converters/integer_to_binary_converter.py index e76c3593d3..90c86d9bb6 100644 --- a/qiskit/optimization/converters/integer_to_binary_converter.py +++ b/qiskit/optimization/converters/integer_to_binary_converter.py @@ -21,7 +21,7 @@ from ..problems.quadratic_program import QuadraticProgram from ..results.optimization_result import OptimizationResult -from ..utils.qiskit_optimization_error import QiskitOptimizationError +from ..exceptions.qiskit_optimization_error import QiskitOptimizationError logger = logging.getLogger(__name__) diff --git a/qiskit/optimization/converters/penalize_linear_equality_constraints.py b/qiskit/optimization/converters/penalize_linear_equality_constraints.py index 8113a6074c..586feb311b 100644 --- a/qiskit/optimization/converters/penalize_linear_equality_constraints.py +++ b/qiskit/optimization/converters/penalize_linear_equality_constraints.py @@ -20,7 +20,7 @@ from collections import defaultdict from ..problems.quadratic_program import QuadraticProgram -from ..utils.qiskit_optimization_error import QiskitOptimizationError +from ..exceptions.qiskit_optimization_error import QiskitOptimizationError class PenalizeLinearEqualityConstraints: diff --git a/qiskit/optimization/converters/quadratic_program_to_operator.py b/qiskit/optimization/converters/quadratic_program_to_operator.py index 772d3980ba..329f517a9d 100644 --- a/qiskit/optimization/converters/quadratic_program_to_operator.py +++ b/qiskit/optimization/converters/quadratic_program_to_operator.py @@ -23,7 +23,7 @@ from qiskit.aqua.operators import WeightedPauliOperator from ..problems.quadratic_program import QuadraticProgram -from ..utils.qiskit_optimization_error import QiskitOptimizationError +from ..exceptions.qiskit_optimization_error import QiskitOptimizationError class QuadraticProgramToOperator: diff --git a/qiskit/optimization/converters/quadratic_program_to_qubo.py b/qiskit/optimization/converters/quadratic_program_to_qubo.py index 79180ffeb1..5f890cbcc7 100644 --- a/qiskit/optimization/converters/quadratic_program_to_qubo.py +++ b/qiskit/optimization/converters/quadratic_program_to_qubo.py @@ -20,7 +20,7 @@ from qiskit.optimization.results import OptimizationResult from qiskit.optimization.converters import (PenalizeLinearEqualityConstraints, IntegerToBinaryConverter) -from qiskit.optimization.utils import QiskitOptimizationError +from qiskit.optimization.exceptions import QiskitOptimizationError class QuadraticProgramToQubo: diff --git a/qiskit/optimization/exceptions/__init__.py b/qiskit/optimization/exceptions/__init__.py new file mode 100644 index 0000000000..482aab65e0 --- /dev/null +++ b/qiskit/optimization/exceptions/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +"""The exceptions module for the Qiskit Optimization stack.""" + +from .qiskit_optimization_error import QiskitOptimizationError + +__all__ = ['QiskitOptimizationError'] diff --git a/qiskit/optimization/utils/qiskit_optimization_error.py b/qiskit/optimization/exceptions/qiskit_optimization_error.py similarity index 100% rename from qiskit/optimization/utils/qiskit_optimization_error.py rename to qiskit/optimization/exceptions/qiskit_optimization_error.py diff --git a/qiskit/optimization/problems/__init__.py b/qiskit/optimization/problems/__init__.py index 8f9207d6a6..ff8e449288 100644 --- a/qiskit/optimization/problems/__init__.py +++ b/qiskit/optimization/problems/__init__.py @@ -32,6 +32,7 @@ QuadraticProgram QuadraticConstraintInterface VariablesInterface + NameIndex N.B. Additional classes LinearConstraintInterface, QuadraticConstraintInterface, ObjectiveInterface, and VariablesInterface @@ -45,11 +46,13 @@ from .quadratic_program import QuadraticProgram from .quadratic_constraint import QuadraticConstraintInterface from .variables import VariablesInterface +from .name_index import NameIndex __all__ = ['LinearConstraintInterface', 'ObjSense', 'ObjectiveInterface', 'QuadraticProgram', 'QuadraticConstraintInterface', - 'VariablesInterface' + 'VariablesInterface', + 'NameIndex', ] diff --git a/qiskit/optimization/utils/base.py b/qiskit/optimization/problems/base.py similarity index 96% rename from qiskit/optimization/utils/base.py rename to qiskit/optimization/problems/base.py index 8a7a630d1b..0188e65ae3 100644 --- a/qiskit/optimization/utils/base.py +++ b/qiskit/optimization/problems/base.py @@ -17,8 +17,8 @@ from abc import ABC from typing import Callable, Sequence, Union, Any, List, Generator -from qiskit.optimization.utils.helpers import NameIndex -from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError +from qiskit.optimization.exceptions import QiskitOptimizationError +from .name_index import NameIndex class BaseInterface(ABC): diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index e0b722916a..5aa307fbdf 100644 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -19,9 +19,10 @@ from typing import Callable, Optional, List, Union import logging -from qiskit.optimization.utils.base import BaseInterface -from qiskit.optimization.utils.helpers import init_list_args, NameIndex -from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError +from qiskit.optimization.problems.name_index import init_list_args, NameIndex +from qiskit.optimization.exceptions import QiskitOptimizationError + +from .base import BaseInterface logger = logging.getLogger(__name__) diff --git a/qiskit/optimization/utils/helpers.py b/qiskit/optimization/problems/name_index.py similarity index 98% rename from qiskit/optimization/utils/helpers.py rename to qiskit/optimization/problems/name_index.py index 2f8b731ccd..5c58278a03 100644 --- a/qiskit/optimization/utils/helpers.py +++ b/qiskit/optimization/problems/name_index.py @@ -16,7 +16,7 @@ from typing import Union, List, Sequence, Tuple -from qiskit.optimization.utils import QiskitOptimizationError +from qiskit.optimization.exceptions import QiskitOptimizationError class NameIndex: diff --git a/qiskit/optimization/problems/objective.py b/qiskit/optimization/problems/objective.py index 7791df11b2..4174bd5f0b 100644 --- a/qiskit/optimization/problems/objective.py +++ b/qiskit/optimization/problems/objective.py @@ -19,10 +19,10 @@ from collections.abc import Sequence from typing import Callable, List, Union, Dict, Tuple import logging - from scipy.sparse import dok_matrix -from qiskit.optimization.utils import BaseInterface, QiskitOptimizationError +from qiskit.optimization.exceptions import QiskitOptimizationError +from .base import BaseInterface logger = logging.getLogger(__name__) diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index d1d110382e..4cc8611ed2 100644 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -21,9 +21,10 @@ from scipy.sparse import dok_matrix -from qiskit.optimization.utils.base import BaseInterface -from qiskit.optimization.utils.helpers import NameIndex -from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError +from qiskit.optimization.problems.name_index import NameIndex +from qiskit.optimization.exceptions import QiskitOptimizationError + +from .base import BaseInterface logger = logging.getLogger(__name__) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index fe15c3a0b2..34efdbd4da 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -27,7 +27,7 @@ from qiskit.optimization.problems.quadratic_constraint import QuadraticConstraintInterface from qiskit.optimization.problems.variables import VariablesInterface from qiskit.optimization.results.solution import SolutionInterface -from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError +from qiskit.optimization.exceptions.qiskit_optimization_error import QiskitOptimizationError logger = logging.getLogger(__name__) diff --git a/qiskit/optimization/problems/variables.py b/qiskit/optimization/problems/variables.py index ce64bae462..7d3d542e32 100644 --- a/qiskit/optimization/problems/variables.py +++ b/qiskit/optimization/problems/variables.py @@ -18,9 +18,9 @@ from typing import List, Optional, Union from qiskit.optimization import infinity -from qiskit.optimization.utils.base import BaseInterface -from qiskit.optimization.utils.helpers import init_list_args, NameIndex -from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError +from qiskit.optimization.problems.name_index import init_list_args, NameIndex +from qiskit.optimization.exceptions import QiskitOptimizationError +from .base import BaseInterface CPX_CONTINUOUS = 'C' CPX_BINARY = 'B' diff --git a/qiskit/optimization/results/solution.py b/qiskit/optimization/results/solution.py index 5f85b70d90..b6113d6af7 100755 --- a/qiskit/optimization/results/solution.py +++ b/qiskit/optimization/results/solution.py @@ -17,7 +17,7 @@ from qiskit.optimization.results.quality_metrics import QualityMetrics from qiskit.optimization.results.solution_status import SolutionStatus -from qiskit.optimization.utils.base import BaseInterface +from ..problems.base import BaseInterface class SolutionInterface(BaseInterface): diff --git a/qiskit/optimization/results/solution_status.py b/qiskit/optimization/results/solution_status.py index a24346c825..c998f2109a 100755 --- a/qiskit/optimization/results/solution_status.py +++ b/qiskit/optimization/results/solution_status.py @@ -14,7 +14,7 @@ """Solution status codes.""" -from qiskit.optimization.utils.qiskit_optimization_error import QiskitOptimizationError +from qiskit.optimization.exceptions.qiskit_optimization_error import QiskitOptimizationError CPX_STAT_ABORT_DETTIME_LIM = 25 CPX_STAT_ABORT_IT_LIM = 10 diff --git a/qiskit/optimization/utils/__init__.py b/qiskit/optimization/utils/__init__.py deleted file mode 100644 index 8069176725..0000000000 --- a/qiskit/optimization/utils/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# 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. -""" -============================================================== -Optimization stack for Aqua (:mod:`qiskit.optimization.utils`) -============================================================== - -.. currentmodule:: qiskit.optimization.utils - -Utility classes and functions -========== - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - BaseInterface - QiskitOptimizationError - -N.B. Utility functions in .aux are intended for internal use. - -""" - -from .qiskit_optimization_error import QiskitOptimizationError -from .base import BaseInterface -from .eigenvector_to_solutions import eigenvector_to_solutions - -__all__ = ["BaseInterface", "eigenvector_to_solutions", "QiskitOptimizationError"] diff --git a/qiskit/optimization/utils/eigenvector_to_solutions.py b/qiskit/optimization/utils/eigenvector_to_solutions.py deleted file mode 100644 index 06ef7c4e6e..0000000000 --- a/qiskit/optimization/utils/eigenvector_to_solutions.py +++ /dev/null @@ -1,100 +0,0 @@ - -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Auxiliary methods to translate eigenvectors into optimization results.""" - -from typing import Union, List, Tuple -import numpy -from qiskit import QuantumCircuit, BasicAer, execute -from qiskit.aqua.operators import WeightedPauliOperator, MatrixOperator - - -def eigenvector_to_solutions(eigenvector: Union[dict, numpy.ndarray], - operator: Union[WeightedPauliOperator, MatrixOperator], - min_probability: float = 1e-6) -> List[Tuple[str, float, float]]: - """Convert the eigenvector to the bitstrings and corresponding eigenvalues. - - Examples: - >>> op = MatrixOperator(numpy.array([[1, 1], [1, -1]]) / numpy.sqrt(2)) - >>> eigenvectors = {'0': 12, '1': 1} - >>> print(eigenvector_to_solutions(eigenvectors, op)) - [('0', 0.7071067811865475, 0.9230769230769231), ('1', -0.7071067811865475, 0.07692307692307693)] - - >>> op = MatrixOperator(numpy.array([[1, 1], [1, -1]]) / numpy.sqrt(2)) - >>> eigenvectors = numpy.array([1, 1] / numpy.sqrt(2), dtype=complex) - >>> print(eigenvector_to_solutions(eigenvectors, op)) - [('0', 0.7071067811865475, 0.4999999999999999), ('1', -0.7071067811865475, 0.4999999999999999)] - - Returns: - For each computational basis state contained in the eigenvector, return the basis - state as bitstring along with the operator evaluated at that bitstring and the - probability of sampling this bitstring from the eigenvector. - - Raises: - TypeError: Invalid Argument - - """ - solutions = [] - if isinstance(eigenvector, dict): - all_counts = sum(eigenvector.values()) - # iterate over all samples - for bitstr, count in eigenvector.items(): - sampling_probability = count / all_counts - # add the bitstring, if the sampling probability exceeds the threshold - if sampling_probability > 0: - if sampling_probability >= min_probability: - value = eval_operator_at_bitstring(operator, bitstr) - solutions += [(bitstr, value, sampling_probability)] - - elif isinstance(eigenvector, numpy.ndarray): - num_qubits = int(numpy.log2(eigenvector.size)) - probabilities = numpy.abs(eigenvector * eigenvector.conj()) - - # iterate over all states and their sampling probabilities - for i, sampling_probability in enumerate(probabilities): - - # add the i-th state if the sampling probability exceeds the threshold - if sampling_probability > 0: - if sampling_probability >= min_probability: - bitstr = '{:b}'.format(i).rjust(num_qubits, '0')[::-1] - value = eval_operator_at_bitstring(operator, bitstr) - solutions += [(bitstr, value, sampling_probability)] - - else: - raise TypeError('Unsupported format of eigenvector. Provide a dict or numpy.ndarray.') - - return solutions - - -def eval_operator_at_bitstring(operator: Union[WeightedPauliOperator, MatrixOperator], - bitstr: str) -> float: - """ - TODO - """ - - # TODO check that operator size and bitstr are compatible - circuit = QuantumCircuit(len(bitstr)) - for i, bit in enumerate(bitstr): - # TODO in which order to iterate over the bitstring??? - if bit == '1': - circuit.x(i) - - # simulate the circuit - result = execute(circuit, BasicAer.get_backend('statevector_simulator')).result() - - # evaluate the operator - value = numpy.real(operator.evaluate_with_statevector(result.get_statevector())[0]) - - return value diff --git a/test/optimization/test_helpers.py b/test/optimization/test_helpers.py index 90fa36d60f..8d8d711767 100644 --- a/test/optimization/test_helpers.py +++ b/test/optimization/test_helpers.py @@ -18,7 +18,7 @@ from test.optimization.optimization_test_case import QiskitOptimizationTestCase from qiskit.optimization import QiskitOptimizationError -from qiskit.optimization.utils.helpers import NameIndex, init_list_args +from qiskit.optimization.problems.name_index import NameIndex, init_list_args class TestHelpers(QiskitOptimizationTestCase): From 880955743f8269271faee8ecbf3187709c30167a Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Fri, 10 Apr 2020 12:04:54 +0200 Subject: [PATCH 185/323] (Re)move results (#68) * move results to algo files * remove solution/qualitymetrics for now they are safely stored on another branch but are currently not used so there is no reason to have them in this release * move the results objects to algorithms * fix cyclic deps --- .pylintdict | 1 - qiskit/optimization/algorithms/__init__.py | 10 +- .../optimization/algorithms/admm_optimizer.py | 4 +- .../algorithms/cobyla_optimizer.py | 3 +- .../algorithms/cplex_optimizer.py | 5 +- .../algorithms/grover_minimum_finder.py | 75 ++++- .../algorithms/minimum_eigen_optimizer.py | 3 +- .../algorithms/optimization_algorithm.py | 84 +++++- .../recursive_minimum_eigen_optimizer.py | 3 +- qiskit/optimization/converters/__init__.py | 9 +- .../inequality_to_equality_converter.py | 6 +- .../converters/integer_to_binary_converter.py | 5 +- .../converters/quadratic_program_to_qubo.py | 3 +- .../problems/quadratic_program.py | 5 +- qiskit/optimization/results/__init__.py | 54 ---- .../results/grover_optimization_results.py | 86 ------ .../results/optimization_result.py | 97 ------- .../optimization/results/quality_metrics.py | 82 ------ qiskit/optimization/results/solution.py | 259 ------------------ .../optimization/results/solution_status.py | 125 --------- test/optimization/test_converters.py | 2 +- 21 files changed, 180 insertions(+), 741 deletions(-) delete mode 100644 qiskit/optimization/results/__init__.py delete mode 100644 qiskit/optimization/results/grover_optimization_results.py delete mode 100644 qiskit/optimization/results/optimization_result.py delete mode 100755 qiskit/optimization/results/quality_metrics.py delete mode 100755 qiskit/optimization/results/solution.py delete mode 100755 qiskit/optimization/results/solution_status.py diff --git a/.pylintdict b/.pylintdict index ee33b98a53..8176210721 100644 --- a/.pylintdict +++ b/.pylintdict @@ -541,7 +541,6 @@ simonetto sj sklearn slsqp -SolutionInterface solutionstatus spsa sqrt diff --git a/qiskit/optimization/algorithms/__init__.py b/qiskit/optimization/algorithms/__init__.py index 5ff48af2fd..6df8d53ca5 100644 --- a/qiskit/optimization/algorithms/__init__.py +++ b/qiskit/optimization/algorithms/__init__.py @@ -46,13 +46,15 @@ """ -from .admm_optimizer import ADMMOptimizer +from .optimization_algorithm import OptimizationResult from .optimization_algorithm import OptimizationAlgorithm +from .admm_optimizer import ADMMOptimizer from .cplex_optimizer import CplexOptimizer from .cobyla_optimizer import CobylaOptimizer from .minimum_eigen_optimizer import MinimumEigenOptimizer from .recursive_minimum_eigen_optimizer import RecursiveMinimumEigenOptimizer -from .grover_minimum_finder import GroverMinimumFinder +from .grover_minimum_finder import GroverMinimumFinder, GroverOptimizationResults -__all__ = ["ADMMOptimizer", "OptimizationAlgorithm", "CplexOptimizer", "CobylaOptimizer", - "MinimumEigenOptimizer", "RecursiveMinimumEigenOptimizer", "GroverMinimumFinder"] +__all__ = ["ADMMOptimizer", "OptimizationAlgorithm", "OptimizationResult", "CplexOptimizer", + "CobylaOptimizer", "MinimumEigenOptimizer", "RecursiveMinimumEigenOptimizer", + "GroverMinimumFinder", "GroverOptimizationResults"] diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index d5ea066149..c3016774ba 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -19,10 +19,10 @@ import numpy as np from qiskit.optimization.algorithms.cplex_optimizer import CplexOptimizer -from qiskit.optimization.algorithms.optimization_algorithm import OptimizationAlgorithm +from qiskit.optimization.algorithms.optimization_algorithm import (OptimizationAlgorithm, + OptimizationResult) from qiskit.optimization.problems.quadratic_program import QuadraticProgram from qiskit.optimization.problems.variables import CPX_BINARY, CPX_CONTINUOUS -from qiskit.optimization.results.optimization_result import OptimizationResult UPDATE_RHO_BY_TEN_PERCENT = 0 UPDATE_RHO_BY_RESIDUALS = 1 diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index c9bcf347e4..cb824f30d9 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -20,8 +20,7 @@ import numpy as np from scipy.optimize import fmin_cobyla -from qiskit.optimization.algorithms import OptimizationAlgorithm -from qiskit.optimization.results import OptimizationResult +from qiskit.optimization.algorithms import OptimizationAlgorithm, OptimizationResult from qiskit.optimization.problems import QuadraticProgram from qiskit.optimization import QiskitOptimizationError from qiskit.optimization import infinity diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index b37bf39ba0..3f9c0e8dd8 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -18,9 +18,8 @@ from typing import Optional import logging -from .optimization_algorithm import OptimizationAlgorithm -from ..exceptions.qiskit_optimization_error import QiskitOptimizationError -from ..results.optimization_result import OptimizationResult +from .optimization_algorithm import OptimizationAlgorithm, OptimizationResult +from ..exceptions import QiskitOptimizationError from ..problems.quadratic_program import QuadraticProgram logger = logging.getLogger(__name__) diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_minimum_finder.py index 38b28f4f5f..11183c1670 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_minimum_finder.py @@ -15,17 +15,15 @@ """GroverMinimumFinder module""" import logging -from typing import Optional, Dict, Union +from typing import Optional, Dict, Union, Tuple import random import math import numpy as np from qiskit.aqua import QuantumInstance -from qiskit.optimization.algorithms import OptimizationAlgorithm +from qiskit.optimization.algorithms import OptimizationAlgorithm, OptimizationResult from qiskit.optimization.problems import QuadraticProgram from qiskit.optimization.converters import (QuadraticProgramToQubo, QuadraticProgramToNegativeValueOracle) -from qiskit.optimization.results import GroverOptimizationResults -from qiskit.optimization.results import OptimizationResult from qiskit.optimization.util import get_qubo_solutions from qiskit.aqua.algorithms.amplitude_amplifiers.grover import Grover from qiskit import Aer, QuantumCircuit @@ -271,3 +269,72 @@ def _bin_to_int(v: str, num_value_bits: int) -> int: int_v = int(v, 2) return int_v + + +class GroverOptimizationResults: + """A results object for Grover Optimization methods.""" + + def __init__(self, operation_counts: Dict[int, Dict[str, int]], rotations: int, + n_input_qubits: int, n_output_qubits: int, + func_dict: Dict[Union[int, Tuple[int, int]], int]) -> None: + """ + Args: + operation_counts: The counts of each operation performed per iteration. + rotations: The total number of Grover rotations performed. + n_input_qubits: The number of qubits used to represent the input. + n_output_qubits: The number of qubits used to represent the output. + func_dict: A dictionary representation of the function, where the keys correspond + to a variable, and the values are the corresponding coefficients. + """ + self._operation_counts = operation_counts + self._rotations = rotations + self._n_input_qubits = n_input_qubits + self._n_output_qubits = n_output_qubits + self._func_dict = func_dict + + @property + def operation_counts(self) -> Dict[int, Dict[str, int]]: + """Get the operation counts. + + Returns: + The counts of each operation performed per iteration. + """ + return self._operation_counts + + @property + def rotation_count(self) -> int: + """Getter of rotation_count + + Returns: + The total number of Grover rotations. + """ + return self._rotations + + @property + def n_input_qubits(self) -> int: + """Getter of n_input_qubits + + Returns: + The number of qubits used to represent the input. + """ + return self._n_input_qubits + + @property + def n_output_qubits(self) -> int: + """Getter of n_output_qubits + + Returns: + The number of qubits used to represent the output. + """ + return self._n_output_qubits + + @property + def func_dict(self) -> Dict[Union[int, Tuple[int, int]], int]: + """Getter of func_dict + + Returns: + A dictionary of coefficients describing a function, where the keys are the subscripts + of the variables (e.g. x1), and the values are the corresponding coefficients. If there + is a constant term, it is referenced by key -1. + """ + return self._func_dict diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 9533f5a6c6..706f1f50f6 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -22,11 +22,10 @@ from qiskit.aqua.algorithms import MinimumEigensolver from qiskit.aqua.operators import WeightedPauliOperator, MatrixOperator -from .optimization_algorithm import OptimizationAlgorithm +from .optimization_algorithm import OptimizationAlgorithm, OptimizationResult from ..problems.quadratic_program import QuadraticProgram from ..converters.quadratic_program_to_operator import QuadraticProgramToOperator from ..converters.quadratic_program_to_qubo import QuadraticProgramToQubo -from ..results.optimization_result import OptimizationResult class MinimumEigenOptimizerResult(OptimizationResult): diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index f5aa2ea0c7..eeb6a4ac34 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -15,10 +15,10 @@ """An abstract class for optimization algorithms in Qiskit Optimization.""" +from typing import Any, Optional from abc import ABC, abstractmethod from ..problems.quadratic_program import QuadraticProgram -from ..results.optimization_result import OptimizationResult class OptimizationAlgorithm(ABC): @@ -47,7 +47,7 @@ def is_compatible(self, problem: QuadraticProgram) -> bool: return len(self.get_compatibility_msg(problem)) == 0 @abstractmethod - def solve(self, problem: QuadraticProgram) -> OptimizationResult: + def solve(self, problem: QuadraticProgram) -> 'OptimizationResult': """Tries to solves the given problem using the optimizer. Runs the optimizer to try to solve the optimization problem. @@ -62,3 +62,83 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ raise NotImplementedError + + +class OptimizationResult: + """The optimization result class. + + The optimization algorithms return an object of the type `OptimizationResult`, which enforces + providing the following attributes. + + Attributes: + x: The optimal value found in the optimization algorithm. + fval: The function value corresponding to the optimal value. + results: The original results object returned from the optimization algorithm. This can + contain more information than only the optimal value and function value. + """ + + def __init__(self, x: Optional[Any] = None, fval: Optional[Any] = None, + results: Optional[Any] = None) -> None: + """Initialize the optimization result.""" + self._val = x + self._fval = fval + self._results = results + + def __repr__(self): + return '([%s] / %s)' % (','.join([str(x_) for x_ in self.x]), self.fval) + + @property + def x(self) -> Any: + """Returns the optimal value found in the optimization. + + Returns: + The optimal value found in the optimization. + """ + return self._val + + @property + def fval(self) -> Any: + """Returns the optimal function value. + + Returns: + The function value corresponding to the optimal value found in the optimization. + """ + return self._fval + + @property + def results(self) -> Any: + """Return the original results object from the algorithm. + + Currently a dump for any leftovers. + + Returns: + Additional result information of the optimization algorithm. + """ + return self._results + + @x.setter + def x(self, x: Any) -> None: + """Set a new optimal value. + + Args: + x: The new optimal value. + """ + self._val = x + + @fval.setter + def fval(self, fval: Any) -> None: + """Set a new optimal function value. + + Args: + fval: The new optimal function value. + """ + self._fval = fval + + @results.setter + def results(self, results: Any) -> None: + """Set results. + + Args: + results: The new additional results of the optimization. + """ + self._results = results diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 240ffc7119..7a72607428 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -22,11 +22,10 @@ from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.aqua.utils.validation import validate_min -from .optimization_algorithm import OptimizationAlgorithm +from .optimization_algorithm import OptimizationAlgorithm, OptimizationResult from .minimum_eigen_optimizer import MinimumEigenOptimizer from ..exceptions.qiskit_optimization_error import QiskitOptimizationError from ..problems.quadratic_program import QuadraticProgram -from ..results.optimization_result import OptimizationResult from ..converters.quadratic_program_to_qubo import QuadraticProgramToQubo logger = logging.getLogger(__name__) diff --git a/qiskit/optimization/converters/__init__.py b/qiskit/optimization/converters/__init__.py index 8693f7d28d..799368f845 100644 --- a/qiskit/optimization/converters/__init__.py +++ b/qiskit/optimization/converters/__init__.py @@ -24,11 +24,14 @@ """ +# no opt problem dependency +from .penalize_linear_equality_constraints import PenalizeLinearEqualityConstraints +from .quadratic_program_to_operator import QuadraticProgramToOperator from .quadratic_program_to_negative_value_oracle import QuadraticProgramToNegativeValueOracle -from .inequality_to_equality_converter import InequalityToEqualityConverter + +# opt problem dependency from .integer_to_binary_converter import IntegerToBinaryConverter -from .quadratic_program_to_operator import QuadraticProgramToOperator -from .penalize_linear_equality_constraints import PenalizeLinearEqualityConstraints +from .inequality_to_equality_converter import InequalityToEqualityConverter from .quadratic_program_to_qubo import QuadraticProgramToQubo __all__ = [ diff --git a/qiskit/optimization/converters/inequality_to_equality_converter.py b/qiskit/optimization/converters/inequality_to_equality_converter.py index 98052a48a1..0965390843 100644 --- a/qiskit/optimization/converters/inequality_to_equality_converter.py +++ b/qiskit/optimization/converters/inequality_to_equality_converter.py @@ -20,8 +20,7 @@ import logging from ..problems.quadratic_program import QuadraticProgram -from ..results.optimization_result import OptimizationResult -from ..exceptions.qiskit_optimization_error import QiskitOptimizationError +from ..exceptions import QiskitOptimizationError logger = logging.getLogger(__name__) @@ -167,7 +166,7 @@ def encode(self, op: QuadraticProgram, name: Optional[str] = None, return self._dst - def decode(self, result: OptimizationResult) -> OptimizationResult: + def decode(self, result: 'OptimizationResult') -> 'OptimizationResult': """Convert a result of a converted problem into that of the original problem. Args: @@ -176,6 +175,7 @@ def decode(self, result: OptimizationResult) -> OptimizationResult: Returns: The result of the original problem. """ + from ..algorithms.optimization_algorithm import OptimizationResult new_result = OptimizationResult() # convert the optimization result into that of the original problem names = self._dst.variables.get_names() diff --git a/qiskit/optimization/converters/integer_to_binary_converter.py b/qiskit/optimization/converters/integer_to_binary_converter.py index 90c86d9bb6..933c98fb1d 100644 --- a/qiskit/optimization/converters/integer_to_binary_converter.py +++ b/qiskit/optimization/converters/integer_to_binary_converter.py @@ -20,8 +20,7 @@ import numpy as np from ..problems.quadratic_program import QuadraticProgram -from ..results.optimization_result import OptimizationResult -from ..exceptions.qiskit_optimization_error import QiskitOptimizationError +from ..exceptions import QiskitOptimizationError logger = logging.getLogger(__name__) @@ -209,7 +208,7 @@ def _substitute_int_var(self): if self._src.quadratic_constraints.get_num() > 0: raise QiskitOptimizationError('Quadratic constraints are not yet supported.') - def decode(self, result: OptimizationResult) -> OptimizationResult: + def decode(self, result: 'OptimizationResult') -> 'OptimizationResult': """Convert the encoded problem (binary variables) back to the original (integer variables). Args: diff --git a/qiskit/optimization/converters/quadratic_program_to_qubo.py b/qiskit/optimization/converters/quadratic_program_to_qubo.py index 5f890cbcc7..e4d0c4497f 100644 --- a/qiskit/optimization/converters/quadratic_program_to_qubo.py +++ b/qiskit/optimization/converters/quadratic_program_to_qubo.py @@ -17,7 +17,6 @@ from typing import Optional from qiskit.optimization.problems import QuadraticProgram -from qiskit.optimization.results import OptimizationResult from qiskit.optimization.converters import (PenalizeLinearEqualityConstraints, IntegerToBinaryConverter) from qiskit.optimization.exceptions import QiskitOptimizationError @@ -74,7 +73,7 @@ def encode(self, problem: QuadraticProgram) -> QuadraticProgram: # return QUBO return problem_ - def decode(self, result: OptimizationResult) -> OptimizationResult: + def decode(self, result: 'OptimizationResult') -> 'OptimizationResult': """ Convert a result of a converted problem into that of the original problem. Args: diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 34efdbd4da..653fd75162 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -26,8 +26,7 @@ from qiskit.optimization.problems.problem_type import ProblemType from qiskit.optimization.problems.quadratic_constraint import QuadraticConstraintInterface from qiskit.optimization.problems.variables import VariablesInterface -from qiskit.optimization.results.solution import SolutionInterface -from qiskit.optimization.exceptions.qiskit_optimization_error import QiskitOptimizationError +from qiskit.optimization.exceptions import QiskitOptimizationError logger = logging.getLogger(__name__) @@ -94,7 +93,6 @@ def __init__(self, file_name: Optional[str] = None): self.linear_constraints = LinearConstraintInterface(varindex=varindex) self.quadratic_constraints = QuadraticConstraintInterface(varindex=varindex) self.objective = ObjectiveInterface(varindex=varindex) - self.solution = SolutionInterface() self.problem_type = ProblemType() # None means it will be detected automatically @@ -233,7 +231,6 @@ def end(self): self.linear_constraints = LinearConstraintInterface(varindex=varindex) self.quadratic_constraints = QuadraticConstraintInterface(varindex=varindex) self.objective = ObjectiveInterface(varindex=varindex) - self.solution = SolutionInterface() self._problem_type = None def __enter__(self) -> 'QuadraticProgram': diff --git a/qiskit/optimization/results/__init__.py b/qiskit/optimization/results/__init__.py deleted file mode 100644 index 1a3750b465..0000000000 --- a/qiskit/optimization/results/__init__.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# 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. - -""" -Optimization Results Objects (:mod:`qiskit.optimization.results`) -Results objects for optimization problems - -.. currentmodule:: qiskit.optimization.results - -Results Objects -========================= - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - grover_optimization_results - -======================================================== -Optimization stack for Aqua (:mod:`qiskit.optimization.results`) -======================================================== - -.. currentmodule:: qiskit.optimization.results - -Structures for defining a solution with metrics of its quality etc -========== - -.. autosummary:: - :toctree: - - SolutionStatus - QualityMetrics - -""" - -from .quality_metrics import QualityMetrics -from .solution import SolutionInterface -from .solution_status import SolutionStatus -from .optimization_result import OptimizationResult -from .grover_optimization_results import GroverOptimizationResults - -__all__ = ["SolutionStatus", "QualityMetrics", "SolutionStatus", "OptimizationResult", - "GroverOptimizationResults"] diff --git a/qiskit/optimization/results/grover_optimization_results.py b/qiskit/optimization/results/grover_optimization_results.py deleted file mode 100644 index 804c56d2e3..0000000000 --- a/qiskit/optimization/results/grover_optimization_results.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""GroverOptimizationResults module""" - -from typing import Dict, Tuple, Union - - -class GroverOptimizationResults: - """A results object for Grover Optimization methods.""" - - def __init__(self, operation_counts: Dict[int, Dict[str, int]], rotations: int, - n_input_qubits: int, n_output_qubits: int, - func_dict: Dict[Union[int, Tuple[int, int]], int]) -> None: - """ - Args: - operation_counts: The counts of each operation performed per iteration. - rotations: The total number of Grover rotations performed. - n_input_qubits: The number of qubits used to represent the input. - n_output_qubits: The number of qubits used to represent the output. - func_dict: A dictionary representation of the function, where the keys correspond - to a variable, and the values are the corresponding coefficients. - """ - self._operation_counts = operation_counts - self._rotations = rotations - self._n_input_qubits = n_input_qubits - self._n_output_qubits = n_output_qubits - self._func_dict = func_dict - - @property - def operation_counts(self) -> Dict[int, Dict[str, int]]: - """Get the operation counts. - - Returns: - The counts of each operation performed per iteration. - """ - return self._operation_counts - - @property - def rotation_count(self) -> int: - """Getter of rotation_count - - Returns: - The total number of Grover rotations. - """ - return self._rotations - - @property - def n_input_qubits(self) -> int: - """Getter of n_input_qubits - - Returns: - The number of qubits used to represent the input. - """ - return self._n_input_qubits - - @property - def n_output_qubits(self) -> int: - """Getter of n_output_qubits - - Returns: - The number of qubits used to represent the output. - """ - return self._n_output_qubits - - @property - def func_dict(self) -> Dict[Union[int, Tuple[int, int]], int]: - """Getter of func_dict - - Returns: - A dictionary of coefficients describing a function, where the keys are the subscripts - of the variables (e.g. x1), and the values are the corresponding coefficients. If there - is a constant term, it is referenced by key -1. - """ - return self._func_dict diff --git a/qiskit/optimization/results/optimization_result.py b/qiskit/optimization/results/optimization_result.py deleted file mode 100644 index df163c2ee5..0000000000 --- a/qiskit/optimization/results/optimization_result.py +++ /dev/null @@ -1,97 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""This file implements the OptimizationResult, returned by the optimization algorithms.""" - -from typing import Any, Optional - - -class OptimizationResult: - """The optimization result class. - - The optimization algorithms return an object of the type `OptimizationResult`, which enforces - providing the following attributes. - - Attributes: - x: The optimal value found in the optimization algorithm. - fval: The function value corresponding to the optimal value. - results: The original results object returned from the optimization algorithm. This can - contain more information than only the optimal value and function value. - """ - - def __init__(self, x: Optional[Any] = None, fval: Optional[Any] = None, - results: Optional[Any] = None) -> None: - """Initialize the optimization result.""" - self._val = x - self._fval = fval - self._results = results - - def __repr__(self): - return '([%s] / %s)' % (','.join([str(x_) for x_ in self.x]), self.fval) - - @property - def x(self) -> Any: - """Returns the optimal value found in the optimization. - - Returns: - The optimal value found in the optimization. - """ - return self._val - - @property - def fval(self) -> Any: - """Returns the optimal function value. - - Returns: - The function value corresponding to the optimal value found in the optimization. - """ - return self._fval - - @property - def results(self) -> Any: - """Return the original results object from the algorithm. - - Currently a dump for any leftovers. - - Returns: - Additional result information of the optimization algorithm. - """ - return self._results - - @x.setter - def x(self, x: Any) -> None: - """Set a new optimal value. - - Args: - x: The new optimal value. - """ - self._val = x - - @fval.setter - def fval(self, fval: Any) -> None: - """Set a new optimal function value. - - Args: - fval: The new optimal function value. - """ - self._fval = fval - - @results.setter - def results(self, results: Any) -> None: - """Set results. - - Args: - results: The new additional results of the optimization. - """ - self._results = results diff --git a/qiskit/optimization/results/quality_metrics.py b/qiskit/optimization/results/quality_metrics.py deleted file mode 100755 index 39fc464486..0000000000 --- a/qiskit/optimization/results/quality_metrics.py +++ /dev/null @@ -1,82 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# 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. - -""" Quality Metrics """ - - -class QualityMetrics: - """A class containing measures of the quality of a solution. - - The __str__ method of this class prints all available measures of - the quality of the solution in human readable form. - - This class may have a different set of data members depending on - the optimization algorithm used and the quality metrics that are - available. - - An instance of this class always has the member quality_type, - which is one of the following strings: - - "quadratically_constrained" - "MIP" - - If self.quality_type is "quadratically_constrained" this instance - has the following members: - - objective - norm_total - norm_max - error_Ax_b_total - error_Ax_b_max - error_xQx_dx_f_total - error_xQx_dx_f_max - x_bound_error_total - x_bound_error_max - slack_bound_error_total - slack_bound_error_max - quadratic_slack_bound_error_total - quadratic_slack_bound_error_max - normalized_error_max - - If self.quality_type is "MIP" and this instance was generated for - the incumbent solution, it has the members: - - solver - objective - x_norm_total - x_norm_max - error_Ax_b_total - error_Ax_b_max - x_bound_error_total - x_bound_error_max - integrality_error_total - integrality_error_max - slack_bound_error_total - slack_bound_error_max - - If solver is "MIQCP" this instance also has the members: - - error_xQx_dx_f_total - error_xQx_dx_f_max - quadratic_slack_bound_error_total - quadratic_slack_bound_error_max - """ - - def __init__(self, soln=-1): - # pylint: disable=unused-argument - self._tostring = "" - - def __str__(self): - # See __init__ (above) to see how this is constructed. - return self._tostring diff --git a/qiskit/optimization/results/solution.py b/qiskit/optimization/results/solution.py deleted file mode 100755 index b6113d6af7..0000000000 --- a/qiskit/optimization/results/solution.py +++ /dev/null @@ -1,259 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# 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. - -"""Methods for querying the solution to an optimization problem.""" - -from qiskit.optimization.results.quality_metrics import QualityMetrics -from qiskit.optimization.results.solution_status import SolutionStatus - -from ..problems.base import BaseInterface - - -class SolutionInterface(BaseInterface): - """Methods for querying the solution to an optimization problem.""" - - # method = SolutionMethod() - # """See `SolutionMethod()` """ - # quality_metric = QualityMetric() - # """See `QualityMetric()` """ - status = SolutionStatus() - """See `SolutionStatus()` """ - - def __init__(self): - """Creates a new SolutionInterface. - - The solution interface is exposed by the top-level `Cplex` class - as Cplex.solution. This constructor is not meant to be used - externally. - """ - # pylint: disable=useless-super-delegation - super().__init__() - # self.progress = ProgressInterface(self) - # """See `ProgressInterface()` """ - # self.MIP = MIPSolutionInterface(self) - # """See `MIPSolutionInterface()` """ - # self.pool = SolnPoolInterface(self) - # """See `SolnPoolInterface()` """ - - def get_status(self): - """Returns the status of the solution. - - Returns an attribute of Cplex.solution.status. - For interpretations of the status codes, see the - reference manual of the CPLEX Callable Library, - especially the group optim.cplex.callable.solutionstatus - - >>> import cplex - >>> c = cplex.Cplex() - >>> out = c.set_results_stream(None) - >>> out = c.set_log_stream(None) - >>> c.read("example.mps") - >>> c.solve() - >>> c.solution.get_status() - 1 - """ - raise NotImplementedError - - def get_method(self): - """Returns the method used to solve the problem. - - Returns an attribute of Cplex.solution.method. - - >>> import cplex - >>> c = cplex.Cplex() - >>> out = c.set_results_stream(None) - >>> out = c.set_log_stream(None) - >>> c.read("example.mps") - >>> c.solve() - >>> c.solution.get_method() - 2 - """ - raise NotImplementedError - - def get_status_string(self, status_code=None): - """Returns a string describing the status of the solution. - - Example usage: - - >>> import cplex - >>> c = cplex.Cplex() - >>> out = c.set_results_stream(None) - >>> out = c.set_log_stream(None) - >>> c.read("example.mps") - >>> c.solve() - >>> c.solution.get_status_string() - 'optimal' - """ - # pylint: disable=unused-argument - # if status_code is None: - # status_code = self.get_status() - raise NotImplementedError - - def get_objective_value(self): - """Returns the value of the objective function. - - Example usage: - - >>> import cplex - >>> c = cplex.Cplex() - >>> out = c.set_results_stream(None) - >>> out = c.set_log_stream(None) - >>> c.read("example.mps") - >>> c.solve() - >>> c.solution.get_objective_value() - -202.5 - """ - raise NotImplementedError - - def get_values(self, *args): - """Returns the values of a set of variables at the solution. - - Can be called by four forms. - - solution.get_values() - return the values of all variables from the problem. - - solution.get_values(i) - i must be a variable name or index. Returns the value of - the variable whose index or name is i. - - solution.get_values(s) - s must be a sequence of variable names or indices. Returns - the values of the variables with indices the members of s. - Equivalent to [solution.get_values(i) for i in s] - - solution.get_values(begin, end) - begin and end must be variable indices or variable names. - Returns the values of the variables with indices between begin - and end, inclusive of end. Equivalent to - solution.get_values(range(begin, end + 1)). - - >>> import cplex - >>> c = cplex.Cplex() - >>> out = c.set_results_stream(None) - >>> out = c.set_log_stream(None) - >>> c.read("lpex.mps") - >>> c.solve() - >>> c.solution.get_values([0, 4, 5]) - [25.5, 0.0, 80.0] - """ - # pylint: disable=unused-argument - raise NotImplementedError - - def get_integer_quality(self, which): - """Returns a measure of the quality of the solution. - - The measure of the quality of a solution can be a single attribute of - solution.quality_metrics or a sequence of such - attributes. - - >>> import cplex - >>> c = cplex.Cplex() - >>> out = c.set_results_stream(None) - >>> out = c.set_log_stream(None) - >>> c.read("lpex.mps") - >>> c.solve() - >>> m = c.solution.quality_metric - >>> c.solution.get_integer_quality([m.max_x, m.max_dual_infeasibility]) - [18, -1] - """ - # if isinstance(which, int): - # return None - # else: - # return [None for a in which] - raise NotImplementedError - - def get_float_quality(self, which): - """Returns a measure of the quality of the solution. - - The measure of the quality of a solution can be a single attribute of - solution.quality_metrics or a sequence of such attributes. - - Note - This corresponds to the CPLEX callable library function - CPXgetdblquality. - - >>> import cplex - >>> c = cplex.Cplex() - >>> out = c.set_results_stream(None) - >>> out = c.set_log_stream(None) - >>> c.read("lpex.mps") - >>> c.solve() - >>> m = c.solution.quality_metric - >>> c.solution.get_float_quality([m.max_x, m.max_dual_infeasibility]) - [500.0, 0.0] - """ - # if isinstance(which, int): - # return None - # else: - # return [None for a in which] - raise NotImplementedError - - def get_solution_type(self): - """Returns the type of the solution. - - Returns an attribute of Cplex.solution.type. - - >>> import cplex - >>> c = cplex.Cplex() - >>> out = c.set_results_stream(None) - >>> out = c.set_log_stream(None) - >>> c.read("lpex.mps") - >>> c.solve() - >>> c.solution.get_solution_type() - 1 - """ - raise NotImplementedError - - def is_primal_feasible(self): - """Returns whether or not the solution is known to be primal feasible. - - Note - Returning False does not necessarily mean that the problem is - not primal feasible, only that it is not proved to be primal - feasible. - - >>> import cplex - >>> c = cplex.Cplex() - >>> out = c.set_results_stream(None) - >>> out = c.set_log_stream(None) - >>> c.read("lpex.mps") - >>> c.solve() - >>> c.solution.is_primal_feasible() - True - """ - raise NotImplementedError - - def get_quality_metrics(self): - """Returns an object containing measures of the solution quality. - - See `QualityMetrics` - """ - return QualityMetrics() - - def write(self, filename): - """Writes the incumbent solution to a file. - - Example usage: - - >>> import cplex - >>> c = cplex.Cplex() - >>> out = c.set_results_stream(None) - >>> out = c.set_log_stream(None) - >>> c.read("lpex.mps") - >>> c.solve() - >>> c.solution.write("lpex.sol") - """ - # pylint: disable=unused-argument - raise NotImplementedError diff --git a/qiskit/optimization/results/solution_status.py b/qiskit/optimization/results/solution_status.py deleted file mode 100755 index c998f2109a..0000000000 --- a/qiskit/optimization/results/solution_status.py +++ /dev/null @@ -1,125 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""Solution status codes.""" - -from qiskit.optimization.exceptions.qiskit_optimization_error import QiskitOptimizationError - -CPX_STAT_ABORT_DETTIME_LIM = 25 -CPX_STAT_ABORT_IT_LIM = 10 -CPX_STAT_ABORT_OBJ_LIM = 12 -CPX_STAT_ABORT_TIME_LIM = 11 -CPX_STAT_ABORT_USER = 13 -CPX_STAT_FEASIBLE = 23 -CPX_STAT_INFEASIBLE = 3 -CPX_STAT_INF_OR_UNBD = 4 -CPX_STAT_OPTIMAL = 1 -CPX_STAT_UNBOUNDED = 2 - -CPXMIP_OPTIMAL = 101 -CPXMIP_INFEASIBLE = 103 -CPXMIP_TIME_LIM_FEAS = 107 -CPXMIP_TIME_LIM_INFEAS = 108 -CPXMIP_FAIL_FEAS = 109 -CPXMIP_FAIL_INFEAS = 110 -CPXMIP_MEM_LIM_FEAS = 111 -CPXMIP_MEM_LIM_INFEAS = 112 -CPXMIP_ABORT_FEAS = 113 -CPXMIP_ABORT_INFEAS = 114 -CPXMIP_UNBOUNDED = 118 -CPXMIP_INF_OR_UNBD = 119 -CPXMIP_FEASIBLE = 127 -CPXMIP_DETTIME_LIM_FEAS = 131 -CPXMIP_DETTIME_LIM_INFEAS = 132 - - -class SolutionStatus: - """Solution status codes. - - For documentation of each status code, see the reference manual - of the CPLEX Callable Library, especially the group - optim.cplex.callable.solutionstatus. - """ - unknown = 0 # There is no constant for this. - optimal = CPX_STAT_OPTIMAL - unbounded = CPX_STAT_UNBOUNDED - infeasible = CPX_STAT_INFEASIBLE - feasible = CPX_STAT_FEASIBLE - infeasible_or_unbounded = CPX_STAT_INF_OR_UNBD - abort_obj_limit = CPX_STAT_ABORT_OBJ_LIM - abort_iteration_limit = CPX_STAT_ABORT_IT_LIM - abort_time_limit = CPX_STAT_ABORT_TIME_LIM - abort_dettime_limit = CPX_STAT_ABORT_DETTIME_LIM - abort_user = CPX_STAT_ABORT_USER - fail_feasible = CPXMIP_FAIL_FEAS - fail_infeasible = CPXMIP_FAIL_INFEAS - mem_limit_feasible = CPXMIP_MEM_LIM_FEAS - mem_limit_infeasible = CPXMIP_MEM_LIM_INFEAS - - MIP_optimal = CPXMIP_OPTIMAL - MIP_infeasible = CPXMIP_INFEASIBLE - MIP_time_limit_feasible = CPXMIP_TIME_LIM_FEAS - MIP_time_limit_infeasible = CPXMIP_TIME_LIM_INFEAS - MIP_dettime_limit_feasible = CPXMIP_DETTIME_LIM_FEAS - MIP_dettime_limit_infeasible = CPXMIP_DETTIME_LIM_INFEAS - MIP_abort_feasible = CPXMIP_ABORT_FEAS - MIP_abort_infeasible = CPXMIP_ABORT_INFEAS - MIP_unbounded = CPXMIP_UNBOUNDED - MIP_infeasible_or_unbounded = CPXMIP_INF_OR_UNBD - MIP_feasible = CPXMIP_FEASIBLE - - def __getitem__(self, item): - """Converts a constant to a string. - - Example usage: - - >>> import cplex - >>> c = cplex.Cplex() - >>> c.solution.status.optimal - 1 - >>> c.solution.status[1] - 'optimal' - """ - solution_status = { - 0: 'unknown', - CPX_STAT_OPTIMAL: 'optimal', - CPX_STAT_UNBOUNDED: 'unbounded', - CPX_STAT_INFEASIBLE: 'infeasible', - CPX_STAT_FEASIBLE: 'feasible', - CPX_STAT_INF_OR_UNBD: 'infeasible_or_unbounded', - CPX_STAT_ABORT_OBJ_LIM: 'abort_obj_limit', - CPX_STAT_ABORT_IT_LIM: 'abort_iteration_limit', - CPX_STAT_ABORT_TIME_LIM: 'abort_time_limit', - CPX_STAT_ABORT_DETTIME_LIM: 'abort_dettime_limit', - CPX_STAT_ABORT_USER: 'abort_user', - CPXMIP_FAIL_FEAS: 'fail_feasible', - CPXMIP_FAIL_INFEAS: 'fail_infeasible', - CPXMIP_MEM_LIM_FEAS: 'mem_limit_feasible', - CPXMIP_MEM_LIM_INFEAS: 'mem_limit_infeasible', - CPXMIP_OPTIMAL: 'MIP_optimal', - CPXMIP_INFEASIBLE: 'MIP_infeasible', - CPXMIP_TIME_LIM_FEAS: 'MIP_time_limit_feasible', - CPXMIP_TIME_LIM_INFEAS: 'MIP_time_limit_infeasible', - CPXMIP_DETTIME_LIM_FEAS: 'MIP_dettime_limit_feasible', - CPXMIP_DETTIME_LIM_INFEAS: 'MIP_dettime_limit_infeasible', - CPXMIP_ABORT_FEAS: 'MIP_abort_feasible', - CPXMIP_ABORT_INFEAS: 'MIP_abort_infeasible', - CPXMIP_UNBOUNDED: 'MIP_unbounded', - CPXMIP_INF_OR_UNBD: 'MIP_infeasible_or_unbounded', - CPXMIP_FEASIBLE: 'MIP_feasible' - } - try: - return solution_status[item] - except KeyError: - raise QiskitOptimizationError('Unexpected solution status code!') diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index 490898d8f7..09da43caad 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -22,7 +22,7 @@ from qiskit.aqua.operators import WeightedPauliOperator from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.optimization import QuadraticProgram, QiskitOptimizationError -from qiskit.optimization.results import OptimizationResult +from qiskit.optimization.algorithms import OptimizationResult from qiskit.optimization.converters import ( InequalityToEqualityConverter, QuadraticProgramToOperator, From caebfc0411b1d598ef7c6f0df556887fc74f050c Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Tue, 14 Apr 2020 22:47:20 +0200 Subject: [PATCH 186/323] update quadratic program, linear expression, linear constraint, variable and tests --- qiskit/optimization/problems/__init__.py | 44 +- qiskit/optimization/problems/base.py | 117 -- qiskit/optimization/problems/constraint.py | 118 ++ .../problems/has_quadratic_program.py | 45 + .../problems/linear_constraint.py | 848 ++------------ .../problems/linear_expression.py | 138 +++ qiskit/optimization/problems/name_index.py | 118 -- qiskit/optimization/problems/objective.py | 565 ---------- qiskit/optimization/problems/problem_type.py | 97 -- .../problems/quadratic_constraint.py | 671 +---------- .../problems/quadratic_expression.py | 4 + .../problems/quadratic_objective.py | 12 + .../problems/quadratic_program.py | 1000 +++++------------ qiskit/optimization/problems/variable.py | 155 +++ qiskit/optimization/problems/variables.py | 689 ------------ test/optimization/test_linear_constraint.py | 132 +++ test/optimization/test_linear_constraints.py | 279 ----- test/optimization/test_linear_expression.py | 106 ++ test/optimization/test_quadratic_program.py | 487 ++------ test/optimization/test_variable.py | 95 ++ test/optimization/test_variables.py | 299 ----- 21 files changed, 1260 insertions(+), 4759 deletions(-) delete mode 100644 qiskit/optimization/problems/base.py create mode 100644 qiskit/optimization/problems/constraint.py create mode 100644 qiskit/optimization/problems/has_quadratic_program.py create mode 100644 qiskit/optimization/problems/linear_expression.py delete mode 100644 qiskit/optimization/problems/name_index.py delete mode 100644 qiskit/optimization/problems/objective.py delete mode 100644 qiskit/optimization/problems/problem_type.py create mode 100644 qiskit/optimization/problems/quadratic_expression.py create mode 100644 qiskit/optimization/problems/quadratic_objective.py create mode 100644 qiskit/optimization/problems/variable.py delete mode 100644 qiskit/optimization/problems/variables.py create mode 100644 test/optimization/test_linear_constraint.py delete mode 100644 test/optimization/test_linear_constraints.py create mode 100644 test/optimization/test_linear_expression.py create mode 100644 test/optimization/test_variable.py delete mode 100644 test/optimization/test_variables.py diff --git a/qiskit/optimization/problems/__init__.py b/qiskit/optimization/problems/__init__.py index ff8e449288..35ba6e6ed0 100644 --- a/qiskit/optimization/problems/__init__.py +++ b/qiskit/optimization/problems/__init__.py @@ -26,33 +26,43 @@ :toctree: ../stubs/ :nosignatures: - LinearConstraintInterface + Constraint + ConstraintSense + LinearExpression + LinearConstraint + QuadraticExpression + QuadraticConstraint + QuadraticObjective ObjSense - ObjectiveInterface QuadraticProgram - QuadraticConstraintInterface - VariablesInterface - NameIndex + Variable + VarType -N.B. Additional classes LinearConstraintInterface, QuadraticConstraintInterface, -ObjectiveInterface, and VariablesInterface +N.B. Additional classes Constraint, LinearConstraint, QuadraticConstraint, +QuadraticObjective, and Variable are not to be instantiated directly. Objects of those types are available within an instantiated QuadraticProgram. """ -from .linear_constraint import LinearConstraintInterface -from .objective import ObjSense, ObjectiveInterface +from .constraint import Constraint +from .constraint import ConstraintSense +from .linear_expression import LinearExpression +from .linear_constraint import LinearConstraint +from .quadratic_expression import QuadraticExpression +from .quadratic_constraint import QuadraticConstraint +from .quadratic_objective import QuadraticObjective, ObjSense from .quadratic_program import QuadraticProgram -from .quadratic_constraint import QuadraticConstraintInterface -from .variables import VariablesInterface -from .name_index import NameIndex +from .variable import Variable, VarType -__all__ = ['LinearConstraintInterface', +__all__ = ['Constraint', + 'ConstraintSense', + 'LinearExpression', + 'LinearConstraint', + 'QuadraticExpression', + 'QuadraticConstraint', + 'QuadraticObjective', 'ObjSense', - 'ObjectiveInterface', 'QuadraticProgram', - 'QuadraticConstraintInterface', - 'VariablesInterface', - 'NameIndex', + 'Variable' ] diff --git a/qiskit/optimization/problems/base.py b/qiskit/optimization/problems/base.py deleted file mode 100644 index 0188e65ae3..0000000000 --- a/qiskit/optimization/problems/base.py +++ /dev/null @@ -1,117 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# 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. - -"""common methods for sub-interfaces within Qiskit Optimization""" - -from abc import ABC -from typing import Callable, Sequence, Union, Any, List, Generator - -from qiskit.optimization.exceptions import QiskitOptimizationError -from .name_index import NameIndex - - -class BaseInterface(ABC): - """ - Abstract class with common methods - for sub-interfaces within Qiskit Optimization. - """ - - def __init__(self): - """Creates a new BaseInterface. - - This class is not meant to be instantiated directly nor used - externally. - """ - self._index = NameIndex() - - def get_indices(self, *name) -> Union[int, List[int]]: - """Converts from names to indices. - - If name is a string, get_indices returns the index of the - object with that name. If no such object exists, an - exception is raised. - - If name is a sequence of strings, get_indices returns a list - of the indices corresponding to the strings in name. - Equivalent to map(self.get_indices, name). - - See `NameIndex.convert` for details. - - If the subclass does not provide an index function (i.e., the - interface is not indexed), then a NotImplementedError is raised. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names=["a", "b"]) - >>> op.variables.get_indices("a") - 0 - >>> op.variables.get_indices(["a", "b"]) - [0, 1] - """ - return self._index.convert(*name) - - @staticmethod - def _setter(setfunc: Callable[[Union[str, int], Any], None], *args) -> None: - """A generic setter method - - Args: - setfunc: A setter function of two parameters: `index` and `val`. - Since `index` can be a string, users need to convert it into an appropriate index - by applying `NameIndex.convert`. - *args: A pair of index and value or a list of pairs of index and value. - `setfunc` is invoked with `args`. - - Raises: - QiskitOptimizationError: Invalid arguments - """ - # check for all elements in args whether they are types - if len(args) == 1 and isinstance(args[0], (Sequence, Generator)): - arg0 = list(args[0]) if isinstance(args[0], Generator) else args[0] - if all(isinstance(pair, Sequence) and len(pair) == 2 for pair in arg0): - for pair in arg0: - setfunc(*pair) - else: - raise QiskitOptimizationError("Invalid arguments: {}".format(args)) - elif len(args) == 2: - setfunc(*args) - else: - raise QiskitOptimizationError("Invalid arguments: {}".format(args)) - - @staticmethod - def _getter(getfunc: Callable[[int], Any], *args) -> Any: - """A generic getter method - - Args: - getfunc(index): A getter function with an argument `index`. - `index` should be already converted by `NameIndex.convert`. - *args: A single index or a list of indices. `getfunc` is invoked with args. - - Returns: - Union[int, List[int]]: if `args` is a single index, this returns a single - value generated by `getfunc`. If `args` is a list of indices, - this returns a list of values. - - Raises: - QiskitOptimizationError: Invalid arguments - """ - if len(args) == 0: - raise QiskitOptimizationError('Empty arguments should be handled in the caller') - if len(args) == 1: - if isinstance(args[0], Sequence): - args = args[0] - else: - return getfunc(args[0]) - return [getfunc(k) for k in args] diff --git a/qiskit/optimization/problems/constraint.py b/qiskit/optimization/problems/constraint.py new file mode 100644 index 0000000000..2cbb29f92e --- /dev/null +++ b/qiskit/optimization/problems/constraint.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019, 2020. +# +# 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. + +"""Abstract Constraint.""" + +from abc import abstractmethod +from enum import Enum +from typing import Union, List, Dict +from numpy import ndarray + +from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram + + +class ConstraintSense(Enum): + """Constants Sense Type.""" + leq = 0 + geq = 1 + eq = 2 # pylint: disable=locally-disabled, invalid-name + + +class Constraint(HasQuadraticProgram): + """Abstract Constraint Class.""" + + def __init__(self, quadratic_program: "QuadraticProgram", name: str, sense: ConstraintSense, + rhs: float) -> None: + """ Initializes the constraint. + + Args: + quadratic_program: The parent QuadraticProgram. + name: The name of the constraint. + sense: The sense of the constraint. + rhs: The right-hand-side of the constraint. + """ + super().__init__(quadratic_program) + self._name = name + self._sense = sense + self._rhs = rhs + + @property + def name(self) -> str: + """Returns the name of the constraint. + + Returns: + The name of the constraint. + """ + return self._name + + @name.setter + def name(self, name: str) -> None: + """Sets the name of the constraint and updates the name index in the corresponding QP. + + Args: + name: The name of the constraint. + + Raises: + QiskitOptimizationError: if the name is already existing. + """ + # TODO: update QP and raise exception if name already exists + self._name = name + + @property + def sense(self) -> ConstraintSense: + """Returns the sense of the constraint. + + Returns: + The sense of the constraint. + """ + return self._sense + + @sense.setter + def sense(self, sense: ConstraintSense) -> None: + """Sets the sense of the constraint. + + Args: + sense: The sense of the constraint. + """ + self._sense = sense + + @property + def rhs(self) -> float: + """Returns the right-hand-side of the constraint. + + Returns: + The right-hand-side of the constraint. + """ + return self._rhs + + @rhs.setter + def rhs(self, rhs: ConstraintSense) -> None: + """Sets the right-hand-side of the constraint. + + Args: + rhs: The right-hand-side of the constraint. + """ + self._rhs = rhs + + @abstractmethod + def evaluate(self, x: Union[ndarray, List, Dict[Union[int, str], float]]) -> float: + """Evaluate left-hand-side of constraint for given values of variables. + + Args: + x: The values to be used for the variables. + + Returns: + The left-hand-side of the constraint. + """ + raise NotImplementedError() diff --git a/qiskit/optimization/problems/has_quadratic_program.py b/qiskit/optimization/problems/has_quadratic_program.py new file mode 100644 index 0000000000..f8ee7d19d8 --- /dev/null +++ b/qiskit/optimization/problems/has_quadratic_program.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019, 2020. +# +# 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. + +"""Interface for all objects that have a parent QuadraticProgram.""" + + +class HasQuadraticProgram: + """Abstract interface class for all objects that have a parent QuadraticProgram.""" + + def __init__(self, quadratic_program: "QuadraticProgram") -> None: + """ Initialize object with parent QuadraticProgram. + + Args: + quadratic_program: The parent QuadraticProgram. + """ + self._quadratic_program = quadratic_program + + @property + def quadratic_program(self) -> "QuadraticProgram": + """Returns the parent QuadraticProgram. + + Returns: + The parent QuadraticProgram. + """ + return self._quadratic_program + + @quadratic_program.setter + def quadratic_program(self, quadratic_program: "QuadraticProgram") -> None: + """Sets the parent QuadraticProgram. + + Args: + quadratic_program: The parent QuadraticProgram. + """ + self._quadratic_program = quadratic_program diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index 5aa307fbdf..8da139fe46 100644 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -12,794 +12,86 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Linear constraints interface""" - -import copy -from collections.abc import Sequence -from typing import Callable, Optional, List, Union -import logging - -from qiskit.optimization.problems.name_index import init_list_args, NameIndex -from qiskit.optimization.exceptions import QiskitOptimizationError - -from .base import BaseInterface - -logger = logging.getLogger(__name__) - -_HAS_CPLEX = False -try: - from cplex import SparsePair - _HAS_CPLEX = True -except ImportError: - logger.info('CPLEX is not installed.') - - -class LinearConstraintInterface(BaseInterface): - """Methods for adding, modifying, and querying linear constraints.""" - - def __init__(self, varindex: Callable): - """Creates a new LinearConstraintInterface. - - The linear constraints interface is exposed by the top-level - `QuadraticProgram` class as `QuadraticProgram.linear_constraints`. - This constructor is not meant to be used externally. - """ - if not _HAS_CPLEX: - raise NameError('CPLEX is not installed.') - - super(LinearConstraintInterface, self).__init__() - self._rhs = [] - self._senses = [] - self._range_values = [] - self._names = [] - self._lin_expr = [] - self._index = NameIndex() - self._varindex = varindex - - def get_num(self) -> int: - """Returns the number of linear constraints. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.linear_constraints.add(names = ["c1", "c2", "c3"]) - >>> op.linear_constraints.get_num() - 3 - """ - return len(self._names) - - def add(self, lin_expr: Optional[List['SparsePair']] = None, senses: str = "", - rhs: Optional[List[float]] = None, range_values: Optional[List[float]] = None, - names: Optional[List[str]] = None) -> range: - """Adds linear constraints to the problem. - - linear_constraints.add accepts the keyword arguments lin_expr, - senses, rhs, range_values, and names. - - If more than one argument is specified, all arguments must - have the same length. - - lin_expr may be either a list of SparsePair instances or a - matrix in list-of-lists format. - - Note - The entries of lin_expr must not contain duplicate indices. - If an entry of lin_expr references a variable more than - once, either by index, name, or a combination of index and - name, an exception will be raised. - - senses must be either a list of single-character strings or a - string containing the senses of the linear constraints. - Each entry must - be one of 'G', 'L', 'E', and 'R', indicating greater-than, - less-than, equality, and ranged constraints, respectively. - - rhs is a list of floats, specifying the righthand side of - each linear constraint. - - range_values is a list of floats, specifying the difference - between lefthand side and righthand side of each linear constraint. - If range_values[i] > 0 (zero) then the constraint i is defined as - rhs[i] <= rhs[i] + range_values[i]. If range_values[i] < 0 (zero) - then constraint i is defined as - rhs[i] + range_value[i] <= a*x <= rhs[i]. - - names is a list of strings. - - Returns an iterator containing the indices of the added linear - constraints. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = ["x1", "x2", "x3"]) - >>> indices = op.linear_constraints.add(\ - lin_expr = [SparsePair(ind = ["x1", "x3"], val = [1.0, -1.0]),\ - SparsePair(ind = ["x1", "x2"], val = [1.0, 1.0]),\ - SparsePair(ind = ["x1", "x2", "x3"], val = [-1.0] * 3),\ - SparsePair(ind = ["x2", "x3"], val = [10.0, -2.0])],\ - senses = ["E", "L", "G", "R"],\ - rhs = [0.0, 1.0, -1.0, 2.0],\ - range_values = [0.0, 0.0, 0.0, -10.0],\ - names = ["c0", "c1", "c2", "c3"]) - >>> op.linear_constraints.get_rhs() - [0.0, 1.0, -1.0, 2.0] - """ - start = self.get_num() - arg_list = init_list_args(lin_expr, senses, rhs, range_values, names) - arg_lengths = [len(x) for x in arg_list] - if len(arg_lengths) == 0: - return range(start, start) - max_length = max(arg_lengths) - if max_length == 0: - return range(start, start) - for arg_length in arg_lengths: - if arg_length > 0 and arg_length != max_length: - raise QiskitOptimizationError("inconsistent arguments in linear_constraints.add().") - - rhs = rhs or [0.0] * max_length - self._rhs.extend(rhs) - - senses = senses or 'E' * max_length - self._senses.extend(senses) - - range_values = range_values or [0.0] * max_length - self._range_values.extend(range_values) - - names = names or [''] * max_length - for i, name in enumerate(names): - if name == '': - names[i] = 'c' + str(start + i + 1) - self._names.extend(names) - self._index.build(self._names) - - lin_expr = lin_expr or [SparsePair()] * max_length - for spair in lin_expr: - lin_expr_dict = {} - if isinstance(spair, SparsePair): - zip_iter = zip(spair.ind, spair.val) - elif isinstance(spair, Sequence) and len(spair) == 2: - zip_iter = zip(spair[0], spair[1]) - else: - raise QiskitOptimizationError('Invalid lin_expr: {}'.format(lin_expr)) - for i, val in zip_iter: - i = self._varindex(i) - if i in lin_expr_dict: - raise QiskitOptimizationError( - 'Variables should only appear once in linear constraint.') - lin_expr_dict[i] = val - self._lin_expr.append(lin_expr_dict) - - return range(start, start + max_length) - - def delete(self, *args): - """Removes linear constraints from the problem. - - There are four forms by which linear_constraints.delete may be - called. - - linear_constraints.delete() - deletes all linear constraints from the problem. - - linear_constraints.delete(i) - i must be a linear constraint name or index. Deletes the - linear constraint whose index or name is i. - - linear_constraints.delete(s) - s must be a sequence of linear constraint names or indices. - Deletes the linear constraints with names or indices contained - within s. Equivalent to [linear_constraints.delete(i) for i in s]. - - linear_constraints.delete(begin, end) - begin and end must be linear constraint indices or linear - constraint names. Deletes the linear constraints with indices - between begin and end, inclusive of end. Equivalent to - linear_constraints.delete(range(begin, end + 1)). This will - give the best performance when deleting batches of linear - constraints. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.linear_constraints.add(names=[str(i) for i in range(10)]) - >>> op.linear_constraints.get_num() - 10 - >>> op.linear_constraints.delete(8) - >>> op.linear_constraints.get_names() - ['0', '1', '2', '3', '4', '5', '6', '7', '9'] - >>> op.linear_constraints.delete("1", 3) - >>> op.linear_constraints.get_names() - ['0', '4', '5', '6', '7', '9'] - >>> op.linear_constraints.delete([2, "0", 5]) - >>> op.linear_constraints.get_names() - ['4', '6', '7'] - >>> op.linear_constraints.delete() - >>> op.linear_constraints.get_names() - [] - """ - if len(args) == 0: - # Delete all - self._rhs = [] - self._senses = [] - self._names = [] - self._lin_expr = [] - self._range_values = [] - self._index = NameIndex() - - keys = self._index.convert(*args) - if isinstance(keys, int): - keys = [keys] - for i in sorted(keys, reverse=True): - del self._rhs[i] - del self._senses[i] - del self._names[i] - del self._lin_expr[i] - del self._range_values[i] - self._index.build(self._names) - - def set_rhs(self, *args): - """Sets the righthand side of a set of linear constraints. - - There are two forms by which linear_constraints.set_rhs may be - called. - - linear_constraints.set_rhs(i, rhs) - i must be a row name or index and rhs must be a real number. - Sets the righthand side of the row whose index or name is - i to rhs. - - linear_constraints.set_rhs(seq_of_pairs) - seq_of_pairs must be a list or tuple of (i, rhs) pairs, each - of which consists of a row name or index and a real - number. Sets the righthand side of the specified rows to - the corresponding values. Equivalent to - [linear_constraints.set_rhs(pair[0], pair[1]) for pair in seq_of_pairs]. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) - >>> op.linear_constraints.get_rhs() - [0.0, 0.0, 0.0, 0.0] - >>> op.linear_constraints.set_rhs("c1", 1.0) - >>> op.linear_constraints.get_rhs() - [0.0, 1.0, 0.0, 0.0] - >>> op.linear_constraints.set_rhs([("c3", 2.0), (2, -1.0)]) - >>> op.linear_constraints.get_rhs() - [0.0, 1.0, -1.0, 2.0] - """ - - def _set(i, v): - self._rhs[self._index.convert(i)] = v - - self._setter(_set, *args) - - def set_names(self, *args): - """Sets the name of a linear constraint or set of linear constraints. - - There are two forms by which linear_constraints.set_names may be - called. - - linear_constraints.set_names(i, name) - i must be a linear constraint name or index and name must be a - string. - - linear_constraints.set_names(seq_of_pairs) - seq_of_pairs must be a list or tuple of (i, name) pairs, - each of which consists of a linear constraint name or index and a - string. Sets the name of the specified linear constraints to the - corresponding strings. Equivalent to - [linear_constraints.set_names(pair[0], pair[1]) for pair in seq_of_pairs]. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) - >>> op.linear_constraints.set_names("c1", "second") - >>> op.linear_constraints.get_names(1) - 'second' - >>> op.linear_constraints.set_names([("c3", "last"), (2, "middle")]) - >>> op.linear_constraints.get_names() - ['c0', 'second', 'middle', 'last'] - """ - - def _set(i, v): - self._names[self._index.convert(i)] = v - - self._setter(_set, *args) - - def set_senses(self, *args): - """Sets the sense of a linear constraint or set of linear constraints. - - There are two forms by which linear_constraints.set_senses may be - called. - - linear_constraints.set_senses(i, type) - i must be a row name or index and name must be a - single-character string. - - linear_constraints.set_senses(seq_of_pairs) - seq_of_pairs must be a list or tuple of (i, sense) pairs, - each of which consists of a row name or index and a - single-character string. Sets the sense of the specified - rows to the corresponding strings. Equivalent to - [linear_constraints.set_senses(pair[0], pair[1]) for pair in seq_of_pairs]. - - The senses of the constraints must be one of 'G', 'L', 'E', - and 'R', indicating greater-than, less-than, equality, and - ranged constraints, respectively. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) - >>> op.linear_constraints.get_senses() - ['E', 'E', 'E', 'E'] - >>> op.linear_constraints.set_senses("c1", "G") - >>> op.linear_constraints.get_senses(1) - 'G' - >>> op.linear_constraints.set_senses([("c3", "L"), (2, "R")]) - >>> op.linear_constraints.get_senses() - ['E', 'G', 'R', 'L'] - """ - - def _set(i, v): - v = v.upper() - if v in ["G", "L", "E", "R"]: - self._senses[self._index.convert(i)] = v - else: - raise QiskitOptimizationError("Wrong sense {}".format(v)) - - self._setter(_set, *args) - - def set_linear_components(self, *args): - """Sets a linear constraint or set of linear constraints. - - There are two forms by which this method may be called: - - linear_constraints.set_linear_components(i, lin) - i must be a row name or index and lin must be either a - SparsePair or a pair of sequences, the first of which - consists of variable names or indices, the second of which - consists of floats. - - linear_constraints.set_linear_components(seq_of_pairs) - seq_of_pairs must be a list or tuple of (i, lin) pairs, - each of which consists of a row name or index and a vector - as described above. Sets the specified rows - to the corresponding vector. Equivalent to - [linear_constraints.set_linear_components(pair[0], pair[1]) for pair in seq_of_pairs]. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) - >>> indices = op.variables.add(names = ["x0", "x1"]) - >>> op.linear_constraints.set_linear_components("c0", [["x0"], [1.0]]) - >>> op.linear_constraints.get_rows("c0") - SparsePair(ind = [0], val = [1.0]) - >>> op.linear_constraints.set_linear_components([ - ("c3", SparsePair(ind = ["x1"], val = [-1.0])),\ - (2, [[0, 1], [-2.0, 3.0]])]) - >>> op.linear_constraints.get_rows() - [SparsePair(ind = [0], val = [1.0]), SparsePair(ind = [], val = []), - SparsePair(ind = [0, 1], val = [-2.0, 3.0]), SparsePair(ind = [1], val = [-1.0])] - """ - - def _set(i, v): - if isinstance(v, SparsePair): - zip_iter = zip(v.ind, v.val) - elif isinstance(v, Sequence) and len(v) == 2: - zip_iter = zip(v[0], v[1]) - else: - raise QiskitOptimizationError( - "Wrong linear expression. A SparsePair is expected: {}".format(v)) - i = self._index.convert(i) - for j, w in zip_iter: - j = self._varindex(j) - if w == 0: - if j in self._lin_expr[i]: - del self._lin_expr[i][j] - else: - self._lin_expr[i][j] = w - - self._setter(_set, *args) - - def set_range_values(self, *args): - """Sets the range values for a set of linear constraints. - - That is, this method sets the lefthand side (lhs) for each ranged - constraint of the form lhs <= lin_expr <= rhs. - - The range values are a list of floats, specifying the difference - between lefthand side and righthand side of each linear constraint. - If range_values[i] > 0 (zero) then the constraint i is defined as - rhs[i] <= rhs[i] + range_values[i]. If range_values[i] < 0 (zero) - then constraint i is defined as - rhs[i] + range_value[i] <= a*x <= rhs[i]. - - Note that changing the range values will not change the sense of a - constraint; you must call the method set_senses() of the class - LinearConstraintInterface to change the sense of a ranged row if - the previous range value was 0 (zero) and the constraint sense was not - 'R'. Similarly, changing the range coefficient from a nonzero value to - 0 (zero) will not change the constraint sense from 'R" to "E"; an - additional call of set_senses() is required to accomplish that. - - There are two forms by which linear_constraints.set_range_values may be - called. - - linear_constraints.set_range_values(i, range) - i must be a row name or index and range must be a real - number. Sets the range value of the row whose index or - name is i to range. - - linear_constraints.set_range_values(seq_of_pairs) - seq_of_pairs must be a list or tuple of (i, range) pairs, each - of which consists of a row name or index and a real - number. Sets the range values for the specified rows to - the corresponding values. Equivalent to - [linear_constraints.set_range_values(pair[0], pair[1]) for pair in seq_of_pairs]. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) - >>> op.linear_constraints.set_range_values("c1", 1.0) - >>> op.linear_constraints.get_range_values() - [0.0, 1.0, 0.0, 0.0] - >>> op.linear_constraints.set_range_values([("c3", 2.0), (2, -1.0)]) - >>> op.linear_constraints.get_range_values() - [0.0, 1.0, -1.0, 2.0] - """ - - def _set(i, v): - self._range_values[self._index.convert(i)] = v - - self._setter(_set, *args) - - def set_coefficients(self, *args): - """Sets individual coefficients of the linear constraint matrix. - - There are two forms by which - linear_constraints.set_coefficients may be called. - - linear_constraints.set_coefficients(row, col, val) - row and col must be indices or names of a linear constraint - and variable, respectively. The corresponding coefficient - is set to val. - - linear_constraints.set_coefficients(coefficients) - coefficients must be a list of (row, col, val) triples as - described above. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"]) - >>> indices = op.variables.add(names = ["x0", "x1"]) - >>> op.linear_constraints.set_coefficients("c0", "x1", 1.0) - >>> op.linear_constraints.get_rows(0) - SparsePair(ind = [1], val = [1.0]) - >>> op.linear_constraints.set_coefficients([("c2", "x0", 2.0),\ - ("c2", "x1", -1.0)]) - >>> op.linear_constraints.get_rows("c2") - SparsePair(ind = [0, 1], val = [2.0, -1.0]) - """ - if len(args) == 3: - arg_list = [args] - elif len(args) == 1 and isinstance(args[0], Sequence): - arg_list = args[0] +"""Linear Constraint.""" + +from typing import Union, List, Dict +from numpy import ndarray +from scipy.sparse import spmatrix + +from qiskit.optimization.problems import Constraint, ConstraintSense, LinearExpression +from qiskit.optimization import QiskitOptimizationError + + +class LinearConstraint(Constraint): + """ Representation of a linear constraint.""" + + def __init__(self, + quadratic_program: "QuadraticProgram", name: str, + linear: Union[ + LinearExpression, ndarray, spmatrix, List[float], Dict[Union[str, int], float] + ], + sense: ConstraintSense, + rhs: float + ) -> None: + """Constructs a linear constraint. + + Args: + quadratic_program: The parent quadratic program. + name: The name of the constraint. + linear: The coefficients specifying the linear constraint. + sense: The sense of the constraint. + rhs: The right-hand-side of the constraint. + + Raises: + QiskitOptimizationError: if the given linear expression has a different parent + QuadraticProgram than the constraint. + """ + super().__init__(quadratic_program, name, sense, rhs) + if isinstance(linear, LinearExpression): + if linear._quadratic_program != quadratic_program: + raise QiskitOptimizationError("Incompatible parent quadratic program!") + self._linear = linear else: - raise QiskitOptimizationError("Invalid arguments {}".format(args)) - for i, j, v in arg_list: - i = self._index.convert(i) - j = self._varindex(j) - if v == 0: - if j in self._lin_expr[i]: - del self._lin_expr[i][j] - else: - self._lin_expr[i][j] = v - - def get_rhs(self, *args) -> Union[float, List[float]]: - """Returns the righthand side of constraints from the problem. - - Can be called by four forms. - - linear_constraints.get_rhs() - return the righthand side of all linear constraints from - the problem. - - linear_constraints.get_rhs(i) - i must be a linear constraint name or index. Returns the - righthand side of the linear constraint whose index or - name is i. - - linear_constraints.get_rhs(s) - s must be a sequence of linear constraint names or indices. - Returns the righthand side of the linear constraints with - indices the members of s. Equivalent to - [linear_constraints.get_rhs(i) for i in s] - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.linear_constraints.add(rhs = [1.5 * i for i in range(10)],\ - names = [str(i) for i in range(10)]) - >>> op.linear_constraints.get_num() - 10 - >>> op.linear_constraints.get_rhs(8) - 12.0 - >>> op.linear_constraints.get_rhs([2,"0",5]) - [3.0, 0.0, 7.5] - >>> op.linear_constraints.get_rhs() - [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] - """ - - def _get(i): - return self._rhs[i] - - if len(args) == 0: - return copy.deepcopy(self._rhs) - keys = self._index.convert(*args) - return self._getter(_get, keys) - - def get_senses(self, *args) -> Union[str, List[str]]: - """Returns the senses of constraints from the problem. - - Can be called by four forms. - - linear_constraints.get_senses() - return the senses of all linear constraints from the - problem. - - linear_constraints.get_senses(i) - i must be a linear constraint name or index. Returns the - sense of the linear constraint whose index or name is i. - - linear_constraints.get_senses(s) - s must be a sequence of linear constraint names or indices. - Returns the senses of the linear constraints with indices - the members of s. Equivalent to - [linear_constraints.get_senses(i) for i in s] - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.linear_constraints.add( - ... senses=["E", "G", "L", "R"], - ... names=[str(i) for i in range(4)]) - >>> op.linear_constraints.get_num() - 4 - >>> op.linear_constraints.get_senses(1) - 'G' - >>> op.linear_constraints.get_senses("1",3) - ['G', 'L', 'R'] - >>> op.linear_constraints.get_senses([2,"0",1]) - ['L', 'E', 'G'] - >>> op.linear_constraints.get_senses() - ['E', 'G', 'L', 'R'] - """ - - def _get(i): - return self._senses[i] - - if len(args) == 0: - return copy.deepcopy(self._senses) - keys = self._index.convert(*args) - return self._getter(_get, keys) - - def get_range_values(self, *args) -> Union[float, List[float]]: - """Returns the range values of linear constraints from the problem. - - That is, this method returns the lefthand side (lhs) for each - ranged constraint of the form lhs <= lin_expr <= rhs. This method - makes sense only for ranged constraints, that is, linear constraints - of sense 'R'. - - The range values are a list of floats, specifying the difference - between lefthand side and righthand side of each linear constraint. - If range_values[i] > 0 (zero) then the constraint i is defined as - rhs[i] <= rhs[i] + range_values[i]. If range_values[i] < 0 (zero) - then constraint i is defined as - rhs[i] + range_value[i] <= a*x <= rhs[i]. - - Can be called by four forms. - - linear_constraints.get_range_values() - return the range values of all linear constraints from the - problem. - - linear_constraints.get_range_values(i) - i must be a linear constraint name or index. Returns the - range value of the linear constraint whose index or name is i. + self._linear = LinearExpression(quadratic_program, linear) - linear_constraints.get_range_values(s) - s must be a sequence of linear constraint names or indices. - Returns the range values of the linear constraints with - indices the members of s. Equivalent to - [linear_constraints.get_range_values(i) for i in s] + @property + def linear(self) -> LinearExpression: + """Returns the linear expression corresponding to the left-hand-side of the constraint. - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.linear_constraints.add(\ - range_values = [1.5 * i for i in range(10)],\ - senses = ["R"] * 10,\ - names = [str(i) for i in range(10)]) - >>> op.linear_constraints.get_num() - 10 - >>> op.linear_constraints.get_range_values(8) - 12.0 - >>> op.linear_constraints.get_range_values("1",3) - [1.5, 3.0, 4.5] - >>> op.linear_constraints.get_range_values([2,"0",5]) - [3.0, 0.0, 7.5] - >>> op.linear_constraints.get_range_values() - [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] + Returns: + The left-hand-side linear expression. """ + return self._linear - def _get(i): - return self._range_values[i] + @linear.setter + def linear(self, linear: + Union[LinearExpression, ndarray, spmatrix, List[float], Dict[Union[str, int], float]] + ) -> None: + """Sets the linear expression corresponding to the left-hand-side of the constraint. + The coefficients can either be given by an array, a (sparse) 1d matrix, a lsit or a + dictionary. - if len(args) == 0: - return copy.deepcopy(self._range_values) - keys = self._index.convert(*args) - return self._getter(_get, keys) + Args: + linear: The linear expression or coefficients of the left-hand-side. - def get_coefficients(self, *args) -> Union[float, List[float]]: - """Returns coefficients by row, column coordinates. - - There are three forms by which - linear_constraints.get_coefficients may be called. - - Without arguments, it returns a dictionary indexed - first by constraints and second by variables. - - With two arguments, - linear_constraints.get_coefficients(row, col) - returns the coefficient. - - With one argument, - linear_constraints.get_coefficients(sequence_of_pairs) - returns a list of coefficients. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = ["x0", "x1"]) - >>> indices = op.linear_constraints.add(\ - names = ["c0", "c1"],\ - lin_expr = [[[1], [1.0]], [[0, 1], [2.0, -1.0]]]) - >>> op.linear_constraints.get_coefficients("c0", "x1") - 1.0 - >>> op.linear_constraints.get_coefficients([("c1", "x0"), ("c1", "x1")]) - [2.0, -1.0] + Raises: + QiskitOptimizationError: if the given linear expression has a different parent + QuadraticProgram than the constraint. """ - - def _get(args): - i, j = args - return self._lin_expr[i].get(j, 0) - - if len(args) == 0: - return copy.deepcopy(self._lin_expr) - elif len(args) == 1 and isinstance(args[0], Sequence): - i, j = zip(*args[0]) - i = self._index.convert(i) - j = self._varindex(j) - return self._getter(_get, *zip(i, j)) - elif len(args) == 2: - i, j = args - i = self._index.convert(i) - j = self._varindex(j) - return _get((i, j)) + if isinstance(linear, LinearExpression): + if linear._quadratic_program != self.quadratic_program: + raise QiskitOptimizationError("Incompatible parent quadratic program!") + self._linear = linear else: - raise QiskitOptimizationError( - "Wrong number of arguments. Please use 2 or one list of pairs: {}".format(args)) - - def get_rows(self, *args) -> Union['SparsePair', List['SparsePair']]: - """Returns a set of rows of the linear constraint matrix. - - Returns a list of SparsePair instances or a single SparsePair - instance, depending on the form by which it was called. + self._linear = LinearExpression(self.quadratic_program, linear) - There are four forms by which linear_constraints.get_rows may be called. + def evaluate(self, x: Union[ndarray, List, Dict[Union[int, str], float]]) -> float: + """Evaluate the left-hand-side of the constraint. - linear_constraints.get_rows() - return the entire linear constraint matrix. + Args: + x: The values of the variables to be evaluated. - linear_constraints.get_rows(i) - i must be a row name or index. Returns the ith row of - the linear constraint matrix. - - linear_constraints.get_rows(s) - s must be a sequence of row names or indices. Returns the - rows of the linear constraint matrix indexed by the members - of s. Equivalent to - [linear_constraints.get_rows(i) for i in s] - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = ["x1", "x2", "x3"]) - >>> indices = op.linear_constraints.add(\ - names = ["c0", "c1", "c2", "c3"],\ - lin_expr = [SparsePair(ind = ["x1", "x3"], val = [1.0, -1.0]),\ - SparsePair(ind = ["x1", "x2"], val = [1.0, 1.0]),\ - SparsePair(ind = ["x1", "x2", "x3"], val = [-1.0] * 3),\ - SparsePair(ind = ["x2", "x3"], val = [10.0, -2.0])]) - >>> op.linear_constraints.get_rows(0) - SparsePair(ind = [0, 2], val = [1.0, -1.0]) - >>> op.linear_constraints.get_rows(["c2", 0]) - [SparsePair(ind = [0, 1, 2], val = [-1.0, -1.0, -1.0]), - SparsePair(ind = [0, 2], val = [1.0, -1.0])] - >>> op.linear_constraints.get_rows() - [ - SparsePair(ind = [0, 2], val = [1.0, -1.0]), - SparsePair(ind = [0, 1], val = [1.0, 1.0]), - SparsePair(ind = [0, 1, 2], val = [-1.0, -1.0, -1.0]), - SparsePair(ind = [1, 2], val = [10.0, -2.0]) - ] - """ - - def _get(i): - keys = list(self._lin_expr[i].keys()) - keys.sort() - return SparsePair(keys, [self._lin_expr[i][k] for k in keys]) - - keys = self._index.convert(*args) - return self._getter(_get, keys) - - def get_num_nonzeros(self) -> int: - """Returns the number of nonzeros in the linear constraint matrix. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = ["x1", "x2", "x3"]) - >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2", "c3"],\ - lin_expr = [SparsePair(ind = ["x1", "x3"], val = [1.0, -1.0]),\ - SparsePair(ind = ["x1", "x2"], val = [1.0, 1.0]),\ - SparsePair(ind = ["x1", "x2", "x3"], val = [-1.0] * 3),\ - SparsePair(ind = ["x2", "x3"], val = [10.0, -2.0])]) - >>> op.linear_constraints.get_num_nonzeros() - 9 - """ - nnz = 0 - for dic in self._lin_expr: - for val in dic.values(): - if val != 0.0: - nnz += 1 - return nnz - - def get_names(self, *args) -> Union[str, List[str]]: - """Returns the names of linear constraints from the problem. - - There are four forms by which linear_constraints.get_names may be called. - - linear_constraints.get_names() - return the names of all linear constraints from the problem. - - linear_constraints.get_names(i) - i must be a linear constraint index. Returns the name of row i. - - linear_constraints.get_names(s) - s must be a sequence of row indices. Returns the names of - the linear constraints with indices the members of s. - Equivalent to [linear_constraints.get_names(i) for i in s] - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.linear_constraints.add(names = ["c" + str(i) for i in range(10)]) - >>> op.linear_constraints.get_num() - 10 - >>> op.linear_constraints.get_names(8) - 'c8' - >>> op.linear_constraints.get_names([2, 0, 5]) - ['c2', 'c0', 'c5'] - >>> op.linear_constraints.get_names() - ['c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9'] + Returns: + The left-hand-side of the constraint given the variable values. """ - - def _get(i): - return self._names[i] - - if len(args) == 0: - return self._names - keys = self._index.convert(*args) - return self._getter(_get, keys) - - def get_histogram(self): - """histogram is not supported""" - raise NotImplementedError("histogram is not supported") + return self.linear.evaluate(x) diff --git a/qiskit/optimization/problems/linear_expression.py b/qiskit/optimization/problems/linear_expression.py new file mode 100644 index 0000000000..7514253bd9 --- /dev/null +++ b/qiskit/optimization/problems/linear_expression.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019, 2020. +# +# 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. + +"""Linear expression interface.""" + + +from typing import List, Union, Dict +from numpy import ndarray +from scipy.sparse import spmatrix, dok_matrix + +from qiskit.optimization import QiskitOptimizationError +from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram + + +class LinearExpression(HasQuadraticProgram): + """ Representation of a linear expression by its coefficients.""" + + def __init__(self, quadratic_program: "QuadraticProgram", + coefficients: Union[ndarray, spmatrix, List[float], + Dict[Union[int, str], float]]) -> None: + """Creates a new linear expression. + + The linear expression can be defined via an array, a list, a sparse matrix, or a dictionary + that uses variable names or indices as keys and stores the values internally as a + dok_matrix. + + Args: + quadratic_program: The parent QuadraticProgram. + coefficients: The (sparse) representation of the coefficients. + + """ + super().__init__(quadratic_program) + self.coefficients = coefficients + + def _coeffs_to_dok_matrix(self, + coefficients: Union[ndarray, spmatrix, + List, Dict[Union[int, str], float]] + ) -> None: + """Maps given 1d-coefficients to a dok_matrix. + + Args: + coefficients: The 1d-coefficients to be mapped. + + Returns: + The given 1d-coefficients as a dok_matrix + + Raises: + QiskitOptimizationError: if coefficients are given in unsupported format. + """ + if isinstance(coefficients, list) or\ + isinstance(coefficients, ndarray) and len(coefficients.shape) == 1: + coefficients = dok_matrix([coefficients]) + elif isinstance(coefficients, spmatrix): + coefficients = dok_matrix(coefficients) + elif isinstance(coefficients, dict): + coeffs = dok_matrix((1, self.quadratic_program.get_num_vars())) + for index, value in coefficients.items(): + if isinstance(index, str): + index = self.quadratic_program.var_index[index] + coeffs[0, index] = value + coefficients = coeffs + else: + raise QiskitOptimizationError("Unsupported format for coefficients.") + return coefficients + + @property + def coefficients(self) -> dok_matrix: + """ Returns the coefficients of the linear expression. + + Returns: + The coefficients of the linear expression. + """ + return self._coefficients + + @coefficients.setter + def coefficients(self, + coefficients: Union[ndarray, spmatrix, + List[float], Dict[Union[str, int], float]] + ) -> None: + """Sets the coefficients of the linear expression. + + Args: + coefficients: The coefficients of the linear expression. + """ + self._coefficients = self._coeffs_to_dok_matrix(coefficients) + + def coefficients_as_array(self) -> ndarray: + """Returns the coefficients of the linear expression as array. + + Returns: + An array with the coefficients corresponding to the linear expression. + """ + return self._coefficients.toarray()[0] + + def coefficients_as_dict(self, use_index: bool = True) -> Dict[Union[int, str], float]: + """Returns the coefficients of the linear expression as dictionary, either using variable + names or indices as keys. + + Args: + use_index: Determines whether to use index or names to refer to variables. + + Returns: + An dictionary with the coefficients corresponding to the linear expression. + """ + if use_index: + return {k: v for (_, k), v in self._coefficients.items()} + else: + return {self.quadratic_program.variables[k].name: v for (_, k), v in + self._coefficients.items()} + + def evaluate(self, x: Union[ndarray, List, Dict[Union[int, str], float]]) -> float: + """Evaluate the linear expression for given variables. + + Args: + x: The values of the variables to be evaluated. + + Returns: + The value of the linear expression given the variable values. + """ + # cast input to dok_matrix if it is a dictionary + x = self._coeffs_to_dok_matrix(x) + + # compute the dot-product of the input and the linear coefficients + val = (x @ self.coefficients.transpose())[0, 0] + + # return the result + return val diff --git a/qiskit/optimization/problems/name_index.py b/qiskit/optimization/problems/name_index.py deleted file mode 100644 index 5c58278a03..0000000000 --- a/qiskit/optimization/problems/name_index.py +++ /dev/null @@ -1,118 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# 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. - -""" Helper Utilities """ - -from typing import Union, List, Sequence, Tuple - -from qiskit.optimization.exceptions import QiskitOptimizationError - - -class NameIndex: - """Convert a string name into an integer index. - This is used for the implementation of `BaseInterface.get_indices`. - """ - - def __init__(self): - """Initialize a dictionary of name and index""" - self._dict = {} - - def __contains__(self, name: str) -> bool: - """Check a name is registered or not. - - Args: - name: a string name - - Returns: - This returns True if the name has been registered. Otherwise it returns False. - - """ - return name in self._dict - - def build(self, names: List[str]) -> None: - """Build a dictionary from scratch. - - Args: - names: a list of names - - Raises: - QiskitOptimizationError: if any duplicate names contained in the list. - """ - self._dict = {} - for i, name in enumerate(names): - if name in self._dict: - raise QiskitOptimizationError('Duplicate name: {}'.format(name)) - self._dict[name] = i - - def _convert_one(self, item: Union[str, int]) -> int: - if isinstance(item, int): - if not 0 <= item < len(self._dict): - raise QiskitOptimizationError('Invalid index: {}'.format(item)) - return item - if not isinstance(item, str): - raise QiskitOptimizationError('Invalid arg: {}'.format(item)) - if item not in self._dict: - raise QiskitOptimizationError('No associated index of name: {}'.format(item)) - return self._dict[item] - - def convert(self, *args) -> Union[int, List[int]]: - """Convert a set of names into a set of indices. - There are three types of arguments. - - - `convert()` - returns all indices. - - - `convert(Union[str, int])` - returns an index corresponding to the argument. - If the argument is already integer, this returns the same integer value. - - - `convert(List[Union[str, int]])` - returns a list of indices - - - `convert(begin, end)` - returns a list of indices in a range starting from `begin` to `end`, - which includes both `begin` and `end`. - Note that it behaves similar to `range(begin, end+1)` - - Returns: - An index of a name or list of indices of names. - - Raises: - QiskitOptimizationError: if arguments are not valid. - """ - if len(args) == 0: - return list(self._dict.values()) - elif len(args) == 1: - a_0 = args[0] - if isinstance(a_0, (int, str)): - return self._convert_one(a_0) - elif isinstance(a_0, Sequence): - return [self._convert_one(e) for e in a_0] - else: - raise QiskitOptimizationError('Invalid argument: {}'.format(args)) - elif len(args) == 2: - begin = self._convert_one(args[0]) - end = self._convert_one(args[1]) + 1 - return list(range(begin, end)) - else: - raise QiskitOptimizationError('Invalid arguments: {}'.format(args)) - - -def init_list_args(*args) -> Tuple: - """Initialize default arguments with empty lists if necessary. - - Returns: - A tuple of arguments where `None` is replaced with `[]`. - """ - return tuple([] if a is None else a for a in args) diff --git a/qiskit/optimization/problems/objective.py b/qiskit/optimization/problems/objective.py deleted file mode 100644 index 4174bd5f0b..0000000000 --- a/qiskit/optimization/problems/objective.py +++ /dev/null @@ -1,565 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# 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. - -"""Objective function interface""" - -import copy -import numbers -from collections.abc import Sequence -from typing import Callable, List, Union, Dict, Tuple -import logging -from scipy.sparse import dok_matrix - -from qiskit.optimization.exceptions import QiskitOptimizationError -from .base import BaseInterface - -logger = logging.getLogger(__name__) - -_HAS_CPLEX = False -try: - from cplex import SparsePair - _HAS_CPLEX = True -except ImportError: - logger.info('CPLEX is not installed.') - -CPX_MAX = -1 -CPX_MIN = 1 - - -class ObjSense: - """Constants defining the sense of the objective function.""" - maximize = CPX_MAX - minimize = CPX_MIN - - def __getitem__(self, item: int) -> str: - """Converts a constant to a string. - - Returns: - Sense name. - - Raises: - QiskitOptimizationError: if the argument is not a valid number. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> op.objective.sense.minimize - 1 - >>> op.objective.sense[1] - 'minimize' - """ - if item == CPX_MAX: - return 'maximize' - if item == CPX_MIN: - return 'minimize' - raise QiskitOptimizationError("Invalid sense: {}".format(item)) - - -class ObjectiveInterface(BaseInterface): - """Contains methods for querying and modifying the objective function.""" - - sense = ObjSense() - - def __init__(self, varindex: Callable): - """Creates a new ObjectiveInterface. - - The objective function interface is exposed by the top-level - `QuadraticProgram` class as `QuadraticProgram.objective`. - This constructor is not meant to be used externally. - """ - if not _HAS_CPLEX: - raise NameError('CPLEX is not installed.') - - super(ObjectiveInterface, self).__init__() - self._linear = {} - self._quadratic = dok_matrix((0, 0)) - self._name = 'obj1' - self._sense = ObjSense.minimize - self._offset = 0.0 - self._varindex = varindex - - def set_linear(self, *args): - """Changes the linear part of the objective function. - - Can be called by two forms: - - objective.set_linear(var, value) - var must be a variable index or name and value must be a - float. Changes the coefficient of the variable identified - by var to value. - - objective.set_linear(sequence) - sequence is a sequence of pairs (var, value) as described - above. Changes the coefficients for the specified - variables to the given values. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = [str(i) for i in range(4)]) - >>> op.objective.get_linear() - [0.0, 0.0, 0.0, 0.0] - >>> op.objective.set_linear(0, 1.0) - >>> op.objective.get_linear() - [1.0, 0.0, 0.0, 0.0] - >>> op.objective.set_linear("3", -1.0) - >>> op.objective.get_linear() - [1.0, 0.0, 0.0, -1.0] - >>> op.objective.set_linear([("2", 2.0), (1, 0.5)]) - >>> op.objective.get_linear() - [1.0, 0.5, 2.0, -1.0] - """ - - def _set(i, v): - i = self._varindex(i) - if v == 0 and i in self._linear: - del self._linear[i] - else: - self._linear[i] = v - - self._setter(_set, *args) - - def set_quadratic(self, coef: Union[List[float], List['SparsePair'], List[Sequence]]): - """Sets the quadratic part of the objective function. - - Call this method with a list with length equal to the number - of variables in the problem. - - If the quadratic objective function is separable, the entries - of the list must all be of type float, int, or long. - - If the quadratic objective function is not separable, the - entries of the list must be either SparsePair instances or - lists of two lists, the first of which contains variable - indices or names, the second of which contains the values that - those variables take. - - Note - Successive calls to set_quadratic will overwrite any previous - quadratic objective function. To modify only part of the - quadratic objective function, use the method - set_quadratic_coefficients. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = [str(i) for i in range(3)]) - >>> op.objective.set_quadratic([SparsePair(ind = [0, 1, 2], val = [1.0, -2.0, 0.5]),\ - SparsePair(ind = [0, 1], val = [-2.0, -1.0]),\ - SparsePair(ind = [0, 2], val = [0.5, -3.0])]) - >>> op.objective.get_quadratic() - [SparsePair(ind = [0, 1, 2], val = [1.0, -2.0, 0.5]), - SparsePair(ind = [0, 1], val = [-2.0, -1.0]), - SparsePair(ind = [0, 2], val = [0.5, -3.0])] - >>> op.objective.set_quadratic([1.0, 2.0, 3.0]) - >>> op.objective.get_quadratic() - [SparsePair(ind = [0], val = [1.0]), SparsePair(ind = [1], val = [2.0]), - SparsePair(ind = [2], val = [3.0])] - """ - - # clear data - self._quadratic = dok_matrix((0, 0)) - - def _set(i, j, val): - if val == 0: - return - i = self._varindex(i) - j = self._varindex(j) - max_ij = max(i, j) - if max_ij >= self._quadratic.shape[0]: - self._quadratic.resize(max_ij + 1, max_ij + 1) - self._quadratic[i, j] = val - - if len(coef) == 0: - logger.warning('Empty argument for objective.set_quadratic') - elif isinstance(coef[0], numbers.Number): - for i, val in enumerate(coef): - _set(i, i, val) - else: - for i, spair in enumerate(coef): - if isinstance(spair, SparsePair): - for j, val in zip(spair.ind, spair.val): - _set(i, j, val) - elif isinstance(spair, Sequence) and len(spair) == 2: - for j, val in zip(spair[0], spair[1]): - _set(i, j, val) - else: - raise QiskitOptimizationError( - "set_quadratic expects a list of the length equal to the number of " - "variables, where each entry has a pair of the indices of the other " - "variables and values, or the corresponding SparsePair" - "{}".format(coef) - ) - - def set_quadratic_coefficients(self, *args): - """Sets coefficients of the quadratic component of the objective function. - - objective.set_quadratic_coefficients(v1, v2, val) - set a single coefficient where v1 and v2 are names or indices of variables and val is - the value for the coefficient. - - objective.set_quadratic_coefficients(sequence) - set multiple coefficients where sequence is a list or tuple of triples (v1, v2, val) as - described above. - - Note - Since the quadratic objective function must be symmetric, each - triple in which v1 is different from v2 is used to set both - the (v1, v2) coefficient and the (v2, v1) coefficient. If - (v1, v2) and (v2, v1) are set with a single call, the second - value is stored. - - Note - Attempting to set many coefficients with set_quadratic_coefficients - can be time consuming. Instead, use the method set_quadratic to set - the quadratic part of the objective efficiently. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = [str(i) for i in range(3)]) - >>> op.objective.set_quadratic_coefficients(0, 1, 1.0) - >>> op.objective.get_quadratic() - [SparsePair(ind = [1], val = [1.0]), SparsePair(ind = [0], val = [1.0]), - SparsePair(ind = [], val = [])] - >>> op.objective.set_quadratic_coefficients([(1, 1, 2.0), (0, 2, 3.0)]) - >>> op.objective.get_quadratic() - [SparsePair(ind = [1, 2], val = [1.0, 3.0]), SparsePair(ind = [0, 1], val = [1.0, 2.0]), - SparsePair(ind = [0], val = [3.0])] - >>> op.objective.set_quadratic_coefficients([(0, 1, 4.0), (1, 0, 5.0)]) - >>> op.objective.get_quadratic() - [SparsePair(ind = [1, 2], val = [5.0, 3.0]), SparsePair(ind = [0, 1], val = [5.0, 2.0]), - SparsePair(ind = [0], val = [3.0])] - """ - - def _set(i, j, val): - i = self._varindex(i) - j = self._varindex(j) - max_ij = max(i, j) - if max_ij >= self._quadratic.shape[0]: - self._quadratic.resize(max_ij + 1, max_ij + 1) - # set a value at symmetric positions - self._quadratic[i, j] = val - self._quadratic[j, i] = val - - if len(args) == 1: - if not isinstance(args[0], Sequence): - raise QiskitOptimizationError("Wrong number of arguments: {}".format(args)) - arg_list = args[0] - elif len(args) == 3: - arg_list = [args] - else: - raise QiskitOptimizationError("Wrong number of arguments: {}".format(args)) - for i, j, val in arg_list: - _set(i, j, val) - - def set_sense(self, sense: int): - """Sets the sense of the objective function. - - The argument to this method must be either - objective.sense.minimize or objective.sense.maximize. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> op.objective.sense[op.objective.get_sense()] - 'minimize' - >>> op.objective.set_sense(op.objective.sense.maximize) - >>> op.objective.sense[op.objective.get_sense()] - 'maximize' - >>> op.objective.set_sense(op.objective.sense.minimize) - >>> op.objective.sense[op.objective.get_sense()] - 'minimize' - """ - if sense in [CPX_MAX, CPX_MIN]: - self._sense = sense - else: - raise QiskitOptimizationError( - "sense should be one of [CPX_MAX, CPX_MIN], i.e., objective.sense.minimize or " + - "objective.sense.maximize.") - - def set_name(self, name: str): - """Sets the name of the objective function. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> op.objective.set_name("cost") - >>> op.objective.get_name() - 'cost' - """ - self._name = name - - def get_linear(self, *args) -> Union[float, List[float]]: - """Returns the linear coefficients of a set of variables. - - Can be called by four forms. - - objective.get_linear() - return the linear objective coefficients of all variables - from the problem. - - objective.get_linear(i) - i must be a variable name or index. Returns the linear - objective coefficient of the variable whose index or name - is i. - - objective.get_linear(s) - s must be a sequence of variable names or indices. Returns - the linear objective coefficient of the variables with - indices the members of s. Equivalent to - [objective.get_linear(i) for i in s] - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(obj = [1.5 * i for i in range(10)],\ - names = [str(i) for i in range(10)]) - >>> op.variables.get_num() - 10 - >>> op.objective.get_linear(8) - 12.0 - >>> op.objective.get_linear([2,"0",5]) - [3.0, 0.0, 7.5] - >>> op.objective.get_linear() - [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] - """ - - def _get(i): - return self._linear.get(i, 0.0) - - keys = self._varindex(*args) - return self._getter(_get, keys) - - def get_linear_dict(self) -> Dict[int, float]: - """Return the linear coefficients of a set of variables in a dictionary form. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> op.variables.add(names=['x', 'y']) - >>> op.objective.set_linear([('x', 1), ('y', 2)]) - >>> print(op.objective.get_linear_dict()) - {0: 1, 1: 2} - """ - return copy.deepcopy(self._linear) - - def get_quadratic(self, *args) -> Union['SparsePair', List['SparsePair']]: - """Returns a set of columns of the quadratic component of the objective function. - - Returns a SparsePair instance or a list of SparsePair instances. - - Can be called by four forms. - - objective.get_quadratic() - return the entire quadratic objective function. - - objective.get_quadratic(i) - i must be a variable name or index. Returns the column of - the quadratic objective function associated with the - variable whose index or name is i. - - objective.get_quadratic(s) - s must be a sequence of variable names or indices. Returns - the columns of the quadratic objective function associated - with the variables with indices the members of s. - Equivalent to [objective.get_quadratic(i) for i in s] - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = [str(i) for i in range(10)]) - >>> op.variables.get_num() - 10 - >>> op.objective.set_quadratic([1.5 * i for i in range(10)]) - >>> op.objective.get_quadratic(8) - SparsePair(ind = [8], val = [12.0]) - >>> for q in c.objective.get_quadratic("1", 3): - ... print(q) - SparsePair(ind = [1], val = [1.5]) - SparsePair(ind = [2], val = [3.0]) - SparsePair(ind = [3], val = [4.5]) - >>> op.objective.get_quadratic([3,"1",5]) - [SparsePair(ind = [3], val = [4.5]), SparsePair(ind = [1], val = [1.5]), - SparsePair(ind = [5], val = [7.5])] - >>> op.objective.get_quadratic() - [SparsePair(ind = [], val = []), SparsePair(ind = [1], val = [1.5]), - SparsePair(ind = [2], val = [3.0]), SparsePair(ind = [3], val = [4.5]), - SparsePair(ind = [4], val = [6.0]), SparsePair(ind = [5], val = [7.5]), - SparsePair(ind = [6], val = [9.0]), SparsePair(ind = [7], val = [10.5]), - SparsePair(ind = [8], val = [12.0]), SparsePair(ind = [9], val = [13.5])] - """ - - def _get(i): - row = self._quadratic[i] if i < self._quadratic.shape[0] else {} - return SparsePair([j for _, j in row.keys()], list(row.values())) - - keys = self._varindex(*args) - return self._getter(_get, keys) - - def get_quadratic_dict(self) -> Dict[Tuple[int, int], float]: - """Return the linear coefficients of a set of variables in a dictionary form. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> op.variables.add(names=['x', 'y']) - >>> op.objective.set_quadratic_coefficients([('x', 'x', 1), ('x', 'y', 2)]) - >>> print(op.objective.get_quadratic_dict()) - {(0, 0): 1, (0, 1): 2, (1, 0): 2} - """ - return dict(self._quadratic.items()) - - def get_quadratic_coefficients(self, *args) -> Union[float, List[float]]: - """Returns individual coefficients from the quadratic objective function. - - objective.get_quadratic_coefficients(v1, v2) - query a single coefficient where v1 and v2 are indices or names of variables. - - objective.get_quadratic_coefficients(sequence) - query multiple coefficients where sequence is a list or tuple of pairs (v1, v2) as - described above. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = [str(i) for i in range(3)]) - >>> op.objective.set_quadratic_coefficients(0, 1, 1.0) - >>> op.objective.get_quadratic_coefficients("1", 0) - 1.0 - >>> op.objective.set_quadratic_coefficients([(1, 1, 2.0), (0, 2, 3.0), (1, 0, 5.0)]) - >>> op.objective.get_quadratic_coefficients([(1, 0), (1, "1"), (2, "0")]) - [5.0, 2.0, 3.0] - """ - - def _get(pair): - i, j = pair - max_ij = max(i, j) - if max_ij >= self._quadratic.shape[0]: - return 0 - return self._quadratic.get((i, j), 0) - - if len(args) == 0: - raise QiskitOptimizationError('Wrong number of arguments') - if len(args) == 1 and isinstance(args[0], Sequence): - i, j = zip(*args[0]) - i = self._varindex(i) - j = self._varindex(j) - return self._getter(_get, *zip(i, j)) - elif len(args) == 2: - i, j = args - i = self._varindex(i) - j = self._varindex(j) - return _get((i, j)) - else: - raise QiskitOptimizationError('Invalid arguments {}'.format(args)) - - def get_sense(self) -> int: - """Returns the sense of the objective function. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> op.objective.sense[op.objective.get_sense()] - 'minimize' - >>> op.objective.set_sense(op.objective.sense.maximize) - >>> op.objective.sense[op.objective.get_sense()] - 'maximize' - >>> op.objective.set_sense(op.objective.sense.minimize) - >>> op.objective.sense[op.objective.get_sense()] - 'minimize' - """ - return self._sense - - def get_name(self) -> str: - """Returns the name of the objective function. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> op.objective.set_name("cost") - >>> op.objective.get_name() - 'cost' - """ - return self._name - - def get_num_quadratic_variables(self) -> int: - """Returns the number of variables with quadratic coefficients. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = [str(i) for i in range(3)]) - >>> op.objective.set_quadratic_coefficients(0, 1, 1.0) - >>> op.objective.get_num_quadratic_variables() - 2 - >>> op.objective.set_quadratic([1.0, 0.0, 0.0]) - >>> op.objective.get_num_quadratic_variables() - 1 - >>> op.objective.set_quadratic_coefficients([(1, 1, 2.0), (0, 2, 3.0)]) - >>> op.objective.get_num_quadratic_variables() - 3 - """ - num = 0 - for i in range(self._quadratic.shape[0]): - if self._quadratic[i].nnz > 0: - num += 1 - return num - - def get_num_quadratic_nonzeros(self) -> int: - """Returns the number of nonzeros in the quadratic objective function. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = [str(i) for i in range(3)]) - >>> op.objective.set_quadratic_coefficients(0, 1, 1.0) - >>> op.objective.get_num_quadratic_nonzeros() - 2 - >>> op.objective.set_quadratic_coefficients([(1, 1, 2.0), (0, 2, 3.0)]) - >>> op.objective.get_num_quadratic_nonzeros() - 5 - >>> op.objective.set_quadratic_coefficients([(0, 1, 4.0), (1, 0, 0.0)]) - >>> op.objective.get_num_quadratic_nonzeros() - 3 - """ - return self._quadratic.nnz - - def get_offset(self) -> float: - """Returns the constant offset of the objective function for a problem. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> offset = op.objective.get_offset() - >>> abs(offset - 0.0) < 1e-6 - True - """ - return self._offset - - def set_offset(self, offset: float): - """Sets the constant offset of the objective function for a problem. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> op.objective.set_offset(3.14) - >>> offset = op.objective.get_offset() - >>> abs(offset - 3.14) < 1e-6 - True - """ - self._offset = offset diff --git a/qiskit/optimization/problems/problem_type.py b/qiskit/optimization/problems/problem_type.py deleted file mode 100644 index a12855b9d8..0000000000 --- a/qiskit/optimization/problems/problem_type.py +++ /dev/null @@ -1,97 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# 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. - -""" Types of problems """ - -from qiskit.optimization import QiskitOptimizationError - -# pylint: disable=invalid-name - -CPXPROB_LP = 0 -CPXPROB_MILP = 1 -CPXPROB_FIXEDMILP = 3 -CPXPROB_NODELP = 4 -CPXPROB_QP = 5 -CPXPROB_MIQP = 7 -CPXPROB_FIXEDMIQP = 8 -CPXPROB_NODEQP = 9 -CPXPROB_QCP = 10 -CPXPROB_MIQCP = 11 -CPXPROB_NODEQCP = 12 - - -class ProblemType: - """ - Types of problems the QuadraticProgram class can encapsulate. - These types are compatible with those of IBM ILOG CPLEX. - For explanations of the problem types, see those topics in the - CPLEX User's Manual in the topic titled Continuous Optimization - for LP, QP, and QCP or the topic titled Discrete Optimization - for MILP, FIXEDMILP, NODELP, NODEQP, MIQCP, NODEQCP. - """ - # pylint: disable=invalid-name - LP = CPXPROB_LP - MILP = CPXPROB_MILP - fixed_MILP = CPXPROB_FIXEDMILP - node_LP = CPXPROB_NODELP - QP = CPXPROB_QP - MIQP = CPXPROB_MIQP - fixed_MIQP = CPXPROB_FIXEDMIQP - node_QP = CPXPROB_NODEQP - QCP = CPXPROB_QCP - MIQCP = CPXPROB_MIQCP - node_QCP = CPXPROB_NODEQCP - - def __getitem__(self, item: int) -> str: - """Converts a constant to a string. - - Returns: - Problem type name. - - Raises: - QiskitOptimizationError: if the argument is not valid. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> op.problem_type.LP - 0 - >>> op.problem_type[0] - 'LP' - """ - # pylint: disable=too-many-return-statements - if item == CPXPROB_LP: - return 'LP' - if item == CPXPROB_MILP: - return 'MILP' - if item == CPXPROB_FIXEDMILP: - return 'fixed_MILP' - if item == CPXPROB_NODELP: - return 'node_LP' - if item == CPXPROB_QP: - return 'QP' - if item == CPXPROB_MIQP: - return 'MIQP' - if item == CPXPROB_FIXEDMIQP: - return 'fixed_MIQP' - if item == CPXPROB_NODEQP: - return 'node_QP' - if item == CPXPROB_QCP: - return 'QCP' - if item == CPXPROB_MIQCP: - return 'MIQCP' - if item == CPXPROB_NODEQCP: - return 'node_QCP' - raise QiskitOptimizationError('Invalid problem type: {}'.format(item)) diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index 4cc8611ed2..397dde9745 100644 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -1,669 +1,4 @@ -# -*- coding: utf-8 -*- +class QuadraticConstraint: -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# 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. - -"""Quadratic constraints interface""" - -import copy -from collections.abc import Sequence -import logging -from typing import List, Dict, Callable, Union, Optional - -from scipy.sparse import dok_matrix - -from qiskit.optimization.problems.name_index import NameIndex -from qiskit.optimization.exceptions import QiskitOptimizationError - -from .base import BaseInterface - -logger = logging.getLogger(__name__) - -_HAS_CPLEX = False -try: - from cplex import SparsePair, SparseTriple - _HAS_CPLEX = True -except ImportError: - logger.info('CPLEX is not installed.') - - -class QuadraticConstraintInterface(BaseInterface): - """Methods for adding, modifying, and querying quadratic constraints.""" - - def __init__(self, varindex: Callable): - """Creates a new QuadraticConstraintInterface. - - The quadratic constraints interface is exposed by the top-level - `QuadraticProgram` class as `QuadraticProgram.quadratic_constraints`. - This constructor is not meant to be used externally. - """ - if not _HAS_CPLEX: - raise NameError('CPLEX is not installed.') - - super().__init__() - self._rhs = [] - self._senses = [] - self._names = [] - self._lin_expr: List[Dict[int, float]] = [] - self._quad_expr: List[dok_matrix] = [] - self._index = NameIndex() - self._varindex = varindex - - def get_num(self) -> int: - """Returns the number of quadratic constraints. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = ['x','y']) - >>> l = SparsePair(ind = ['x'], val = [1.0]) - >>> q = SparseTriple(ind1 = ['x'], ind2 = ['y'], val = [1.0]) - >>> [op.quadratic_constraints.add(name=str(i), lin_expr=l, quad_expr=q) - ... for i in range(10)] - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - >>> op.quadratic_constraints.get_num() - 10 - """ - return len(self._names) - - def add(self, - lin_expr: Optional['SparsePair'] = None, - quad_expr: Optional['SparseTriple'] = None, - sense: str = "L", rhs: float = 0.0, name: str = "") -> int: - """Adds a quadratic constraint to the problem. - - Takes up to following five keyword arguments. - - Args: - lin_expr: either a SparsePair or a list of two lists specifying - the linear component of the constraint. - - Note - lin_expr must not contain duplicate indices. If lin_expr - references a variable more than once, either by index, name, - or a combination of index and name, an exception will be - raised. - - quad_expr: either a SparseTriple or a list of three lists - specifying the quadratic component of the constraint. - - Note - quad_expr must not contain duplicate indices. If quad_expr - references a matrix entry more than once, either by indices, - names, or a combination of indices and names, an exception - will be raised. - - sense: either "L", "G", or "E" - - rhs: a float specifying the righthand side of the constraint. - - name: the name of the constraint. - - Returns: - The index of the added quadratic constraint. - - Raises: - QiskitOptimizationError: if arguments are not valid. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = ['x','y']) - >>> l = SparsePair(ind = ['x'], val = [1.0]) - >>> q = SparseTriple(ind1 = ['x'], ind2 = ['y'], val = [1.0]) - >>> op.quadratic_constraints.add(name = "my_quad", - ... lin_expr = l, - ... quad_expr = q, - ... rhs = 1.0, - ... sense = "G") - 0 - """ - # We only ever create one quadratic constraint at a time. - - # check constraint name - if name == '': - name = 'q{}'.format(1 + self.get_num()) - if name in self._index: - raise QiskitOptimizationError('Duplicate quadratic constraint name: {}'.format(name)) - self._names.append(name) - self._index.build(self._names) - - # linear terms - lin_expr_dict = {} - if lin_expr is None: - ind, val = [], [] - elif isinstance(lin_expr, SparsePair): - ind, val = lin_expr.ind, lin_expr.val - elif isinstance(lin_expr, Sequence): - if len(lin_expr) != 2 or len(lin_expr[0]) != len(lin_expr[1]): - raise QiskitOptimizationError('Invalid lin_expr: {}'.format(lin_expr)) - ind, val = lin_expr - else: - raise QiskitOptimizationError('Invalid lin_expr: {}'.format(lin_expr)) - for i, val in zip(ind, val): - i_2 = self._varindex(i) - if i_2 in lin_expr_dict: - raise QiskitOptimizationError('lin_expr contains duplicate index: {}'.format(i)) - lin_expr_dict[i_2] = val - self._lin_expr.append(lin_expr_dict) - - # quadratic terms - quad_matrix = dok_matrix((0, 0)) - if quad_expr is None: - ind1, ind2, val = [], [], [] - elif isinstance(quad_expr, SparseTriple): - ind1, ind2, val = quad_expr.ind1, quad_expr.ind2, quad_expr.val - elif isinstance(quad_expr, Sequence): - if len(quad_expr) != 3 or len(quad_expr[0]) != len(quad_expr[1]) or \ - len(quad_expr[1]) != len(quad_expr[2]): - raise QiskitOptimizationError('Invalid quad_expr: {}'.format(quad_expr)) - ind1, ind2, val = quad_expr - else: - raise QiskitOptimizationError('Invalid quad_expr: {}'.format(quad_expr)) - for i, j, val in zip(ind1, ind2, val): - i_2 = self._varindex(i) - j_2 = self._varindex(j) - if i_2 < j_2: - # to reproduce CPLEX's behavior, swap i_2 and j_2 so that i_2 >= j_2 - i_2, j_2 = j_2, i_2 - if (i_2, j_2) in quad_matrix: - raise QiskitOptimizationError( - 'quad_expr contains duplicate index: {} {}'.format(i, j)) - max_ij = max(i_2, j_2) - if max_ij >= quad_matrix.shape[0]: - quad_matrix.resize(max_ij + 1, max_ij + 1) - quad_matrix[i_2, j_2] = val - self._quad_expr.append(quad_matrix) - - if sense not in ['L', 'G', 'E']: - raise QiskitOptimizationError('Invalid sense: {}'.format(sense)) - self._senses.append(sense) - self._rhs.append(rhs) - - return self._index.convert(name) - - def delete(self, *args): - """Deletes quadratic constraints from the problem. - - There are four forms by which quadratic_constraints.delete may be - called. - - quadratic_constraints.delete() - deletes all quadratic constraints from the problem. - - quadratic_constraints.delete(i) - i must be a quadratic constraint name or index. Deletes - the quadratic constraint whose index or name is i. - - quadratic_constraints.delete(s) - s must be a sequence of quadratic constraint names or - indices. Deletes the quadratic constraints with names or - indices contained within s. Equivalent to - [quadratic_constraints.delete(i) for i in s]. - - quadratic_constraints.delete(begin, end) - begin and end must be quadratic constraint indices or quadratic - constraint names. Deletes the quadratic constraints with - indices between begin and end, inclusive of end. Equivalent to - quadratic_constraints.delete(range(begin, end + 1)). This will - give the best performance when deleting batches of quadratic - constraints. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names=['x', 'y']) - >>> l = SparsePair(ind=['x'], val=[1.0]) - >>> q = SparseTriple(ind1=['x'], ind2=['y'], val=[1.0]) - >>> [op.quadratic_constraints.add( - ... name=str(i), lin_expr=l, quad_expr=q) - ... for i in range(10)] - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - >>> op.quadratic_constraints.get_num() - 10 - >>> op.quadratic_constraints.delete(8) - >>> op.quadratic_constraints.get_names() - ['0', '1', '2', '3', '4', '5', '6', '7', '9'] - >>> op.quadratic_constraints.delete("1", 3) - >>> op.quadratic_constraints.get_names() - ['0', '4', '5', '6', '7', '9'] - >>> op.quadratic_constraints.delete([2, "0", 5]) - >>> op.quadratic_constraints.get_names() - ['4', '6', '7'] - >>> op.quadratic_constraints.delete() - >>> op.quadratic_constraints.get_names() - [] - """ - if len(args) == 0: - # delete all - self._rhs = [] - self._senses = [] - self._names = [] - self._lin_expr = [] - self._quad_expr = [] - self._index = NameIndex() - return - - keys = self._index.convert(*args) - if isinstance(keys, int): - keys = [keys] - for i in sorted(keys, reverse=True): - del self._rhs[i] - del self._senses[i] - del self._names[i] - del self._lin_expr[i] - del self._quad_expr[i] - self._index.build(self._names) - - def get_rhs(self, *args) -> Union[float, List[float]]: - """Returns the righthand side of a set of quadratic constraints. - - Can be called by four forms. - - quadratic_constraints.get_rhs() - return the righthand side of all quadratic constraints - from the problem. - - quadratic_constraints.get_rhs(i) - i must be a quadratic constraint name or index. Returns the - righthand side of the quadratic constraint whose index or - name is i. - - quadratic_constraints.get_rhs(s) - s must be a sequence of quadratic constraint names or - indices. Returns the righthand side of the quadratic - constraints with indices the members of s. Equivalent to - [quadratic_constraints.get_rhs(i) for i in s] - - quadratic_constraints.get_rhs(begin, end) - begin and end must be quadratic constraint indices or quadratic - constraint names. Returns the righthand side of the quadratic - constraints with indices between begin and end, inclusive of - end. Equivalent to - quadratic_constraints.get_rhs(range(begin, end + 1)). - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = [str(i) for i in range(10)]) - >>> [op.quadratic_constraints.add(rhs=1.5 * i, name=str(i)) - ... for i in range(10)] - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - >>> op.quadratic_constraints.get_num() - 10 - >>> op.quadratic_constraints.get_rhs(8) - 12.0 - >>> op.quadratic_constraints.get_rhs("1",3) - [1.5, 3.0, 4.5] - >>> op.quadratic_constraints.get_rhs([2,"0",5]) - [3.0, 0.0, 7.5] - >>> op.quadratic_constraints.get_rhs() - [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] - """ - - def _get(i): - return self._rhs[i] - - if len(args) == 0: - return copy.deepcopy(self._rhs) - keys = self._index.convert(*args) - return self._getter(_get, keys) - - def get_senses(self, *args) -> Union[str, List[str]]: - """Returns the senses of a set of quadratic constraints. - - Can be called by four forms. - - quadratic_constraints.get_senses() - return the senses of all quadratic constraints from the - problem. - - quadratic_constraints.get_senses(i) - i must be a quadratic constraint name or index. Returns the - sense of the quadratic constraint whose index or name is i. - - quadratic_constraints.get_senses(s) - s must be a sequence of quadratic constraint names or - indices. Returns the senses of the quadratic constraints - with indices the members of s. Equivalent to - [quadratic_constraints.get_senses(i) for i in s] - - quadratic_constraints.get_senses(begin, end) - begin and end must be quadratic constraint indices or quadratic - constraint names. Returns the senses of the quadratic - constraints with indices between begin and end, inclusive of - end. Equivalent to - quadratic_constraints.get_senses(range(begin, end + 1)). - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = ["x0"]) - >>> [op.quadratic_constraints.add(name=str(i), sense=j) - ... for i, j in enumerate("GGLL")] - [0, 1, 2, 3] - >>> op.quadratic_constraints.get_num() - 4 - >>> op.quadratic_constraints.get_senses(1) - 'G' - >>> op.quadratic_constraints.get_senses("1",3) - ['G', 'L', 'L'] - >>> op.quadratic_constraints.get_senses([2,"0",1]) - ['L', 'G', 'G'] - >>> op.quadratic_constraints.get_senses() - ['G', 'G', 'L', 'L'] - """ - - def _get(i): - return self._senses[i] - - if len(args) == 0: - return copy.deepcopy(self._senses) - keys = self._index.convert(*args) - return self._getter(_get, keys) - - def get_linear_num_nonzeros(self, *args) -> Union[int, List[int]]: - """Returns the number of nonzeros in the linear part of a set of quadratic constraints. - - Can be called by four forms. - - quadratic_constraints.get_linear_num_nonzeros() - return the number of nonzeros in all quadratic constraints - from the problem. - - quadratic_constraints.get_linear_num_nonzeros(i) - i must be a quadratic constraint name or index. Returns the - number of nonzeros in the quadratic constraint whose index - or name is i. - - quadratic_constraints.get_linear_num_nonzeros(s) - s must be a sequence of quadratic constraint names or - indices. Returns the number of nonzeros in the quadratic - constraints with indices the members of s. Equivalent to - [quadratic_constraints.get_linear_num_nonzeros(i) for i in s] - - quadratic_constraints.get_linear_num_nonzeros(begin, end) - begin and end must be quadratic constraint indices or quadratic - constraint names. Returns the number of nonzeros in the - quadratic constraints with indices between begin and end, - inclusive of end. Equivalent to - quadratic_constraints.get_linear_num_nonzeros(range(begin, end + 1)). - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = [str(i) for i in range(11)], types = "B" * 11) - >>> [op.quadratic_constraints.add( - ... name = str(i), - ... lin_expr = [range(i), [1.0 * (j+1.0) for j in range(i)]]) - ... for i in range(10)] - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - >>> op.quadratic_constraints.get_num() - 10 - >>> op.quadratic_constraints.get_linear_num_nonzeros(8) - 8 - >>> op.quadratic_constraints.get_linear_num_nonzeros("1",3) - [1, 2, 3] - >>> op.quadratic_constraints.get_linear_num_nonzeros([2,"0",5]) - [2, 0, 5] - >>> op.quadratic_constraints.get_linear_num_nonzeros() - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - """ - - def _nonzero(i) -> int: - tab = self._lin_expr[i] - return len([0 for v in tab.values() if v != 0.0]) - - keys = self._index.convert(*args) - return self._getter(_nonzero, keys) - - def get_linear_components(self, *args) -> Union['SparsePair', List['SparsePair']]: - """Returns the linear part of a set of quadratic constraints. - - Returns a list of SparsePair instances or one SparsePair instance. - - Can be called by four forms. - - quadratic_constraints.get_linear_components() - return the linear components of all quadratic constraints - from the problem. - - quadratic_constraints.get_linear_components(i) - i must be a quadratic constraint name or index. Returns the - linear component of the quadratic constraint whose index or - name is i. - - quadratic_constraints.get_linear_components(s) - s must be a sequence of quadratic constraint names or - indices. Returns the linear components of the quadratic - constraints with indices the members of s. Equivalent to - [quadratic_constraints.get_linear_components(i) for i in s] - - quadratic_constraints.get_linear_components(begin, end) - begin and end must be quadratic constraint indices or quadratic - constraint names. Returns the linear components of the - quadratic constraints with indices between begin and end, - inclusive of end. Equivalent to - quadratic_constraints.get_linear_components(range(begin, end + 1)). - - Examples: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add( - ... names=[str(i) for i in range(4)], - ... types="B" * 4 - ... ) - >>> [op.quadratic_constraints.add( - ... name=str(i), - ... lin_expr=[range(i), [1.0 * (j+1.0) for j in range(i)]]) - ... for i in range(3)] - [0, 1, 2] - >>> op.quadratic_constraints.get_num() - 3 - >>> op.quadratic_constraints.get_linear_components(2) - SparsePair(ind = [0, 1], val = [1.0, 2.0]) - >>> for row in op.quadratic_constraints.get_linear_components("0", 1): - ... print(row) - SparsePair(ind = [], val = []) - SparsePair(ind = [0], val = [1.0]) - >>> for row in op.quadratic_constraints.get_linear_components([1, "0"]): - ... print(row) - SparsePair(ind = [0], val = [1.0]) - SparsePair(ind = [], val = []) - >>> for row in op.quadratic_constraints.get_linear_components(): - ... print(row) - SparsePair(ind = [], val = []) - SparsePair(ind = [0], val = [1.0]) - SparsePair(ind = [0, 1], val = [1.0, 2.0]) - """ - - def _linear_component(i) -> SparsePair: - tab = self._lin_expr[i] - return SparsePair(ind=list(tab.keys()), val=list(tab.values())) - - keys = self._index.convert(*args) - return self._getter(_linear_component, keys) - - def get_quad_num_nonzeros(self, *args) -> Union[int, List[int]]: - """Returns the number of nonzeros in the quadratic part of a set of quadratic constraints. - - Can be called by four forms. - - quadratic_constraints.get_quad_num_nonzeros() - Returns the number of nonzeros in all quadratic constraints - from the problem. - - quadratic_constraints.get_quad_num_nonzeros(i) - i must be a quadratic constraint name or index. Returns the - number of nonzeros in the quadratic constraint whose index - or name is i. - - quadratic_constraints.get_quad_num_nonzeros(s) - s must be a sequence of quadratic constraint names or - indices. Returns the number of nonzeros in the quadratic - constraints with indices the members of s. Equivalent to - [quadratic_constraints.get_quad_num_nonzeros(i) for i in s] - - quadratic_constraints.get_quad_num_nonzeros(begin, end) - begin and end must be quadratic constraint indices or quadratic - constraint names. Returns the number of nonzeros in the - quadratic constraints with indices between begin and end, - inclusive of end. Equivalent to - quadratic_constraints.get_quad_num_nonzeros(range(begin, end + 1)). - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = [str(i) for i in range(11)]) - >>> [op.quadratic_constraints.add( - ... name = str(i), - ... quad_expr = [range(i), range(i), [1.0 * (j+1.0) for j in range(i)]]) - ... for i in range(1, 11)] - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - >>> op.quadratic_constraints.get_num() - 10 - >>> op.quadratic_constraints.get_quad_num_nonzeros(8) - 9 - >>> op.quadratic_constraints.get_quad_num_nonzeros("1",2) - [1, 2, 3] - >>> op.quadratic_constraints.get_quad_num_nonzeros([2,"1",5]) - [3, 1, 6] - >>> op.quadratic_constraints.get_quad_num_nonzeros() - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - """ - - def _nonzero(i) -> int: - return self._quad_expr[i].nnz - - keys = self._index.convert(*args) - return self._getter(_nonzero, keys) - - def get_quadratic_components(self, *args) -> Union['SparseTriple', List['SparseTriple']]: - """Returns the quadratic part of a set of quadratic constraints. - - Can be called by four forms. - - quadratic_constraints.get_quadratic_components() - return the quadratic components of all quadratic constraints - from the problem. - - quadratic_constraints.get_quadratic_components(i) - i must be a quadratic constraint name or index. Returns the - quadratic component of the quadratic constraint whose index or - name is i. - - quadratic_constraints.get_quadratic_components(s) - s must be a sequence of quadratic constraint names or - indices. Returns the quadratic components of the quadratic - constraints with indices the members of s. Equivalent to - [quadratic_constraints.get_quadratic_components(i) for i in s] - - quadratic_constraints.get_quadratic_components(begin, end) - begin and end must be quadratic constraint indices or quadratic - constraint names. Returns the quadratic components of the - quadratic constraints with indices between begin and end, - inclusive of end. Equivalent to - quadratic_constraints.get_quadratic_components(range(begin, end + 1)). - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add( - ... names=[str(i) for i in range(4)] - ... ) - >>> [op.quadratic_constraints.add( - ... name="q{0}".format(i), - ... quad_expr=[range(i), range(i), - ... [1.0 * (j+1.0) for j in range(i)]]) - ... for i in range(1, 3)] - [0, 1] - >>> op.quadratic_constraints.get_num() - 2 - >>> op.quadratic_constraints.get_quadratic_components(1) - SparseTriple(ind1 = [0, 1], ind2 = [0, 1], val = [1.0, 2.0]) - >>> for quad in op.quadratic_constraints.get_quadratic_components("q1", 1): - ... print(quad) - SparseTriple(ind1 = [0], ind2 = [0], val = [1.0]) - SparseTriple(ind1 = [0, 1], ind2 = [0, 1], val = [1.0, 2.0]) - >>> for quad in op.quadratic_constraints.get_quadratic_components(["q2", 0]): - ... print(quad) - SparseTriple(ind1 = [0, 1], ind2 = [0, 1], val = [1.0, 2.0]) - SparseTriple(ind1 = [0], ind2 = [0], val = [1.0]) - >>> for quad in op.quadratic_constraints.get_quadratic_components(): - ... print(quad) - SparseTriple(ind1 = [0], ind2 = [0], val = [1.0]) - SparseTriple(ind1 = [0, 1], ind2 = [0, 1], val = [1.0, 2.0]) - """ - - def _quadratic_component(k) -> SparseTriple: - ind1 = [] - ind2 = [] - val = [] - mat = self._quad_expr[k] - for (i, j), v in mat.items(): - ind1.append(i) - ind2.append(j) - val.append(v) - return SparseTriple(ind1=ind1, ind2=ind2, val=val) - - keys = self._index.convert(*args) - return self._getter(_quadratic_component, keys) - - def get_names(self, *args) -> Union[str, List[str]]: - """Returns the names of a set of quadratic constraints. - - Can be called by four forms. - - quadratic_constraints.get_names() - return the names of all quadratic constraints from the - problem. - - quadratic_constraints.get_names(i) - i must be a quadratic constraint index. Returns the name - of constraint i. - - quadratic_constraints.get_names(s) - s must be a sequence of quadratic constraint indices. - Returns the names of the quadratic constraints with indices - the members of s. Equivalent to - [quadratic_constraints.get_names(i) for i in s] - - quadratic_constraints.get_names(begin, end) - begin and end must be quadratic constraint indices. Returns - the names of the quadratic constraints with indices between - begin and end, inclusive of end. Equivalent to - quadratic_constraints.get_names(range(begin, end + 1)). - - >>> from qiskit.optimization.problems import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = [str(i) for i in range(11)]) - >>> [op.quadratic_constraints.add( - ... name = "q" + str(i), - ... quad_expr = [range(i), range(i), [1.0 * (j+1.0) for j in range(i)]]) - ... for i in range(1, 11)] - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - >>> op.quadratic_constraints.get_num() - 10 - >>> op.quadratic_constraints.get_names(8) - 'q9' - >>> op.quadratic_constraints.get_names(1, 3) - ['q2', 'q3', 'q4'] - >>> op.quadratic_constraints.get_names([2, 0, 5]) - ['q3', 'q1', 'q6'] - >>> op.quadratic_constraints.get_names() - ['q1', 'q2', 'q3', 'q4', 'q5', 'q6', 'q7', 'q8', 'q9', 'q10'] - """ - - def _get(i): - return self._names[i] - - if len(args) == 0: - return self._names - keys = self._index.convert(*args) - return self._getter(_get, keys) + def __init__(self): + pass diff --git a/qiskit/optimization/problems/quadratic_expression.py b/qiskit/optimization/problems/quadratic_expression.py new file mode 100644 index 0000000000..c1d91ac72b --- /dev/null +++ b/qiskit/optimization/problems/quadratic_expression.py @@ -0,0 +1,4 @@ +class QuadraticExpression: + + def __init__(self): + pass diff --git a/qiskit/optimization/problems/quadratic_objective.py b/qiskit/optimization/problems/quadratic_objective.py new file mode 100644 index 0000000000..68c0e12feb --- /dev/null +++ b/qiskit/optimization/problems/quadratic_objective.py @@ -0,0 +1,12 @@ +from enum import Enum + + +class ObjSense(Enum): + minimize = 1 + maximize = -1 + + +class QuadraticObjective: + + def __init__(self): + pass diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 653fd75162..c4f5869cda 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -12,822 +12,340 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Mixed integer quadratically constrained quadratic program""" +"""Quadratic Program.""" -from enum import Enum -from math import fsum -from typing import Optional, Tuple -import logging +from typing import List, Union, Dict, Optional +from numpy import ndarray +from scipy.sparse import spmatrix -from docplex.mp.model import Model as DocplexModel - -from qiskit.optimization.problems.linear_constraint import LinearConstraintInterface -from qiskit.optimization.problems.objective import ObjectiveInterface -from qiskit.optimization.problems.problem_type import ProblemType -from qiskit.optimization.problems.quadratic_constraint import QuadraticConstraintInterface -from qiskit.optimization.problems.variables import VariablesInterface -from qiskit.optimization.exceptions import QiskitOptimizationError - -logger = logging.getLogger(__name__) - -_HAS_CPLEX = False -try: - from cplex import Cplex, SparsePair - from cplex import SparseTriple, infinity - from cplex.exceptions import CplexSolverError - _HAS_CPLEX = True -except ImportError: - logger.info('CPLEX is not installed.') +from qiskit.optimization import infinity, QiskitOptimizationError +from qiskit.optimization.problems.variable import Variable, VarType +from qiskit.optimization.problems.constraint import ConstraintSense +from qiskit.optimization.problems.linear_constraint import LinearConstraint class QuadraticProgram: - """A class encapsulating an optimization problem, modeled after Python CPLEX API. - - An instance of the QuadraticProgram class provides methods for creating, - modifying, and querying an optimization problem, solving it, and - querying aspects of the solution. + """Representation of a Quadratically Constrained Quadratic Program supporting inequality and + equality constraints as well as continuous, binary, and integer variables. """ - def __init__(self, file_name: Optional[str] = None): - """Constructor of the QuadraticProgram class. - - The QuadraticProgram constructor accepts four types of argument lists. + def __init__(self, name: str = '') -> None: + """Constructs a quadratic program. Args: - file_name: read a model from a file. - - Raises: - QiskitOptimizationError: if it cannot load a file. - NameError: CPLEX is not installed. + name: The name of the quadratic program. + """ + self._name = name - Examples: + self._variables: List[Variable] = [] + self._variables_index: Dict[str, int] = {} - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - op is a new problem with no data + self._linear_constraints: List[LinearConstraint] = [] + self._linear_constraints_index: Dict[str, int] = {} - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram("filename") - op is a new problem containing the data in filename. If - filename does not exist, an exception is raised. + @property + def name(self) -> str: + """Returns the name of the quadratic program. - The QuadraticProgram object is a context manager and can be used, like so: + Returns: + The name of the quadratic program. + """ + return self._name - >>> from qiskit.optimization import QuadraticProgram - >>> with QuadraticProgram() as op: - >>> # do stuff - >>> op.solve() + @name.setter + def name(self, name: str) -> None: + """Sets the name of the quadratic program. - When the with block is finished, the end() method will be called automatically. + Args: + name: The name of the quadratic program. """ - if not _HAS_CPLEX: - raise NameError('CPLEX is not installed.') + self._name = name - self._name = '' + @property + def variables(self) -> List[Variable]: + """Returns the list of variables of the quadratic program. - self.variables = VariablesInterface() + Returns: + List of variables. + """ + return self._variables - # convert variable names into indices - varindex = self.variables.get_indices + @property + def variables_index(self) -> Dict[str, int]: + """Returns the dictionary that maps the name of a variable to its index. - self.linear_constraints = LinearConstraintInterface(varindex=varindex) - self.quadratic_constraints = QuadraticConstraintInterface(varindex=varindex) - self.objective = ObjectiveInterface(varindex=varindex) - self.problem_type = ProblemType() + Returns: + The variable index dictionary. + """ + return self._variables_index - # None means it will be detected automatically - self._problem_type = None + def _add_variables(self, name: Optional[str] = None, lowerbound: float = 0, + upperbound: float = infinity, vartype: VarType = VarType.continuous) -> None: + """Checks whether a variable name is already taken and adds the variable to list and index + if not. - self.substitution_status = SubstitutionStatus + Args: + name: The name of the variable. + lowerbound: The lowerbound of the variable. + upperbound: The upperbound of the variable. + vartype: The type of the variable. - # read from file in case filename is given - if file_name: - try: - self.read(file_name) - except CplexSolverError: - raise QiskitOptimizationError('Could not load file: {}'.format(file_name)) + Returns: + The added variable. - def from_cplex(self, op: 'Cplex'): - """Loads an optimization problem from a Cplex object + Raises: + QiskitOptimizationError: if the variable name is already taken. - Args: - op: a Cplex object """ - # make sure the current problem is clean - self.end() - - self.set_problem_name(op.get_problem_name()) - self.set_problem_type(op.get_problem_type()) - - # Note: CPLEX raises a "Not a MIP (3003)"-error if there is no variable whose type is not - # specified. As a workaround, we add an dummy variable with a type and then delete it. - idx = op.variables.add(types='B') - op.variables.delete(idx[0]) - - # set variables (obj is set via objective interface) - lowerbounds = op.variables.get_lower_bounds() - upperbounds = op.variables.get_upper_bounds() - types = op.variables.get_types() - names = op.variables.get_names() - self.variables.add(lb=lowerbounds, ub=upperbounds, types=types, names=names) - - # set objective function - try: - # if no name is set for objective function, CPLEX raises CplexSolverError - obj_name = op.objective.get_name() - except CplexSolverError: - obj_name = '' - self.objective.set_name(obj_name) - self.objective.set_sense(op.objective.get_sense()) - self.objective.set_offset(op.objective.get_offset()) - self.objective.set_linear((i, v) for i, v in enumerate(op.objective.get_linear())) - if op.objective.get_num_quadratic_nonzeros() > 0: - self.objective.set_quadratic(op.objective.get_quadratic()) - - # set linear constraints - lin_expr = op.linear_constraints.get_rows() - senses = op.linear_constraints.get_senses() - rhs = op.linear_constraints.get_rhs() - range_values = op.linear_constraints.get_range_values() - names = op.linear_constraints.get_names() - self.linear_constraints.add( - lin_expr=lin_expr, senses=senses, rhs=rhs, range_values=range_values, names=names) - - # set quadratic constraints - names = op.quadratic_constraints.get_names() - senses = op.quadratic_constraints.get_senses() - rhs = op.quadratic_constraints.get_rhs() - lin_expr = op.quadratic_constraints.get_linear_components() - quad_expr = op.quadratic_constraints.get_quadratic_components() - for i in range(op.quadratic_constraints.get_num()): - self.quadratic_constraints.add( - lin_expr=lin_expr[i], quad_expr=quad_expr[i], sense=senses[i], rhs=rhs[i], - name=names[i]) - - def from_docplex(self, model: DocplexModel): - """Loads an optimization problem from a Docplex model + if name: + if name in self._variables_index: + raise QiskitOptimizationError("Variable name already exists!") + else: + k = self.get_num_vars() + while 'x{}'.format(k) in self._variables_index: + k += 1 + name = 'x{}'.format(k) + self.variables_index[name] = len(self.variables) + variable = Variable(self, name, lowerbound, upperbound, vartype) + self.variables.append(variable) + return variable + + def continuous_var(self, name: Optional[str] = None, lowerbound: float = 0, + upperbound: float = infinity) -> Variable: + """Adds a continuous variable to the quadratic program. Args: - model: Docplex model - """ - cpl = model.get_cplex() - self.from_cplex(cpl) - # Docplex does not copy the model name. We need to do it manually. - self.set_problem_name(model.get_name()) - - def to_cplex(self) -> 'Cplex': - """Converts the optimization problem into a Cplex object. + name: The name of the variable. + lowerbound: The lowerbound of the variable. + upperbound: The upperbound of the variable. - Returns: Cplex object - """ + Returns: + The added variable. - # create a new CPLEX model - op = Cplex() - op.set_problem_name(self.get_problem_name()) - - # problem type will be set automatically by CPLEX - - # set variables - names = self.variables.get_names() - lowerbounds = self.variables.get_lower_bounds() - upperbounds = self.variables.get_upper_bounds() - types = self.variables.get_types() - op.variables.add(lb=lowerbounds, ub=upperbounds, types=types, names=names) - - # set objective function - op.objective.set_name(self.objective.get_name()) - op.objective.set_sense(self.objective.get_sense()) - op.objective.set_offset(self.objective.get_offset()) - op.objective.set_linear((i, v) for i, v in self.objective.get_linear_dict().items()) - if self.objective.get_num_quadratic_nonzeros() > 0: - op.objective.set_quadratic(self.objective.get_quadratic()) - - # set linear constraints - lin_expr = self.linear_constraints.get_rows() - senses = self.linear_constraints.get_senses() - rhs = self.linear_constraints.get_rhs() - range_values = self.linear_constraints.get_range_values() - names = self.linear_constraints.get_names() - op.linear_constraints.add( - lin_expr=lin_expr, senses=senses, rhs=rhs, range_values=range_values, names=names) - - # set quadratic constraints - names = self.quadratic_constraints.get_names() - senses = self.quadratic_constraints.get_senses() - rhs = self.quadratic_constraints.get_rhs() - lin_expr = self.quadratic_constraints.get_linear_components() - quad_expr = self.quadratic_constraints.get_quadratic_components() - for i in range(self.quadratic_constraints.get_num()): - op.quadratic_constraints.add( - lin_expr=lin_expr[i], quad_expr=quad_expr[i], sense=senses[i], rhs=rhs[i], - name=names[i]) - return op - - def end(self): - """Releases the QuadraticProgram object.""" - self._name = '' - self.variables = VariablesInterface() - varindex = self.variables.get_indices - self.linear_constraints = LinearConstraintInterface(varindex=varindex) - self.quadratic_constraints = QuadraticConstraintInterface(varindex=varindex) - self.objective = ObjectiveInterface(varindex=varindex) - self._problem_type = None - - def __enter__(self) -> 'QuadraticProgram': - """To implement a ContextManager, as in Cplex.""" - return self - - def __exit__(self, *exc) -> bool: - """To implement a ContextManager, as in Cplex.""" - self.end() - return False - - def read(self, filename: str, filetype: str = ""): - """Reads a problem from file. - - The first argument is a string specifying the filename from - which the problem will be read. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> op.read("lpex.mps") + Raises: + QiskitOptimizationError: if the variable name is already occupied. """ - cplex = Cplex() - cplex.read(filename, filetype) - self.from_cplex(cplex) + return self._add_variables(name, lowerbound, upperbound, VarType.continuous) - def write(self, filename: str, filetype: str = ""): - """Writes a problem to file. + def binary_var(self, name: Optional[str] = None) -> Variable: + """Adds a binary variable to the quadratic program. - The first argument is a string specifying the filename to - which the problem will be written. + Args: + name: The name of the variable. - Example usage: + Returns: + The added variable. - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names=['x1', 'x2', 'x3']) - >>> op.write("example.lp") + Raises: + QiskitOptimizationError: if the variable name is already occupied. """ - cplex = self.to_cplex() - cplex.write(filename, filetype) + return self._add_variables(name, 0, 1, VarType.binary) - def write_to_stream(self, stream: object, filetype: str = 'LP', comptype: str = ''): - """Writes a problem to a file-like object in the given file format. + def integer_var(self, name: Optional[str] = None, lowerbound: float = 0, + upperbound: float = infinity) -> Variable: + """Adds an integer variable to the quadratic program. - The filetype argument can be any of "sav" (a binary format), "lp" - (the default), "mps", "rew", "rlp", or "alp" (see `QuadraticProgram.write` - for an explanation of these). + Args: + name: The name of the variable. + lowerbound: The lowerbound of the variable. + upperbound: The upperbound of the variable. - If comptype is "bz2" (for BZip2) or "gz" (for GNU Zip), a - compressed file is written. + Returns: + The added variable. Raises: - QiskitOptimizationError: if `stream` does not have methods `write` and `flush`. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names=['x1', 'x2', 'x3']) - >>> class NoOpStream(object): - ... def __init__(self): - ... self.was_called = False - ... def write(self, bytes): - ... self.was_called = True - ... pass - ... def flush(self): - ... pass - >>> stream = NoOpStream() - >>> op.write_to_stream(stream) - >>> stream.was_called - True + QiskitOptimizationError: if the variable name is already occupied. """ - if not hasattr(stream, 'write') or not callable(stream.write): - raise QiskitOptimizationError("stream must have a write method") - if not hasattr(stream, 'flush') or not callable(stream.flush): - raise QiskitOptimizationError("stream must have a flush method") - op = self.to_cplex() - op.write_to_stream(stream, filetype, comptype) - - def write_as_string(self, filetype: str = 'LP', comptype: str = '') -> str: - """Writes a problem as a string in the given file format. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names=['x1', 'x2', 'x3']) - >>> lp_str = op.write_as_string("lp") - >>> len(lp_str) > 0 - True + return self._add_variables(name, lowerbound, upperbound, VarType.integer) + + def get_variable(self, i: Union[int, str]) -> Variable: + """Returns a variable for a given name or index. + + Args: + i: the index or name of the variable. + + Returns: + The corresponding variable. """ - op = self.to_cplex() - return op.write_as_string(filetype, comptype) + if isinstance(i, int): + return self.variables[i] + else: + return self.variables[self._variables_index[i]] - def get_problem_type(self) -> int: - """Returns the problem type. + def get_num_vars(self, vartype: Optional[VarType] = None) -> int: + """Returns the total number of variables or the number of variables of the specified type. - The return value is an attribute of self.problem_type. + Args: + vartype: The type to be filtered on. All variables are counted if None. - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> op.read("lpex.mps") - >>> op.get_problem_type() - 0 - >>> op.problem_type[op.get_problem_type()] - 'LP' + Returns: + The total number of variables. """ - if self._problem_type: - return self._problem_type - return self._detect_problem_type() - - def _detect_problem_type(self) -> int: - typ = self.problem_type.LP - if self.variables.get_num() > 0: - typ = self.problem_type.MILP - if self.objective.get_num_quadratic_nonzeros() > 0: - typ = self.problem_type.MIQP - if self.quadratic_constraints.get_num() > 0: - typ = self.problem_type.MIQCP - return typ - - def set_problem_type(self, problem_type): - """Changes the problem type. - - If only one argument is given, that argument specifies the new - problem type. It must be one of the following: - - qiskit.optimization.problem_type.LP - qiskit.optimization.problem_type.MILP - qiskit.optimization.problem_type.fixed_MILP - qiskit.optimization.problem_type.QP - qiskit.optimization.problem_type.MIQP - qiskit.optimization.problem_type.fixed_MIQP - qiskit.optimization.problem_type.QCP - qiskit.optimization.problem_type.MIQCP + if vartype: + return sum([variable.vartype == vartype for variable in self.variables]) + else: + return len(self.variables) + + def get_num_continuous_vars(self) -> int: + """Returns the total number of continuous variables. + + Returns: + The total number of continuous variables. """ - self._problem_type = problem_type + return self.get_num_vars(VarType.continuous) + + def get_num_binary_vars(self) -> int: + """Returns the total number of binary variables. - def solve(self): - """Prints out a message to ask users to use `OptimizationAlgorithm`. - Users need to apply one of `OptimiztionAlgorithm`s instead of this method. + Returns: + The total number of binary variables. """ - logger.warning('`QuadraticProgram.solve` is intentionally empty.' - 'You can solve it by applying `OptimizationAlgorithm.solve`.') + return self.get_num_vars(VarType.binary) - def set_problem_name(self, name: str): - """Sets the problem name""" - self._name = name + def get_num_integer_vars(self) -> int: + """Returns the total number of integer variables. - def get_problem_name(self) -> str: - """Returns the problem name""" - return self._name + Returns: + The total number of integer variables. + """ + return self.get_num_vars(VarType.integer) - def substitute_variables(self, constants: Optional['SparsePair'] = None, - variables: Optional['SparseTriple'] = None) \ - -> Tuple['QuadraticProgram', 'SubstitutionStatus']: - """Substitutes variables with constants or other variables. + @property + def linear_constraints(self) -> List[LinearConstraint]: + """Returns the list of linear constraints of the quadratic program. - Args: - constants: replace variable by constant - i.e., SparsePair.ind (variable) -> SparsePair.val (constant) + Returns: + List of linear constraints. + """ + return self._linear_constraints - variables: replace variables by weighted other variable - need to copy everything using name reference to make sure that indices are matched - correctly. The lower and upper bounds are updated accordingly. - i.e., SparseTriple.ind1 (variable) - -> SparseTriple.ind2 (variable) * SparseTriple.val (constant) + @property + def linear_constraints_index(self) -> Dict[str, int]: + """Returns the dictionary that maps the name of a linear constraint to its index. + + Returns: + The linear constraint index dictionary. + """ + return self._linear_constraints_index + + def _add_linear_constraint(self, + name: Optional[str] = None, + linear_coefficients: Union[ndarray, spmatrix, List[float], + Dict[Union[int, str], float]] = None, + sense: ConstraintSense = ConstraintSense.leq, + rhs: float = 0.0 + ) -> None: + """Checks whether a constraint name is already taken and adds the constraint to list and + index if not. + + Args: + name: The name of the constraint. + linear_coefficients: The linear coefficients of the constraint. + sense: The constraint sense. + rhs: The right-hand-side of the constraint. Returns: - An optimization problem by substituting variables and the status. - If the resulting problem has no issue, the status is `success`. - Otherwise, an empty problem and status `infeasible` are returned. + The added constraint. Raises: - QiskitOptimizationError: if the substitution is invalid as follows. - - Same variable is substituted multiple times. - - Coefficient of variable substitution is zero. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> from cplex import SparsePair, SparseTriple - >>> op = QuadraticProgram() - >>> op.variables.add(names=['x', 'y'], types='I'*2, lb=[-1]*2, ub=[2]*2) - >>> op.objective.set_sense(op.objective.sense.minimize) - >>> op.objective.set_linear([('x', 1), ('y', 2)]) - >>> op.linear_constraints.add(lin_expr=[(['x', 'y'], [1.0, -1.0])], senses=['L'], rhs=[1.0]) - >>> print(op.write_as_string()) - \\ENCODING=ISO-8859-1 - \\Problem name: - - Minimize - obj1: x + 2 y - Subject To - c1: x - y <= 1 - Bounds - -1 <= x <= 2 - -1 <= y <= 2 - Generals - x y - End - >>> # substitute x <- 2 - >>> op2, st = op.substitute_variables(constants=SparsePair(ind=['x'], val=[2])) - >>> print(st) - SubstitutionStatus.success - >>> print(op2.write_as_string()) - \\ENCODING=ISO-8859-1 - \\Problem name: - - Minimize - obj1: 2 y + 2 - Subject To - c1: - y <= -1 - Bounds - -1 <= y <= 2 - Generals - y - End - >>> # substitute y <- -x - >>> op3, st = op.substitute_variables(variables=SparseTriple(\ - ind1=['y'], ind2=['x'], val=[-1])) - >>> print(st) - SubstitutionStatus.success - >>> print(op3.write_as_string()) - \\ENCODING=ISO-8859-1 - \\Problem name: - - Minimize - obj1: - x - Subject To - c1: 2 x <= 1 - Bounds - -1 <= x <= 1 - Generals - x - End + QiskitOptimizationError: if the constraint name is already taken. + """ - subs = SubstituteVariables() - return subs.substitute_variables(src=self, constants=constants, variables=variables) + if name: + if name in self.linear_constraints_index: + raise QiskitOptimizationError("Variable name already exists!") + else: + k = self.get_num_linear_constraints() + while 'c{}'.format(k) in self.linear_constraints_index: + k += 1 + name = 'c{}'.format(k) + self.linear_constraints_index[name] = len(self.linear_constraints) + if linear_coefficients is None: + linear_coefficients = {} + constraint = LinearConstraint(self, name, linear_coefficients, sense, rhs) + self.linear_constraints.append(constraint) + return constraint + + def linear_eq_constraint(self, name: Optional[str] = None, + linear_coefficients: Union[ndarray, spmatrix, List[float], + Dict[Union[int, str], float]] = None, + rhs: float = 0.0) -> LinearConstraint: + """Adds a linear equality constraint to the quadratic program of the form: + linear_coeffs * x == rhs. + Args: + name: The name of the constraint. + linear_coefficients: The linear coefficients of the left-hand-side of the constraint. + rhs: The right hand side of the constraint. -class SubstitutionStatus(Enum): - """Status of `QuadraticProgram.substitute_variables`""" - success = 1 - infeasible = 2 + Returns: + The added constraint. + Raises: + QiskitOptimizationError: if the constraint name already exists. + """ + return self._add_linear_constraint(name, linear_coefficients, ConstraintSense.eq, rhs) -class SubstituteVariables: - """A class to substitute variables of an optimization problem with constants for other - variables""" + def linear_geq_constraint(self, name: Optional[str] = None, + linear_coefficients: Union[ndarray, spmatrix, List[float], + Dict[Union[int, str], float]] = None, + rhs: float = 0.0) -> LinearConstraint: + """Adds a linear "greater-than-or-equal-to" (geq) constraint to the quadratic program + of the form: + linear_coeffs * x >= rhs. - CONST = -1 + Args: + name: The name of the constraint. + linear_coefficients: The linear coefficients of the left-hand-side of the constraint. + rhs: The right hand side of the constraint. - def __init__(self): - self._src: QuadraticProgram = None - self._dst: QuadraticProgram = None - self._subs = {} + Returns: + The added constraint. - def substitute_variables(self, src: QuadraticProgram, - constants: Optional['SparsePair'] = None, - variables: Optional['SparseTriple'] = None) \ - -> Tuple[QuadraticProgram, SubstitutionStatus]: - """Substitutes variables with constants or other variables. + Raises: + QiskitOptimizationError: if the constraint name already exists. + """ + return self._add_linear_constraint(name, linear_coefficients, ConstraintSense.geq, rhs) - Args: - src: an optimization problem whose variables will be substituted - constants: replace variable by constant - i.e., SparsePair.ind (variable) -> SparsePair.val (constant) + def linear_leq_constraint(self, name: Optional[str] = None, + linear_coefficients: Union[ndarray, spmatrix, List[float], + Dict[Union[int, str], float]] = None, + rhs: float = 0.0) -> LinearConstraint: + """Adds a linear "less-than-or-equal-to" (leq) constraint to the quadratic program + of the form: + linear_coeffs * x <= rhs. - variables: replace variables by weighted other variable - need to copy everything using name reference to make sure that indices are matched - correctly - i.e., SparseTriple.ind1 (variable) - -> SparseTriple.ind2 (variable) * SparseTriple.val (constant) + Args: + name: The name of the constraint. + linear_coefficients: The linear coefficients of the left-hand-side of the constraint. + rhs: The right hand side of the constraint. Returns: - An optimization problem by substituting variables and the status. - If the resulting problem has no issue, the status is `success`. - Otherwise, an empty problem and status `infeasible` are returned. + The added constraint. Raises: - QiskitOptimizationError: if the substitution is invalid as follows. - - Same variable is substituted multiple times. - - Coefficient of variable substitution is zero. + QiskitOptimizationError: if the constraint name already exists. """ + return self._add_linear_constraint(name, linear_coefficients, ConstraintSense.leq, rhs) + + def get_linear_constraint(self, i: Union[int, str]) -> LinearConstraint: + """Returns a linear constraint for a given name or index. + + Args: + i: the index or name of the constraint. - self._src = src - self._dst = QuadraticProgram() - self._dst.set_problem_name(src.get_problem_name()) - # do not set problem type, then it detects its type automatically - - self._subs_dict(constants, variables) - - results = [ - self._variables(), - self._objective(), - self._linear_constraints(), - self._quadratic_constraints(), - ] - if any(r == SubstitutionStatus.infeasible for r in results): - ret = SubstitutionStatus.infeasible + Returns: + The corresponding constraint. + """ + if isinstance(i, int): + return self.linear_constraints[i] else: - ret = SubstitutionStatus.success - return self._dst, ret + return self.linear_constraints[self._linear_constraints_index[i]] - @staticmethod - def _feasible(sense: str, rhs: float, range_value: float = 0) -> bool: - """Checks feasibility of the following condition - 0 `sense` rhs + def get_num_linear_constraints(self) -> int: + """Returns the number of linear constraints. + + Returns: + The number of linear constraints. """ - # I use the following pylint option because `rhs` should come to right - # pylint: disable=misplaced-comparison-constant - if sense == 'E': - if 0 == rhs: - return True - elif sense == 'L': - if 0 <= rhs: - return True - elif sense == 'G': - if 0 >= rhs: - return True - else: # sense == 'R' - if range_value >= 0: - if rhs <= 0 <= rhs + range_value: - return True - else: - if rhs + range_value <= 0 <= rhs: - return True - return False - - @staticmethod - def _replace_dict_keys_with_names(op, dic): - key = [] - val = [] - for k in sorted(dic.keys()): - key.append(op.variables.get_names(k)) - val.append(dic[k]) - return key, val - - def _subs_dict(self, constants, variables): - src = self._src - - # guarantee that there is no overlap between variables to be replaced and combine input - subs = {} - if constants is not None: - if not isinstance(constants, SparsePair): - raise QiskitOptimizationError( - 'substitution with constant should be SparsePair: {}'.format(constants)) - for i, v in zip(constants.ind, constants.val): - # substitute i <- v - i_2 = src.variables.get_indices(i) - if i_2 in subs: - raise QiskitOptimizationError( - 'cannot substitute the same variable twice: {} <- {}'.format(i, v)) - subs[i_2] = (self.CONST, v) - - if variables is not None: - if not isinstance(variables, SparseTriple): - raise QiskitOptimizationError( - 'substitution with variable should be SparseTriple: {}'.format(variables)) - for i, j, v in zip(variables.ind1, variables.ind2, variables.val): - if v == 0: - raise QiskitOptimizationError( - 'coefficient should not be zero: {} {} {}'.format(i, j, v)) - # substitute i <- j * v - i_2 = src.variables.get_indices(i) - j_2 = src.variables.get_indices(j) - if i_2 == j_2: - raise QiskitOptimizationError( - 'Cannot substitute the same variable: {} <- {} {}'.format(i, j, v)) - if i_2 in subs: - raise QiskitOptimizationError( - 'Cannot substitute the same variable twice: {} <- {} {}'.format(i, j, v)) - if j_2 in subs: - raise QiskitOptimizationError( - 'Cannot substitute by variable that gets substituted itself: ' - '{} <- {} {}'.format(i, j, v)) - subs[i_2] = (j_2, v) - - self._subs = subs - - def _variables(self) -> SubstitutionStatus: - src = self._src - dst = self._dst - subs = self._subs - - # copy variables that are not replaced - for name, var_type, lowerbound, upperbound in zip( - src.variables.get_names(), - src.variables.get_types(), - src.variables.get_lower_bounds(), - src.variables.get_upper_bounds(), - ): - i = src.variables.get_indices(name) - if i not in subs: - dst.variables.add(lb=[lowerbound], ub=[upperbound], types=var_type, names=[name]) - - for i, (j, v) in subs.items(): - var_i = src.variables.get_names(i) - lb_i = src.variables.get_lower_bounds(i) - ub_i = src.variables.get_upper_bounds(i) - if j == self.CONST: - if not lb_i <= v <= ub_i: - logger.warning( - 'Infeasible substitution for variable: %s', var_i) - return SubstitutionStatus.infeasible - else: - # substitute i <- j * v - # lb_i <= i <= ub_i --> lb_i / v <= j <= ub_i / v if v > 0 - # ub_i / v <= j <= lb_i / v if v < 0 - if v == 0: - raise QiskitOptimizationError( - 'Coefficient of variable substitution should be nonzero: ' - '{} {} {}'.format(i, j, v)) - var_j = src.variables.get_names(j) - if abs(lb_i) < infinity: - new_lb_i = lb_i / v - else: - new_lb_i = lb_i if v > 0 else -lb_i - if abs(ub_i) < infinity: - new_ub_i = ub_i / v - else: - new_ub_i = ub_i if v > 0 else -ub_i - lb_j = dst.variables.get_lower_bounds(var_j) - ub_j = dst.variables.get_upper_bounds(var_j) - if v > 0: - dst.variables.set_lower_bounds(var_j, max(lb_j, new_lb_i)) - dst.variables.set_upper_bounds(var_j, min(ub_j, new_ub_i)) - else: - dst.variables.set_lower_bounds(var_j, max(lb_j, new_ub_i)) - dst.variables.set_upper_bounds(var_j, min(ub_j, new_lb_i)) - - for var in dst.variables.get_names(): - lowerbound = dst.variables.get_lower_bounds(var) - upperbound = dst.variables.get_upper_bounds(var) - if lowerbound > upperbound: - logger.warning( - 'Infeasible lower and upper bound: %s %f %f', var, lowerbound, upperbound) - return SubstitutionStatus.infeasible - - return SubstitutionStatus.success - - def _objective(self) -> SubstitutionStatus: - src = self._src - dst = self._dst - subs = self._subs - - # initialize - offset = [src.objective.get_offset()] - lin_dict = {} - quad_dict = {} - - # substitute quadratic terms of the objective function - for (i, j), w_ij in src.objective.get_quadratic_dict().items(): - repl_i = subs[i] if i in subs else (i, 1) - repl_j = subs[j] if j in subs else (j, 1) - idx = tuple(x for x, _ in [repl_i, repl_j] if x != self.CONST) - prod = w_ij * repl_i[1] * repl_j[1] - if len(idx) == 2: - if idx not in quad_dict: - quad_dict[idx] = 0 - quad_dict[idx] += prod - elif len(idx) == 1: - k = idx[0] - if k not in lin_dict: - lin_dict[k] = 0 - lin_dict[k] += prod / 2 - else: - offset.append(prod / 2) - - # substitute linear terms of the objective function - for i, w_i in src.objective.get_linear_dict().items(): - repl_i = subs[i] if i in subs else (i, 1) - prod = w_i * repl_i[1] - if repl_i[0] == self.CONST: - offset.append(prod) - else: - k = repl_i[0] - if k not in lin_dict: - lin_dict[k] = 0 - lin_dict[k] += prod - - dst.objective.set_offset(fsum(offset)) - if len(lin_dict) > 0: - ind, val = self._replace_dict_keys_with_names(src, lin_dict) - dst.objective.set_linear([(i, v) for i, v in zip(ind, val) if v != 0]) - if len(quad_dict) > 0: - ind_pair, val = self._replace_dict_keys_with_names(src, quad_dict) - ind1, ind2 = zip(*ind_pair) - dst.objective.set_quadratic_coefficients( - [(i, j, v) for i, j, v in zip(ind1, ind2, val) if v != 0]) - - return SubstitutionStatus.success - - def _linear_constraints(self) -> SubstitutionStatus: - src = self._src - dst = self._dst - subs = self._subs - - for name, row, rhs, sense, range_value in zip( - src.linear_constraints.get_names(), - src.linear_constraints.get_rows(), - src.linear_constraints.get_rhs(), - src.linear_constraints.get_senses(), - src.linear_constraints.get_range_values() - ): - lin_dict = {} - rhs = [rhs] - for i, w_i in zip(row.ind, row.val): - repl_i = subs[i] if i in subs else (i, 1) - prod = w_i * repl_i[1] - if repl_i[0] == self.CONST: - rhs.append(-prod) - else: - k = repl_i[0] - if k not in lin_dict: - lin_dict[k] = 0 - lin_dict[k] += prod - if len(lin_dict) > 0: - ind, val = self._replace_dict_keys_with_names(src, lin_dict) - dst.linear_constraints.add( - lin_expr=[SparsePair(ind=ind, val=val)], - senses=sense, rhs=[fsum(rhs)], range_values=[range_value], names=[name]) - else: - if not self._feasible(sense, fsum(rhs), range_value): - logger.warning('constraint %s is infeasible due to substitution', name) - return SubstitutionStatus.infeasible - - return SubstitutionStatus.success - - def _quadratic_constraints(self) -> SubstitutionStatus: - src = self._src - dst = self._dst - subs = self._subs - - for name, lin_expr, quad_expr, sense, rhs in zip( - src.quadratic_constraints.get_names(), - src.quadratic_constraints.get_linear_components(), - src.quadratic_constraints.get_quadratic_components(), - src.quadratic_constraints.get_senses(), - src.quadratic_constraints.get_rhs() - ): - quad_dict = {} - lin_dict = {} - rhs = [rhs] - for i, j, w_ij in zip(quad_expr.ind1, quad_expr.ind2, quad_expr.val): - repl_i = subs[i] if i in subs else (i, 1) - repl_j = subs[j] if j in subs else (j, 1) - idx = tuple(x for x, _ in [repl_i, repl_j] if x != self.CONST) - prod = w_ij * repl_i[1] * repl_j[1] - if len(idx) == 2: - if idx[0] < idx[1]: - idx = (idx[1], idx[0]) - if idx not in quad_dict: - quad_dict[idx] = 0 - quad_dict[idx] += prod - elif len(idx) == 1: - k = idx[0] - if k not in lin_dict: - lin_dict[k] = 0 - lin_dict[k] += prod - else: - rhs.append(-prod) - for i, w_i in zip(lin_expr.ind, lin_expr.val): - repl_i = subs[i] if i in subs else (i, 1) - prod = w_i * repl_i[1] - if repl_i[0] == self.CONST: - rhs.append(-prod) - else: - k = repl_i[0] - if k not in lin_dict: - lin_dict[k] = 0 - lin_dict[k] += prod - ind, val = self._replace_dict_keys_with_names(src, lin_dict) - lin_expr = SparsePair(ind=ind, val=val) - if len(quad_dict) > 0: - ind_pair, val = self._replace_dict_keys_with_names(src, quad_dict) - ind1, ind2 = zip(*ind_pair) - quad_expr = SparseTriple(ind1=ind1, ind2=ind2, val=val) - dst.quadratic_constraints.add( - name=name, - lin_expr=lin_expr, - quad_expr=quad_expr, - sense=sense, - rhs=fsum(rhs) - ) - elif len(lin_dict) > 0: - lin_names = set(dst.linear_constraints.get_names()) - while name in lin_names: - name = '_' + name - dst.linear_constraints.add( - names=[name], - lin_expr=[lin_expr], - senses=sense, - rhs=[fsum(rhs)] - ) - else: - if not self._feasible(sense, fsum(rhs)): - logger.warning('constraint %s is infeasible due to substitution', name) - return SubstitutionStatus.infeasible - - return SubstitutionStatus.success + return len(self.linear_constraints) diff --git a/qiskit/optimization/problems/variable.py b/qiskit/optimization/problems/variable.py new file mode 100644 index 0000000000..b4d4370074 --- /dev/null +++ b/qiskit/optimization/problems/variable.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019, 2020. +# +# 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. + +"""Variable interface""" + +from enum import Enum +from typing import Tuple + +from qiskit.optimization import infinity, QiskitOptimizationError +from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram + + +class VarType(Enum): + """Constants defining variable type.""" + continuous = 0 + binary = 1 + integer = 2 + + +class Variable(HasQuadraticProgram): + """Representation of a variable.""" + + def __init__(self, quadratic_program: 'QuadraticProgram', name: str, lowerbound: float = 0, + upperbound: float = infinity, vartype: VarType = VarType.continuous) -> None: + """Creates a new Variable. + + The variables is exposed by the top-level `QuadraticProgram` class + in `QuadraticProgram.variables`. This constructor is not meant to be used + externally. + + Args: + quadratic_program: The parent QuadraticProgram. + name: The variable name. + lowerbound: The variable lowerbound. + upperbound: The variable upperbound. + vartype: The variable type. + + Raises: + QiskitOptimizationError: if lowerbound is greater than upperbound. + """ + if lowerbound > upperbound: + raise QiskitOptimizationError("Lowerbound is greater than upperbound!") + + super().__init__(quadratic_program) + self._name = name + self._lowerbound = lowerbound + self._upperbound = upperbound + self._vartype = vartype + + @property + def name(self) -> str: + """Returns the name of the variable. + + Returns: + The name of the variable. + """ + return self._name + + @name.setter + def name(self, name: str) -> None: + """Sets the name of the variable and updates the name index in the corresponding QP. + + Args: + name: The name of the variable. + + Raises: + QiskitOptimizationError: if the name is already existing. + """ + # TODO: update QP and raise exception if name already exists + self._name = name + + @property + def lowerbound(self) -> float: + """Returns the lowerbound of the variable. + + Returns: + The lower bound of the variable. + """ + return self._lowerbound + + @lowerbound.setter + def lowerbound(self, lowerbound: float) -> None: + """Sets the lowerbound of the variable. + + Args: + lowerbound: The lower bound of the variable. + + Raises: + QiskitOptimizationError: if lowerbound is greater than upperbound. + """ + if lowerbound > self.upperbound: + raise QiskitOptimizationError("Lowerbound is greater than upperbound!") + self._lowerbound = lowerbound + + @property + def upperbound(self) -> float: + """Returns the upperbound of the variable. + + Returns: + The upperbound of the variable. + """ + return self._upperbound + + @upperbound.setter + def upperbound(self, upperbound: float) -> None: + """Sets the upperbound of the variable. + + Args: + upperbound: The upperbound of the variable. + + Raises: + QiskitOptimizationError: if upperbound is smaller than lowerbound. + """ + if self.lowerbound > upperbound: + raise QiskitOptimizationError("Lowerbound is greater than upperbound!") + self._upperbound = upperbound + + @property + def vartype(self) -> VarType: + """Returns the type of the variable. + + Returns: + The variable type. + + """ + return self._vartype + + @vartype.setter + def vartype(self, vartype: VarType) -> None: + """Sets the type of the variable. + + Args: + vartype: The variable type. + """ + self._vartype = vartype + + def as_tuple(self) -> Tuple[str, float, float, VarType]: + """ Returns a tuple corresponding to this variable. + + Returns: + A tuple corresponding to this variable consisting of name, lowerbound, upperbound and + variable type. + """ + return (self.name, self.lowerbound, self.upperbound, self.vartype) diff --git a/qiskit/optimization/problems/variables.py b/qiskit/optimization/problems/variables.py deleted file mode 100644 index 7d3d542e32..0000000000 --- a/qiskit/optimization/problems/variables.py +++ /dev/null @@ -1,689 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# 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. - -"""Variable interface""" - -import copy -from typing import List, Optional, Union - -from qiskit.optimization import infinity -from qiskit.optimization.problems.name_index import init_list_args, NameIndex -from qiskit.optimization.exceptions import QiskitOptimizationError -from .base import BaseInterface - -CPX_CONTINUOUS = 'C' -CPX_BINARY = 'B' -CPX_INTEGER = 'I' -CPX_SEMICONT = 'S' -CPX_SEMIINT = 'N' - - -class VarTypes: - """Constants defining variable types - - These constants are compatible with IBM ILOG CPLEX. - For a definition of each type, see those topics in the CPLEX User's - Manual. - """ - continuous = CPX_CONTINUOUS - binary = CPX_BINARY - integer = CPX_INTEGER - semi_integer = CPX_SEMIINT - semi_continuous = CPX_SEMICONT - - def __getitem__(self, item: str) -> str: - """Converts a constant to a string. - - Returns: - Variable type name. - - Raises: - QiskitOptimizationError: if the argument is not a valid type. - - Example usage: - - >>> from qiskit.optimization.problems import QuadraticProgram - >>> op = QuadraticProgram() - >>> op.variables.type.binary - 'B' - >>> op.variables.type['B'] - 'binary' - """ - if item == CPX_CONTINUOUS: - return 'continuous' - if item == CPX_BINARY: - return 'binary' - if item == CPX_INTEGER: - return 'integer' - if item == CPX_SEMIINT: - return 'semi_integer' - if item == CPX_SEMICONT: - return 'semi_continuous' - raise QiskitOptimizationError('Invalid variable type: {}'.format(item)) - - -class VariablesInterface(BaseInterface): - """Methods for adding, querying, and modifying variables. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = ["x0", "x1", "x2"]) - >>> # default values for lower_bounds are 0.0 - >>> op.variables.get_lower_bounds() - [0.0, 0.0, 0.0] - >>> # values can be set either one at a time or many at a time - >>> op.variables.set_lower_bounds(0, 1.0) - >>> op.variables.set_lower_bounds([("x1", -1.0), (2, 3.0)]) - >>> # values can be queried as a range - >>> op.variables.get_lower_bounds(0, "x1") - [1.0, -1.0] - >>> # values can be queried as a sequence in arbitrary order - >>> op.variables.get_lower_bounds(["x1", "x2", 0]) - [-1.0, 3.0, 1.0] - >>> # can query the number of variables - >>> op.variables.get_num() - 3 - >>> op.variables.set_types(0, op.variables.type.binary) - >>> op.variables.get_num_binary() - 1 - """ - - type = VarTypes() - - def __init__(self): - """Creates a new VariablesInterface. - - The variables interface is exposed by the top-level `QuadraticProgram` class - as `QuadraticProgram.variables`. This constructor is not meant to be used - externally. - """ - super(VariablesInterface, self).__init__() - self._names = [] - self._lb = [] - self._ub = [] - self._types = [] - # self._obj = [] - # self._columns = [] - self._index = NameIndex() - - def get_num(self) -> int: - """Returns the number of variables in the problem. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> t = op.variables.type - >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) - >>> op.variables.get_num() - 3 - """ - return len(self._names) - - def get_num_continuous(self) -> int: - """Returns the number of continuous variables in the problem. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> t = op.variables.type - >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) - >>> op.variables.get_num_continuous() - 1 - """ - return self._types.count(VarTypes.continuous) - - def get_num_integer(self) -> int: - """Returns the number of integer variables in the problem. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> t = op.variables.type - >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) - >>> op.variables.get_num_integer() - 1 - """ - return self._types.count(VarTypes.integer) - - def get_num_binary(self) -> int: - """Returns the number of binary variables in the problem. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> t = op.variables.type - >>> indices = op.variables.add(types = [t.semi_continuous, t.binary, t.integer]) - >>> op.variables.get_num_binary() - 1 - """ - return self._types.count(VarTypes.binary) - - def get_num_semicontinuous(self) -> int: - """Returns the number of semi-continuous variables in the problem. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> t = op.variables.type - >>> indices = op.variables.add(types = [t.semi_continuous, t.semi_integer, t.semi_integer]) - >>> op.variables.get_num_semicontinuous() - 1 - """ - return self._types.count(VarTypes.semi_continuous) - - def get_num_semiinteger(self) -> int: - """Returns the number of semi-integer variables in the problem. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> t = op.variables.type - >>> indices = op.variables.add(types = [t.semi_continuous, t.semi_integer, t.semi_integer]) - >>> op.variables.get_num_semiinteger() - 2 - """ - return self._types.count(VarTypes.semi_integer) - - # pylint: disable=invalid-name - def add(self, obj: None = None, lb: Optional[List[float]] = None, - ub: Optional[List[float]] = None, types: str = "", names: Optional[List[str]] = None, - columns: None = None) -> range: - """Adds variables and related data to the problem. - - variables.add accepts the keyword arguments obj, lb, ub, types, names, and columns. - If more than one argument is specified, all arguments must have the same length. - - Note - `obj` and `columns` have not been supported yet. - Use `objective` and `linear_constraint` instead. - - Args: - lb: a list of floats specifying the lower bounds on the variables. - - ub: a list of floats specifying the upper bounds on the variables. - - types: must be either a list of single-character strings or a string containing - the types of the variables. - - Note - If types is specified, the problem type will be a MIP, even if all variables are - specified to be continuous. - - names: a list of strings. - - obj: not supported by Qiskit Aqua. Use `objective` instead. - - columns: not supported by Qiskit Aqua. Use `linear_constraints` instead. - - Returns: - an iterator containing the indices of the added variables. - - Raises: - QiskitOptimizationError: if arguments are not valid. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> from cplex import SparsePair, infinity - >>> op = QuadraticProgram() - >>> indices = op.linear_constraints.add(names = ["c0", "c1", "c2"]) - >>> indices = op.variables.add(obj = [1.0, 2.0, 3.0],\ - types = [op.variables.type.integer] * 3) - >>> indices = op.variables.add(obj = [1.0, 2.0, 3.0],\ - lb = [-1.0, 1.0, 0.0],\ - ub = [100.0, infinity, infinity],\ - types = [op.variables.type.integer] * 3,\ - names = ["0", "1", "2"],\ - columns = [SparsePair(ind = ['c0', 2], val = [1.0, -1.0]),\ - [['c2'],[2.0]],\ - SparsePair(ind = [0, 1], val = [3.0, 4.0])]) - - >>> op.variables.get_lower_bounds() - [0.0, 0.0, 0.0, -1.0, 1.0, 0.0] - >>> op.variables.get_cols("1") - SparsePair(ind = [2], val = [2.0]) - """ - if obj: - raise QiskitOptimizationError("Please use ObjectiveInterface instead of obj.") - if columns: - raise QiskitOptimizationError( - "Please use LinearConstraintInterface instead of columns.") - - start = self.get_num() - arg_list = init_list_args(lb, ub, types, names) - arg_lengths = [len(x) for x in arg_list] - if len(arg_lengths) == 0: - return range(start, start) - max_length = max(arg_lengths) - if max_length == 0: - return range(start, start) - for arg_length in arg_lengths: - if arg_length > 0 and arg_length != max_length: - raise QiskitOptimizationError("inconsistent arguments") - - lb = lb or [0] * max_length - ub = ub or [infinity] * max_length - types = types or [VarTypes.continuous] * max_length - for i, t in enumerate(types): - if t == VarTypes.binary and ub[i] == infinity: - ub[i] = 1 - self._lb.extend(lb) - self._ub.extend(ub) - self._types.extend(types) - - names = names or [''] * max_length - for i, name in enumerate(names): - if name == '': - names[i] = 'x' + str(start + i + 1) - self._names.extend(names) - self._index.build(self._names) - - return range(start, start + max_length) - - def delete(self, *args): - """Deletes variables from the problem. - - There are four forms by which variables.delete may be called. - - variables.delete() - deletes all variables from the problem. - - variables.delete(i) - i must be a variable name or index. Deletes the variable - whose index or name is i. - - variables.delete(s) - s must be a sequence of variable names or indices. Deletes - the variables with names or indices contained within s. - Equivalent to [variables.delete(i) for i in s]. - - variables.delete(begin, end) - begin and end must be variable indices or variable names. - Deletes the variables with indices between begin and end, - inclusive of end. Equivalent to - variables.delete(range(begin, end + 1)). This will give the - best performance when deleting batches of variables. - - Example usage: - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names=[str(i) for i in range(10)]) - >>> op.variables.get_num() - 10 - >>> op.variables.delete(8) - >>> op.variables.get_names() - ['0', '1', '2', '3', '4', '5', '6', '7', '9'] - >>> op.variables.delete("1", 3) - >>> op.variables.get_names() - ['0', '4', '5', '6', '7', '9'] - >>> op.variables.delete([2, "0", 5]) - >>> op.variables.get_names() - ['4', '6', '7'] - >>> op.variables.delete() - >>> op.variables.get_names() - [] - """ - - def _delete(i): - del self._names[i] - del self._ub[i] - del self._lb[i] - del self._types[i] - - if len(args) == 0: - # Delete all - self._names = [] - self._ub = [] - self._lb = [] - self._types = [] - # self._columns = [] - self._index = NameIndex() - - keys = self._index.convert(*args) - if isinstance(keys, int): - keys = [keys] - for i in sorted(keys, reverse=True): - _delete(i) - self._index.build(self._names) - - def set_lower_bounds(self, *args): - """Sets the lower bound for a variable or set of variables. - - There are two forms by which variables.set_lower_bounds may be - called. - - variables.set_lower_bounds(i, lb) - i must be a variable name or index and lb must be a real - number. Sets the lower bound of the variable whose index - or name is i to lb. - - variables.set_lower_bounds(seq_of_pairs) - seq_of_pairs must be a list or tuple of (i, lb) pairs, each - of which consists of a variable name or index and a real - number. Sets the lower bound of the specified variables to - the corresponding values. Equivalent to - [variables.set_lower_bounds(pair[0], pair[1]) for pair in seq_of_pairs]. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = ["x0", "x1", "x2"]) - >>> op.variables.set_lower_bounds(0, 1.0) - >>> op.variables.get_lower_bounds() - [1.0, 0.0, 0.0] - >>> op.variables.set_lower_bounds([(2, 3.0), ("x1", -1.0)]) - >>> op.variables.get_lower_bounds() - [1.0, -1.0, 3.0] - """ - - def _set(i, v): - self._lb[self._index.convert(i)] = v - - self._setter(_set, *args) - - def set_upper_bounds(self, *args): - """Sets the upper bound for a variable or set of variables. - - There are two forms by which variables.set_upper_bounds may be - called. - - variables.set_upper_bounds(i, ub) - i must be a variable name or index and ub must be a real - number. Sets the upper bound of the variable whose index - or name is i to ub. - - variables.set_upper_bounds(seq_of_pairs) - seq_of_pairs must be a list or tuple of (i, ub) pairs, each - of which consists of a variable name or index and a real - number. Sets the upper bound of the specified variables to - the corresponding values. Equivalent to - [variables.set_upper_bounds(pair[0], pair[1]) for pair in seq_of_pairs]. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = ["x0", "x1", "x2"]) - >>> op.variables.set_upper_bounds(0, 1.0) - >>> op.variables.set_upper_bounds([("x1", 10.0), (2, 3.0)]) - >>> op.variables.get_upper_bounds() - [1.0, 10.0, 3.0] - """ - - def _set(i, v): - self._ub[self._index.convert(i)] = v - - self._setter(_set, *args) - - def set_names(self, *args): - """Sets the name of a variable or set of variables. - - There are two forms by which variables.set_names may be - called. - - variables.set_names(i, name) - i must be a variable name or index and name must be a - string. - - variables.set_names(seq_of_pairs) - seq_of_pairs must be a list or tuple of (i, name) pairs, - each of which consists of a variable name or index and a - string. Sets the name of the specified variables to the - corresponding strings. Equivalent to - [variables.set_names(pair[0], pair[1]) for pair in seq_of_pairs]. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> t = op.variables.type - >>> indices = op.variables.add(types = [t.continuous, t.binary, t.integer]) - >>> op.variables.set_names(0, "first") - >>> op.variables.set_names([(2, "third"), (1, "second")]) - >>> op.variables.get_names() - ['first', 'second', 'third'] - """ - - def _set(i, v): - self._names[self._index.convert(i)] = v - - self._setter(_set, *args) - self._index.build(self._names) - - def set_types(self, *args): - """Sets the type of a variable or set of variables. - - There are two forms by which variables.set_types may be - called. - - variables.set_types(i, type) - i must be a variable name or index and name must be a - single-character string. - - variables.set_types(seq_of_pairs) - seq_of_pairs must be a list or tuple of (i, type) pairs, - each of which consists of a variable name or index and a - single-character string. Sets the type of the specified - variables to the corresponding strings. Equivalent to - [variables.set_types(pair[0], pair[1]) for pair in seq_of_pairs]. - - Note - If the types are set, the problem will be treated as a MIP, - even if all variable types are continuous. - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = [str(i) for i in range(5)]) - >>> op.variables.set_types(0, op.variables.type.continuous) - >>> op.variables.set_types([("1", op.variables.type.integer),\ - ("2", op.variables.type.binary),\ - ("3", op.variables.type.semi_continuous),\ - ("4", op.variables.type.semi_integer)]) - >>> op.variables.get_types() - ['C', 'I', 'B', 'S', 'N'] - >>> op.variables.type[op.variables.get_types(0)] - 'continuous' - """ - - def _set(i, v): - if v not in [CPX_CONTINUOUS, CPX_BINARY, CPX_INTEGER, CPX_SEMICONT, CPX_SEMIINT]: - raise QiskitOptimizationError( - "Second argument must be a string, as per VarTypes constants.") - self._types[self._index.convert(i)] = v - - self._setter(_set, *args) - - def get_lower_bounds(self, *args) -> Union[float, List[float]]: - """Returns the lower bounds on variables from the problem. - - There are four forms by which variables.get_lower_bounds may be called. - - variables.get_lower_bounds() - return the lower bounds on all variables from the problem. - - variables.get_lower_bounds(i) - i must be a variable name or index. Returns the lower - bound on the variable whose index or name is i. - - variables.get_lower_bounds(s) - s must be a sequence of variable names or indices. Returns - the lower bounds on the variables with indices the members - of s. Equivalent to - [variables.get_lower_bounds(i) for i in s] - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(lb = [1.5 * i for i in range(10)],\ - names = [str(i) for i in range(10)]) - >>> op.variables.get_num() - 10 - >>> op.variables.get_lower_bounds(8) - 12.0 - >>> op.variables.get_lower_bounds("1",3) - [1.5, 3.0, 4.5] - >>> op.variables.get_lower_bounds([2,"0",5]) - [3.0, 0.0, 7.5] - >>> op.variables.get_lower_bounds() - [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5] - """ - - def _get(i): - return self._lb[i] - - if len(args) == 0: - return copy.deepcopy(self._lb) - keys = self._index.convert(*args) - return self._getter(_get, keys) - - def get_upper_bounds(self, *args) -> Union[float, List[float]]: - """Returns the upper bounds on variables from the problem. - - There are four forms by which variables.get_upper_bounds may be called. - - variables.get_upper_bounds() - return the upper bounds on all variables from the problem. - - variables.get_upper_bounds(i) - i must be a variable name or index. Returns the upper - bound on the variable whose index or name is i. - - variables.get_upper_bounds(s) - s must be a sequence of variable names or indices. Returns - the upper bounds on the variables with indices the members - of s. Equivalent to - [variables.get_upper_bounds(i) for i in s] - - variables.get_upper_bounds(begin, end) - begin and end must be variable indices or variable names. - Returns the upper bounds on the variables with indices between - begin and end, inclusive of end. Equivalent to - variables.get_upper_bounds(range(begin, end + 1)). - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(ub = [(1.5 * i) + 1.0 for i in range(10)],\ - names = [str(i) for i in range(10)]) - >>> op.variables.get_num() - 10 - >>> op.variables.get_upper_bounds(8) - 13.0 - >>> op.variables.get_upper_bounds("1",3) - [2.5, 4.0, 5.5] - >>> op.variables.get_upper_bounds([2,"0",5]) - [4.0, 1.0, 8.5] - >>> op.variables.get_upper_bounds() - [1.0, 2.5, 4.0, 5.5, 7.0, 8.5, 10.0, 11.5, 13.0, 14.5] - """ - - def _get(i): - return self._ub[i] - - if len(args) == 0: - return copy.deepcopy(self._ub) - keys = self._index.convert(*args) - return self._getter(_get, keys) - - def get_names(self, *args) -> Union[str, List[str]]: - """Returns the names of variables from the problem. - - There are four forms by which variables.get_names may be called. - - variables.get_names() - return the names of all variables from the problem. - - variables.get_names(i) - i must be a variable index. Returns the name of variable i. - - variables.get_names(s) - s must be a sequence of variable indices. Returns the - names of the variables with indices the members of s. - Equivalent to [variables.get_names(i) for i in s] - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> indices = op.variables.add(names = ['x' + str(i) for i in range(10)]) - >>> op.variables.get_num() - 10 - >>> op.variables.get_names(8) - 'x8' - >>> op.variables.get_names(1,3) - ['x1', 'x2', 'x3'] - >>> op.variables.get_names([2,0,5]) - ['x2', 'x0', 'x5'] - >>> op.variables.get_names() - ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9'] - """ - - def _get(i): - return self._names[i] - - if len(args) == 0: - return copy.deepcopy(self._names) - keys = self._index.convert(*args) - return self._getter(_get, keys) - - def get_types(self, *args) -> Union[str, List[str]]: - """Returns the types of variables from the problem. - - There are four forms by which variables.types may be called. - - variables.types() - return the types of all variables from the problem. - - variables.types(i) - i must be a variable name or index. Returns the type of - the variable whose index or name is i. - - variables.types(s) - s must be a sequence of variable names or indices. Returns - the types of the variables with indices the members of s. - Equivalent to [variables.get_types(i) for i in s] - - >>> from qiskit.optimization import QuadraticProgram - >>> op = QuadraticProgram() - >>> t = op.variables.type - >>> indices = op.variables.add(names = [str(i) for i in range(5)],\ - types = [t.continuous, t.integer,\ - t.binary, t.semi_continuous, t.semi_integer]) - >>> op.variables.get_num() - 5 - >>> op.variables.get_types(3) - 'S' - >>> op.variables.get_types(1,3) - ['I', 'B', 'S'] - >>> op.variables.get_types([2,0,4]) - ['B', 'C', 'N'] - >>> op.variables.get_types() - ['C', 'I', 'B', 'S', 'N'] - """ - - def _get(i): - return self._types[i] - - if len(args) == 0: - return copy.deepcopy(self._types) - keys = self._index.convert(*args) - return self._getter(_get, keys) - - def get_cols(self, *args): - """get_cols is not supported""" - raise NotImplementedError("Please use LinearConstraintInterface instead.") - - def get_obj(self, *args): - """get_obj is not supported""" - raise NotImplementedError("Please use ObjectiveInterface instead.") diff --git a/test/optimization/test_linear_constraint.py b/test/optimization/test_linear_constraint.py new file mode 100644 index 0000000000..3216e728d5 --- /dev/null +++ b/test/optimization/test_linear_constraint.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" Test LinearConstraint """ + +import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase +import logging +import numpy as np + +from qiskit.optimization import QuadraticProgram, QiskitOptimizationError +from qiskit.optimization.problems import ConstraintSense + +logger = logging.getLogger(__name__) + + +class TestLinearConstraint(QiskitOptimizationTestCase): + """Test LinearConstraintInterface.""" + + def test_init(self): + """ test init. """ + + quadratic_program = QuadraticProgram() + for _ in range(5): + quadratic_program.continuous_var() + self.assertEqual(quadratic_program.get_num_linear_constraints(), 0) + + coefficients = np.array([i for i in range(5)]) + + # equality constraints + quadratic_program.linear_eq_constraint() + self.assertEqual(quadratic_program.get_num_linear_constraints(), 1) + self.assertEqual(quadratic_program.linear_constraints[0].name, 'c0') + self.assertEqual(len(quadratic_program.linear_constraints[0].linear.coefficients_as_dict()), + 0) + self.assertEqual(quadratic_program.linear_constraints[0].sense, ConstraintSense.eq) + self.assertEqual(quadratic_program.linear_constraints[0].rhs, 0.0) + self.assertEqual(quadratic_program.linear_constraints[0], + quadratic_program.get_linear_constraint('c0')) + self.assertEqual(quadratic_program.linear_constraints[0], + quadratic_program.get_linear_constraint(0)) + + with self.assertRaises(QiskitOptimizationError): + quadratic_program.linear_eq_constraint(name='c0') + + quadratic_program.linear_eq_constraint('c1', coefficients, 1.0) + self.assertEqual(quadratic_program.get_num_linear_constraints(), 2) + self.assertEqual(quadratic_program.linear_constraints[1].name, 'c1') + self.assertTrue(( + quadratic_program.linear_constraints[1].linear.coefficients_as_array( + ) == coefficients + ).all()) + self.assertEqual(quadratic_program.linear_constraints[1].sense, ConstraintSense.eq) + self.assertEqual(quadratic_program.linear_constraints[1].rhs, 1.0) + self.assertEqual(quadratic_program.linear_constraints[1], + quadratic_program.get_linear_constraint('c1')) + self.assertEqual(quadratic_program.linear_constraints[1], + quadratic_program.get_linear_constraint(1)) + + # geq constraints + quadratic_program.linear_geq_constraint() + self.assertEqual(quadratic_program.get_num_linear_constraints(), 3) + self.assertEqual(quadratic_program.linear_constraints[2].name, 'c2') + self.assertEqual(len(quadratic_program.linear_constraints[2].linear.coefficients_as_dict()), + 0) + self.assertEqual(quadratic_program.linear_constraints[2].sense, ConstraintSense.geq) + self.assertEqual(quadratic_program.linear_constraints[2].rhs, 0.0) + self.assertEqual(quadratic_program.linear_constraints[2], + quadratic_program.get_linear_constraint('c2')) + self.assertEqual(quadratic_program.linear_constraints[2], + quadratic_program.get_linear_constraint(2)) + + with self.assertRaises(QiskitOptimizationError): + quadratic_program.linear_geq_constraint(name='c2') + + quadratic_program.linear_geq_constraint('c3', coefficients, 1.0) + self.assertEqual(quadratic_program.get_num_linear_constraints(), 4) + self.assertEqual(quadratic_program.linear_constraints[3].name, 'c3') + self.assertTrue(( + quadratic_program.linear_constraints[3].linear.coefficients_as_array( + ) == coefficients + ).all()) + self.assertEqual(quadratic_program.linear_constraints[3].sense, ConstraintSense.geq) + self.assertEqual(quadratic_program.linear_constraints[3].rhs, 1.0) + self.assertEqual(quadratic_program.linear_constraints[3], + quadratic_program.get_linear_constraint('c3')) + self.assertEqual(quadratic_program.linear_constraints[3], + quadratic_program.get_linear_constraint(3)) + + # leq constraints + quadratic_program.linear_leq_constraint() + self.assertEqual(quadratic_program.get_num_linear_constraints(), 5) + self.assertEqual(quadratic_program.linear_constraints[4].name, 'c4') + self.assertEqual(len(quadratic_program.linear_constraints[4].linear.coefficients_as_dict()), + 0) + self.assertEqual(quadratic_program.linear_constraints[4].sense, ConstraintSense.leq) + self.assertEqual(quadratic_program.linear_constraints[4].rhs, 0.0) + self.assertEqual(quadratic_program.linear_constraints[4], + quadratic_program.get_linear_constraint('c4')) + self.assertEqual(quadratic_program.linear_constraints[4], + quadratic_program.get_linear_constraint(4)) + + with self.assertRaises(QiskitOptimizationError): + quadratic_program.linear_leq_constraint(name='c4') + + quadratic_program.linear_leq_constraint('c5', coefficients, 1.0) + self.assertEqual(quadratic_program.get_num_linear_constraints(), 6) + self.assertEqual(quadratic_program.linear_constraints[5].name, 'c5') + self.assertTrue(( + quadratic_program.linear_constraints[5].linear.coefficients_as_array( + ) == coefficients + ).all()) + self.assertEqual(quadratic_program.linear_constraints[5].sense, ConstraintSense.leq) + self.assertEqual(quadratic_program.linear_constraints[5].rhs, 1.0) + self.assertEqual(quadratic_program.linear_constraints[5], + quadratic_program.get_linear_constraint('c5')) + self.assertEqual(quadratic_program.linear_constraints[5], + quadratic_program.get_linear_constraint(5)) + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_linear_constraints.py b/test/optimization/test_linear_constraints.py deleted file mode 100644 index f9879fbe1f..0000000000 --- a/test/optimization/test_linear_constraints.py +++ /dev/null @@ -1,279 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -""" Test LinearConstraintInterface """ - -import unittest -from test.optimization.optimization_test_case import QiskitOptimizationTestCase -import logging - -from qiskit.optimization import QuadraticProgram - -logger = logging.getLogger(__name__) - -_HAS_CPLEX = False -try: - from cplex import SparsePair - _HAS_CPLEX = True -except ImportError: - logger.info('CPLEX is not installed.') - - -class TestLinearConstraints(QiskitOptimizationTestCase): - """Test LinearConstraintInterface.""" - - def setUp(self) -> None: - super().setUp() - if not _HAS_CPLEX: - self.skipTest('CPLEX is not installed.') - - def test_get_num(self): - """ test get num """ - op = QuadraticProgram() - op.linear_constraints.add(names=["c1", "c2", "c3"]) - self.assertEqual(op.linear_constraints.get_num(), 3) - - def test_add(self): - """ test add """ - op = QuadraticProgram() - op.variables.add(names=["x1", "x2", "x3"]) - op.linear_constraints.add( - lin_expr=[SparsePair(ind=["x1", "x3"], val=[1.0, -1.0]), - SparsePair(ind=["x1", "x2"], val=[1.0, 1.0]), - SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), - SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])], - senses=["E", "L", "G", "R"], - rhs=[0.0, 1.0, -1.0, 2.0], - range_values=[0.0, 0.0, 0.0, -10.0], - names=["c0", "c1", "c2", "c3"]) - self.assertListEqual(op.linear_constraints.get_rhs(), [0.0, 1.0, -1.0, 2.0]) - - def test_delete(self): - """ test delete """ - op = QuadraticProgram() - op.linear_constraints.add(names=[str(i) for i in range(10)]) - self.assertEqual(op.linear_constraints.get_num(), 10) - op.linear_constraints.delete(8) - self.assertListEqual(op.linear_constraints.get_names(), - ['0', '1', '2', '3', '4', '5', '6', '7', '9']) - op.linear_constraints.delete("1", 3) - self.assertListEqual(op.linear_constraints.get_names(), ['0', '4', '5', '6', '7', '9']) - op.linear_constraints.delete([2, "0", 5]) - self.assertListEqual(op.linear_constraints.get_names(), ['4', '6', '7']) - op.linear_constraints.delete() - self.assertListEqual(op.linear_constraints.get_names(), []) - - def test_rhs(self): - """ test rhs """ - op = QuadraticProgram() - op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) - self.assertListEqual(op.linear_constraints.get_rhs(), [0.0, 0.0, 0.0, 0.0]) - op.linear_constraints.set_rhs("c1", 1.0) - self.assertListEqual(op.linear_constraints.get_rhs(), [0.0, 1.0, 0.0, 0.0]) - op.linear_constraints.set_rhs([("c3", 2.0), (2, -1.0)]) - self.assertListEqual(op.linear_constraints.get_rhs(), [0.0, 1.0, -1.0, 2.0]) - - def test_set_names(self): - """ test set names """ - op = QuadraticProgram() - op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) - op.linear_constraints.set_names("c1", "second") - self.assertEqual(op.linear_constraints.get_names(1), 'second') - op.linear_constraints.set_names([("c3", "last"), (2, "middle")]) - self.assertListEqual(op.linear_constraints.get_names(), ['c0', 'second', 'middle', 'last']) - - def test_set_senses(self): - """ test set senses """ - op = QuadraticProgram() - op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) - self.assertListEqual(op.linear_constraints.get_senses(), ['E', 'E', 'E', 'E']) - op.linear_constraints.set_senses("c1", "G") - self.assertEqual(op.linear_constraints.get_senses(1), 'G') - op.linear_constraints.set_senses([("c3", "L"), (2, "R")]) - self.assertListEqual(op.linear_constraints.get_senses(), ['E', 'G', 'R', 'L']) - - def test_set_linear_components(self): - """ test set linear components """ - op = QuadraticProgram() - op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) - op.variables.add(names=["x0", "x1"]) - op.linear_constraints.set_linear_components("c0", [["x0"], [1.0]]) - s_p = op.linear_constraints.get_rows("c0") - self.assertListEqual(s_p.ind, [0]) - self.assertListEqual(s_p.val, [1.0]) - op.linear_constraints.set_linear_components( - [("c3", SparsePair(ind=["x1"], val=[-1.0])), - (2, [[0, 1], [-2.0, 3.0]])] - ) - s_p = op.linear_constraints.get_rows("c3") - self.assertListEqual(s_p.ind, [1]) - self.assertListEqual(s_p.val, [-1.0]) - s_p = op.linear_constraints.get_rows(2) - self.assertListEqual(s_p.ind, [0, 1]) - self.assertListEqual(s_p.val, [-2.0, 3.0]) - - def test_set_range_values(self): - """ test set range values """ - op = QuadraticProgram() - op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) - op.linear_constraints.set_range_values("c1", 1.0) - self.assertListEqual(op.linear_constraints.get_range_values(), [0.0, 1.0, 0.0, 0.0]) - op.linear_constraints.set_range_values([("c3", 2.0), (2, -1.0)]) - self.assertListEqual(op.linear_constraints.get_range_values(), [0.0, 1.0, -1.0, 2.0]) - - def test_set_coeffients(self): - """ test set coefficients """ - op = QuadraticProgram() - op.linear_constraints.add(names=["c0", "c1", "c2", "c3"]) - op.variables.add(names=["x0", "x1"]) - op.linear_constraints.set_coefficients("c0", "x1", 1.0) - s_p = op.linear_constraints.get_rows(0) - self.assertListEqual(s_p.ind, [1]) - self.assertListEqual(s_p.val, [1.0]) - op.linear_constraints.set_coefficients([("c2", "x0", 2.0), - ("c2", "x1", -1.0)]) - s_p = op.linear_constraints.get_rows("c2") - self.assertListEqual(s_p.ind, [0, 1]) - self.assertListEqual(s_p.val, [2.0, -1.0]) - - def test_get_rhs(self): - """ test get rhs """ - op = QuadraticProgram() - op.linear_constraints.add(rhs=[1.5 * i for i in range(10)], - names=[str(i) for i in range(10)]) - self.assertEqual(op.linear_constraints.get_num(), 10) - self.assertAlmostEqual(op.linear_constraints.get_rhs(8), 12.0) - self.assertListEqual(op.linear_constraints.get_rhs([2, "0", 5]), [3.0, 0.0, 7.5]) - self.assertEqual(op.linear_constraints.get_rhs(), - [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5]) - - def test_get_senses(self): - """ test get senses """ - op = QuadraticProgram() - op.linear_constraints.add( - senses=["E", "G", "L", "R"], - names=[str(i) for i in range(4)]) - self.assertEqual(op.linear_constraints.get_num(), 4) - self.assertEqual(op.linear_constraints.get_senses(1), 'G') - self.assertListEqual(op.linear_constraints.get_senses("1", 3), ['G', 'L', 'R']) - self.assertListEqual(op.linear_constraints.get_senses([2, "0", 1]), ['L', 'E', 'G']) - self.assertListEqual(op.linear_constraints.get_senses(), ['E', 'G', 'L', 'R']) - - def test_get_range_values(self): - """ test get range values """ - op = QuadraticProgram() - op.linear_constraints.add( - range_values=[1.5 * i for i in range(10)], - senses=["R"] * 10, - names=[str(i) for i in range(10)]) - self.assertEqual(op.linear_constraints.get_num(), 10) - self.assertEqual(op.linear_constraints.get_range_values(8), 12.0) - self.assertListEqual(op.linear_constraints.get_range_values("1", 3), [1.5, 3.0, 4.5]) - self.assertListEqual(op.linear_constraints.get_range_values([2, "0", 5]), [3.0, 0.0, 7.5]) - self.assertListEqual(op.linear_constraints.get_range_values(), - [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5]) - - def test_get_coefficients(self): - """ test get coefficients """ - op = QuadraticProgram() - op.variables.add(names=["x0", "x1"]) - op.linear_constraints.add( - names=["c0", "c1"], - lin_expr=[[[1], [1.0]], [[0, 1], [2.0, -1.0]]]) - self.assertAlmostEqual(op.linear_constraints.get_coefficients("c0", "x1"), 1.0) - self.assertListEqual( - op.linear_constraints.get_coefficients([("c1", "x0"), ("c1", "x1")]), [2.0, -1.0]) - - def test_get_rows(self): - """ test get rows """ - op = QuadraticProgram() - op.variables.add(names=["x1", "x2", "x3"]) - op.linear_constraints.add( - names=["c0", "c1", "c2", "c3"], - lin_expr=[SparsePair(ind=["x1", "x3"], val=[1.0, -1.0]), - SparsePair(ind=["x1", "x2"], val=[1.0, 1.0]), - SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), - SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])]) - s_p = op.linear_constraints.get_rows(0) - self.assertListEqual(s_p.ind, [0, 2]) - self.assertListEqual(s_p.val, [1.0, -1.0]) - - s_p = op.linear_constraints.get_rows(1, 3) - self.assertListEqual(s_p[0].ind, [0, 1]) - self.assertListEqual(s_p[0].val, [1.0, 1.0]) - self.assertListEqual(s_p[1].ind, [0, 1, 2]) - self.assertListEqual(s_p[1].val, [-1.0, -1.0, -1.0]) - self.assertListEqual(s_p[2].ind, [1, 2]) - self.assertListEqual(s_p[2].val, [10.0, -2.0]) - - s_p = op.linear_constraints.get_rows(['c2', 0]) - self.assertListEqual(s_p[0].ind, [0, 1, 2]) - self.assertListEqual(s_p[0].val, [-1.0, -1.0, -1.0]) - self.assertListEqual(s_p[1].ind, [0, 2]) - self.assertListEqual(s_p[1].val, [1.0, -1.0]) - - s_p = op.linear_constraints.get_rows() - self.assertListEqual(s_p[0].ind, [0, 2]) - self.assertListEqual(s_p[0].val, [1.0, -1.0]) - self.assertListEqual(s_p[1].ind, [0, 1]) - self.assertListEqual(s_p[1].val, [1.0, 1.0]) - self.assertListEqual(s_p[2].ind, [0, 1, 2]) - self.assertListEqual(s_p[2].val, [-1.0, -1.0, -1.0]) - self.assertListEqual(s_p[3].ind, [1, 2]) - self.assertListEqual(s_p[3].val, [10.0, -2.0]) - - def test_get_num_nonzeros(self): - """ test get num non zeros """ - op = QuadraticProgram() - op.variables.add(names=["x1", "x2", "x3"]) - op.linear_constraints.add( - names=["c0", "c1", "c2", "c3"], - lin_expr=[SparsePair(ind=["x1", "x3"], val=[1.0, -1.0]), - SparsePair(ind=["x1", "x2"], val=[1.0, 1.0]), - SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), - SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])]) - self.assertEqual(op.linear_constraints.get_num_nonzeros(), 9) - op.linear_constraints.set_coefficients("c0", "x3", 0) - self.assertEqual(op.linear_constraints.get_num_nonzeros(), 8) - - def test_get_names(self): - """ test get names """ - op = QuadraticProgram() - op.linear_constraints.add(names=["c" + str(i) for i in range(10)]) - self.assertEqual(op.linear_constraints.get_num(), 10) - self.assertEqual(op.linear_constraints.get_names(8), 'c8') - self.assertListEqual(op.linear_constraints.get_names([2, 0, 5]), ['c2', 'c0', 'c5']) - self.assertEqual(op.linear_constraints.get_names(), - ['c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9']) - - def test_get_histogram(self): - """ test get histogram """ - op = QuadraticProgram() - with self.assertRaises(NotImplementedError): - op.linear_constraints.get_histogram() - - def test_empty_names(self): - """ test empty names """ - op = QuadraticProgram() - r = op.linear_constraints.add(names=['', '', '']) - self.assertListEqual(op.linear_constraints.get_names(), ['c1', 'c2', 'c3']) - self.assertEqual(r, range(0, 3)) - r = op.linear_constraints.add(names=['a', '', 'c']) - self.assertEqual(r, range(3, 6)) - self.assertListEqual(op.linear_constraints.get_names(), - ['c1', 'c2', 'c3', 'a', 'c5', 'c']) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/optimization/test_linear_expression.py b/test/optimization/test_linear_expression.py new file mode 100644 index 0000000000..648853a064 --- /dev/null +++ b/test/optimization/test_linear_expression.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" Test LinearExpression """ + +import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase +import logging +import numpy as np +from scipy.sparse import dok_matrix + +from qiskit.optimization import QuadraticProgram +from qiskit.optimization.problems import LinearExpression + +logger = logging.getLogger(__name__) + + +class TestLinearExpression(QiskitOptimizationTestCase): + """Test LinearExpression.""" + + def test_init(self): + """ test init. """ + + quadratic_program = QuadraticProgram() + for _ in range(5): + quadratic_program.continuous_var() + + coefficients_list = [i for i in range(5)] + coefficients_array = np.array(coefficients_list) + coefficients_dok = dok_matrix([coefficients_list]) + coefficients_dict_int = {i: i for i in range(1, 5)} + coefficients_dict_str = {'x{}'.format(i): i for i in range(1, 5)} + + for coeffs in [coefficients_list, + coefficients_array, + coefficients_dok, + coefficients_dict_int, + coefficients_dict_str]: + + linear = LinearExpression(quadratic_program, coeffs) + self.assertEqual((linear.coefficients != coefficients_dok).nnz, 0) + self.assertTrue((linear.coefficients_as_array() == coefficients_list).all()) + self.assertDictEqual(linear.coefficients_as_dict(use_index=True), coefficients_dict_int) + self.assertDictEqual(linear.coefficients_as_dict(use_index=False), + coefficients_dict_str) + + def test_setters(self): + """ test setters. """ + + quadratic_program = QuadraticProgram() + for _ in range(5): + quadratic_program.continuous_var() + + zeros = np.zeros(quadratic_program.get_num_vars()) + linear = LinearExpression(quadratic_program, zeros) + + coefficients_list = [i for i in range(5)] + coefficients_array = np.array(coefficients_list) + coefficients_dok = dok_matrix([coefficients_list]) + coefficients_dict_int = {i: i for i in range(1, 5)} + coefficients_dict_str = {'x{}'.format(i): i for i in range(1, 5)} + + for coeffs in [coefficients_list, + coefficients_array, + coefficients_dok, + coefficients_dict_int, + coefficients_dict_str]: + + linear.coefficients = coeffs + self.assertEqual((linear.coefficients != coefficients_dok).nnz, 0) + self.assertTrue((linear.coefficients_as_array() == coefficients_list).all()) + self.assertDictEqual(linear.coefficients_as_dict(use_index=True), coefficients_dict_int) + self.assertDictEqual(linear.coefficients_as_dict(use_index=False), + coefficients_dict_str) + + def test_evaluate(self): + """ test evaluate. """ + + quadratic_program = QuadraticProgram() + x = [quadratic_program.continuous_var() for _ in range(5)] + + coefficients_list = [i for i in range(5)] + linear = LinearExpression(quadratic_program, coefficients_list) + + values_list = [i for i in range(len(x))] + values_array = np.array(values_list) + values_dict_int = {i: i for i in range(len(x))} + values_dict_str = {'x{}'.format(i): i for i in range(len(x))} + + for values in [values_list, values_array, values_dict_int, values_dict_str]: + self.assertEqual(linear.evaluate(values), 30) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index 692231c585..f8cb2b09ea 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -14,416 +14,121 @@ """ Test QuadraticProgram """ -import os.path -import tempfile import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase import logging -from qiskit.optimization import QuadraticProgram, QiskitOptimizationError -from qiskit.optimization.problems.quadratic_program import SubstitutionStatus +from qiskit.optimization import QuadraticProgram, QiskitOptimizationError, infinity +from qiskit.optimization.problems import VarType logger = logging.getLogger(__name__) -_HAS_CPLEX = False -try: - from cplex import Cplex, SparsePair, SparseTriple, infinity - _HAS_CPLEX = True -except ImportError: - logger.info('CPLEX is not installed.') - class TestQuadraticProgram(QiskitOptimizationTestCase): """Test QuadraticProgram without the members that have separate test classes (VariablesInterface, etc).""" - def setUp(self): - super().setUp() - if not _HAS_CPLEX: - self.skipTest('CPLEX is not installed.') - - self.resource_file = './test/optimization/resources/op_ip2.lp' - - def test_constructor1(self): + def test_constructor(self): """ test constructor """ - op = QuadraticProgram() - self.assertEqual(op.get_problem_name(), '') - op.variables.add(names=['x1', 'x2', 'x3']) - self.assertEqual(op.variables.get_num(), 3) + quadratic_program = QuadraticProgram() + self.assertEqual(quadratic_program.name, '') + + quadratic_program = QuadraticProgram('test') + self.assertEqual(quadratic_program.name, 'test') + + quadratic_program.name = '' + self.assertEqual(quadratic_program.name, '') + + def test_variables_handling(self): + """ test add variables """ + quadratic_program = QuadraticProgram() + + self.assertEqual(quadratic_program.get_num_vars(), 0) + self.assertEqual(quadratic_program.get_num_continuous_vars(), 0) + self.assertEqual(quadratic_program.get_num_binary_vars(), 0) + self.assertEqual(quadratic_program.get_num_integer_vars(), 0) + + x_0 = quadratic_program.continuous_var() + self.assertEqual(x_0.name, 'x0') + self.assertEqual(x_0.lowerbound, 0) + self.assertEqual(x_0.upperbound, infinity) + self.assertEqual(x_0.vartype, VarType.continuous) + + self.assertEqual(quadratic_program.get_num_vars(), 1) + self.assertEqual(quadratic_program.get_num_continuous_vars(), 1) + self.assertEqual(quadratic_program.get_num_binary_vars(), 0) + self.assertEqual(quadratic_program.get_num_integer_vars(), 0) + + x_1 = quadratic_program.continuous_var(name='x1', lowerbound=5, upperbound=10) + self.assertEqual(x_1.name, 'x1') + self.assertEqual(x_1.lowerbound, 5) + self.assertEqual(x_1.upperbound, 10) + self.assertEqual(x_1.vartype, VarType.continuous) + + self.assertEqual(quadratic_program.get_num_vars(), 2) + self.assertEqual(quadratic_program.get_num_continuous_vars(), 2) + self.assertEqual(quadratic_program.get_num_binary_vars(), 0) + self.assertEqual(quadratic_program.get_num_integer_vars(), 0) + + x_2 = quadratic_program.binary_var() + self.assertEqual(x_2.name, 'x2') + self.assertEqual(x_2.lowerbound, 0) + self.assertEqual(x_2.upperbound, 1) + self.assertEqual(x_2.vartype, VarType.binary) + + self.assertEqual(quadratic_program.get_num_vars(), 3) + self.assertEqual(quadratic_program.get_num_continuous_vars(), 2) + self.assertEqual(quadratic_program.get_num_binary_vars(), 1) + self.assertEqual(quadratic_program.get_num_integer_vars(), 0) + + x_3 = quadratic_program.binary_var(name='x3') + self.assertEqual(x_3.name, 'x3') + self.assertEqual(x_3.lowerbound, 0) + self.assertEqual(x_3.upperbound, 1) + self.assertEqual(x_3.vartype, VarType.binary) + + self.assertEqual(quadratic_program.get_num_vars(), 4) + self.assertEqual(quadratic_program.get_num_continuous_vars(), 2) + self.assertEqual(quadratic_program.get_num_binary_vars(), 2) + self.assertEqual(quadratic_program.get_num_integer_vars(), 0) + + x_4 = quadratic_program.integer_var() + self.assertEqual(x_4.name, 'x4') + self.assertEqual(x_4.lowerbound, 0) + self.assertEqual(x_4.upperbound, infinity) + self.assertEqual(x_4.vartype, VarType.integer) + + self.assertEqual(quadratic_program.get_num_vars(), 5) + self.assertEqual(quadratic_program.get_num_continuous_vars(), 2) + self.assertEqual(quadratic_program.get_num_binary_vars(), 2) + self.assertEqual(quadratic_program.get_num_integer_vars(), 1) + + x_5 = quadratic_program.integer_var(name='x5', lowerbound=5, upperbound=10) + self.assertEqual(x_5.name, 'x5') + self.assertEqual(x_5.lowerbound, 5) + self.assertEqual(x_5.upperbound, 10) + self.assertEqual(x_5.vartype, VarType.integer) + + self.assertEqual(quadratic_program.get_num_vars(), 6) + self.assertEqual(quadratic_program.get_num_continuous_vars(), 2) + self.assertEqual(quadratic_program.get_num_binary_vars(), 2) + self.assertEqual(quadratic_program.get_num_integer_vars(), 2) - def test_constructor2(self): - """ test constructor 2 """ with self.assertRaises(QiskitOptimizationError): - _ = QuadraticProgram("unknown") - # If filename does not exist, an exception is raised. - - def test_constructor_context(self): - """ test constructor context """ - with QuadraticProgram() as op: - op.variables.add(names=['x1', 'x2', 'x3']) - self.assertEqual(op.variables.get_num(), 3) - - def test_end(self): - """ test end """ - op = QuadraticProgram() - self.assertIsNone(op.end()) - - def test_solve(self): - """ test solve """ - op = QuadraticProgram() - self.assertIsNone(op.solve()) - - def test_read1(self): - """ test read 1""" - op = QuadraticProgram() - op.read(self.resource_file) - self.assertEqual(op.variables.get_num(), 3) - - def test_write1(self): - """ test write 1 """ - op = QuadraticProgram() - op.variables.add(names=['x1', 'x2', 'x3']) - file, filename = tempfile.mkstemp(suffix='.lp') - os.close(file) - op.write(filename) - self.assertEqual(os.path.exists(filename), 1) - - def test_write2(self): - """ test write 2 """ - op1 = QuadraticProgram() - op1.variables.add(names=['x1', 'x2', 'x3']) - file, filename = tempfile.mkstemp(suffix='.lp') - os.close(file) - op1.write(filename) - op2 = QuadraticProgram() - op2.read(filename) - self.assertEqual(op2.variables.get_num(), 3) + quadratic_program.continuous_var(name='x0') - def test_write3(self): - """ test write 3 """ - op = QuadraticProgram() - op.variables.add(names=['x1', 'x2', 'x3']) - - class NoOpStream: - """ stream """ - - def __init__(self): - self.was_called = False - - def write(self, byt): - """ write """ - # pylint: disable=unused-argument - self.was_called = True - pass - - def flush(self): - """ flush """ - pass - - stream = NoOpStream() - op.write_to_stream(stream) - self.assertEqual(stream.was_called, True) with self.assertRaises(QiskitOptimizationError): - op.write_to_stream("this-is-no-stream") - - def test_write4(self): - """ test write 4 """ - # Writes a problem as a string in the given file format. - op = QuadraticProgram() - op.variables.add(names=['x1', 'x2', 'x3']) - lp_str = op.write_as_string("lp") - self.assertGreater(len(lp_str), 0) - - def test_problem_type1(self): - """ test problem type 1 """ - op = QuadraticProgram() - op.read(self.resource_file) - self.assertEqual(op.get_problem_type(), op.problem_type.QP) - self.assertEqual(op.problem_type[op.get_problem_type()], 'QP') - - def test_problem_type2(self): - """ test problem type 2""" - op = QuadraticProgram() - op.set_problem_type(op.problem_type.LP) - self.assertEqual(op.get_problem_type(), op.problem_type.LP) - self.assertEqual(op.problem_type[op.get_problem_type()], 'LP') - - def test_problem_type3(self): - """ test problem type 3""" - op = QuadraticProgram() - self.assertEqual(op.get_problem_type(), op.problem_type.LP) - op.variables.add(names=['x1', 'x2', 'x3'], types='B' * 3) - op.objective.set_linear([('x1', 2.0), ('x3', 0.5)]) - self.assertEqual(op.get_problem_type(), op.problem_type.MILP) - op.objective.set_quadratic([ - SparsePair(ind=[0, 1], val=[2.0, 3.0]), - SparsePair(ind=[0], val=[3.0]), - SparsePair(ind=[], val=[]) - ]) - self.assertEqual(op.get_problem_type(), op.problem_type.MIQP) - op.linear_constraints.add( - lin_expr=[SparsePair(ind=["x1", "x3"], val=[1.0, -1.0]), - SparsePair(ind=["x1", "x2"], val=[1.0, 1.0]), - SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), - SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])], - senses=["E", "L", "G", "R"], - rhs=[0.0, 1.0, -1.0, 2.0], - range_values=[0.0, 0.0, 0.0, -10.0], - names=["c0", "c1", "c2", "c3"]) - self.assertEqual(op.get_problem_type(), op.problem_type.MIQP) - op.quadratic_constraints.add( - lin_expr=SparsePair(ind=['x1', 'x3'], val=[1.0, -1.0]), - quad_expr=SparseTriple(ind1=['x1', 'x2'], ind2=['x2', 'x3'], val=[1.0, -1.0]), - sense='E', - rhs=1.0 - ) - self.assertEqual(op.get_problem_type(), op.problem_type.MIQCP) - - def test_problem_name(self): - """ test problem name """ - op = QuadraticProgram() - op.set_problem_name("test") - # test - self.assertEqual(op.get_problem_name(), "test") - - def test_from_and_to_cplex(self): - """ test from_cplex and to_cplex """ - op = Cplex() - op.variables.add(names=['x1', 'x2', 'x3'], types='B' * 3) - op.objective.set_linear([('x1', 2.0), ('x3', 0.5)]) - op.objective.set_quadratic([ - SparsePair(ind=[0, 1], val=[2.0, 3.0]), - SparsePair(ind=[0], val=[3.0]), - SparsePair(ind=[], val=[]) - ]) - op.linear_constraints.add( - lin_expr=[SparsePair(ind=["x1", "x3"], val=[1.0, -1.0]), - SparsePair(ind=["x1", "x2"], val=[1.0, 1.0]), - SparsePair(ind=["x1", "x2", "x3"], val=[-1.0] * 3), - SparsePair(ind=["x2", "x3"], val=[10.0, -2.0])], - senses=["E", "L", "G", "R"], - rhs=[0.0, 1.0, -1.0, 2.0], - range_values=[0.0, 0.0, 0.0, -10.0], - names=["c0", "c1", "c2", "c3"]) - op.quadratic_constraints.add( - lin_expr=SparsePair(ind=['x1', 'x3'], val=[1.0, -1.0]), - quad_expr=SparseTriple(ind1=['x1', 'x2'], ind2=['x2', 'x3'], val=[1.0, -1.0]), - sense='E', - rhs=1.0 - ) - orig = op.write_as_string() - op2 = QuadraticProgram() - op2.from_cplex(op) - self.assertEqual(op2.write_as_string(), orig) - op3 = op2.to_cplex() - self.assertEqual(op3.write_as_string(), orig) - - op.set_problem_name('test') - orig = op.write_as_string() - op2 = QuadraticProgram() - op2.from_cplex(op) - self.assertEqual(op2.write_as_string(), orig) - op3 = op2.to_cplex() - self.assertEqual(op3.write_as_string(), orig) - - def test_substitute_variables_bounds1(self): - """ test substitute variables bounds 1 """ - op = QuadraticProgram() - op.set_problem_name('before') - n = 5 - op.variables.add(names=['x' + str(i) for i in range(n)], types='I' * n, - lb=[-2] * n, ub=[4] * n) - op2, status = op.substitute_variables(constants=SparsePair(ind=['x0'], val=[100])) - self.assertEqual(status, SubstitutionStatus.infeasible) - op2, status = op.substitute_variables( - constants=SparsePair(ind=['x0'], val=[3.0]), - variables=SparseTriple(ind1=['x1', 'x3'], ind2=['x2', 'x4'], val=[2.0, -2.0]) - ) - self.assertEqual(status, SubstitutionStatus.success) - self.assertListEqual(op2.variables.get_names(), ['x2', 'x4']) - self.assertListEqual(op2.variables.get_lower_bounds(), [-1, -2]) - self.assertListEqual(op2.variables.get_upper_bounds(), [2, 1]) - - def test_substitute_variables_bounds2(self): - """ test substitute variables bounds 2 """ - op = QuadraticProgram() - op.set_problem_name('before') - n = 5 - op.variables.add(names=['x' + str(i) for i in range(n)], types='I' * n, - lb=[0] * n, ub=[infinity] * n) - op2, status = op.substitute_variables(constants=SparsePair(ind=['x0'], val=[-1])) - self.assertEqual(status, SubstitutionStatus.infeasible) - op2, status = op.substitute_variables( - constants=SparsePair(ind=['x0'], val=[1.0]), - variables=SparseTriple(ind1=['x1', 'x3'], ind2=['x2', 'x4'], val=[2.0, -2.0]) - ) - self.assertEqual(status, SubstitutionStatus.success) - self.assertListEqual(op2.variables.get_names(), ['x2', 'x4']) - self.assertListEqual(op2.variables.get_lower_bounds(), [0, 0]) - self.assertListEqual(op2.variables.get_upper_bounds(), [infinity, 0]) - - def test_substitute_variables_obj(self): - """ test substitute variables objective """ - op = QuadraticProgram() - op.set_problem_name('before') - op.variables.add(names=['x1', 'x2', 'x3'], types='I' * 3, lb=[-2] * 3, ub=[4] * 3) - op.objective.set_linear([('x1', 1.0), ('x2', 2.0)]) - op.objective.set_quadratic_coefficients([ - ('x1', 'x1', 1), - ('x2', 'x3', 2) - ]) - op2, status = op.substitute_variables( - constants=SparsePair(ind=['x1'], val=[3]), - variables=SparseTriple(ind1=['x2'], ind2=['x3'], val=[-2]) - ) - self.assertEqual(status, SubstitutionStatus.success) - self.assertListEqual(op2.variables.get_names(), ['x3']) - self.assertEqual(op2.objective.get_offset(), 7.5) - self.assertListEqual(op2.objective.get_linear(), [-4]) - self.assertEqual(op2.objective.get_quadratic_coefficients(0, 0), -8) - self.assertEqual(op.objective.get_sense(), op2.objective.get_sense()) - - def test_substitute_variables_lin_cst1(self): - """ test substitute variables linear constraints 1 """ - op = QuadraticProgram() - n = 5 - op.variables.add(names=['x' + str(i) for i in range(n)], types='I' * n, - lb=[-10] * n, ub=[14] * n) - op.linear_constraints.add( - lin_expr=[SparsePair(ind=['x0'], val=[1.0]), - SparsePair(ind=['x1'], val=[1.0]), - SparsePair(ind=['x2'], val=[1.0]), - SparsePair(ind=['x3'], val=[1.0]), - SparsePair(ind=['x4'], val=[1.0])], - senses=["L", "E", "G", "R", "R"], - rhs=[-1.0, 1.0, 1.0, 2.0, 2.0], - range_values=[0.0, 0.0, 0.0, 10.0, -10.0], - names=["c0", "c1", "c2", "c3", "c4"]) - _, status = op.substitute_variables(constants=SparsePair(ind=['x0'], val=[3])) - self.assertEqual(status, SubstitutionStatus.infeasible) - _, status = op.substitute_variables(constants=SparsePair(ind=['x1'], val=[3])) - self.assertEqual(status, SubstitutionStatus.infeasible) - _, status = op.substitute_variables(constants=SparsePair(ind=['x1'], val=[1])) - self.assertEqual(status, SubstitutionStatus.success) - _, status = op.substitute_variables(constants=SparsePair(ind=['x2'], val=[-1])) - self.assertEqual(status, SubstitutionStatus.infeasible) - _, status = op.substitute_variables(constants=SparsePair(ind=['x3'], val=[1.99])) - self.assertEqual(status, SubstitutionStatus.infeasible) - _, status = op.substitute_variables(constants=SparsePair(ind=['x3'], val=[2])) - self.assertEqual(status, SubstitutionStatus.success) - _, status = op.substitute_variables(constants=SparsePair(ind=['x3'], val=[12])) - self.assertEqual(status, SubstitutionStatus.success) - _, status = op.substitute_variables(constants=SparsePair(ind=['x3'], val=[12.01])) - self.assertEqual(status, SubstitutionStatus.infeasible) - _, status = op.substitute_variables(constants=SparsePair(ind=['x4'], val=[-8.01])) - self.assertEqual(status, SubstitutionStatus.infeasible) - _, status = op.substitute_variables(constants=SparsePair(ind=['x4'], val=[-8])) - self.assertEqual(status, SubstitutionStatus.success) - _, status = op.substitute_variables(constants=SparsePair(ind=['x4'], val=[2])) - self.assertEqual(status, SubstitutionStatus.success) - _, status = op.substitute_variables(constants=SparsePair(ind=['x4'], val=[2.01])) - self.assertEqual(status, SubstitutionStatus.infeasible) - - def test_substitute_variables_lin_cst2(self): - """ test substitute variables linear constraints 2 """ - op = QuadraticProgram() - n = 3 - op.variables.add(names=['x' + str(i) for i in range(n)], types='I' * n, - lb=[-2] * n, ub=[4] * n) - op.linear_constraints.add( - lin_expr=[SparsePair(ind=["x0", "x2"], val=[1.0, -1.0]), - SparsePair(ind=["x0", "x1"], val=[1.0, 1.0]), - SparsePair(ind=["x0", "x1", "x2"], val=[-1.0] * 3), - SparsePair(ind=["x1", "x2"], val=[10.0, -2.0])], - senses=["E", "L", "G", "R"], - rhs=[0.0, 1.0, -1.0, 2.0], - range_values=[0.0, 0.0, 0.0, -10.0], - names=["c0", "c1", "c2", "c3"]) - op2, status = op.substitute_variables( - SparsePair(ind=['x0'], val=[2.0]), - SparseTriple(ind1=['x1'], ind2=['x2'], val=[3.0]) - ) - self.assertEqual(status, SubstitutionStatus.success) - self.assertListEqual(op2.variables.get_names(), ['x2']) - rows = op2.linear_constraints.get_rows() - self.assertListEqual(rows[0].ind, [0]) - self.assertListEqual(rows[0].val, [-1]) - self.assertListEqual(rows[1].ind, [0]) - self.assertListEqual(rows[1].val, [3]) - self.assertListEqual(rows[2].ind, [0]) - self.assertListEqual(rows[2].val, [-4]) - self.assertListEqual(rows[3].ind, [0]) - self.assertListEqual(rows[3].val, [28]) - self.assertListEqual(op2.linear_constraints.get_rhs(), [-2, -1, 1, 2]) - - def test_substitute_variables_quad_cst1(self): - """ test substitute variables quadratic constraints 1 """ - op = QuadraticProgram() - n = 3 - op.variables.add(names=['x' + str(i) for i in range(n)], types='I' * n, - lb=[-2] * n, ub=[4] * n) - op.quadratic_constraints.add( - lin_expr=SparsePair(ind=['x0', 'x1'], val=[1.0, -1.0]), - quad_expr=SparseTriple(ind1=['x0', 'x1', 'x2'], ind2=['x1', 'x2', 'x2'], - val=[1.0, -2.0, 3.0]), - sense='L', - rhs=1.0 - ) - op2, status = op.substitute_variables( - constants=SparsePair(ind=['x0', 'x1', 'x2'], val=[1, 1, 1])) - self.assertEqual(status, SubstitutionStatus.infeasible) - - op2, status = op.substitute_variables(SparsePair(ind=['x0', 'x1', 'x2'], val=[-1, 1, 1])) - self.assertEqual(status, SubstitutionStatus.success) - - op2, status = op.substitute_variables(SparsePair(ind=['x0', 'x1'], val=[1, -1])) - self.assertEqual(status, SubstitutionStatus.success) - self.assertEqual(op2.quadratic_constraints.get_num(), 1) - lin = op2.quadratic_constraints.get_linear_components(0) - self.assertListEqual(lin.ind, [0]) - self.assertListEqual(lin.val, [2]) - q = op2.quadratic_constraints.get_quadratic_components(0) - self.assertListEqual(q.ind1, [0]) - self.assertListEqual(q.ind2, [0]) - self.assertListEqual(q.val, [3]) - self.assertEqual(op2.quadratic_constraints.get_senses(0), 'L') - self.assertEqual(op2.quadratic_constraints.get_rhs(0), 0) + quadratic_program.binary_var(name='x0') with self.assertRaises(QiskitOptimizationError): - op.substitute_variables( - variables=SparseTriple(ind1=['x0'], ind2=['x0'], val=[2])) - with self.assertRaises(QiskitOptimizationError): - op.substitute_variables( - variables=SparseTriple(ind1=['x1', 'x0'], ind2=['x2', 'x1'], val=[1.5, 1])) - with self.assertRaises(QiskitOptimizationError): - op.substitute_variables( - variables=SparseTriple(ind1=['x1', 'x1'], ind2=['x2', 'x0'], val=[1.5, 1])) - - op2, status = op.substitute_variables( - variables=SparseTriple(ind1=['x1'], ind2=['x2'], val=[1.5])) - self.assertEqual(status, op.substitution_status.success) - lin = op2.quadratic_constraints.get_linear_components(0) - self.assertListEqual(lin.ind, [0, 1]) - self.assertListEqual(lin.val, [1, -1.5]) - q = op2.quadratic_constraints.get_quadratic_components(0) - self.assertListEqual(q.ind1, [1]) - self.assertListEqual(q.ind2, [0]) - self.assertListEqual(q.val, [1.5]) - self.assertEqual(op2.quadratic_constraints.get_senses(0), 'L') - self.assertEqual(op2.quadratic_constraints.get_rhs(0), 1) - - op2, status = op.substitute_variables( - constants=SparsePair(ind=['x2'], val=[2])) - self.assertEqual(status, op.substitution_status.success) - lin = op2.quadratic_constraints.get_linear_components(0) - self.assertListEqual(lin.ind, [0, 1]) - self.assertListEqual(lin.val, [1, -5]) - q = op2.quadratic_constraints.get_quadratic_components(0) - self.assertListEqual(q.ind1, [1]) - self.assertListEqual(q.ind2, [0]) - self.assertListEqual(q.val, [1]) - self.assertEqual(op2.quadratic_constraints.get_senses(0), 'L') - self.assertEqual(op2.quadratic_constraints.get_rhs(0), -11) + quadratic_program.integer_var(name='x0') + + variables = [x_0, x_1, x_2, x_3, x_4, x_5] + for i, x in enumerate(variables): + y = quadratic_program.get_variable(i) + z = quadratic_program.get_variable(x.name) + self.assertEqual(x, y) + self.assertEqual(x, z) if __name__ == '__main__': diff --git a/test/optimization/test_variable.py b/test/optimization/test_variable.py new file mode 100644 index 0000000000..91cfeb277a --- /dev/null +++ b/test/optimization/test_variable.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +"""Test Variable.""" + +import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase +import logging + +from qiskit.optimization.problems import QuadraticProgram, Variable, VarType +from qiskit.optimization import infinity, QiskitOptimizationError + +logger = logging.getLogger(__name__) + + +class TestVariable(QiskitOptimizationTestCase): + """Test Variable.""" + + def test_init(self): + """ test init """ + + quadratic_program = QuadraticProgram() + name = 'variable' + lowerbound = 0 + upperbound = 10 + vartype = VarType.integer + + variable = Variable(quadratic_program, name, lowerbound, upperbound, vartype) + + self.assertEqual(variable.name, name) + self.assertEqual(variable.lowerbound, lowerbound) + self.assertEqual(variable.upperbound, upperbound) + self.assertEqual(variable.vartype, VarType.integer) + + def test_init_default(self): + """ test init with default values.""" + + quadratic_program = QuadraticProgram() + name = 'variable' + + variable = Variable(quadratic_program, name) + + self.assertEqual(variable.name, name) + self.assertEqual(variable.lowerbound, 0) + self.assertEqual(variable.upperbound, infinity) + self.assertEqual(variable.vartype, VarType.continuous) + + def test_setters(self): + """ test setters. """ + + quadratic_program = QuadraticProgram() + name = 'variable' + lowerbound = 0 + upperbound = 10 + vartype = VarType.continuous + + variable = Variable(quadratic_program, name, lowerbound, upperbound, vartype) + + variable.name = 'test' + self.assertEqual(variable.name, 'test') + + self.assertEqual(variable.lowerbound, lowerbound) + variable.lowerbound = 1 + self.assertEqual(variable.lowerbound, 1) + with self.assertRaises(QiskitOptimizationError): + variable.lowerbound = 20 + + self.assertEqual(variable.upperbound, upperbound) + variable.upperbound = 5 + self.assertEqual(variable.upperbound, 5) + with self.assertRaises(QiskitOptimizationError): + variable.upperbound = 0 + + self.assertEqual(variable.vartype, vartype) + variable.vartype = VarType.integer + self.assertEqual(variable.vartype, VarType.integer) + variable.vartype = VarType.binary + self.assertEqual(variable.vartype, VarType.binary) + variable.vartype = VarType.continuous + self.assertEqual(variable.vartype, VarType.continuous) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_variables.py b/test/optimization/test_variables.py deleted file mode 100644 index feb69a32bd..0000000000 --- a/test/optimization/test_variables.py +++ /dev/null @@ -1,299 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -""" Test VariablesInterface """ - -import unittest -from test.optimization.optimization_test_case import QiskitOptimizationTestCase -import logging - -from qiskit.optimization import QuadraticProgram, QiskitOptimizationError - -logger = logging.getLogger(__name__) - -_HAS_CPLEX = False -try: - from cplex import infinity - _HAS_CPLEX = True -except ImportError: - logger.info('CPLEX is not installed.') - - -class TestVariables(QiskitOptimizationTestCase): - """Test VariablesInterface.""" - - def setUp(self) -> None: - super().setUp() - if not _HAS_CPLEX: - self.skipTest('CPLEX is not installed.') - - def test_type(self): - """ test type """ - op = QuadraticProgram() - self.assertEqual(op.variables.type.binary, 'B') - self.assertEqual(op.variables.type['B'], 'binary') - - def test_initial(self): - """ test initial """ - op = QuadraticProgram() - op.variables.add(names=["x0", "x1", "x2"]) - self.assertListEqual(op.variables.get_lower_bounds(), [0.0, 0.0, 0.0]) - op.variables.set_lower_bounds(0, 1.0) - op.variables.set_lower_bounds([("x1", -1.0), (2, 3.0)]) - self.assertListEqual(op.variables.get_lower_bounds(0, "x1"), [1.0, -1.0]) - self.assertListEqual(op.variables.get_lower_bounds(["x1", "x2", 0]), [-1.0, 3.0, 1.0]) - self.assertEqual(op.variables.get_num(), 3) - op.variables.set_types(0, op.variables.type.binary) - self.assertEqual(op.variables.get_num_binary(), 1) - - def test_get_num(self): - """ test get num """ - op = QuadraticProgram() - typ = op.variables.type - op.variables.add(types=[typ.continuous, typ.binary, typ.integer]) - self.assertEqual(op.variables.get_num(), 3) - - def test_get_num_continuous(self): - """ test get num continuous """ - op = QuadraticProgram() - typ = op.variables.type - op.variables.add(types=[typ.continuous, typ.binary, typ.integer]) - self.assertEqual(op.variables.get_num_continuous(), 1) - - def test_get_num_integer(self): - """ test get num integer """ - op = QuadraticProgram() - typ = op.variables.type - op.variables.add(types=[typ.continuous, typ.binary, typ.integer]) - self.assertEqual(op.variables.get_num_integer(), 1) - - def test_get_num_binary(self): - """ test get num binary """ - op = QuadraticProgram() - typ = op.variables.type - op.variables.add(types=[typ.semi_continuous, typ.binary, typ.integer]) - self.assertEqual(op.variables.get_num_binary(), 1) - - def test_get_num_semicontinuous(self): - """ test get num semi continuous """ - op = QuadraticProgram() - typ = op.variables.type - op.variables.add(types=[typ.semi_continuous, typ.semi_integer, typ.semi_integer]) - self.assertEqual(op.variables.get_num_semicontinuous(), 1) - - def test_get_num_semiinteger(self): - """ test get num semi integer """ - op = QuadraticProgram() - typ = op.variables.type - op.variables.add(types=[typ.semi_continuous, typ.semi_integer, typ.semi_integer]) - self.assertEqual(op.variables.get_num_semiinteger(), 2) - - def test_add(self): - """ add test """ - op = QuadraticProgram() - op.linear_constraints.add(names=["c0", "c1", "c2"]) - op.variables.add(types=[op.variables.type.integer] * 3) - op.variables.add( - lb=[-1.0, 1.0, 0.0], - ub=[100.0, infinity, infinity], - types=[op.variables.type.integer] * 3, - names=["0", "1", "2"] - ) - self.assertListEqual( - op.variables.get_lower_bounds(), - [0.0, 0.0, 0.0, -1.0, 1.0, 0.0]) - self.assertListEqual( - op.variables.get_upper_bounds(), - [infinity, infinity, infinity, 100.0, infinity, infinity]) - - def test_delete(self): - """ test delete """ - op = QuadraticProgram() - op.variables.add(names=[str(i) for i in range(10)]) - self.assertEqual(op.variables.get_num(), 10) - self.assertListEqual(op.variables.get_names(), - ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']) - op.variables.delete(8) - self.assertListEqual(op.variables.get_names(), - ['0', '1', '2', '3', '4', '5', '6', '7', '9']) - op.variables.delete("1", 3) - self.assertListEqual(op.variables.get_names(), ['0', '4', '5', '6', '7', '9']) - op.variables.delete([2, '0', 5]) - self.assertListEqual(op.variables.get_names(), ['4', '6', '7']) - op.variables.delete() - self.assertListEqual(op.variables.get_names(), []) - - def test_set_lower_bounds(self): - """ test set lower bounds """ - op = QuadraticProgram() - op.variables.add(names=["x0", "x1", "x2"]) - op.variables.set_lower_bounds(0, 1.0) - self.assertListEqual(op.variables.get_lower_bounds(), [1.0, 0.0, 0.0]) - op.variables.set_lower_bounds([(2, 3.0), ("x1", -1.0)]) - self.assertListEqual(op.variables.get_lower_bounds(), [1.0, -1.0, 3.0]) - - def test_set_upper_bounds(self): - """ test set upper bounds """ - op = QuadraticProgram() - op.variables.add(names=["x0", "x1", "x2"]) - op.variables.set_upper_bounds(0, 1.0) - op.variables.set_upper_bounds([("x1", 10.0), (2, 3.0)]) - self.assertListEqual(op.variables.get_upper_bounds(), [1.0, 10.0, 3.0]) - - def test_set_names(self): - """ test set names """ - op = QuadraticProgram() - typ = op.variables.type - op.variables.add(types=[typ.continuous, typ.binary, typ.integer]) - op.variables.set_names(0, "first") - op.variables.set_names([(2, "third"), (1, "second")]) - self.assertListEqual(op.variables.get_names(), ['first', 'second', 'third']) - - def test_set_types(self): - """ test set types """ - op = QuadraticProgram() - op.variables.add(names=[str(i) for i in range(5)]) - op.variables.set_types(0, op.variables.type.continuous) - op.variables.set_types([("1", op.variables.type.integer), - ("2", op.variables.type.binary), - ("3", op.variables.type.semi_continuous), - ("4", op.variables.type.semi_integer)]) - self.assertListEqual(op.variables.get_types(), ['C', 'I', 'B', 'S', 'N']) - self.assertEqual(op.variables.type[op.variables.get_types(0)], 'continuous') - - def test_get_lower_bounds(self): - """ test get lower bounds """ - op = QuadraticProgram() - op.variables.add(lb=[1.5 * i for i in range(10)], - names=[str(i) for i in range(10)]) - self.assertEqual(op.variables.get_num(), 10) - self.assertEqual(op.variables.get_lower_bounds(8), 12.0) - self.assertListEqual(op.variables.get_lower_bounds('1', 3), [1.5, 3.0, 4.5]) - self.assertListEqual(op.variables.get_lower_bounds([2, "0", 5]), [3.0, 0.0, 7.5]) - self.assertEqual(op.variables.get_lower_bounds(), - [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5]) - - def test_get_upper_bounds(self): - """ test get upper bounds """ - op = QuadraticProgram() - op.variables.add(ub=[(1.5 * i) + 1.0 for i in range(10)], - names=[str(i) for i in range(10)]) - self.assertEqual(op.variables.get_num(), 10) - self.assertEqual(op.variables.get_upper_bounds(8), 13.0) - self.assertListEqual(op.variables.get_upper_bounds('1', 3), [2.5, 4.0, 5.5]) - self.assertListEqual(op.variables.get_upper_bounds([2, "0", 5]), [4.0, 1.0, 8.5]) - self.assertListEqual(op.variables.get_upper_bounds(), - [1.0, 2.5, 4.0, 5.5, 7.0, 8.5, 10.0, 11.5, 13.0, 14.5]) - - def test_get_names(self): - """ test get names """ - op = QuadraticProgram() - op.variables.add(names=['x' + str(i) for i in range(10)]) - self.assertEqual(op.variables.get_num(), 10) - self.assertEqual(op.variables.get_names(8), 'x8') - self.assertListEqual(op.variables.get_names(1, 3), ['x1', 'x2', 'x3']) - self.assertListEqual(op.variables.get_names([2, 0, 5]), ['x2', 'x0', 'x5']) - self.assertListEqual(op.variables.get_names(), - ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9']) - - def test_set_types2(self): - """ test set types 2 """ - op = QuadraticProgram() - typ = op.variables.type - op.variables.add(names=[str(i) for i in range(5)], - types=[typ.continuous, typ.integer, - typ.binary, typ.semi_continuous, typ.semi_integer]) - self.assertEqual(op.variables.get_num(), 5) - self.assertEqual(op.variables.get_types(3), 'S') - - types = op.variables.get_types(1, 3) - self.assertListEqual(types, ['I', 'B', 'S']) - - types = op.variables.get_types([2, 0, 4]) - self.assertListEqual(types, ['B', 'C', 'N']) - - types = op.variables.get_types() - self.assertEqual(types, ['C', 'I', 'B', 'S', 'N']) - - def test_get_cols(self): - """ test get cols """ - op = QuadraticProgram() - with self.assertRaises(NotImplementedError): - op.variables.get_cols() - - def test_get_obj(self): - """ test get obj """ - op = QuadraticProgram() - with self.assertRaises(NotImplementedError): - op.variables.get_obj() - - def test_get_indices(self): - """ test get indices """ - op = QuadraticProgram() - op.variables.add(names=['a', 'b']) - self.assertEqual(op.variables.get_indices('a'), 0) - self.assertListEqual(op.variables.get_indices(['a', 'b']), [0, 1]) - - def test_add2(self): - """ test add2 """ - op = QuadraticProgram() - op.variables.add(names=['x']) - self.assertEqual(op.variables.get_indices('x'), 0) - self.assertListEqual(op.variables.get_indices(), [0]) - op.variables.add(names=['y']) - self.assertEqual(op.variables.get_indices('x'), 0) - self.assertEqual(op.variables.get_indices('y'), 1) - self.assertListEqual(op.variables.get_indices(), [0, 1]) - - def test_default_bounds(self): - """ test default bounds """ - op = QuadraticProgram() - types = ['B', 'I', 'C', 'S', 'N'] - op.variables.add(names=types, types=types) - self.assertListEqual(op.variables.get_lower_bounds(), [0.0] * 5) - # the upper bound of binary variable is 1. - self.assertListEqual(op.variables.get_upper_bounds(), [1.0] + [infinity] * 4) - - def test_empty_names(self): - """ test empty names """ - op = QuadraticProgram() - r = op.variables.add(names=['', '', '']) - self.assertEqual(r, range(0, 3)) - self.assertListEqual(op.variables.get_names(), ['x1', 'x2', 'x3']) - r = op.variables.add(names=['a', '', 'c']) - self.assertEqual(r, range(3, 6)) - self.assertListEqual(op.variables.get_names(), - ['x1', 'x2', 'x3', 'a', 'x5', 'c']) - - def test_duplicate_names(self): - """ test duplicate names """ - op = QuadraticProgram() - op.variables.add(names=['', '', '']) - with self.assertRaises(QiskitOptimizationError): - op.variables.add(names=['a', 'a']) - with self.assertRaises(QiskitOptimizationError): - op.variables.add(names=['x1']) - - def test_add3(self): - """ test add 3 """ - op = QuadraticProgram() - op.variables.add(names=['c'], types=['C'], lb=[-10], ub=[10]) - op.variables.add(names=['b'], types=['B'], lb=[-10], ub=[10]) - op.variables.add(names=['b2'], types=['B']) - self.assertListEqual(op.variables.get_lower_bounds(), [-10, -10, 0]) - self.assertListEqual(op.variables.get_upper_bounds(), [10, 10, 1]) - - -if __name__ == '__main__': - unittest.main() From d2872645c4d922eb548042fc3578cdc2680da99d Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Tue, 14 Apr 2020 23:17:13 +0200 Subject: [PATCH 187/323] add quadratic_expression --- .../problems/linear_expression.py | 2 +- .../problems/quadratic_expression.py | 154 +++++++++++++++++- test/optimization/test_linear_constraint.py | 19 ++- .../optimization/test_quadratic_expression.py | 111 +++++++++++++ 4 files changed, 273 insertions(+), 13 deletions(-) create mode 100644 test/optimization/test_quadratic_expression.py diff --git a/qiskit/optimization/problems/linear_expression.py b/qiskit/optimization/problems/linear_expression.py index 7514253bd9..a30b4fb928 100644 --- a/qiskit/optimization/problems/linear_expression.py +++ b/qiskit/optimization/problems/linear_expression.py @@ -67,7 +67,7 @@ def _coeffs_to_dok_matrix(self, coeffs = dok_matrix((1, self.quadratic_program.get_num_vars())) for index, value in coefficients.items(): if isinstance(index, str): - index = self.quadratic_program.var_index[index] + index = self.quadratic_program.variables_index[index] coeffs[0, index] = value coefficients = coeffs else: diff --git a/qiskit/optimization/problems/quadratic_expression.py b/qiskit/optimization/problems/quadratic_expression.py index c1d91ac72b..50d14ad1d3 100644 --- a/qiskit/optimization/problems/quadratic_expression.py +++ b/qiskit/optimization/problems/quadratic_expression.py @@ -1,4 +1,152 @@ -class QuadraticExpression: +# -*- coding: utf-8 -*- - def __init__(self): - pass +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019, 2020. +# +# 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. + +"""Quadratic expression interface.""" + + +from typing import List, Union, Dict, Tuple +import numpy as np +from numpy import ndarray +from scipy.sparse import spmatrix, dok_matrix + +from qiskit.optimization import QiskitOptimizationError +from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram + + +class QuadraticExpression(HasQuadraticProgram): + """ Representation of a quadratic expression by its coefficients.""" + + def __init__(self, quadratic_program: "QuadraticProgram", + coefficients: Union[ndarray, spmatrix, List[List[float]], + Dict[Tuple[Union[int, str], Union[int, str]], float]]) -> None: + """Creates a new quadratic expression. + + The quadratic expression can be defined via an array, a list, a sparse matrix, or a + dictionary that uses variable names or indices as keys and stores the values internally as a + dok_matrix. + + Args: + quadratic_program: The parent QuadraticProgram. + coefficients: The (sparse) representation of the coefficients. + + """ + super().__init__(quadratic_program) + self.coefficients = coefficients + + def _coeffs_to_dok_matrix(self, + coefficients: Union[ndarray, spmatrix, List[List[float]], + Dict[ + Tuple[Union[int, str], Union[int, str]], + float]]) -> None: + """Maps given coefficients to a dok_matrix. + + Args: + coefficients: The coefficients to be mapped. + + Returns: + The given coefficients as a dok_matrix + + Raises: + QiskitOptimizationError: if coefficients are given in unsupported format. + """ + if isinstance(coefficients, list)\ + or isinstance(coefficients, ndarray)\ + or isinstance(coefficients, spmatrix): + coefficients = dok_matrix(coefficients) + elif isinstance(coefficients, dict): + n = self.quadratic_program.get_num_vars() + coeffs = dok_matrix((n, n)) + for (i, j), value in coefficients.items(): + if isinstance(i, str): + i = self.quadratic_program.variables_index[i] + if isinstance(j, str): + j = self.quadratic_program.variables_index[j] + coeffs[i, j] = value + coefficients = coeffs + else: + raise QiskitOptimizationError("Unsupported format for coefficients.") + return coefficients + + @property + def coefficients(self) -> dok_matrix: + """ Returns the coefficients of the quadratic expression. + + Returns: + The coefficients of the quadratic expression. + """ + return self._coefficients + + @coefficients.setter + def coefficients(self, + coefficients: Union[ndarray, spmatrix, List[List[float]], + Dict[Tuple[Union[int, str], Union[int, str]], float]] + ) -> None: + """Sets the coefficients of the quadratic expression. + + Args: + coefficients: The coefficients of the quadratic expression. + """ + self._coefficients = self._coeffs_to_dok_matrix(coefficients) + + def coefficients_as_array(self) -> ndarray: + """Returns the coefficients of the quadratic expression as array. + + Returns: + An array with the coefficients corresponding to the quadratic expression. + """ + return self._coefficients.toarray() + + def coefficients_as_dict(self, use_index: bool = True + ) -> Dict[Union[Tuple[int, int], Tuple[str, str]], float]: + """Returns the coefficients of the quadratic expression as dictionary, either using tuples + of variable names or indices as keys. + + Args: + use_index: Determines whether to use index or names to refer to variables. + + Returns: + An dictionary with the coefficients corresponding to the quadratic expression. + """ + if use_index: + return {(i, j): v for (i, j), v in self._coefficients.items()} + else: + return {(self.quadratic_program.variables[i].name, + self.quadratic_program.variables[j].name): v + for (i, j), v in self._coefficients.items()} + + def evaluate(self, x: Union[ndarray, List, Dict[Union[int, str], float]]) -> float: + """Evaluate the quadratic expression for given variables: x * Q * x. + + Args: + x: The values of the variables to be evaluated. + + Returns: + The value of the quadratic expression given the variable values. + """ + # cast input to dok_matrix if it is a dictionary + if isinstance(x, dict): + x_ = np.zeros(self.quadratic_program.get_num_vars()) + for i, v in x.items(): + if isinstance(i, str): + i = self.quadratic_program.variables_index[i] + x_[i] = v + x = x_ + if isinstance(x, List): + x = np.array(x) + + # compute x * Q * x for the quadratic expression + val = x @ self.coefficients @ x + + # return the result + return val diff --git a/test/optimization/test_linear_constraint.py b/test/optimization/test_linear_constraint.py index 3216e728d5..c2be3a4dcb 100644 --- a/test/optimization/test_linear_constraint.py +++ b/test/optimization/test_linear_constraint.py @@ -58,9 +58,9 @@ def test_init(self): self.assertEqual(quadratic_program.get_num_linear_constraints(), 2) self.assertEqual(quadratic_program.linear_constraints[1].name, 'c1') self.assertTrue(( - quadratic_program.linear_constraints[1].linear.coefficients_as_array( - ) == coefficients - ).all()) + quadratic_program.linear_constraints[1].linear.coefficients_as_array( + ) == coefficients + ).all()) self.assertEqual(quadratic_program.linear_constraints[1].sense, ConstraintSense.eq) self.assertEqual(quadratic_program.linear_constraints[1].rhs, 1.0) self.assertEqual(quadratic_program.linear_constraints[1], @@ -88,9 +88,9 @@ def test_init(self): self.assertEqual(quadratic_program.get_num_linear_constraints(), 4) self.assertEqual(quadratic_program.linear_constraints[3].name, 'c3') self.assertTrue(( - quadratic_program.linear_constraints[3].linear.coefficients_as_array( - ) == coefficients - ).all()) + quadratic_program.linear_constraints[3].linear.coefficients_as_array( + ) == coefficients + ).all()) self.assertEqual(quadratic_program.linear_constraints[3].sense, ConstraintSense.geq) self.assertEqual(quadratic_program.linear_constraints[3].rhs, 1.0) self.assertEqual(quadratic_program.linear_constraints[3], @@ -118,9 +118,9 @@ def test_init(self): self.assertEqual(quadratic_program.get_num_linear_constraints(), 6) self.assertEqual(quadratic_program.linear_constraints[5].name, 'c5') self.assertTrue(( - quadratic_program.linear_constraints[5].linear.coefficients_as_array( - ) == coefficients - ).all()) + quadratic_program.linear_constraints[5].linear.coefficients_as_array( + ) == coefficients + ).all()) self.assertEqual(quadratic_program.linear_constraints[5].sense, ConstraintSense.leq) self.assertEqual(quadratic_program.linear_constraints[5].rhs, 1.0) self.assertEqual(quadratic_program.linear_constraints[5], @@ -128,5 +128,6 @@ def test_init(self): self.assertEqual(quadratic_program.linear_constraints[5], quadratic_program.get_linear_constraint(5)) + if __name__ == '__main__': unittest.main() diff --git a/test/optimization/test_quadratic_expression.py b/test/optimization/test_quadratic_expression.py new file mode 100644 index 0000000000..af9115da00 --- /dev/null +++ b/test/optimization/test_quadratic_expression.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" Test QuadraticExpression """ + +import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase +import logging +import numpy as np +from scipy.sparse import dok_matrix + +from qiskit.optimization import QuadraticProgram +from qiskit.optimization.problems import QuadraticExpression + +logger = logging.getLogger(__name__) + + +class TestQuadraticExpression(QiskitOptimizationTestCase): + """Test QuadraticExpression.""" + + def test_init(self): + """ test init. """ + + quadratic_program = QuadraticProgram() + for _ in range(5): + quadratic_program.continuous_var() + + coefficients_list = [[i*j for i in range(5)] for j in range(5)] + coefficients_array = np.array(coefficients_list) + coefficients_dok = dok_matrix(coefficients_list) + coefficients_dict_int = {(i, j): v for (i, j), v in coefficients_dok.items()} + coefficients_dict_str = {('x{}'.format(i), 'x{}'.format(j)): v for (i, j), v in + coefficients_dok.items()} + + for coeffs in [coefficients_list, + coefficients_array, + coefficients_dok, + coefficients_dict_int, + coefficients_dict_str]: + + quadratic = QuadraticExpression(quadratic_program, coeffs) + self.assertEqual((quadratic.coefficients != coefficients_dok).nnz, 0) + self.assertTrue((quadratic.coefficients_as_array() == coefficients_list).all()) + self.assertDictEqual(quadratic.coefficients_as_dict( + use_index=True), coefficients_dict_int) + self.assertDictEqual(quadratic.coefficients_as_dict(use_index=False), + coefficients_dict_str) + + def test_setters(self): + """ test setters. """ + + quadratic_program = QuadraticProgram() + for _ in range(5): + quadratic_program.continuous_var() + + n = quadratic_program.get_num_vars() + zeros = np.zeros((n, n)) + quadratic = QuadraticExpression(quadratic_program, zeros) + + coefficients_list = [[i*j for i in range(5)] for j in range(5)] + coefficients_array = np.array(coefficients_list) + coefficients_dok = dok_matrix(coefficients_list) + coefficients_dict_int = {(i, j): v for (i, j), v in coefficients_dok.items()} + coefficients_dict_str = {('x{}'.format(i), 'x{}'.format(j)): v for (i, j), v in + coefficients_dok.items()} + + for coeffs in [coefficients_list, + coefficients_array, + coefficients_dok, + coefficients_dict_int, + coefficients_dict_str]: + + quadratic.coefficients = coeffs + self.assertEqual((quadratic.coefficients != coefficients_dok).nnz, 0) + self.assertTrue((quadratic.coefficients_as_array() == coefficients_list).all()) + self.assertDictEqual(quadratic.coefficients_as_dict( + use_index=True), coefficients_dict_int) + self.assertDictEqual(quadratic.coefficients_as_dict(use_index=False), + coefficients_dict_str) + + def test_evaluate(self): + """ test evaluate. """ + + quadratic_program = QuadraticProgram() + x = [quadratic_program.continuous_var() for _ in range(5)] + + coefficients_list = [[i*j for i in range(5)] for j in range(5)] + quadratic = QuadraticExpression(quadratic_program, coefficients_list) + + values_list = [i for i in range(len(x))] + values_array = np.array(values_list) + values_dict_int = {i: i for i in range(len(x))} + values_dict_str = {'x{}'.format(i): i for i in range(len(x))} + + for values in [values_list, values_array, values_dict_int, values_dict_str]: + self.assertEqual(quadratic.evaluate(values), 900) + + +if __name__ == '__main__': + unittest.main() From ae8ca24cc7ed3c6e3f12c8bbfae5dbc6592108b0 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Tue, 14 Apr 2020 23:56:56 +0200 Subject: [PATCH 188/323] add quadratic constraint and tests --- .../problems/linear_constraint.py | 32 +- .../problems/quadratic_constraint.py | 106 ++++- .../problems/quadratic_expression.py | 6 +- .../problems/quadratic_program.py | 216 +++++++++- test/optimization/test_linear_constraint.py | 2 +- .../optimization/test_quadratic_constraint.py | 150 +++++++ .../test_quadratic_constraints.py | 378 ------------------ 7 files changed, 460 insertions(+), 430 deletions(-) create mode 100644 test/optimization/test_quadratic_constraint.py delete mode 100644 test/optimization/test_quadratic_constraints.py diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index 8da139fe46..d7ffc65507 100644 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -19,7 +19,6 @@ from scipy.sparse import spmatrix from qiskit.optimization.problems import Constraint, ConstraintSense, LinearExpression -from qiskit.optimization import QiskitOptimizationError class LinearConstraint(Constraint): @@ -27,9 +26,8 @@ class LinearConstraint(Constraint): def __init__(self, quadratic_program: "QuadraticProgram", name: str, - linear: Union[ - LinearExpression, ndarray, spmatrix, List[float], Dict[Union[str, int], float] - ], + linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float] + ], sense: ConstraintSense, rhs: float ) -> None: @@ -41,18 +39,9 @@ def __init__(self, linear: The coefficients specifying the linear constraint. sense: The sense of the constraint. rhs: The right-hand-side of the constraint. - - Raises: - QiskitOptimizationError: if the given linear expression has a different parent - QuadraticProgram than the constraint. """ super().__init__(quadratic_program, name, sense, rhs) - if isinstance(linear, LinearExpression): - if linear._quadratic_program != quadratic_program: - raise QiskitOptimizationError("Incompatible parent quadratic program!") - self._linear = linear - else: - self._linear = LinearExpression(quadratic_program, linear) + self._linear = LinearExpression(quadratic_program, linear) @property def linear(self) -> LinearExpression: @@ -65,25 +54,16 @@ def linear(self) -> LinearExpression: @linear.setter def linear(self, linear: - Union[LinearExpression, ndarray, spmatrix, List[float], Dict[Union[str, int], float]] + Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]] ) -> None: """Sets the linear expression corresponding to the left-hand-side of the constraint. The coefficients can either be given by an array, a (sparse) 1d matrix, a lsit or a dictionary. Args: - linear: The linear expression or coefficients of the left-hand-side. - - Raises: - QiskitOptimizationError: if the given linear expression has a different parent - QuadraticProgram than the constraint. + linear: The linear coefficients of the left-hand-side. """ - if isinstance(linear, LinearExpression): - if linear._quadratic_program != self.quadratic_program: - raise QiskitOptimizationError("Incompatible parent quadratic program!") - self._linear = linear - else: - self._linear = LinearExpression(self.quadratic_program, linear) + self._linear = LinearExpression(self.quadratic_program, linear) def evaluate(self, x: Union[ndarray, List, Dict[Union[int, str], float]]) -> float: """Evaluate the left-hand-side of the constraint. diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index 397dde9745..fd09728bbd 100644 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -1,4 +1,104 @@ -class QuadraticConstraint: +# -*- coding: utf-8 -*- - def __init__(self): - pass +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019, 2020. +# +# 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. + +"""Quadratic Constraint.""" + +from typing import Union, List, Dict, Tuple +from numpy import ndarray +from scipy.sparse import spmatrix + +from qiskit.optimization.problems import (Constraint, ConstraintSense, LinearExpression, + QuadraticExpression) + + +class QuadraticConstraint(Constraint): + """ Representation of a linear constraint.""" + + def __init__(self, + quadratic_program: "QuadraticProgram", name: str, + linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]], + quadratic: Union[ndarray, spmatrix, List[List[float]], + Dict[Tuple[Union[int, str], Union[int, str]], float]], + sense: ConstraintSense, + rhs: float + ) -> None: + """Constructs a quadratic constraint, consisting of a linear and a quadratic term. + + Args: + quadratic_program: The parent quadratic program. + name: The name of the constraint. + linear: The coefficients specifying the linear part of the constraint. + quadratic: The coefficients specifying the linear part of the constraint. + sense: The sense of the constraint. + rhs: The right-hand-side of the constraint. + """ + super().__init__(quadratic_program, name, sense, rhs) + self._linear = LinearExpression(quadratic_program, linear) + self._quadratic = QuadraticExpression(quadratic_program, quadratic) + + @property + def linear(self) -> LinearExpression: + """Returns the linear expression corresponding to the left-hand-side of the constraint. + + Returns: + The left-hand-side linear expression. + """ + return self._linear + + @linear.setter + def linear(self, linear: + Union[LinearExpression, ndarray, spmatrix, List[float], Dict[Union[str, int], float]] + ) -> None: + """Sets the linear expression corresponding to the left-hand-side of the constraint. + The coefficients can either be given by an array, a (sparse) 1d matrix, a list or a + dictionary. + + Args: + linear: The linear coefficients of the left-hand-side. + """ + self._linear = LinearExpression(self.quadratic_program, linear) + + @property + def quadratic(self) -> QuadraticExpression: + """Returns the quadratic expression corresponding to the left-hand-side of the constraint. + + Returns: + The left-hand-side quadratic expression. + """ + return self._quadratic + + @quadratic.setter + def quadratic(self, quadratic: + Union[ndarray, spmatrix, List[List[float]], + Dict[Tuple[Union[int, str], Union[int, str]], float]] + ) -> None: + """Sets the quadratic expression corresponding to the left-hand-side of the constraint. + The coefficients can either be given by an array, a (sparse) matrix, a list or a + dictionary. + + Args: + quadratic: The quadratic coefficients of the left-hand-side. + """ + self._linear = QuadraticExpression(self.quadratic_program, quadratic) + + def evaluate(self, x: Union[ndarray, List, Dict[Union[int, str], float]]) -> float: + """Evaluate the left-hand-side of the constraint. + + Args: + x: The values of the variables to be evaluated. + + Returns: + The left-hand-side of the constraint given the variable values. + """ + return self.linear.evaluate(x) + self.quadratic.evaluate(x) diff --git a/qiskit/optimization/problems/quadratic_expression.py b/qiskit/optimization/problems/quadratic_expression.py index 50d14ad1d3..4ed24f58ce 100644 --- a/qiskit/optimization/problems/quadratic_expression.py +++ b/qiskit/optimization/problems/quadratic_expression.py @@ -136,12 +136,12 @@ def evaluate(self, x: Union[ndarray, List, Dict[Union[int, str], float]]) -> flo """ # cast input to dok_matrix if it is a dictionary if isinstance(x, dict): - x_ = np.zeros(self.quadratic_program.get_num_vars()) + x_aux = np.zeros(self.quadratic_program.get_num_vars()) for i, v in x.items(): if isinstance(i, str): i = self.quadratic_program.variables_index[i] - x_[i] = v - x = x_ + x_aux[i] = v + x = x_aux if isinstance(x, List): x = np.array(x) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index c4f5869cda..8f9c364910 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -14,7 +14,7 @@ """Quadratic Program.""" -from typing import List, Union, Dict, Optional +from typing import List, Union, Dict, Optional, Tuple from numpy import ndarray from scipy.sparse import spmatrix @@ -22,6 +22,7 @@ from qiskit.optimization.problems.variable import Variable, VarType from qiskit.optimization.problems.constraint import ConstraintSense from qiskit.optimization.problems.linear_constraint import LinearConstraint +from qiskit.optimization.problems.quadratic_constraint import QuadraticConstraint class QuadraticProgram: @@ -43,6 +44,9 @@ def __init__(self, name: str = '') -> None: self._linear_constraints: List[LinearConstraint] = [] self._linear_constraints_index: Dict[str, int] = {} + self._quadratic_constraints: List[QuadraticConstraint] = [] + self._quadratic_constraints_index: Dict[str, int] = {} + @property def name(self) -> str: """Returns the name of the quadratic program. @@ -230,8 +234,8 @@ def linear_constraints_index(self) -> Dict[str, int]: def _add_linear_constraint(self, name: Optional[str] = None, - linear_coefficients: Union[ndarray, spmatrix, List[float], - Dict[Union[int, str], float]] = None, + coefficients: Union[ndarray, spmatrix, List[float], + Dict[Union[int, str], float]] = None, sense: ConstraintSense = ConstraintSense.leq, rhs: float = 0.0 ) -> None: @@ -240,7 +244,7 @@ def _add_linear_constraint(self, Args: name: The name of the constraint. - linear_coefficients: The linear coefficients of the constraint. + coefficients: The linear coefficients of the constraint. sense: The constraint sense. rhs: The right-hand-side of the constraint. @@ -260,22 +264,22 @@ def _add_linear_constraint(self, k += 1 name = 'c{}'.format(k) self.linear_constraints_index[name] = len(self.linear_constraints) - if linear_coefficients is None: - linear_coefficients = {} - constraint = LinearConstraint(self, name, linear_coefficients, sense, rhs) + if coefficients is None: + coefficients = {} + constraint = LinearConstraint(self, name, coefficients, sense, rhs) self.linear_constraints.append(constraint) return constraint def linear_eq_constraint(self, name: Optional[str] = None, - linear_coefficients: Union[ndarray, spmatrix, List[float], - Dict[Union[int, str], float]] = None, + coefficients: Union[ndarray, spmatrix, List[float], + Dict[Union[int, str], float]] = None, rhs: float = 0.0) -> LinearConstraint: """Adds a linear equality constraint to the quadratic program of the form: linear_coeffs * x == rhs. Args: name: The name of the constraint. - linear_coefficients: The linear coefficients of the left-hand-side of the constraint. + coefficients: The linear coefficients of the left-hand-side of the constraint. rhs: The right hand side of the constraint. Returns: @@ -284,11 +288,11 @@ def linear_eq_constraint(self, name: Optional[str] = None, Raises: QiskitOptimizationError: if the constraint name already exists. """ - return self._add_linear_constraint(name, linear_coefficients, ConstraintSense.eq, rhs) + return self._add_linear_constraint(name, coefficients, ConstraintSense.eq, rhs) def linear_geq_constraint(self, name: Optional[str] = None, - linear_coefficients: Union[ndarray, spmatrix, List[float], - Dict[Union[int, str], float]] = None, + coefficients: Union[ndarray, spmatrix, List[float], + Dict[Union[int, str], float]] = None, rhs: float = 0.0) -> LinearConstraint: """Adds a linear "greater-than-or-equal-to" (geq) constraint to the quadratic program of the form: @@ -296,7 +300,7 @@ def linear_geq_constraint(self, name: Optional[str] = None, Args: name: The name of the constraint. - linear_coefficients: The linear coefficients of the left-hand-side of the constraint. + coefficients: The linear coefficients of the left-hand-side of the constraint. rhs: The right hand side of the constraint. Returns: @@ -305,11 +309,11 @@ def linear_geq_constraint(self, name: Optional[str] = None, Raises: QiskitOptimizationError: if the constraint name already exists. """ - return self._add_linear_constraint(name, linear_coefficients, ConstraintSense.geq, rhs) + return self._add_linear_constraint(name, coefficients, ConstraintSense.geq, rhs) def linear_leq_constraint(self, name: Optional[str] = None, - linear_coefficients: Union[ndarray, spmatrix, List[float], - Dict[Union[int, str], float]] = None, + coefficients: Union[ndarray, spmatrix, List[float], + Dict[Union[int, str], float]] = None, rhs: float = 0.0) -> LinearConstraint: """Adds a linear "less-than-or-equal-to" (leq) constraint to the quadratic program of the form: @@ -317,7 +321,7 @@ def linear_leq_constraint(self, name: Optional[str] = None, Args: name: The name of the constraint. - linear_coefficients: The linear coefficients of the left-hand-side of the constraint. + coefficients: The linear coefficients of the left-hand-side of the constraint. rhs: The right hand side of the constraint. Returns: @@ -326,7 +330,7 @@ def linear_leq_constraint(self, name: Optional[str] = None, Raises: QiskitOptimizationError: if the constraint name already exists. """ - return self._add_linear_constraint(name, linear_coefficients, ConstraintSense.leq, rhs) + return self._add_linear_constraint(name, coefficients, ConstraintSense.leq, rhs) def get_linear_constraint(self, i: Union[int, str]) -> LinearConstraint: """Returns a linear constraint for a given name or index. @@ -349,3 +353,177 @@ def get_num_linear_constraints(self) -> int: The number of linear constraints. """ return len(self.linear_constraints) + + @property + def quadratic_constraints(self) -> List[QuadraticConstraint]: + """Returns the list of quadratic constraints of the quadratic program. + + Returns: + List of quadratic constraints. + """ + return self._quadratic_constraints + + @property + def quadratic_constraints_index(self) -> Dict[str, int]: + """Returns the dictionary that maps the name of a quadratic constraint to its index. + + Returns: + The quadratic constraint index dictionary. + """ + return self._quadratic_constraints_index + + def _add_quadratic_constraint(self, + name: Optional[str] = None, + linear_coefficients: Union[ndarray, spmatrix, List[float], + Dict[Union[int, str], float]] = None, + quadratic_coefficients: Union[ndarray, spmatrix, + List[List[float]], + Dict[ + Tuple[Union[int, str], + Union[int, str]], + float]] = None, + sense: ConstraintSense = ConstraintSense.leq, + rhs: float = 0.0 + ) -> None: + """Checks whether a constraint name is already taken and adds the constraint to list and + index if not. + + Args: + name: The name of the constraint. + linear_coefficients: The linear coefficients of the constraint. + quadratic_coefficients: The quadratic coefficients of the constraint. + sense: The constraint sense. + rhs: The right-hand-side of the constraint. + + Returns: + The added constraint. + + Raises: + QiskitOptimizationError: if the constraint name is already taken. + + """ + if name: + if name in self.quadratic_constraints_index: + raise QiskitOptimizationError("Variable name already exists!") + else: + k = self.get_num_quadratic_constraints() + while 'c{}'.format(k) in self.quadratic_constraints_index: + k += 1 + name = 'c{}'.format(k) + self.quadratic_constraints_index[name] = len(self.quadratic_constraints) + if linear_coefficients is None: + linear_coefficients = {} + if quadratic_coefficients is None: + quadratic_coefficients = {} + constraint = QuadraticConstraint(self, name, linear_coefficients, quadratic_coefficients, + sense, rhs) + self.quadratic_constraints.append(constraint) + return constraint + + def quadratic_eq_constraint(self, name: Optional[str] = None, + linear_coefficients: Union[ndarray, spmatrix, List[float], + Dict[Union[int, str], float]] = None, + quadratic_coefficients: Union[ndarray, spmatrix, + List[List[float]], + Dict[ + Tuple[Union[int, str], + Union[int, str]], + float]] = None, + rhs: float = 0.0) -> QuadraticConstraint: + """Adds a quadratic equality constraint to the quadratic program of the form: + x * Q * x == rhs. + + Args: + name: The name of the constraint. + linear_coefficients: The linear coefficients of the constraint. + quadratic_coefficients: The quadratic coefficients of the constraint. + rhs: The right hand side of the constraint. + + Returns: + The added constraint. + + Raises: + QiskitOptimizationError: if the constraint name already exists. + """ + return self._add_quadratic_constraint(name, linear_coefficients, quadratic_coefficients, + ConstraintSense.eq, rhs) + + def quadratic_geq_constraint(self, name: Optional[str] = None, + linear_coefficients: Union[ndarray, spmatrix, List[float], + Dict[Union[int, str], float]] = None, + quadratic_coefficients: Union[ndarray, spmatrix, + List[List[float]], + Dict[ + Tuple[Union[int, str], + Union[int, str]], + float]] = None, + rhs: float = 0.0) -> QuadraticConstraint: + """Adds a quadratic "greater-than-or-equal-to" (geq) constraint to the quadratic program + of the form: + x * Q * x >= rhs. + + Args: + name: The name of the constraint. + linear_coefficients: The linear coefficients of the constraint. + quadratic_coefficients: The quadratic coefficients of the constraint. + rhs: The right hand side of the constraint. + + Returns: + The added constraint. + + Raises: + QiskitOptimizationError: if the constraint name already exists. + """ + return self._add_quadratic_constraint(name, linear_coefficients, quadratic_coefficients, + ConstraintSense.geq, rhs) + + def quadratic_leq_constraint(self, name: Optional[str] = None, + linear_coefficients: Union[ndarray, spmatrix, List[float], + Dict[Union[int, str], float]] = None, + quadratic_coefficients: Union[ndarray, spmatrix, + List[List[float]], + Dict[ + Tuple[Union[int, str], + Union[int, str]], + float]] = None, + rhs: float = 0.0) -> QuadraticConstraint: + """Adds a quadratic "less-than-or-equal-to" (leq) constraint to the quadratic program + of the form: + x * Q * x <= rhs. + + Args: + name: The name of the constraint. + linear_coefficients: The linear coefficients of the constraint. + quadratic_coefficients: The quadratic coefficients of the constraint. + rhs: The right hand side of the constraint. + + Returns: + The added constraint. + + Raises: + QiskitOptimizationError: if the constraint name already exists. + """ + return self._add_quadratic_constraint(name, linear_coefficients, quadratic_coefficients, + ConstraintSense.leq, rhs) + + def get_quadratic_constraint(self, i: Union[int, str]) -> QuadraticConstraint: + """Returns a quadratic constraint for a given name or index. + + Args: + i: the index or name of the constraint. + + Returns: + The corresponding constraint. + """ + if isinstance(i, int): + return self.quadratic_constraints[i] + else: + return self.quadratic_constraints[self._quadratic_constraints_index[i]] + + def get_num_quadratic_constraints(self) -> int: + """Returns the number of quadratic constraints. + + Returns: + The number of quadratic constraints. + """ + return len(self.quadratic_constraints) diff --git a/test/optimization/test_linear_constraint.py b/test/optimization/test_linear_constraint.py index c2be3a4dcb..d1a9495e54 100644 --- a/test/optimization/test_linear_constraint.py +++ b/test/optimization/test_linear_constraint.py @@ -26,7 +26,7 @@ class TestLinearConstraint(QiskitOptimizationTestCase): - """Test LinearConstraintInterface.""" + """Test LinearConstraint.""" def test_init(self): """ test init. """ diff --git a/test/optimization/test_quadratic_constraint.py b/test/optimization/test_quadratic_constraint.py new file mode 100644 index 0000000000..4b02635966 --- /dev/null +++ b/test/optimization/test_quadratic_constraint.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" Test QuadraticConstraint """ + +import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase +import logging +import numpy as np + +from qiskit.optimization import QuadraticProgram, QiskitOptimizationError +from qiskit.optimization.problems import ConstraintSense + +logger = logging.getLogger(__name__) + + +class TestQuadraticConstraint(QiskitOptimizationTestCase): + """Test QuadraticConstraint.""" + + def test_init(self): + """ test init. """ + + quadratic_program = QuadraticProgram() + for _ in range(5): + quadratic_program.continuous_var() + self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 0) + + linear_coeffs = np.array([i for i in range(5)]) + quadratic_coeffs = np.array([[i*j for i in range(5)] for j in range(5)]) + + # equality constraints + quadratic_program.quadratic_eq_constraint() + self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 1) + self.assertEqual(quadratic_program.quadratic_constraints[0].name, 'c0') + self.assertEqual( + len(quadratic_program.quadratic_constraints[0].linear.coefficients_as_dict()), 0) + self.assertEqual( + len(quadratic_program.quadratic_constraints[0].quadratic.coefficients_as_dict()), 0) + self.assertEqual(quadratic_program.quadratic_constraints[0].sense, ConstraintSense.eq) + self.assertEqual(quadratic_program.quadratic_constraints[0].rhs, 0.0) + self.assertEqual(quadratic_program.quadratic_constraints[0], + quadratic_program.get_quadratic_constraint('c0')) + self.assertEqual(quadratic_program.quadratic_constraints[0], + quadratic_program.get_quadratic_constraint(0)) + + with self.assertRaises(QiskitOptimizationError): + quadratic_program.quadratic_eq_constraint(name='c0') + + quadratic_program.quadratic_eq_constraint('c1', linear_coeffs, quadratic_coeffs, 1.0) + self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 2) + self.assertEqual(quadratic_program.quadratic_constraints[1].name, 'c1') + self.assertTrue(( + quadratic_program.quadratic_constraints[1].linear.coefficients_as_array( + ) == linear_coeffs + ).all()) + self.assertTrue(( + quadratic_program.quadratic_constraints[1].quadratic.coefficients_as_array( + ) == quadratic_coeffs + ).all()) + self.assertEqual(quadratic_program.quadratic_constraints[1].sense, ConstraintSense.eq) + self.assertEqual(quadratic_program.quadratic_constraints[1].rhs, 1.0) + self.assertEqual(quadratic_program.quadratic_constraints[1], + quadratic_program.get_quadratic_constraint('c1')) + self.assertEqual(quadratic_program.quadratic_constraints[1], + quadratic_program.get_quadratic_constraint(1)) + + # geq constraints + quadratic_program.quadratic_geq_constraint() + self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 3) + self.assertEqual(quadratic_program.quadratic_constraints[2].name, 'c2') + self.assertEqual( + len(quadratic_program.quadratic_constraints[2].linear.coefficients_as_dict()), 0) + self.assertEqual( + len(quadratic_program.quadratic_constraints[2].quadratic.coefficients_as_dict()), 0) + self.assertEqual(quadratic_program.quadratic_constraints[2].sense, ConstraintSense.geq) + self.assertEqual(quadratic_program.quadratic_constraints[2].rhs, 0.0) + self.assertEqual(quadratic_program.quadratic_constraints[2], + quadratic_program.get_quadratic_constraint('c2')) + self.assertEqual(quadratic_program.quadratic_constraints[2], + quadratic_program.get_quadratic_constraint(2)) + + with self.assertRaises(QiskitOptimizationError): + quadratic_program.quadratic_geq_constraint(name='c2') + + quadratic_program.quadratic_geq_constraint('c3', linear_coeffs, quadratic_coeffs, 1.0) + self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 4) + self.assertEqual(quadratic_program.quadratic_constraints[3].name, 'c3') + self.assertTrue(( + quadratic_program.quadratic_constraints[3].linear.coefficients_as_array( + ) == linear_coeffs + ).all()) + self.assertTrue(( + quadratic_program.quadratic_constraints[3].quadratic.coefficients_as_array( + ) == quadratic_coeffs + ).all()) + self.assertEqual(quadratic_program.quadratic_constraints[3].sense, ConstraintSense.geq) + self.assertEqual(quadratic_program.quadratic_constraints[3].rhs, 1.0) + self.assertEqual(quadratic_program.quadratic_constraints[3], + quadratic_program.get_quadratic_constraint('c3')) + self.assertEqual(quadratic_program.quadratic_constraints[3], + quadratic_program.get_quadratic_constraint(3)) + + # leq constraints + quadratic_program.quadratic_leq_constraint() + self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 5) + self.assertEqual(quadratic_program.quadratic_constraints[4].name, 'c4') + self.assertEqual( + len(quadratic_program.quadratic_constraints[4].linear.coefficients_as_dict()), 0) + self.assertEqual(quadratic_program.quadratic_constraints[4].sense, ConstraintSense.leq) + self.assertEqual(quadratic_program.quadratic_constraints[4].rhs, 0.0) + self.assertEqual(quadratic_program.quadratic_constraints[4], + quadratic_program.get_quadratic_constraint('c4')) + self.assertEqual(quadratic_program.quadratic_constraints[4], + quadratic_program.get_quadratic_constraint(4)) + + with self.assertRaises(QiskitOptimizationError): + quadratic_program.quadratic_leq_constraint(name='c4') + + quadratic_program.quadratic_leq_constraint('c5', linear_coeffs, quadratic_coeffs, 1.0) + self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 6) + self.assertEqual(quadratic_program.quadratic_constraints[5].name, 'c5') + self.assertTrue(( + quadratic_program.quadratic_constraints[5].linear.coefficients_as_array( + ) == linear_coeffs + ).all()) + self.assertTrue(( + quadratic_program.quadratic_constraints[5].quadratic.coefficients_as_array( + ) == quadratic_coeffs + ).all()) + self.assertEqual(quadratic_program.quadratic_constraints[5].sense, ConstraintSense.leq) + self.assertEqual(quadratic_program.quadratic_constraints[5].rhs, 1.0) + self.assertEqual(quadratic_program.quadratic_constraints[5], + quadratic_program.get_quadratic_constraint('c5')) + self.assertEqual(quadratic_program.quadratic_constraints[5], + quadratic_program.get_quadratic_constraint(5)) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/optimization/test_quadratic_constraints.py b/test/optimization/test_quadratic_constraints.py deleted file mode 100644 index a962b96161..0000000000 --- a/test/optimization/test_quadratic_constraints.py +++ /dev/null @@ -1,378 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -""" Test QuadraticConstraintInterface """ - -import unittest -from test.optimization.optimization_test_case import QiskitOptimizationTestCase -import logging - -from qiskit.optimization import QiskitOptimizationError -from qiskit.optimization.problems import QuadraticProgram - -logger = logging.getLogger(__name__) - -_HAS_CPLEX = False -try: - from cplex import SparsePair, SparseTriple - _HAS_CPLEX = True -except ImportError: - logger.info('CPLEX is not installed.') - - -class TestQuadraticConstraints(QiskitOptimizationTestCase): - """Test QuadraticConstraintInterface.""" - - def setUp(self) -> None: - super().setUp() - if not _HAS_CPLEX: - self.skipTest('CPLEX is not installed.') - - def test_initial1(self): - """ test initial 1""" - op = QuadraticProgram() - c_1 = op.quadratic_constraints.add(name='c1') - c_2 = op.quadratic_constraints.add(name='c2') - c_3 = op.quadratic_constraints.add(name='c3') - self.assertEqual(op.quadratic_constraints.get_num(), 3) - self.assertListEqual(op.quadratic_constraints.get_names(), ['c1', 'c2', 'c3']) - self.assertListEqual([c_1, c_2, c_3], [0, 1, 2]) - with self.assertRaises(QiskitOptimizationError): - op.quadratic_constraints.add(name='c1') - - def test_initial2(self): - """ test initial 2""" - op = QuadraticProgram() - op.variables.add(names=['x1', 'x2', 'x3'], types='B' * 3) - op.quadratic_constraints.add( - lin_expr=SparsePair(ind=['x1', 'x3'], val=[1.0, -1.0]), - quad_expr=SparseTriple(ind1=['x1', 'x2'], ind2=['x2', 'x3'], val=[1.0, -1.0]), - sense='E', - rhs=1.0 - ) - quad = op.quadratic_constraints - self.assertEqual(quad.get_num(), 1) - self.assertListEqual(quad.get_names(), ['q1']) - self.assertListEqual(quad.get_rhs(), [1.0]) - self.assertListEqual(quad.get_senses(), ['E']) - self.assertListEqual(quad.get_linear_num_nonzeros(), [2]) - self.assertListEqual(quad.get_quad_num_nonzeros(), [2]) - lin = quad.get_linear_components() - self.assertEqual(len(lin), 1) - self.assertListEqual(lin[0].ind, [0, 2]) - self.assertListEqual(lin[0].val, [1.0, -1.0]) - q = quad.get_quadratic_components() - self.assertEqual(len(q), 1) - self.assertListEqual(q[0].ind1, [1, 2]) - self.assertListEqual(q[0].ind2, [0, 1]) - self.assertListEqual(q[0].val, [1.0, -1.0]) - - def test_initial3(self): - """ test initial 3""" - op = QuadraticProgram() - op.variables.add(names=['x', 'y']) - with self.assertRaises(QiskitOptimizationError): - op.quadratic_constraints.add(lin_expr=([0, 0], [1, 1])) - op.quadratic_constraints.add(quad_expr=([0, 0, 1, 1], [0, 1, 0, 1], [1, 1, 1, 1])) - - def test_get_num(self): - """ test get num """ - op = QuadraticProgram() - op.variables.add(names=['x', 'y']) - lin = SparsePair(ind=['x'], val=[1.0]) - q = SparseTriple(ind1=['x'], ind2=['y'], val=[1.0]) - n = 10 - for i in range(n): - self.assertEqual( - op.quadratic_constraints.add(name=str(i), lin_expr=lin, quad_expr=q), i) - self.assertEqual(op.quadratic_constraints.get_num(), n) - - def test_add(self): - """ test add """ - op = QuadraticProgram() - op.variables.add(names=['x', 'y']) - lin = SparsePair(ind=['x'], val=[1.0]) - q = SparseTriple(ind1=['x'], ind2=['y'], val=[1.0]) - self.assertEqual(op.quadratic_constraints.add( - name='my quad', lin_expr=lin, quad_expr=q, rhs=1.0, sense='G'), 0) - - def test_delete(self): - """ test delete """ - op = QuadraticProgram() - q_0 = [op.quadratic_constraints.add(name=str(i)) for i in range(10)] - self.assertListEqual(q_0, list(range(10))) - q = op.quadratic_constraints - self.assertListEqual(q.get_names(), ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']) - q.delete(8) - self.assertListEqual(q.get_names(), ['0', '1', '2', '3', '4', '5', '6', '7', '9']) - q.delete("1", 3) - self.assertListEqual(q.get_names(), ['0', '4', '5', '6', '7', '9']) - q.delete([2, "0", 5]) - self.assertListEqual(q.get_names(), ['4', '6', '7']) - q.delete() - self.assertListEqual(q.get_names(), []) - - def test_get_rhs(self): - """ test get rhs """ - op = QuadraticProgram() - op.variables.add(names=[str(i) for i in range(10)]) - q_0 = [op.quadratic_constraints.add(rhs=1.5 * i, name=str(i)) for i in range(10)] - self.assertListEqual(q_0, list(range(10))) - q = op.quadratic_constraints - self.assertEqual(q.get_num(), 10) - self.assertEqual(q.get_rhs(8), 12.0) - self.assertListEqual(q.get_rhs('1', 3), [1.5, 3.0, 4.5]) - self.assertListEqual(q.get_rhs([2, '0', 5]), [3.0, 0.0, 7.5]) - self.assertListEqual(q.get_rhs(), [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5]) - - def test_get_senses(self): - """ test get senses """ - op = QuadraticProgram() - op.variables.add(names=["x0"]) - q = op.quadratic_constraints - q_0 = [q.add(name=str(i), sense=j) for i, j in enumerate('GGLL')] - self.assertListEqual(q_0, [0, 1, 2, 3]) - self.assertEqual(q.get_senses(1), 'G') - self.assertListEqual(q.get_senses('1', 3), ['G', 'L', 'L']) - self.assertListEqual(q.get_senses([2, '0', 1]), ['L', 'G', 'G']) - self.assertListEqual(q.get_senses(), ['G', 'G', 'L', 'L']) - - def test_get_linear_num_nonzeros(self): - """ test get linear num non zeros """ - op = QuadraticProgram() - op.variables.add(names=[str(i) for i in range(11)], types="B" * 11) - q = op.quadratic_constraints - n = 10 - _ = [q.add(name=str(i), - lin_expr=[range(i), [1.0 * (j + 1.0) for j in range(i)]]) - for i in range(n)] - self.assertEqual(q.get_num(), n) - self.assertEqual(q.get_linear_num_nonzeros(8), 8) - self.assertListEqual(q.get_linear_num_nonzeros('1', 3), [1, 2, 3]) - self.assertListEqual(q.get_linear_num_nonzeros([2, '0', 5]), [2, 0, 5]) - self.assertListEqual(q.get_linear_num_nonzeros(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - - def test_get_linear_components(self): - """ test get linear components """ - op = QuadraticProgram() - op.variables.add(names=[str(i) for i in range(4)], types="B" * 4) - q = op.quadratic_constraints - z = [q.add(name=str(i), - lin_expr=[range(i), [1.0 * (j + 1.0) for j in range(i)]]) for i in range(3)] - self.assertListEqual(z, [0, 1, 2]) - self.assertEqual(q.get_num(), 3) - - s_p = q.get_linear_components(2) - self.assertListEqual(s_p.ind, [0, 1]) - self.assertListEqual(s_p.val, [1.0, 2.0]) - - s_p = q.get_linear_components('0', 1) - self.assertListEqual(s_p[0].ind, []) - self.assertListEqual(s_p[0].val, []) - self.assertListEqual(s_p[1].ind, [0]) - self.assertListEqual(s_p[1].val, [1.0]) - - s_p = q.get_linear_components([1, '0']) - self.assertListEqual(s_p[0].ind, [0]) - self.assertListEqual(s_p[0].val, [1.0]) - self.assertListEqual(s_p[1].ind, []) - self.assertListEqual(s_p[1].val, []) - - s_p = q.get_linear_components() - self.assertListEqual(s_p[0].ind, []) - self.assertListEqual(s_p[0].val, []) - self.assertListEqual(s_p[1].ind, [0]) - self.assertListEqual(s_p[1].val, [1.0]) - self.assertListEqual(s_p[2].ind, [0, 1]) - self.assertListEqual(s_p[2].val, [1.0, 2.0]) - - def test_get_linear_components2(self): - """ test get linear components 2 """ - op = QuadraticProgram() - op.variables.add(names=[str(i) for i in range(11)], types="B" * 11) - q = op.quadratic_constraints - _ = [q.add(name=str(i), - lin_expr=[range(i), [1.0 * (j + 1.0) for j in range(i)]]) - for i in range(10)] - s_p = q.get_linear_components(8) - self.assertListEqual(s_p.ind, [0, 1, 2, 3, 4, 5, 6, 7]) - self.assertListEqual(s_p.val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]) - - s_p = q.get_linear_components('1', 3) - self.assertEqual(len(s_p), 3) - self.assertListEqual(s_p[0].ind, [0]) - self.assertListEqual(s_p[0].val, [1.0]) - self.assertListEqual(s_p[1].ind, [0, 1]) - self.assertListEqual(s_p[1].val, [1.0, 2.0]) - self.assertListEqual(s_p[2].ind, [0, 1, 2]) - self.assertListEqual(s_p[2].val, [1.0, 2.0, 3.0]) - - s_p = q.get_linear_components([2, '0', 5]) - self.assertEqual(len(s_p), 3) - self.assertListEqual(s_p[0].ind, [0, 1]) - self.assertListEqual(s_p[0].val, [1.0, 2.0]) - self.assertListEqual(s_p[1].ind, []) - self.assertListEqual(s_p[1].val, []) - self.assertListEqual(s_p[2].ind, [0, 1, 2, 3, 4]) - self.assertListEqual(s_p[2].val, [1.0, 2.0, 3.0, 4.0, 5.0]) - - q.delete(4, 9) - s_p = q.get_linear_components() - self.assertEqual(len(s_p), 4) - self.assertListEqual(s_p[0].ind, []) - self.assertListEqual(s_p[0].val, []) - self.assertListEqual(s_p[1].ind, [0]) - self.assertListEqual(s_p[1].val, [1.0]) - self.assertListEqual(s_p[2].ind, [0, 1]) - self.assertListEqual(s_p[2].val, [1.0, 2.0]) - self.assertListEqual(s_p[3].ind, [0, 1, 2]) - self.assertListEqual(s_p[3].val, [1.0, 2.0, 3.0]) - - def test_quad_num_nonzeros(self): - """ test quad num non zeros """ - op = QuadraticProgram() - op.variables.add(names=[str(i) for i in range(11)]) - q = op.quadratic_constraints - _ = [q.add(name=str(i), - quad_expr=[range(i), range(i), [1.0 * (j + 1.0) for j in range(i)]]) - for i in range(1, 11)] - self.assertEqual(q.get_num(), 10) - self.assertEqual(q.get_quad_num_nonzeros(8), 9) - self.assertListEqual(q.get_quad_num_nonzeros('1', 2), [1, 2, 3]) - self.assertListEqual(q.get_quad_num_nonzeros([2, '1', 5]), [3, 1, 6]) - self.assertListEqual(q.get_quad_num_nonzeros(), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) - - def test_get_quadratic_components(self): - """ test get quadratic components """ - op = QuadraticProgram() - op.variables.add(names=[str(i) for i in range(4)]) - q = op.quadratic_constraints - z = [q.add(name="q{0}".format(i), - quad_expr=[range(i), range(i), [1.0 * (j + 1.0) for j in range(i)]]) - for i in range(1, 3)] - self.assertListEqual(z, [0, 1]) - self.assertEqual(q.get_num(), 2) - - s_t = q.get_quadratic_components(1) - self.assertListEqual(s_t.ind1, [0, 1]) - self.assertListEqual(s_t.ind2, [0, 1]) - self.assertListEqual(s_t.val, [1.0, 2.0]) - - s_t = q.get_quadratic_components('q1', 1) - self.assertListEqual(s_t[0].ind1, [0]) - self.assertListEqual(s_t[0].ind2, [0]) - self.assertListEqual(s_t[0].val, [1.0]) - self.assertListEqual(s_t[1].ind1, [0, 1]) - self.assertListEqual(s_t[1].ind2, [0, 1]) - self.assertListEqual(s_t[1].val, [1.0, 2.0]) - - s_t = q.get_quadratic_components(['q2', 0]) - self.assertListEqual(s_t[0].ind1, [0, 1]) - self.assertListEqual(s_t[0].ind2, [0, 1]) - self.assertListEqual(s_t[0].val, [1.0, 2.0]) - self.assertListEqual(s_t[1].ind1, [0]) - self.assertListEqual(s_t[1].ind2, [0]) - self.assertListEqual(s_t[1].val, [1.0]) - - s_t = q.get_quadratic_components() - self.assertListEqual(s_t[0].ind1, [0]) - self.assertListEqual(s_t[0].ind2, [0]) - self.assertListEqual(s_t[0].val, [1.0]) - self.assertListEqual(s_t[1].ind1, [0, 1]) - self.assertListEqual(s_t[1].ind2, [0, 1]) - self.assertListEqual(s_t[1].val, [1.0, 2.0]) - - def test_get_quadratic_components2(self): - """ test get quadratic components 2 """ - op = QuadraticProgram() - op.variables.add(names=[str(i) for i in range(11)]) - q = op.quadratic_constraints - _ = [q.add(name=str(i), - quad_expr=[range(i), range(i), [1.0 * (j + 1.0) for j in range(i)]]) - for i in range(1, 11)] - s_t = q.get_quadratic_components(8) - self.assertListEqual(s_t.ind1, [0, 1, 2, 3, 4, 5, 6, 7, 8]) - self.assertListEqual(s_t.ind2, [0, 1, 2, 3, 4, 5, 6, 7, 8]) - self.assertListEqual(s_t.val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]) - - s_t = q.get_quadratic_components('1', 3) - self.assertEqual(len(s_t), 4) - self.assertListEqual(s_t[0].ind1, [0]) - self.assertListEqual(s_t[0].ind2, [0]) - self.assertListEqual(s_t[0].val, [1.0]) - self.assertListEqual(s_t[1].ind1, [0, 1]) - self.assertListEqual(s_t[1].ind2, [0, 1]) - self.assertListEqual(s_t[1].val, [1.0, 2.0]) - self.assertListEqual(s_t[2].ind1, [0, 1, 2]) - self.assertListEqual(s_t[2].ind2, [0, 1, 2]) - self.assertListEqual(s_t[2].val, [1.0, 2.0, 3.0]) - self.assertListEqual(s_t[3].ind1, [0, 1, 2, 3]) - self.assertListEqual(s_t[3].ind2, [0, 1, 2, 3]) - self.assertListEqual(s_t[3].val, [1.0, 2.0, 3.0, 4.0]) - - s_t = q.get_quadratic_components([2, '1', 5]) - self.assertEqual(len(s_t), 3) - self.assertListEqual(s_t[0].ind1, [0, 1, 2]) - self.assertListEqual(s_t[0].ind2, [0, 1, 2]) - self.assertListEqual(s_t[0].val, [1.0, 2.0, 3.0]) - self.assertListEqual(s_t[1].ind1, [0]) - self.assertListEqual(s_t[1].ind2, [0]) - self.assertListEqual(s_t[1].val, [1.0]) - self.assertListEqual(s_t[2].ind1, [0, 1, 2, 3, 4, 5]) - self.assertListEqual(s_t[2].ind2, [0, 1, 2, 3, 4, 5]) - self.assertListEqual(s_t[2].val, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) - - q.delete(4, 9) - s_t = q.get_quadratic_components() - self.assertEqual(len(s_t), 4) - self.assertListEqual(s_t[0].ind1, [0]) - self.assertListEqual(s_t[0].ind2, [0]) - self.assertListEqual(s_t[0].val, [1.0]) - self.assertListEqual(s_t[1].ind1, [0, 1]) - self.assertListEqual(s_t[1].ind2, [0, 1]) - self.assertListEqual(s_t[1].val, [1.0, 2.0]) - self.assertListEqual(s_t[2].ind1, [0, 1, 2]) - self.assertListEqual(s_t[2].ind2, [0, 1, 2]) - self.assertListEqual(s_t[2].val, [1.0, 2.0, 3.0]) - self.assertListEqual(s_t[3].ind1, [0, 1, 2, 3]) - self.assertListEqual(s_t[3].ind2, [0, 1, 2, 3]) - self.assertListEqual(s_t[3].val, [1.0, 2.0, 3.0, 4.0]) - - def test_get_names(self): - """ test get names """ - op = QuadraticProgram() - op.variables.add(names=[str(i) for i in range(11)]) - q = op.quadratic_constraints - _ = [q.add(name="q" + str(i), - quad_expr=[range(i), range(i), [1.0 * (j + 1.0) for j in range(i)]]) - for i in range(1, 11)] - self.assertEqual(q.get_num(), 10) - self.assertEqual(q.get_names(8), 'q9') - self.assertListEqual(q.get_names(1, 3), ['q2', 'q3', 'q4']) - self.assertListEqual(q.get_names([2, 0, 5]), ['q3', 'q1', 'q6']) - self.assertListEqual(q.get_names(), - ['q1', 'q2', 'q3', 'q4', 'q5', 'q6', 'q7', 'q8', 'q9', 'q10']) - - def test_empty_names(self): - """ test empty names """ - op = QuadraticProgram() - op.variables.add(names=['x']) - op.quadratic_constraints.add(name='a') - op.quadratic_constraints.add(name='') - op.quadratic_constraints.add(name='c') - self.assertListEqual(op.quadratic_constraints.get_names(), ['a', 'q2', 'c']) - - -if __name__ == '__main__': - unittest.main() From 5ec7fd3a6b5dbb6cf9ffd0923d6b82539eb1d976 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 15 Apr 2020 00:37:13 +0200 Subject: [PATCH 189/323] add quadratic objective --- .../problems/quadratic_constraint.py | 2 +- .../problems/quadratic_objective.py | 143 +++++++- .../problems/quadratic_program.py | 65 ++++ test/optimization/test_objective.py | 305 ------------------ .../optimization/test_quadratic_constraint.py | 12 + test/optimization/test_quadratic_objective.py | 103 ++++++ 6 files changed, 321 insertions(+), 309 deletions(-) delete mode 100644 test/optimization/test_objective.py create mode 100644 test/optimization/test_quadratic_objective.py diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index fd09728bbd..55d9d71e88 100644 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -23,7 +23,7 @@ class QuadraticConstraint(Constraint): - """ Representation of a linear constraint.""" + """ Representation of a quadratic constraint.""" def __init__(self, quadratic_program: "QuadraticProgram", name: str, diff --git a/qiskit/optimization/problems/quadratic_objective.py b/qiskit/optimization/problems/quadratic_objective.py index 68c0e12feb..ea3bda08b9 100644 --- a/qiskit/optimization/problems/quadratic_objective.py +++ b/qiskit/optimization/problems/quadratic_objective.py @@ -1,12 +1,149 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019, 2020. +# +# 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. + +"""Quadratic Objective.""" + from enum import Enum +from typing import Union, List, Dict, Tuple +from numpy import ndarray +from scipy.sparse import spmatrix + +from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram +from qiskit.optimization.problems import LinearExpression, QuadraticExpression class ObjSense(Enum): + """Objective Sense Type.""" minimize = 1 maximize = -1 -class QuadraticObjective: +class QuadraticObjective(HasQuadraticProgram): + """Representation of quadratic objective function of the form: + constant + linear * x + x * quadratic * x. + """ + + def __init__(self, quadratic_program: "QuadraticProgram", + constant: float = 0.0, + linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]] = None, + quadratic: Union[ndarray, spmatrix, List[List[float]], + Dict[Tuple[Union[int, str], Union[int, str]], float]] = None, + sense: ObjSense = ObjSense.minimize + ) -> None: + """Constructs a quadratic objective function. + + Args: + quadratic_program: The parent quadratic program. + constant: The constant offset of the objective. + linear: The coefficients of the linear part of the objective. + quadratic: The coefficients of the quadratic part of the objective. + sense: The optimizaiton sense of the objective. + """ + super().__init__(quadratic_program) + self._constant = constant + if linear is None: + linear = {} + self._linear = LinearExpression(quadratic_program, linear) + if quadratic is None: + quadratic = {} + self._quadratic = QuadraticExpression(quadratic_program, quadratic) + self._sense = sense + + @property + def constant(self) -> float: + """Returns the constant part of the objective function. + + Returns: + The constant part of the objective function. + """ + return self._constant + + @constant.setter + def constant(self, constant: float) -> None: + """Sets the constant part of the objective function. + + Args: + constant: The constant part of the objective function. + """ + self._constant = constant + + @property + def linear(self) -> LinearExpression: + """Returns the linear part of the objective function. + + Returns: + The linear part of the objective function. + """ + return self._linear + + @linear.setter + def linear(self, linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]] + ) -> None: + """Sets the coefficients of the linear part of the objective function. + + Args: + linear: The coefficients of the linear part of the objective function. + + """ + self._linear = LinearExpression(self.quadratic_program, linear) + + @property + def quadratic(self) -> QuadraticExpression: + """Returns the quadratic part of the objective function. + + Returns: + The quadratic part of the objective function. + """ + return self._quadratic + + @quadratic.setter + def quadratic(self, quadratic: Union[ndarray, spmatrix, List[List[float]], + Dict[Tuple[Union[int, str], Union[int, str]], float]] + ) -> None: + """Sets the coefficients of the quadratic part of the objective function. + + Args: + quadratic: The coefficients of the quadratic part of the objective function. + + """ + self._quadratic = QuadraticExpression(self.quadratic_program, quadratic) + + @property + def sense(self) -> ObjSense: + """Returns the sense of the objective function. + + Returns: + The sense of the objective function. + """ + return self._sense + + @sense.setter + def sense(self, sense: ObjSense) -> None: + """Sets the sense of the objective function. + + Args: + sense: The sense of the objective function. + """ + self._sense = sense + + def evaluate(self, x: Union[ndarray, List, Dict[Union[int, str], float]]) -> float: + """Evaluate the quadratic objective for given variable values. + + Args: + x: The values of the variables to be evaluated. - def __init__(self): - pass + Returns: + The value of the quadratic objective given the variable values. + """ + return self.constant + self.linear.evaluate(x) + self.quadratic.evaluate(x) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 8f9c364910..fcc761bed3 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -23,6 +23,7 @@ from qiskit.optimization.problems.constraint import ConstraintSense from qiskit.optimization.problems.linear_constraint import LinearConstraint from qiskit.optimization.problems.quadratic_constraint import QuadraticConstraint +from qiskit.optimization.problems.quadratic_objective import QuadraticObjective, ObjSense class QuadraticProgram: @@ -47,6 +48,25 @@ def __init__(self, name: str = '') -> None: self._quadratic_constraints: List[QuadraticConstraint] = [] self._quadratic_constraints_index: Dict[str, int] = {} + self._objective = QuadraticObjective(self) + + def clear(self) -> None: + """Clears the quadratic program, i.e., deletes all variables, constraints, the + objective function as well as the name. + """ + self._name = '' + + self._variables: List[Variable] = [] + self._variables_index: Dict[str, int] = {} + + self._linear_constraints: List[LinearConstraint] = [] + self._linear_constraints_index: Dict[str, int] = {} + + self._quadratic_constraints: List[QuadraticConstraint] = [] + self._quadratic_constraints_index: Dict[str, int] = {} + + self._objective = QuadraticObjective(self) + @property def name(self) -> str: """Returns the name of the quadratic program. @@ -527,3 +547,48 @@ def get_num_quadratic_constraints(self) -> int: The number of quadratic constraints. """ return len(self.quadratic_constraints) + + @property + def objective(self) -> QuadraticObjective: + """Returns the quadratic objective. + + Returns: + The quadratic objective. + """ + return self._objective + + def minimize(self, + constant: float = 0.0, + linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]] = None, + quadratic: Union[ndarray, spmatrix, List[List[float]], + Dict[Tuple[Union[int, str], Union[int, str]], float]] = None + ) -> QuadraticObjective: + """Sets a quadrartic objective to be minimized. + + Args: + constant: the constant offset of the objective. + linear: the coefficients of the linear part of the objective. + quadratic: the coefficients of the quadratic part of the objective. + + Returns: + The created quadratic objective. + """ + self._objective = QuadraticObjective(self, constant, linear, quadratic, ObjSense.minimize) + + def maximize(self, + constant: float = 0.0, + linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]] = None, + quadratic: Union[ndarray, spmatrix, List[List[float]], + Dict[Tuple[Union[int, str], Union[int, str]], float]] = None + ) -> QuadraticObjective: + """Sets a quadrartic objective to be maximized. + + Args: + constant: the constant offset of the objective. + linear: the coefficients of the linear part of the objective. + quadratic: the coefficients of the quadratic part of the objective. + + Returns: + The created quadratic objective. + """ + self._objective = QuadraticObjective(self, constant, linear, quadratic, ObjSense.maximize) diff --git a/test/optimization/test_objective.py b/test/optimization/test_objective.py deleted file mode 100644 index 7dd22616b6..0000000000 --- a/test/optimization/test_objective.py +++ /dev/null @@ -1,305 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -""" Test ObjectiveInterface """ - -import unittest -from test.optimization.optimization_test_case import QiskitOptimizationTestCase -import logging - -from qiskit.optimization import QuadraticProgram, QiskitOptimizationError - -logger = logging.getLogger(__name__) - -_HAS_CPLEX = False -try: - from cplex import SparsePair - _HAS_CPLEX = True -except ImportError: - logger.info('CPLEX is not installed.') - - -class TestObjective(QiskitOptimizationTestCase): - """Test ObjectiveInterface""" - - def setUp(self) -> None: - super().setUp() - if not _HAS_CPLEX: - self.skipTest('CPLEX is not installed.') - - def test_obj_sense(self): - """ test obj sense """ - op = QuadraticProgram() - self.assertEqual(op.objective.sense.minimize, 1) - self.assertEqual(op.objective.sense.maximize, -1) - self.assertEqual(op.objective.sense[1], 'minimize') - self.assertEqual(op.objective.sense[-1], 'maximize') - - def test_set_linear(self): - """ test set linear """ - op = QuadraticProgram() - n = 4 - op.variables.add(names=[str(i) for i in range(n)]) - self.assertListEqual(op.objective.get_linear(), [0.0, 0.0, 0.0, 0.0]) - self.assertDictEqual(op.objective.get_linear_dict(), {}) - op.objective.set_linear(0, 1.0) - self.assertListEqual(op.objective.get_linear(), [1.0, 0.0, 0.0, 0.0]) - self.assertDictEqual(op.objective.get_linear_dict(), {0: 1.0}) - op.objective.set_linear('3', -1.0) - self.assertListEqual(op.objective.get_linear(), [1.0, 0.0, 0.0, -1.0]) - self.assertDictEqual(op.objective.get_linear_dict(), {0: 1.0, 3: -1.0}) - op.objective.set_linear([("2", 2.0), (1, 0.5)]) - self.assertListEqual(op.objective.get_linear(), [1.0, 0.5, 2.0, -1.0]) - self.assertDictEqual(op.objective.get_linear_dict(), {0: 1.0, 1: 0.5, 2: 2.0, 3: -1.0}) - - def test_set_empty_quadratic(self): - """ test set empty quadratic """ - op = QuadraticProgram() - self.assertIsNone(op.objective.set_quadratic([])) - with self.assertRaises(TypeError): - op.objective.set_quadratic() - - def test_set_quadratic(self): - """ test set quadratic """ - op = QuadraticProgram() - n = 3 - op.variables.add(names=[str(i) for i in range(n)]) - obj = op.objective - obj.set_quadratic([SparsePair(ind=[0, 1, 2], val=[1.0, -2.0, 0.5]), - ([0, 1], [-2.0, -1.0]), - SparsePair(ind=[0, 2], val=[0.5, -3.0])]) - lst = obj.get_quadratic() - self.assertListEqual(lst[0].ind, [0, 1, 2]) - self.assertListEqual(lst[0].val, [1.0, -2.0, 0.5]) - self.assertListEqual(lst[1].ind, [0, 1]) - self.assertListEqual(lst[1].val, [-2.0, -1.0]) - self.assertListEqual(lst[2].ind, [0, 2]) - self.assertListEqual(lst[2].val, [0.5, -3.0]) - self.assertDictEqual( - obj.get_quadratic_dict(), - {(0, 0): 1.0, (0, 1): -2.0, (0, 2): 0.5, (1, 0): -2.0, (1, 1): -1.0, - (2, 0): 0.5, (2, 2): -3.0} - ) - - obj.set_quadratic([1.0, 2.0, 3.0]) - lst = obj.get_quadratic() - self.assertListEqual(lst[0].ind, [0]) - self.assertListEqual(lst[0].val, [1.0]) - self.assertListEqual(lst[1].ind, [1]) - self.assertListEqual(lst[1].val, [2.0]) - self.assertListEqual(lst[2].ind, [2]) - self.assertListEqual(lst[2].val, [3.0]) - self.assertDictEqual( - obj.get_quadratic_dict(), - {(0, 0): 1.0, (1, 1): 2.0, (2, 2): 3.0} - ) - - def test_get_quadratic_dict(self): - """ test get quadratic dict """ - op = QuadraticProgram() - op.variables.add(names=['x', 'y']) - op.objective.set_quadratic_coefficients([('x', 'x', 1), ('x', 'y', 2)]) - self.assertDictEqual(op.objective.get_quadratic_dict(), - {(0, 0): 1, (0, 1): 2, (1, 0): 2}) - - def test_set_quadratic_coefficients(self): - """ test set quadratic coefficients """ - op = QuadraticProgram() - n = 3 - op.variables.add(names=[str(i) for i in range(n)]) - obj = op.objective - obj.set_quadratic_coefficients(0, 1, 1.0) - with self.assertRaises(QiskitOptimizationError): - obj.get_quadratic_coefficients() - lst = op.objective.get_quadratic(range(n)) - self.assertListEqual(lst[0].ind, [1]) - self.assertListEqual(lst[0].val, [1.0]) - self.assertListEqual(lst[1].ind, [0]) - self.assertListEqual(lst[1].val, [1.0]) - self.assertListEqual(lst[2].ind, []) - self.assertListEqual(lst[2].val, []) - - obj.set_quadratic_coefficients([(1, 1, 2.0), (0, 2, 3.0)]) - lst = op.objective.get_quadratic(range(n)) - self.assertListEqual(lst[0].ind, [1, 2]) - self.assertListEqual(lst[0].val, [1.0, 3.0]) - self.assertListEqual(lst[1].ind, [0, 1]) - self.assertListEqual(lst[1].val, [1.0, 2.0]) - self.assertListEqual(lst[2].ind, [0]) - self.assertListEqual(lst[2].val, [3.0]) - - obj.set_quadratic_coefficients([(0, 1, 4.0), (1, 0, 5.0)]) - lst = op.objective.get_quadratic(range(n)) - self.assertListEqual(lst[0].ind, [1, 2]) - self.assertListEqual(lst[0].val, [5.0, 3.0]) - self.assertListEqual(lst[1].ind, [0, 1]) - self.assertListEqual(lst[1].val, [5.0, 2.0]) - self.assertListEqual(lst[2].ind, [0]) - self.assertListEqual(lst[2].val, [3.0]) - - def test_set_senses(self): - """ test set senses """ - op = QuadraticProgram() - self.assertEqual(op.objective.sense[op.objective.get_sense()], 'minimize') - op.objective.set_sense(op.objective.sense.maximize) - self.assertEqual(op.objective.sense[op.objective.get_sense()], 'maximize') - op.objective.set_sense(op.objective.sense.minimize) - self.assertEqual(op.objective.sense[op.objective.get_sense()], 'minimize') - - def test_set_name(self): - """ test set name """ - op = QuadraticProgram() - op.objective.set_name('cost') - self.assertEqual(op.objective.get_name(), 'cost') - - def test_get_linear(self): - """ test get linear """ - op = QuadraticProgram() - n = 10 - op.variables.add(names=[str(i) for i in range(n)]) - obj = op.objective - obj.set_linear([(i, 1.5 * i) for i in range(n)]) - self.assertEqual(op.variables.get_num(), 10) - self.assertEqual(obj.get_linear(8), 12) - self.assertListEqual(obj.get_linear('1', 3), [1.5, 3.0, 4.5]) - self.assertListEqual(obj.get_linear([2, '0', 5]), [3.0, 0.0, 7.5]) - self.assertListEqual(obj.get_linear(), - [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5]) - - def test_get_linear_dict(self): - """ test get linear dict """ - op = QuadraticProgram() - op.variables.add(names=['x', 'y']) - op.objective.set_linear([('x', 1), ('y', 2)]) - self.assertDictEqual(op.objective.get_linear_dict(), {0: 1, 1: 2}) - - def test_get_quadratic(self): - """ test get quadratic """ - op = QuadraticProgram() - n = 10 - op.variables.add(names=[str(i) for i in range(n)]) - obj = op.objective - obj.set_quadratic([1.5 * i for i in range(n)]) - s_p = obj.get_quadratic(8) - self.assertListEqual(s_p.ind, [8]) - self.assertListEqual(s_p.val, [12.0]) - - s_p = obj.get_quadratic('1', 3) - self.assertListEqual(s_p[0].ind, [1]) - self.assertListEqual(s_p[0].val, [1.5]) - self.assertListEqual(s_p[1].ind, [2]) - self.assertListEqual(s_p[1].val, [3.0]) - self.assertListEqual(s_p[2].ind, [3]) - self.assertListEqual(s_p[2].val, [4.5]) - - s_p = obj.get_quadratic([3, '1', 5]) - self.assertListEqual(s_p[0].ind, [3]) - self.assertListEqual(s_p[0].val, [4.5]) - self.assertListEqual(s_p[1].ind, [1]) - self.assertListEqual(s_p[1].val, [1.5]) - self.assertListEqual(s_p[2].ind, [5]) - self.assertListEqual(s_p[2].val, [7.5]) - - s_p = obj.get_quadratic() - for i in range(n): - if i == 0: - self.assertListEqual(s_p[i].ind, []) - self.assertListEqual(s_p[i].val, []) - else: - self.assertListEqual(s_p[i].ind, [i]) - self.assertListEqual(s_p[i].val, [1.5 * i]) - - def test_get_quadratic_coefficients(self): - """ test get quadratic coefficients """ - op = QuadraticProgram() - n = 3 - op.variables.add(names=[str(i) for i in range(n)]) - obj = op.objective - obj.set_quadratic_coefficients(0, 1, 1.0) - with self.assertRaises(QiskitOptimizationError): - obj.get_quadratic_coefficients() - self.assertEqual(obj.get_quadratic_coefficients('1', 0), 1.0) - obj.set_quadratic_coefficients([(1, 1, 2.0), (0, 2, 3.0), (1, 0, 5.0)]) - self.assertListEqual(obj.get_quadratic_coefficients([(1, 0), (1, "1"), (2, "0")]), - [5.0, 2.0, 3.0]) - - def test_get_sense(self): - """ test get sense """ - op = QuadraticProgram() - self.assertEqual(op.objective.sense[op.objective.get_sense()], 'minimize') - op.objective.set_sense(op.objective.sense.maximize) - self.assertEqual(op.objective.sense[op.objective.get_sense()], 'maximize') - op.objective.set_sense(op.objective.sense.minimize) - self.assertEqual(op.objective.sense[op.objective.get_sense()], 'minimize') - - def test_get_name(self): - """ test get name """ - op = QuadraticProgram() - op.objective.set_name('cost') - self.assertEqual(op.objective.get_name(), 'cost') - - def test_get_num_quadratic_variables(self): - """ test get num quadratic variables """ - op = QuadraticProgram() - n = 3 - op.variables.add(names=[str(i) for i in range(n)]) - obj = op.objective - obj.set_quadratic_coefficients(0, 1, 1.0) - self.assertEqual(obj.get_num_quadratic_variables(), 2) - obj.set_quadratic([1.0, 0.0, 0.0]) - self.assertEqual(obj.get_num_quadratic_variables(), 1) - obj.set_quadratic_coefficients([(1, 1, 2.0), (0, 2, 3.0)]) - self.assertEqual(obj.get_num_quadratic_variables(), 3) - - def test_get_num_quadratic_nonzeros(self): - """ test get num quadratic non zeros """ - op = QuadraticProgram() - n = 3 - op.variables.add(names=[str(i) for i in range(n)]) - obj = op.objective - obj.set_quadratic_coefficients(0, 1, 1.0) - self.assertEqual(obj.get_num_quadratic_nonzeros(), 2) - obj.set_quadratic_coefficients([(1, 1, 2.0), (0, 2, 3.0)]) - self.assertEqual(obj.get_num_quadratic_nonzeros(), 5) - obj.set_quadratic_coefficients([(0, 1, 4.0), (1, 0, 0.0)]) - self.assertEqual(obj.get_num_quadratic_nonzeros(), 3) - - def test_offset(self): - """ test offset """ - op = QuadraticProgram() - self.assertEqual(op.objective.get_offset(), 0.0) - op.objective.set_offset(3.14) - self.assertEqual(op.objective.get_offset(), 3.14) - - def test_set_quadratic_coefficients2(self): - """ test set quadratic coefficients 2 """ - op = QuadraticProgram() - n = 2 - op.variables.add(names=[str(i) for i in range(n)]) - obj = op.objective - obj.set_quadratic_coefficients([(0, 1, 1.0), (1, 0, -1.0)]) - lst = op.objective.get_quadratic() - self.assertListEqual(lst[0].ind, [1]) - self.assertListEqual(lst[0].val, [-1.0]) - self.assertListEqual(lst[1].ind, [0]) - self.assertListEqual(lst[1].val, [-1.0]) - - def test_default_name(self): - """ test default name """ - op = QuadraticProgram() - self.assertEqual(op.objective.get_name(), 'obj1') - - -if __name__ == '__main__': - unittest.main() diff --git a/test/optimization/test_quadratic_constraint.py b/test/optimization/test_quadratic_constraint.py index 4b02635966..c7de5ccaa2 100644 --- a/test/optimization/test_quadratic_constraint.py +++ b/test/optimization/test_quadratic_constraint.py @@ -54,6 +54,8 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[0], quadratic_program.get_quadratic_constraint(0)) + self.assertEqual(quadratic_program.quadratic_constraints[0].evaluate(linear_coeffs), 0.0) + with self.assertRaises(QiskitOptimizationError): quadratic_program.quadratic_eq_constraint(name='c0') @@ -75,6 +77,8 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[1], quadratic_program.get_quadratic_constraint(1)) + self.assertEqual(quadratic_program.quadratic_constraints[1].evaluate(linear_coeffs), 930.0) + # geq constraints quadratic_program.quadratic_geq_constraint() self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 3) @@ -90,6 +94,8 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[2], quadratic_program.get_quadratic_constraint(2)) + self.assertEqual(quadratic_program.quadratic_constraints[2].evaluate(linear_coeffs), 0.0) + with self.assertRaises(QiskitOptimizationError): quadratic_program.quadratic_geq_constraint(name='c2') @@ -111,6 +117,8 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[3], quadratic_program.get_quadratic_constraint(3)) + self.assertEqual(quadratic_program.quadratic_constraints[3].evaluate(linear_coeffs), 930.0) + # leq constraints quadratic_program.quadratic_leq_constraint() self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 5) @@ -124,6 +132,8 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[4], quadratic_program.get_quadratic_constraint(4)) + self.assertEqual(quadratic_program.quadratic_constraints[4].evaluate(linear_coeffs), 0.0) + with self.assertRaises(QiskitOptimizationError): quadratic_program.quadratic_leq_constraint(name='c4') @@ -145,6 +155,8 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[5], quadratic_program.get_quadratic_constraint(5)) + self.assertEqual(quadratic_program.quadratic_constraints[5].evaluate(linear_coeffs), 930.0) + if __name__ == '__main__': unittest.main() diff --git a/test/optimization/test_quadratic_objective.py b/test/optimization/test_quadratic_objective.py new file mode 100644 index 0000000000..7cfc4061cf --- /dev/null +++ b/test/optimization/test_quadratic_objective.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +""" Test QuadraticObjective """ + +import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase +import logging +import numpy as np + +from qiskit.optimization import QuadraticProgram +from qiskit.optimization.problems.quadratic_objective import ObjSense + +logger = logging.getLogger(__name__) + + +class TestQuadraticObjective(QiskitOptimizationTestCase): + """Test QuadraticObjective""" + + def test_init(self): + """ test init. """ + + quadratic_program = QuadraticProgram() + for _ in range(5): + quadratic_program.continuous_var() + + self.assertEqual(quadratic_program.objective.constant, 0.0) + self.assertEqual( + len(quadratic_program.objective.linear.coefficients_as_dict()), 0) + self.assertEqual( + len(quadratic_program.objective.quadratic.coefficients_as_dict()), 0) + self.assertEqual(quadratic_program.objective.sense, ObjSense.minimize) + + constant = 1.0 + linear_coeffs = np.array([i for i in range(5)]) + quadratic_coeffs = np.array([[i*j for i in range(5)] for j in range(5)]) + + quadratic_program.minimize(constant, linear_coeffs, quadratic_coeffs) + + self.assertEqual(quadratic_program.objective.constant, constant) + self.assertTrue( + (quadratic_program.objective.linear.coefficients_as_array() == linear_coeffs).all()) + self.assertTrue( + (quadratic_program.objective.quadratic.coefficients_as_array() == quadratic_coeffs) + .all()) + self.assertEqual(quadratic_program.objective.sense, ObjSense.minimize) + + quadratic_program.maximize(constant, linear_coeffs, quadratic_coeffs) + + self.assertEqual(quadratic_program.objective.constant, constant) + self.assertTrue( + (quadratic_program.objective.linear.coefficients_as_array() == linear_coeffs).all()) + self.assertTrue( + (quadratic_program.objective.quadratic.coefficients_as_array() == quadratic_coeffs) + .all()) + self.assertEqual(quadratic_program.objective.sense, ObjSense.maximize) + + self.assertEqual(quadratic_program.objective.evaluate(linear_coeffs), 931.0) + + def test_setters(self): + """ test setters. """ + + quadratic_program = QuadraticProgram() + for _ in range(5): + quadratic_program.continuous_var() + + constant = 1.0 + linear_coeffs = np.array([i for i in range(5)]) + quadratic_coeffs = np.array([[i*j for i in range(5)] for j in range(5)]) + + quadratic_program.objective.constant = constant + quadratic_program.objective.linear = linear_coeffs + quadratic_program.objective.quadratic = quadratic_coeffs + + self.assertEqual(quadratic_program.objective.constant, constant) + self.assertTrue( + (quadratic_program.objective.linear.coefficients_as_array() == linear_coeffs).all()) + self.assertTrue( + (quadratic_program.objective.quadratic.coefficients_as_array() == quadratic_coeffs) + .all()) + + self.assertEqual(quadratic_program.objective.sense, ObjSense.minimize) + + quadratic_program.objective.sense = ObjSense.maximize + self.assertEqual(quadratic_program.objective.sense, ObjSense.maximize) + + quadratic_program.objective.sense = ObjSense.minimize + self.assertEqual(quadratic_program.objective.sense, ObjSense.minimize) + + +if __name__ == '__main__': + unittest.main() From bc5705d35ff4569b2ad987b5006f9fb6b5c8de5a Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 15 Apr 2020 01:15:04 +0200 Subject: [PATCH 190/323] Update quadratic_program.py --- .../problems/quadratic_program.py | 219 ++++++++++++++++++ 1 file changed, 219 insertions(+) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index fcc761bed3..72cc2f8ba3 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -17,6 +17,8 @@ from typing import List, Union, Dict, Optional, Tuple from numpy import ndarray from scipy.sparse import spmatrix +from docplex.mp.model import Model +from docplex.mp.linear import Var from qiskit.optimization import infinity, QiskitOptimizationError from qiskit.optimization.problems.variable import Variable, VarType @@ -592,3 +594,220 @@ def maximize(self, The created quadratic objective. """ self._objective = QuadraticObjective(self, constant, linear, quadratic, ObjSense.maximize) + + def from_docplex(self, model: Model) -> None: + """Loads this quadratic program from a docplex model + + Args: + model: The docplex model to be loaded. + + Raises: + QiskitOptimizationError: if the model contains unsupported elements. + """ + + # clear current problem + self.clear() + + # get name + self.name = model.name + + # get variables + for x in model.iter_variables(): + if x.get_vartype().one_letter_symbol() == 'C': + self.continuous_var(x.name, x.lb, x.ub) + elif x.get_vartype().one_letter_symbol() == 'B': + self.binary_var(x.name) + elif x.get_vartype().one_letter_symbol() == 'I': + self.integer_var(x.name, x.lb, x.ub) + else: + raise QiskitOptimizationError("Unsupported variable type!") + + # objective sense + minimize = model.objective_sense.is_minimize() + + # get objective offset + constant = model.objective_expr.constant + + # get linear part of objective + linear_part = model.objective_expr.get_linear_part() + linear = {} + for x in linear_part.iter_variables(): + linear[x.name] = linear_part.get_coef(x) + + # get quadratic part of objective + quadratic = {} + for quad_triplet in model.objective_expr.generate_quad_triplets(): + i = quad_triplet[0].name + j = quad_triplet[1].name + v = quad_triplet[2] + quadratic[(i, j)] = v + + # set objective + if minimize: + self.minimize(constant, linear, quadratic) + else: + self.maximize(constant, linear, quadratic) + + # get linear constraints + for i in range(model.number_of_linear_constraints): + constraint = model.get_constraint_by_index(i) + name = constraint.name + sense = constraint.sense + + rhs = 0 + if not isinstance(constraint.lhs, Var): + rhs -= constraint.lhs.constant + if not isinstance(constraint.rhs, Var): + rhs += constraint.rhs.constant + + lhs = {} + for x in constraint.iter_net_linear_coefs(): + lhs[x[0].name] = x[1] + + if sense == sense.EQ: + self.linear_eq_constraint(name, lhs, rhs) + elif sense == sense.GE: + self.linear_geq_constraint(name, lhs, rhs) + elif sense == sense.LE: + self.linear_leq_constraint(name, lhs, rhs) + else: + raise QiskitOptimizationError("Unsupported constraint sense!") + + # get quadratic constraints + for i in range(model.number_of_quadratic_constraints): + constraint = model.get_quadratic_by_index(i) + name = constraint.name + sense = constraint.sense + + left_expr = constraint.get_left_expr() + right_expr = constraint.get_right_expr() + + rhs = right_expr.constant - left_expr.constant + linear = {} + quadratic = {} + + if left_expr.is_quad_expr(): + for x in left_expr.linear_part.iter_variables(): + linear[x.name] = left_expr.linear_part.get_coef(x) + for quad_triplet in left_expr.iter_quad_triplets(): + i = quad_triplet[0].name + j = quad_triplet[1].name + v = quad_triplet[2] + quadratic[(i, j)] = v + else: + for x in left_expr.iter_variables(): + linear[x.name] = left_expr.get_coef(x) + + if right_expr.is_quad_expr(): + for x in right_expr.linear_part.iter_variables(): + linear[x.name] = linear.get(x.name, 0.0) - right_expr.linear_part.get_coef(x) + for quad_triplet in right_expr.iter_quad_triplets(): + i = quad_triplet[0].name + j = quad_triplet[1].name + v = quad_triplet[2] + quadratic[(i, j)] = quadratic.get((i, j), 0.0) - v + else: + for x in right_expr.iter_variables(): + linear[x.name] = linear.get(x.name, 0.0) - right_expr.get_coef(x) + + if sense == sense.EQ: + self.quadratic_eq_constraint(name, linear, quadratic, rhs) + elif sense == sense.GE: + self.quadratic_geq_constraint(name, linear, quadratic, rhs) + elif sense == sense.LE: + self.quadratic_leq_constraint(name, linear, quadratic, rhs) + else: + raise QiskitOptimizationError("Unsupported constraint sense!") + + def to_docplex(self) -> Model: + """Returns a docplex model corresponding to this quadratic program. + + Returns: + The docplex model corresponding to this quadratic program. + + Raises: + QiskitOptimizationError: if non-supported elements (should never happen). + """ + + # initialize model + mdl = Model(self.name) + + # add variables + var = {} + for i, x in enumerate(self.variables): + if x.vartype == VarType.continuous: + var[i] = mdl.continuous_var(lb=x.lowerbound, ub=x.upperbound, name=x.name) + elif x.vartype == VarType.binary: + var[i] = mdl.binary_var(name=x.name) + elif x.vartype == VarType.integer: + var[i] = mdl.integer_var(lb=x.lowerbound, ub=x.upperbound, name=x.name) + else: + # should never happen + raise QiskitOptimizationError('Unknown variable type: %s!' % x.vartype) + + # add objective + objective = self.objective.constant + for i, v in self.objective.linear.coefficients_as_dict().items(): + objective += v * var[i] + for (i, j), v in self.objective.quadratic.coefficients_as_dict().items(): + objective += v * var[i] * var[j] + if self.objective.sense == ObjSense.minimize: + mdl.minimize(objective) + else: + mdl.maximize(objective) + + # add linear constraints + for i, constraint in enumerate(self.linear_constraints): + name = constraint.name + rhs = constraint.rhs + linear_expr = 0 + for j, v in constraint.linear.coefficients_as_dict().items(): + linear_expr += v * var[j] + sense = constraint.sense + if sense == ConstraintSense.eq: + mdl.add_constraint(linear_expr == rhs, ctname=name) + elif sense == ConstraintSense.geq: + mdl.add_constraint(linear_expr >= rhs, ctname=name) + elif sense == ConstraintSense.leq: + mdl.add_constraint(linear_expr <= rhs, ctname=name) + else: + # should never happen + raise QiskitOptimizationError('Unknown sense: %s!' % sense) + + # add quadratic constraints + for i, constraint in enumerate(self.quadratic_constraints): + name = constraint.name + rhs = constraint.rhs + quadratic_expr = 0 + for j, v in constraint.linear.coefficients_as_dict().items(): + quadratic_expr += v * var[j] + for (j, k), v in constraint.quadratic.coefficients_as_dict().items(): + quadratic_expr += v * var[j] * var[k] + sense = constraint.sense + if sense == ConstraintSense.eq: + mdl.add_constraint(quadratic_expr == rhs, ctname=name) + elif sense == ConstraintSense.geq: + mdl.add_constraint(quadratic_expr >= rhs, ctname=name) + elif sense == ConstraintSense.leq: + mdl.add_constraint(quadratic_expr <= rhs, ctname=name) + else: + # should never happen + raise QiskitOptimizationError('Unknown sense: %s!' % sense) + + return mdl + + def pprint_as_string(self) -> str: + """Pretty prints the quadratic program as a string. + + Returns: + A string representing the quadratic program. + """ + return self.to_docplex().pprint_as_string() + + def prettyprint(self, out=None) -> None: + """Pretty prints the quadratic program to a given output stream (None = default). + + Args: + out: The output stream to print to. + """ + self.to_docplex().prettyprint(out) From 0c6e32464ddcd06b14ea0b171b903aed549592bf Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Wed, 15 Apr 2020 12:31:37 +0900 Subject: [PATCH 191/323] add linear_constraint --- .../problems/quadratic_program.py | 70 +++++++++++++++---- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 72cc2f8ba3..b14cdf960b 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -15,17 +15,18 @@ """Quadratic Program.""" from typing import List, Union, Dict, Optional, Tuple + +from docplex.mp.linear import Var +from docplex.mp.model import Model from numpy import ndarray from scipy.sparse import spmatrix -from docplex.mp.model import Model -from docplex.mp.linear import Var from qiskit.optimization import infinity, QiskitOptimizationError -from qiskit.optimization.problems.variable import Variable, VarType from qiskit.optimization.problems.constraint import ConstraintSense from qiskit.optimization.problems.linear_constraint import LinearConstraint from qiskit.optimization.problems.quadratic_constraint import QuadraticConstraint from qiskit.optimization.problems.quadratic_objective import QuadraticObjective, ObjSense +from qiskit.optimization.problems.variable import Variable, VarType class QuadraticProgram: @@ -105,8 +106,9 @@ def variables_index(self) -> Dict[str, int]: """ return self._variables_index - def _add_variables(self, name: Optional[str] = None, lowerbound: float = 0, - upperbound: float = infinity, vartype: VarType = VarType.continuous) -> None: + def _add_variable(self, name: Optional[str] = None, lowerbound: float = 0, + upperbound: float = infinity, + vartype: VarType = VarType.continuous) -> Variable: """Checks whether a variable name is already taken and adds the variable to list and index if not. @@ -151,7 +153,7 @@ def continuous_var(self, name: Optional[str] = None, lowerbound: float = 0, Raises: QiskitOptimizationError: if the variable name is already occupied. """ - return self._add_variables(name, lowerbound, upperbound, VarType.continuous) + return self._add_variable(name, lowerbound, upperbound, VarType.continuous) def binary_var(self, name: Optional[str] = None) -> Variable: """Adds a binary variable to the quadratic program. @@ -165,7 +167,7 @@ def binary_var(self, name: Optional[str] = None) -> Variable: Raises: QiskitOptimizationError: if the variable name is already occupied. """ - return self._add_variables(name, 0, 1, VarType.binary) + return self._add_variable(name, 0, 1, VarType.binary) def integer_var(self, name: Optional[str] = None, lowerbound: float = 0, upperbound: float = infinity) -> Variable: @@ -182,7 +184,7 @@ def integer_var(self, name: Optional[str] = None, lowerbound: float = 0, Raises: QiskitOptimizationError: if the variable name is already occupied. """ - return self._add_variables(name, lowerbound, upperbound, VarType.integer) + return self._add_variable(name, lowerbound, upperbound, VarType.integer) def get_variable(self, i: Union[int, str]) -> Variable: """Returns a variable for a given name or index. @@ -208,7 +210,7 @@ def get_num_vars(self, vartype: Optional[VarType] = None) -> int: The total number of variables. """ if vartype: - return sum([variable.vartype == vartype for variable in self.variables]) + return sum(variable.vartype == vartype for variable in self.variables) else: return len(self.variables) @@ -260,7 +262,7 @@ def _add_linear_constraint(self, Dict[Union[int, str], float]] = None, sense: ConstraintSense = ConstraintSense.leq, rhs: float = 0.0 - ) -> None: + ) -> LinearConstraint: """Checks whether a constraint name is already taken and adds the constraint to list and index if not. @@ -292,6 +294,40 @@ def _add_linear_constraint(self, self.linear_constraints.append(constraint) return constraint + def linear_constraint(self, name: Optional[str] = None, + coefficients: Union[ndarray, spmatrix, List[float], + Dict[Union[int, str], float]] = None, + sense: str = '==', + rhs: float = 0.0) -> LinearConstraint: + """Adds a linear equality constraint to the quadratic program of the form: + linear_coeffs * x sense rhs. + + Args: + name: The name of the constraint. + coefficients: The linear coefficients of the left-hand-side of the constraint. + sense: The sense of the constraint, + - '==' and 'E' denote 'equal to' + - '>=' and 'G' denote 'greater-than-or-equal-to' + - '<=' and 'L' denote 'less-than-or-equal-to' + rhs: The right hand side of the constraint. + + Returns: + The added constraint. + + Raises: + QiskitOptimizationError: if the constraint name already exists or the sense is not + valid. + """ + if sense not in ['E', 'L', 'G', '==', '<=', '>=']: + raise QiskitOptimizationError('Invalid sense: {}'.format(sense)) + if sense in ['E', '==']: + csense = ConstraintSense.eq + elif sense in ['L', '<=']: + csense = ConstraintSense.leq + else: + csense = ConstraintSense.geq + return self._add_linear_constraint(name, coefficients, csense, rhs) + def linear_eq_constraint(self, name: Optional[str] = None, coefficients: Union[ndarray, spmatrix, List[float], Dict[Union[int, str], float]] = None, @@ -406,7 +442,7 @@ def _add_quadratic_constraint(self, float]] = None, sense: ConstraintSense = ConstraintSense.leq, rhs: float = 0.0 - ) -> None: + ) -> QuadraticConstraint: """Checks whether a constraint name is already taken and adds the constraint to list and index if not. @@ -564,7 +600,7 @@ def minimize(self, linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]] = None, quadratic: Union[ndarray, spmatrix, List[List[float]], Dict[Tuple[Union[int, str], Union[int, str]], float]] = None - ) -> QuadraticObjective: + ) -> None: """Sets a quadrartic objective to be minimized. Args: @@ -582,7 +618,7 @@ def maximize(self, linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]] = None, quadratic: Union[ndarray, spmatrix, List[List[float]], Dict[Tuple[Union[int, str], Union[int, str]], float]] = None - ) -> QuadraticObjective: + ) -> None: """Sets a quadrartic objective to be maximized. Args: @@ -811,3 +847,11 @@ def prettyprint(self, out=None) -> None: out: The output stream to print to. """ self.to_docplex().prettyprint(out) + + def print_as_lp_string(self) -> str: + """Prints the quadratic program as a string of LP format. + + Returns: + A string representing the quadratic program. + """ + return self.to_docplex().export_as_lp_string() From 848a13186b7fa0e38b534f36a32301a689ad03ef Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Wed, 15 Apr 2020 15:42:01 +0900 Subject: [PATCH 192/323] revise constraints --- qiskit/optimization/__init__.py | 0 qiskit/optimization/problems/constraint.py | 14 +- .../problems/linear_expression.py | 10 +- .../problems/quadratic_expression.py | 8 +- .../problems/quadratic_program.py | 225 +++++------------- qiskit/optimization/problems/variable.py | 13 - 6 files changed, 74 insertions(+), 196 deletions(-) mode change 100755 => 100644 qiskit/optimization/__init__.py diff --git a/qiskit/optimization/__init__.py b/qiskit/optimization/__init__.py old mode 100755 new mode 100644 diff --git a/qiskit/optimization/problems/constraint.py b/qiskit/optimization/problems/constraint.py index 2cbb29f92e..0ae92666e5 100644 --- a/qiskit/optimization/problems/constraint.py +++ b/qiskit/optimization/problems/constraint.py @@ -17,6 +17,7 @@ from abc import abstractmethod from enum import Enum from typing import Union, List, Dict + from numpy import ndarray from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram @@ -56,19 +57,6 @@ def name(self) -> str: """ return self._name - @name.setter - def name(self, name: str) -> None: - """Sets the name of the constraint and updates the name index in the corresponding QP. - - Args: - name: The name of the constraint. - - Raises: - QiskitOptimizationError: if the name is already existing. - """ - # TODO: update QP and raise exception if name already exists - self._name = name - @property def sense(self) -> ConstraintSense: """Returns the sense of the constraint. diff --git a/qiskit/optimization/problems/linear_expression.py b/qiskit/optimization/problems/linear_expression.py index a30b4fb928..e8817e04e9 100644 --- a/qiskit/optimization/problems/linear_expression.py +++ b/qiskit/optimization/problems/linear_expression.py @@ -14,8 +14,8 @@ """Linear expression interface.""" - from typing import List, Union, Dict + from numpy import ndarray from scipy.sparse import spmatrix, dok_matrix @@ -46,7 +46,7 @@ def __init__(self, quadratic_program: "QuadraticProgram", def _coeffs_to_dok_matrix(self, coefficients: Union[ndarray, spmatrix, List, Dict[Union[int, str], float]] - ) -> None: + ) -> dok_matrix: """Maps given 1d-coefficients to a dok_matrix. Args: @@ -58,7 +58,7 @@ def _coeffs_to_dok_matrix(self, Raises: QiskitOptimizationError: if coefficients are given in unsupported format. """ - if isinstance(coefficients, list) or\ + if isinstance(coefficients, list) or \ isinstance(coefficients, ndarray) and len(coefficients.shape) == 1: coefficients = dok_matrix([coefficients]) elif isinstance(coefficients, spmatrix): @@ -116,8 +116,8 @@ def coefficients_as_dict(self, use_index: bool = True) -> Dict[Union[int, str], if use_index: return {k: v for (_, k), v in self._coefficients.items()} else: - return {self.quadratic_program.variables[k].name: v for (_, k), v in - self._coefficients.items()} + return {self.quadratic_program.variables[k].name: v + for (_, k), v in self._coefficients.items()} def evaluate(self, x: Union[ndarray, List, Dict[Union[int, str], float]]) -> float: """Evaluate the linear expression for given variables. diff --git a/qiskit/optimization/problems/quadratic_expression.py b/qiskit/optimization/problems/quadratic_expression.py index 4ed24f58ce..76b4c5c015 100644 --- a/qiskit/optimization/problems/quadratic_expression.py +++ b/qiskit/optimization/problems/quadratic_expression.py @@ -14,8 +14,8 @@ """Quadratic expression interface.""" - from typing import List, Union, Dict, Tuple + import numpy as np from numpy import ndarray from scipy.sparse import spmatrix, dok_matrix @@ -48,7 +48,7 @@ def _coeffs_to_dok_matrix(self, coefficients: Union[ndarray, spmatrix, List[List[float]], Dict[ Tuple[Union[int, str], Union[int, str]], - float]]) -> None: + float]]) -> dok_matrix: """Maps given coefficients to a dok_matrix. Args: @@ -60,9 +60,7 @@ def _coeffs_to_dok_matrix(self, Raises: QiskitOptimizationError: if coefficients are given in unsupported format. """ - if isinstance(coefficients, list)\ - or isinstance(coefficients, ndarray)\ - or isinstance(coefficients, spmatrix): + if isinstance(coefficients, (list, ndarray, spmatrix)): coefficients = dok_matrix(coefficients) elif isinstance(coefficients, dict): n = self.quadratic_program.get_num_vars() diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index b14cdf960b..cb6755c2cf 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -127,7 +127,7 @@ def _add_variable(self, name: Optional[str] = None, lowerbound: float = 0, """ if name: if name in self._variables_index: - raise QiskitOptimizationError("Variable name already exists!") + raise QiskitOptimizationError("Variable name already exists: {}".format(name)) else: k = self.get_num_vars() while 'x{}'.format(k) in self._variables_index: @@ -281,7 +281,8 @@ def _add_linear_constraint(self, """ if name: if name in self.linear_constraints_index: - raise QiskitOptimizationError("Variable name already exists!") + raise QiskitOptimizationError( + "Linear constraint's name already exists: {}".format(name)) else: k = self.get_num_linear_constraints() while 'c{}'.format(k) in self.linear_constraints_index: @@ -294,101 +295,56 @@ def _add_linear_constraint(self, self.linear_constraints.append(constraint) return constraint - def linear_constraint(self, name: Optional[str] = None, - coefficients: Union[ndarray, spmatrix, List[float], - Dict[Union[int, str], float]] = None, - sense: str = '==', - rhs: float = 0.0) -> LinearConstraint: - """Adds a linear equality constraint to the quadratic program of the form: - linear_coeffs * x sense rhs. + @staticmethod + def _convert_sense(sense: Union[str, ConstraintSense]) -> ConstraintSense: + """Convert a string into a corresponding sense of constraints Args: - name: The name of the constraint. - coefficients: The linear coefficients of the left-hand-side of the constraint. - sense: The sense of the constraint, - - '==' and 'E' denote 'equal to' - - '>=' and 'G' denote 'greater-than-or-equal-to' - - '<=' and 'L' denote 'less-than-or-equal-to' - rhs: The right hand side of the constraint. + sense: A string or sense of constraints Returns: - The added constraint. + The sense of constraints Raises: - QiskitOptimizationError: if the constraint name already exists or the sense is not - valid. + QiskitOptimizationError: if the input string is invalid. """ - if sense not in ['E', 'L', 'G', '==', '<=', '>=']: + if isinstance(sense, ConstraintSense): + return sense + sense = sense.upper() + if sense not in ['E', 'L', 'G', 'EQ', 'LE', 'GE', '=', '==', '<=', '>=']: raise QiskitOptimizationError('Invalid sense: {}'.format(sense)) - if sense in ['E', '==']: - csense = ConstraintSense.eq - elif sense in ['L', '<=']: - csense = ConstraintSense.leq + if sense in ['E', 'EQ', '=', '==']: + return ConstraintSense.eq + elif sense in ['L', 'LE', '<=']: + return ConstraintSense.leq else: - csense = ConstraintSense.geq - return self._add_linear_constraint(name, coefficients, csense, rhs) + return ConstraintSense.geq - def linear_eq_constraint(self, name: Optional[str] = None, - coefficients: Union[ndarray, spmatrix, List[float], - Dict[Union[int, str], float]] = None, - rhs: float = 0.0) -> LinearConstraint: + def linear_constraint(self, name: Optional[str] = None, + coefficients: Union[ndarray, spmatrix, List[float], + Dict[Union[int, str], float]] = None, + sense: Union[str, ConstraintSense] = '==', + rhs: float = 0.0) -> LinearConstraint: """Adds a linear equality constraint to the quadratic program of the form: - linear_coeffs * x == rhs. - - Args: - name: The name of the constraint. - coefficients: The linear coefficients of the left-hand-side of the constraint. - rhs: The right hand side of the constraint. - - Returns: - The added constraint. - - Raises: - QiskitOptimizationError: if the constraint name already exists. - """ - return self._add_linear_constraint(name, coefficients, ConstraintSense.eq, rhs) - - def linear_geq_constraint(self, name: Optional[str] = None, - coefficients: Union[ndarray, spmatrix, List[float], - Dict[Union[int, str], float]] = None, - rhs: float = 0.0) -> LinearConstraint: - """Adds a linear "greater-than-or-equal-to" (geq) constraint to the quadratic program - of the form: - linear_coeffs * x >= rhs. - - Args: - name: The name of the constraint. - coefficients: The linear coefficients of the left-hand-side of the constraint. - rhs: The right hand side of the constraint. - - Returns: - The added constraint. - - Raises: - QiskitOptimizationError: if the constraint name already exists. - """ - return self._add_linear_constraint(name, coefficients, ConstraintSense.geq, rhs) - - def linear_leq_constraint(self, name: Optional[str] = None, - coefficients: Union[ndarray, spmatrix, List[float], - Dict[Union[int, str], float]] = None, - rhs: float = 0.0) -> LinearConstraint: - """Adds a linear "less-than-or-equal-to" (leq) constraint to the quadratic program - of the form: - linear_coeffs * x <= rhs. + linear_coeffs * x sense rhs. Args: name: The name of the constraint. coefficients: The linear coefficients of the left-hand-side of the constraint. + sense: The sense of the constraint, + - '==', '=', 'E', and 'EQ' denote 'equal to'. + - '>=', 'G', and 'GE' denote 'greater-than-or-equal-to'. + - '<=', 'L', and 'LE' denote 'less-than-or-equal-to'. rhs: The right hand side of the constraint. Returns: The added constraint. Raises: - QiskitOptimizationError: if the constraint name already exists. + QiskitOptimizationError: if the constraint name already exists or the sense is not + valid. """ - return self._add_linear_constraint(name, coefficients, ConstraintSense.leq, rhs) + return self._add_linear_constraint(name, coefficients, self._convert_sense(sense), rhs) def get_linear_constraint(self, i: Union[int, str]) -> LinearConstraint: """Returns a linear constraint for a given name or index. @@ -462,12 +418,13 @@ def _add_quadratic_constraint(self, """ if name: if name in self.quadratic_constraints_index: - raise QiskitOptimizationError("Variable name already exists!") + raise QiskitOptimizationError( + "Quadratic constraint name already exists: {}".format(name)) else: k = self.get_num_quadratic_constraints() - while 'c{}'.format(k) in self.quadratic_constraints_index: + while 'q{}'.format(k) in self.quadratic_constraints_index: k += 1 - name = 'c{}'.format(k) + name = 'q{}'.format(k) self.quadratic_constraints_index[name] = len(self.quadratic_constraints) if linear_coefficients is None: linear_coefficients = {} @@ -478,16 +435,17 @@ def _add_quadratic_constraint(self, self.quadratic_constraints.append(constraint) return constraint - def quadratic_eq_constraint(self, name: Optional[str] = None, - linear_coefficients: Union[ndarray, spmatrix, List[float], - Dict[Union[int, str], float]] = None, - quadratic_coefficients: Union[ndarray, spmatrix, - List[List[float]], - Dict[ - Tuple[Union[int, str], - Union[int, str]], - float]] = None, - rhs: float = 0.0) -> QuadraticConstraint: + def quadratic_constraint(self, name: Optional[str] = None, + linear_coefficients: Union[ndarray, spmatrix, List[float], + Dict[Union[int, str], float]] = None, + quadratic_coefficients: Union[ndarray, spmatrix, + List[List[float]], + Dict[ + Tuple[Union[int, str], + Union[int, str]], + float]] = None, + sense: Union[str, ConstraintSense] = '==', + rhs: float = 0.0) -> QuadraticConstraint: """Adds a quadratic equality constraint to the quadratic program of the form: x * Q * x == rhs. @@ -495,6 +453,10 @@ def quadratic_eq_constraint(self, name: Optional[str] = None, name: The name of the constraint. linear_coefficients: The linear coefficients of the constraint. quadratic_coefficients: The quadratic coefficients of the constraint. + sense: The sense of the constraint, + - '==', '=', 'E', and 'EQ' denote 'equal to'. + - '>=', 'G', and 'GE' denote 'greater-than-or-equal-to'. + - '<=', 'L', and 'LE' denote 'less-than-or-equal-to'. rhs: The right hand side of the constraint. Returns: @@ -504,65 +466,7 @@ def quadratic_eq_constraint(self, name: Optional[str] = None, QiskitOptimizationError: if the constraint name already exists. """ return self._add_quadratic_constraint(name, linear_coefficients, quadratic_coefficients, - ConstraintSense.eq, rhs) - - def quadratic_geq_constraint(self, name: Optional[str] = None, - linear_coefficients: Union[ndarray, spmatrix, List[float], - Dict[Union[int, str], float]] = None, - quadratic_coefficients: Union[ndarray, spmatrix, - List[List[float]], - Dict[ - Tuple[Union[int, str], - Union[int, str]], - float]] = None, - rhs: float = 0.0) -> QuadraticConstraint: - """Adds a quadratic "greater-than-or-equal-to" (geq) constraint to the quadratic program - of the form: - x * Q * x >= rhs. - - Args: - name: The name of the constraint. - linear_coefficients: The linear coefficients of the constraint. - quadratic_coefficients: The quadratic coefficients of the constraint. - rhs: The right hand side of the constraint. - - Returns: - The added constraint. - - Raises: - QiskitOptimizationError: if the constraint name already exists. - """ - return self._add_quadratic_constraint(name, linear_coefficients, quadratic_coefficients, - ConstraintSense.geq, rhs) - - def quadratic_leq_constraint(self, name: Optional[str] = None, - linear_coefficients: Union[ndarray, spmatrix, List[float], - Dict[Union[int, str], float]] = None, - quadratic_coefficients: Union[ndarray, spmatrix, - List[List[float]], - Dict[ - Tuple[Union[int, str], - Union[int, str]], - float]] = None, - rhs: float = 0.0) -> QuadraticConstraint: - """Adds a quadratic "less-than-or-equal-to" (leq) constraint to the quadratic program - of the form: - x * Q * x <= rhs. - - Args: - name: The name of the constraint. - linear_coefficients: The linear coefficients of the constraint. - quadratic_coefficients: The quadratic coefficients of the constraint. - rhs: The right hand side of the constraint. - - Returns: - The added constraint. - - Raises: - QiskitOptimizationError: if the constraint name already exists. - """ - return self._add_quadratic_constraint(name, linear_coefficients, quadratic_coefficients, - ConstraintSense.leq, rhs) + self._convert_sense(sense), rhs) def get_quadratic_constraint(self, i: Union[int, str]) -> QuadraticConstraint: """Returns a quadratic constraint for a given name or index. @@ -601,7 +505,7 @@ def minimize(self, quadratic: Union[ndarray, spmatrix, List[List[float]], Dict[Tuple[Union[int, str], Union[int, str]], float]] = None ) -> None: - """Sets a quadrartic objective to be minimized. + """Sets a quadratic objective to be minimized. Args: constant: the constant offset of the objective. @@ -619,7 +523,7 @@ def maximize(self, quadratic: Union[ndarray, spmatrix, List[List[float]], Dict[Tuple[Union[int, str], Union[int, str]], float]] = None ) -> None: - """Sets a quadrartic objective to be maximized. + """Sets a quadratic objective to be maximized. Args: constant: the constant offset of the objective. @@ -676,7 +580,7 @@ def from_docplex(self, model: Model) -> None: i = quad_triplet[0].name j = quad_triplet[1].name v = quad_triplet[2] - quadratic[(i, j)] = v + quadratic[i, j] = v # set objective if minimize: @@ -701,11 +605,11 @@ def from_docplex(self, model: Model) -> None: lhs[x[0].name] = x[1] if sense == sense.EQ: - self.linear_eq_constraint(name, lhs, rhs) + self.linear_constraint(name, lhs, '==', rhs) elif sense == sense.GE: - self.linear_geq_constraint(name, lhs, rhs) + self.linear_constraint(name, lhs, '>=', rhs) elif sense == sense.LE: - self.linear_leq_constraint(name, lhs, rhs) + self.linear_constraint(name, lhs, '<=', rhs) else: raise QiskitOptimizationError("Unsupported constraint sense!") @@ -729,7 +633,7 @@ def from_docplex(self, model: Model) -> None: i = quad_triplet[0].name j = quad_triplet[1].name v = quad_triplet[2] - quadratic[(i, j)] = v + quadratic[i, j] = v else: for x in left_expr.iter_variables(): linear[x.name] = left_expr.get_coef(x) @@ -741,17 +645,17 @@ def from_docplex(self, model: Model) -> None: i = quad_triplet[0].name j = quad_triplet[1].name v = quad_triplet[2] - quadratic[(i, j)] = quadratic.get((i, j), 0.0) - v + quadratic[i, j] = quadratic.get((i, j), 0.0) - v else: for x in right_expr.iter_variables(): linear[x.name] = linear.get(x.name, 0.0) - right_expr.get_coef(x) if sense == sense.EQ: - self.quadratic_eq_constraint(name, linear, quadratic, rhs) + self.quadratic_constraint(name, linear, quadratic, '==', rhs) elif sense == sense.GE: - self.quadratic_geq_constraint(name, linear, quadratic, rhs) + self.quadratic_constraint(name, linear, quadratic, '>=', rhs) elif sense == sense.LE: - self.quadratic_leq_constraint(name, linear, quadratic, rhs) + self.quadratic_constraint(name, linear, quadratic, '<=', rhs) else: raise QiskitOptimizationError("Unsupported constraint sense!") @@ -840,11 +744,12 @@ def pprint_as_string(self) -> str: """ return self.to_docplex().pprint_as_string() - def prettyprint(self, out=None) -> None: + def prettyprint(self, out: Optional[str] = None) -> None: """Pretty prints the quadratic program to a given output stream (None = default). Args: - out: The output stream to print to. + out: The output stream or file name to print to. + if you specify a file name, the output file name is has '.mod' as suffix. """ self.to_docplex().prettyprint(out) diff --git a/qiskit/optimization/problems/variable.py b/qiskit/optimization/problems/variable.py index b4d4370074..271a744028 100644 --- a/qiskit/optimization/problems/variable.py +++ b/qiskit/optimization/problems/variable.py @@ -67,19 +67,6 @@ def name(self) -> str: """ return self._name - @name.setter - def name(self, name: str) -> None: - """Sets the name of the variable and updates the name index in the corresponding QP. - - Args: - name: The name of the variable. - - Raises: - QiskitOptimizationError: if the name is already existing. - """ - # TODO: update QP and raise exception if name already exists - self._name = name - @property def lowerbound(self) -> float: """Returns the lowerbound of the variable. From 5951bbac0c46414b2fb85764982be441743394ce Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Wed, 15 Apr 2020 16:08:45 +0900 Subject: [PATCH 193/323] fix tests and avoid cyclic import --- .../problems/linear_constraint.py | 12 +++--- .../problems/quadratic_constraint.py | 19 ++++----- .../problems/quadratic_objective.py | 4 +- .../problems/quadratic_program.py | 4 +- test/optimization/test_linear_constraint.py | 21 +++++----- .../optimization/test_quadratic_constraint.py | 18 ++++----- test/optimization/test_variable.py | 40 ++----------------- 7 files changed, 43 insertions(+), 75 deletions(-) diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index d7ffc65507..77ad4e7c56 100644 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -15,10 +15,12 @@ """Linear Constraint.""" from typing import Union, List, Dict + from numpy import ndarray from scipy.sparse import spmatrix -from qiskit.optimization.problems import Constraint, ConstraintSense, LinearExpression +from qiskit.optimization.problems.constraint import Constraint, ConstraintSense +from qiskit.optimization.problems.linear_expression import LinearExpression class LinearConstraint(Constraint): @@ -26,8 +28,7 @@ class LinearConstraint(Constraint): def __init__(self, quadratic_program: "QuadraticProgram", name: str, - linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float] - ], + linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]], sense: ConstraintSense, rhs: float ) -> None: @@ -53,9 +54,8 @@ def linear(self) -> LinearExpression: return self._linear @linear.setter - def linear(self, linear: - Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]] - ) -> None: + def linear(self, linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]]) \ + -> None: """Sets the linear expression corresponding to the left-hand-side of the constraint. The coefficients can either be given by an array, a (sparse) 1d matrix, a lsit or a dictionary. diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index 55d9d71e88..ce48518287 100644 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -15,11 +15,13 @@ """Quadratic Constraint.""" from typing import Union, List, Dict, Tuple + from numpy import ndarray from scipy.sparse import spmatrix -from qiskit.optimization.problems import (Constraint, ConstraintSense, LinearExpression, - QuadraticExpression) +from qiskit.optimization.problems.constraint import Constraint, ConstraintSense +from qiskit.optimization.problems.linear_expression import LinearExpression +from qiskit.optimization.problems.quadratic_expression import QuadraticExpression class QuadraticConstraint(Constraint): @@ -57,9 +59,8 @@ def linear(self) -> LinearExpression: return self._linear @linear.setter - def linear(self, linear: - Union[LinearExpression, ndarray, spmatrix, List[float], Dict[Union[str, int], float]] - ) -> None: + def linear(self, linear: Union[LinearExpression, ndarray, spmatrix, List[float], + Dict[Union[str, int], float]]) -> None: """Sets the linear expression corresponding to the left-hand-side of the constraint. The coefficients can either be given by an array, a (sparse) 1d matrix, a list or a dictionary. @@ -67,6 +68,7 @@ def linear(self, linear: Args: linear: The linear coefficients of the left-hand-side. """ + self._linear = LinearExpression(self.quadratic_program, linear) @property @@ -79,10 +81,9 @@ def quadratic(self) -> QuadraticExpression: return self._quadratic @quadratic.setter - def quadratic(self, quadratic: - Union[ndarray, spmatrix, List[List[float]], - Dict[Tuple[Union[int, str], Union[int, str]], float]] - ) -> None: + def quadratic(self, quadratic: Union[ndarray, spmatrix, List[List[float]], + Dict[Tuple[Union[int, str], Union[int, str]], float]]) \ + -> None: """Sets the quadratic expression corresponding to the left-hand-side of the constraint. The coefficients can either be given by an array, a (sparse) matrix, a list or a dictionary. diff --git a/qiskit/optimization/problems/quadratic_objective.py b/qiskit/optimization/problems/quadratic_objective.py index ea3bda08b9..402bc8d1fa 100644 --- a/qiskit/optimization/problems/quadratic_objective.py +++ b/qiskit/optimization/problems/quadratic_objective.py @@ -16,11 +16,13 @@ from enum import Enum from typing import Union, List, Dict, Tuple + from numpy import ndarray from scipy.sparse import spmatrix from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram -from qiskit.optimization.problems import LinearExpression, QuadraticExpression +from qiskit.optimization.problems.linear_constraint import LinearExpression +from qiskit.optimization.problems.quadratic_expression import QuadraticExpression class ObjSense(Enum): diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index cb6755c2cf..98d10c39ab 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -422,9 +422,9 @@ def _add_quadratic_constraint(self, "Quadratic constraint name already exists: {}".format(name)) else: k = self.get_num_quadratic_constraints() - while 'q{}'.format(k) in self.quadratic_constraints_index: + while 'c{}'.format(k) in self.quadratic_constraints_index: k += 1 - name = 'q{}'.format(k) + name = 'c{}'.format(k) self.quadratic_constraints_index[name] = len(self.quadratic_constraints) if linear_coefficients is None: linear_coefficients = {} diff --git a/test/optimization/test_linear_constraint.py b/test/optimization/test_linear_constraint.py index d1a9495e54..af12a00e8a 100644 --- a/test/optimization/test_linear_constraint.py +++ b/test/optimization/test_linear_constraint.py @@ -14,13 +14,12 @@ """ Test LinearConstraint """ -import unittest -from test.optimization.optimization_test_case import QiskitOptimizationTestCase import logging import numpy as np from qiskit.optimization import QuadraticProgram, QiskitOptimizationError from qiskit.optimization.problems import ConstraintSense +from test.optimization.optimization_test_case import QiskitOptimizationTestCase logger = logging.getLogger(__name__) @@ -39,7 +38,7 @@ def test_init(self): coefficients = np.array([i for i in range(5)]) # equality constraints - quadratic_program.linear_eq_constraint() + quadratic_program.linear_constraint(sense='==') self.assertEqual(quadratic_program.get_num_linear_constraints(), 1) self.assertEqual(quadratic_program.linear_constraints[0].name, 'c0') self.assertEqual(len(quadratic_program.linear_constraints[0].linear.coefficients_as_dict()), @@ -52,9 +51,9 @@ def test_init(self): quadratic_program.get_linear_constraint(0)) with self.assertRaises(QiskitOptimizationError): - quadratic_program.linear_eq_constraint(name='c0') + quadratic_program.linear_constraint(name='c0') - quadratic_program.linear_eq_constraint('c1', coefficients, 1.0) + quadratic_program.linear_constraint('c1', coefficients, '==', 1.0) self.assertEqual(quadratic_program.get_num_linear_constraints(), 2) self.assertEqual(quadratic_program.linear_constraints[1].name, 'c1') self.assertTrue(( @@ -69,7 +68,7 @@ def test_init(self): quadratic_program.get_linear_constraint(1)) # geq constraints - quadratic_program.linear_geq_constraint() + quadratic_program.linear_constraint(sense='>=') self.assertEqual(quadratic_program.get_num_linear_constraints(), 3) self.assertEqual(quadratic_program.linear_constraints[2].name, 'c2') self.assertEqual(len(quadratic_program.linear_constraints[2].linear.coefficients_as_dict()), @@ -82,9 +81,9 @@ def test_init(self): quadratic_program.get_linear_constraint(2)) with self.assertRaises(QiskitOptimizationError): - quadratic_program.linear_geq_constraint(name='c2') + quadratic_program.linear_constraint(name='c2', sense='>=') - quadratic_program.linear_geq_constraint('c3', coefficients, 1.0) + quadratic_program.linear_constraint('c3', coefficients, '>=', 1.0) self.assertEqual(quadratic_program.get_num_linear_constraints(), 4) self.assertEqual(quadratic_program.linear_constraints[3].name, 'c3') self.assertTrue(( @@ -99,7 +98,7 @@ def test_init(self): quadratic_program.get_linear_constraint(3)) # leq constraints - quadratic_program.linear_leq_constraint() + quadratic_program.linear_constraint(sense='<=') self.assertEqual(quadratic_program.get_num_linear_constraints(), 5) self.assertEqual(quadratic_program.linear_constraints[4].name, 'c4') self.assertEqual(len(quadratic_program.linear_constraints[4].linear.coefficients_as_dict()), @@ -112,9 +111,9 @@ def test_init(self): quadratic_program.get_linear_constraint(4)) with self.assertRaises(QiskitOptimizationError): - quadratic_program.linear_leq_constraint(name='c4') + quadratic_program.linear_constraint(name='c4', sense='<=') - quadratic_program.linear_leq_constraint('c5', coefficients, 1.0) + quadratic_program.linear_constraint('c5', coefficients, '<=', 1.0) self.assertEqual(quadratic_program.get_num_linear_constraints(), 6) self.assertEqual(quadratic_program.linear_constraints[5].name, 'c5') self.assertTrue(( diff --git a/test/optimization/test_quadratic_constraint.py b/test/optimization/test_quadratic_constraint.py index c7de5ccaa2..2319d67339 100644 --- a/test/optimization/test_quadratic_constraint.py +++ b/test/optimization/test_quadratic_constraint.py @@ -40,7 +40,7 @@ def test_init(self): quadratic_coeffs = np.array([[i*j for i in range(5)] for j in range(5)]) # equality constraints - quadratic_program.quadratic_eq_constraint() + quadratic_program.quadratic_constraint(sense='==') self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 1) self.assertEqual(quadratic_program.quadratic_constraints[0].name, 'c0') self.assertEqual( @@ -57,9 +57,9 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[0].evaluate(linear_coeffs), 0.0) with self.assertRaises(QiskitOptimizationError): - quadratic_program.quadratic_eq_constraint(name='c0') + quadratic_program.quadratic_constraint(name='c0', sense='==') - quadratic_program.quadratic_eq_constraint('c1', linear_coeffs, quadratic_coeffs, 1.0) + quadratic_program.quadratic_constraint('c1', linear_coeffs, quadratic_coeffs, '==', 1.0) self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 2) self.assertEqual(quadratic_program.quadratic_constraints[1].name, 'c1') self.assertTrue(( @@ -80,7 +80,7 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[1].evaluate(linear_coeffs), 930.0) # geq constraints - quadratic_program.quadratic_geq_constraint() + quadratic_program.quadratic_constraint(sense='>=') self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 3) self.assertEqual(quadratic_program.quadratic_constraints[2].name, 'c2') self.assertEqual( @@ -97,9 +97,9 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[2].evaluate(linear_coeffs), 0.0) with self.assertRaises(QiskitOptimizationError): - quadratic_program.quadratic_geq_constraint(name='c2') + quadratic_program.quadratic_constraint(name='c2', sense='>=') - quadratic_program.quadratic_geq_constraint('c3', linear_coeffs, quadratic_coeffs, 1.0) + quadratic_program.quadratic_constraint('c3', linear_coeffs, quadratic_coeffs, '>=', 1.0) self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 4) self.assertEqual(quadratic_program.quadratic_constraints[3].name, 'c3') self.assertTrue(( @@ -120,7 +120,7 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[3].evaluate(linear_coeffs), 930.0) # leq constraints - quadratic_program.quadratic_leq_constraint() + quadratic_program.quadratic_constraint(sense='<=') self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 5) self.assertEqual(quadratic_program.quadratic_constraints[4].name, 'c4') self.assertEqual( @@ -135,9 +135,9 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[4].evaluate(linear_coeffs), 0.0) with self.assertRaises(QiskitOptimizationError): - quadratic_program.quadratic_leq_constraint(name='c4') + quadratic_program.quadratic_constraint(name='c4', sense='<=') - quadratic_program.quadratic_leq_constraint('c5', linear_coeffs, quadratic_coeffs, 1.0) + quadratic_program.quadratic_constraint('c5', linear_coeffs, quadratic_coeffs, '<=', 1.0) self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 6) self.assertEqual(quadratic_program.quadratic_constraints[5].name, 'c5') self.assertTrue(( diff --git a/test/optimization/test_variable.py b/test/optimization/test_variable.py index 91cfeb277a..c7d31738cc 100644 --- a/test/optimization/test_variable.py +++ b/test/optimization/test_variable.py @@ -14,12 +14,12 @@ """Test Variable.""" -import unittest -from test.optimization.optimization_test_case import QiskitOptimizationTestCase import logging +import unittest +from qiskit.optimization import infinity from qiskit.optimization.problems import QuadraticProgram, Variable, VarType -from qiskit.optimization import infinity, QiskitOptimizationError +from test.optimization.optimization_test_case import QiskitOptimizationTestCase logger = logging.getLogger(__name__) @@ -56,40 +56,6 @@ def test_init_default(self): self.assertEqual(variable.upperbound, infinity) self.assertEqual(variable.vartype, VarType.continuous) - def test_setters(self): - """ test setters. """ - - quadratic_program = QuadraticProgram() - name = 'variable' - lowerbound = 0 - upperbound = 10 - vartype = VarType.continuous - - variable = Variable(quadratic_program, name, lowerbound, upperbound, vartype) - - variable.name = 'test' - self.assertEqual(variable.name, 'test') - - self.assertEqual(variable.lowerbound, lowerbound) - variable.lowerbound = 1 - self.assertEqual(variable.lowerbound, 1) - with self.assertRaises(QiskitOptimizationError): - variable.lowerbound = 20 - - self.assertEqual(variable.upperbound, upperbound) - variable.upperbound = 5 - self.assertEqual(variable.upperbound, 5) - with self.assertRaises(QiskitOptimizationError): - variable.upperbound = 0 - - self.assertEqual(variable.vartype, vartype) - variable.vartype = VarType.integer - self.assertEqual(variable.vartype, VarType.integer) - variable.vartype = VarType.binary - self.assertEqual(variable.vartype, VarType.binary) - variable.vartype = VarType.continuous - self.assertEqual(variable.vartype, VarType.continuous) - if __name__ == '__main__': unittest.main() From c582ef641e3ee4d154d0ac2bff0db805f876612b Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Wed, 15 Apr 2020 16:28:32 +0900 Subject: [PATCH 194/323] fix default quadratic constraint name --- .../problems/quadratic_program.py | 4 +-- .../optimization/test_quadratic_constraint.py | 36 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 98d10c39ab..cb6755c2cf 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -422,9 +422,9 @@ def _add_quadratic_constraint(self, "Quadratic constraint name already exists: {}".format(name)) else: k = self.get_num_quadratic_constraints() - while 'c{}'.format(k) in self.quadratic_constraints_index: + while 'q{}'.format(k) in self.quadratic_constraints_index: k += 1 - name = 'c{}'.format(k) + name = 'q{}'.format(k) self.quadratic_constraints_index[name] = len(self.quadratic_constraints) if linear_coefficients is None: linear_coefficients = {} diff --git a/test/optimization/test_quadratic_constraint.py b/test/optimization/test_quadratic_constraint.py index 2319d67339..2b0757584b 100644 --- a/test/optimization/test_quadratic_constraint.py +++ b/test/optimization/test_quadratic_constraint.py @@ -42,7 +42,7 @@ def test_init(self): # equality constraints quadratic_program.quadratic_constraint(sense='==') self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 1) - self.assertEqual(quadratic_program.quadratic_constraints[0].name, 'c0') + self.assertEqual(quadratic_program.quadratic_constraints[0].name, 'q0') self.assertEqual( len(quadratic_program.quadratic_constraints[0].linear.coefficients_as_dict()), 0) self.assertEqual( @@ -50,18 +50,18 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[0].sense, ConstraintSense.eq) self.assertEqual(quadratic_program.quadratic_constraints[0].rhs, 0.0) self.assertEqual(quadratic_program.quadratic_constraints[0], - quadratic_program.get_quadratic_constraint('c0')) + quadratic_program.get_quadratic_constraint('q0')) self.assertEqual(quadratic_program.quadratic_constraints[0], quadratic_program.get_quadratic_constraint(0)) self.assertEqual(quadratic_program.quadratic_constraints[0].evaluate(linear_coeffs), 0.0) with self.assertRaises(QiskitOptimizationError): - quadratic_program.quadratic_constraint(name='c0', sense='==') + quadratic_program.quadratic_constraint(name='q0', sense='==') - quadratic_program.quadratic_constraint('c1', linear_coeffs, quadratic_coeffs, '==', 1.0) + quadratic_program.quadratic_constraint('q1', linear_coeffs, quadratic_coeffs, '==', 1.0) self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 2) - self.assertEqual(quadratic_program.quadratic_constraints[1].name, 'c1') + self.assertEqual(quadratic_program.quadratic_constraints[1].name, 'q1') self.assertTrue(( quadratic_program.quadratic_constraints[1].linear.coefficients_as_array( ) == linear_coeffs @@ -73,7 +73,7 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[1].sense, ConstraintSense.eq) self.assertEqual(quadratic_program.quadratic_constraints[1].rhs, 1.0) self.assertEqual(quadratic_program.quadratic_constraints[1], - quadratic_program.get_quadratic_constraint('c1')) + quadratic_program.get_quadratic_constraint('q1')) self.assertEqual(quadratic_program.quadratic_constraints[1], quadratic_program.get_quadratic_constraint(1)) @@ -82,7 +82,7 @@ def test_init(self): # geq constraints quadratic_program.quadratic_constraint(sense='>=') self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 3) - self.assertEqual(quadratic_program.quadratic_constraints[2].name, 'c2') + self.assertEqual(quadratic_program.quadratic_constraints[2].name, 'q2') self.assertEqual( len(quadratic_program.quadratic_constraints[2].linear.coefficients_as_dict()), 0) self.assertEqual( @@ -90,18 +90,18 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[2].sense, ConstraintSense.geq) self.assertEqual(quadratic_program.quadratic_constraints[2].rhs, 0.0) self.assertEqual(quadratic_program.quadratic_constraints[2], - quadratic_program.get_quadratic_constraint('c2')) + quadratic_program.get_quadratic_constraint('q2')) self.assertEqual(quadratic_program.quadratic_constraints[2], quadratic_program.get_quadratic_constraint(2)) self.assertEqual(quadratic_program.quadratic_constraints[2].evaluate(linear_coeffs), 0.0) with self.assertRaises(QiskitOptimizationError): - quadratic_program.quadratic_constraint(name='c2', sense='>=') + quadratic_program.quadratic_constraint(name='q2', sense='>=') - quadratic_program.quadratic_constraint('c3', linear_coeffs, quadratic_coeffs, '>=', 1.0) + quadratic_program.quadratic_constraint('q3', linear_coeffs, quadratic_coeffs, '>=', 1.0) self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 4) - self.assertEqual(quadratic_program.quadratic_constraints[3].name, 'c3') + self.assertEqual(quadratic_program.quadratic_constraints[3].name, 'q3') self.assertTrue(( quadratic_program.quadratic_constraints[3].linear.coefficients_as_array( ) == linear_coeffs @@ -113,7 +113,7 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[3].sense, ConstraintSense.geq) self.assertEqual(quadratic_program.quadratic_constraints[3].rhs, 1.0) self.assertEqual(quadratic_program.quadratic_constraints[3], - quadratic_program.get_quadratic_constraint('c3')) + quadratic_program.get_quadratic_constraint('q3')) self.assertEqual(quadratic_program.quadratic_constraints[3], quadratic_program.get_quadratic_constraint(3)) @@ -122,24 +122,24 @@ def test_init(self): # leq constraints quadratic_program.quadratic_constraint(sense='<=') self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 5) - self.assertEqual(quadratic_program.quadratic_constraints[4].name, 'c4') + self.assertEqual(quadratic_program.quadratic_constraints[4].name, 'q4') self.assertEqual( len(quadratic_program.quadratic_constraints[4].linear.coefficients_as_dict()), 0) self.assertEqual(quadratic_program.quadratic_constraints[4].sense, ConstraintSense.leq) self.assertEqual(quadratic_program.quadratic_constraints[4].rhs, 0.0) self.assertEqual(quadratic_program.quadratic_constraints[4], - quadratic_program.get_quadratic_constraint('c4')) + quadratic_program.get_quadratic_constraint('q4')) self.assertEqual(quadratic_program.quadratic_constraints[4], quadratic_program.get_quadratic_constraint(4)) self.assertEqual(quadratic_program.quadratic_constraints[4].evaluate(linear_coeffs), 0.0) with self.assertRaises(QiskitOptimizationError): - quadratic_program.quadratic_constraint(name='c4', sense='<=') + quadratic_program.quadratic_constraint(name='q4', sense='<=') - quadratic_program.quadratic_constraint('c5', linear_coeffs, quadratic_coeffs, '<=', 1.0) + quadratic_program.quadratic_constraint('q5', linear_coeffs, quadratic_coeffs, '<=', 1.0) self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 6) - self.assertEqual(quadratic_program.quadratic_constraints[5].name, 'c5') + self.assertEqual(quadratic_program.quadratic_constraints[5].name, 'q5') self.assertTrue(( quadratic_program.quadratic_constraints[5].linear.coefficients_as_array( ) == linear_coeffs @@ -151,7 +151,7 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[5].sense, ConstraintSense.leq) self.assertEqual(quadratic_program.quadratic_constraints[5].rhs, 1.0) self.assertEqual(quadratic_program.quadratic_constraints[5], - quadratic_program.get_quadratic_constraint('c5')) + quadratic_program.get_quadratic_constraint('q5')) self.assertEqual(quadratic_program.quadratic_constraints[5], quadratic_program.get_quadratic_constraint(5)) From fe60f272bac25b5b7c6d0bb0cafc39306cedc83b Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 15 Apr 2020 12:08:10 +0200 Subject: [PATCH 195/323] Update test_quadratic_program.py --- test/optimization/test_quadratic_program.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index f8cb2b09ea..8d00fd2353 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -130,6 +130,18 @@ def test_variables_handling(self): self.assertEqual(x, y) self.assertEqual(x, z) + def test_linear_constraints_handling(self): + # TODO + pass + + def test_quadratic_constraints_handling(self): + # TODO + pass + + def test_objective_handling(self): + # TODO + pass + if __name__ == '__main__': unittest.main() From 5f8f11ad5440d1648d9958c360fd86958656e886 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Wed, 15 Apr 2020 11:37:00 +0100 Subject: [PATCH 196/323] moving to a new optimization stack --- qiskit/optimization/algorithms/admm_optimizer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 809ccfc285..dfbf076870 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -171,6 +171,7 @@ def state(self) -> Optional[ADMMState]: class ADMMOptimizer(OptimizationAlgorithm): + """An implementation of the ADMM-based heuristic introduced here: Gambella, C., & Simonetto, A. (2020). Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical and Quantum Computers. From c629866f124d9b9c8c8c05f831a77a0f51e2ba10 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 15 Apr 2020 14:07:09 +0200 Subject: [PATCH 197/323] update constraints handling --- qiskit/optimization/problems/constraint.py | 26 +++ .../problems/quadratic_constraint.py | 2 +- .../problems/quadratic_program.py | 155 ++++-------------- 3 files changed, 63 insertions(+), 120 deletions(-) diff --git a/qiskit/optimization/problems/constraint.py b/qiskit/optimization/problems/constraint.py index 0ae92666e5..a4e90cb365 100644 --- a/qiskit/optimization/problems/constraint.py +++ b/qiskit/optimization/problems/constraint.py @@ -21,6 +21,7 @@ from numpy import ndarray from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram +from qiskit.optimization import QiskitOptimizationError class ConstraintSense(Enum): @@ -29,6 +30,31 @@ class ConstraintSense(Enum): geq = 1 eq = 2 # pylint: disable=locally-disabled, invalid-name + @staticmethod + def convert(sense: Union[str, ConstraintSense]) -> ConstraintSense: + """Convert a string into a corresponding sense of constraints + + Args: + sense: A string or sense of constraints + + Returns: + The sense of constraints + + Raises: + QiskitOptimizationError: if the input string is invalid. + """ + if isinstance(sense, ConstraintSense): + return sense + sense = sense.upper() + if sense not in ['E', 'L', 'G', 'EQ', 'LE', 'GE', '=', '==', '<=', '<', '>=', '>']: + raise QiskitOptimizationError('Invalid sense: {}'.format(sense)) + if sense in ['E', 'EQ', '=', '==']: + return ConstraintSense.eq + elif sense in ['L', 'LE', '<=', '<']: + return ConstraintSense.leq + else: + return ConstraintSense.geq + class Constraint(HasQuadraticProgram): """Abstract Constraint Class.""" diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index ce48518287..f6e9254002 100644 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -59,7 +59,7 @@ def linear(self) -> LinearExpression: return self._linear @linear.setter - def linear(self, linear: Union[LinearExpression, ndarray, spmatrix, List[float], + def linear(self, linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]]) -> None: """Sets the linear expression corresponding to the left-hand-side of the constraint. The coefficients can either be given by an array, a (sparse) 1d matrix, a list or a diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index cb6755c2cf..9985f12735 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -256,28 +256,29 @@ def linear_constraints_index(self) -> Dict[str, int]: """ return self._linear_constraints_index - def _add_linear_constraint(self, - name: Optional[str] = None, - coefficients: Union[ndarray, spmatrix, List[float], - Dict[Union[int, str], float]] = None, - sense: ConstraintSense = ConstraintSense.leq, - rhs: float = 0.0 - ) -> LinearConstraint: - """Checks whether a constraint name is already taken and adds the constraint to list and - index if not. + def linear_constraint(self, name: Optional[str] = None, + coefficients: Union[ndarray, spmatrix, List[float], + Dict[Union[int, str], float]] = None, + sense: Union[str, ConstraintSense] = '<=', + rhs: float = 0.0) -> LinearConstraint: + """Adds a linear equality constraint to the quadratic program of the form: + linear_coeffs * x sense rhs. Args: name: The name of the constraint. - coefficients: The linear coefficients of the constraint. - sense: The constraint sense. - rhs: The right-hand-side of the constraint. + coefficients: The linear coefficients of the left-hand-side of the constraint. + sense: The sense of the constraint, + - '==', '=', 'E', and 'EQ' denote 'equal to'. + - '>=', '>', 'G', and 'GE' denote 'greater-than-or-equal-to'. + - '<=', '<', 'L', and 'LE' denote 'less-than-or-equal-to'. + rhs: The right hand side of the constraint. Returns: The added constraint. Raises: - QiskitOptimizationError: if the constraint name is already taken. - + QiskitOptimizationError: if the constraint name already exists or the sense is not + valid. """ if name: if name in self.linear_constraints_index: @@ -291,61 +292,10 @@ def _add_linear_constraint(self, self.linear_constraints_index[name] = len(self.linear_constraints) if coefficients is None: coefficients = {} - constraint = LinearConstraint(self, name, coefficients, sense, rhs) + constraint = LinearConstraint(self, name, coefficients, ConstraintSense.convert(sense), rhs) self.linear_constraints.append(constraint) return constraint - @staticmethod - def _convert_sense(sense: Union[str, ConstraintSense]) -> ConstraintSense: - """Convert a string into a corresponding sense of constraints - - Args: - sense: A string or sense of constraints - - Returns: - The sense of constraints - - Raises: - QiskitOptimizationError: if the input string is invalid. - """ - if isinstance(sense, ConstraintSense): - return sense - sense = sense.upper() - if sense not in ['E', 'L', 'G', 'EQ', 'LE', 'GE', '=', '==', '<=', '>=']: - raise QiskitOptimizationError('Invalid sense: {}'.format(sense)) - if sense in ['E', 'EQ', '=', '==']: - return ConstraintSense.eq - elif sense in ['L', 'LE', '<=']: - return ConstraintSense.leq - else: - return ConstraintSense.geq - - def linear_constraint(self, name: Optional[str] = None, - coefficients: Union[ndarray, spmatrix, List[float], - Dict[Union[int, str], float]] = None, - sense: Union[str, ConstraintSense] = '==', - rhs: float = 0.0) -> LinearConstraint: - """Adds a linear equality constraint to the quadratic program of the form: - linear_coeffs * x sense rhs. - - Args: - name: The name of the constraint. - coefficients: The linear coefficients of the left-hand-side of the constraint. - sense: The sense of the constraint, - - '==', '=', 'E', and 'EQ' denote 'equal to'. - - '>=', 'G', and 'GE' denote 'greater-than-or-equal-to'. - - '<=', 'L', and 'LE' denote 'less-than-or-equal-to'. - rhs: The right hand side of the constraint. - - Returns: - The added constraint. - - Raises: - QiskitOptimizationError: if the constraint name already exists or the sense is not - valid. - """ - return self._add_linear_constraint(name, coefficients, self._convert_sense(sense), rhs) - def get_linear_constraint(self, i: Union[int, str]) -> LinearConstraint: """Returns a linear constraint for a given name or index. @@ -386,35 +336,35 @@ def quadratic_constraints_index(self) -> Dict[str, int]: """ return self._quadratic_constraints_index - def _add_quadratic_constraint(self, - name: Optional[str] = None, - linear_coefficients: Union[ndarray, spmatrix, List[float], - Dict[Union[int, str], float]] = None, - quadratic_coefficients: Union[ndarray, spmatrix, - List[List[float]], - Dict[ - Tuple[Union[int, str], - Union[int, str]], - float]] = None, - sense: ConstraintSense = ConstraintSense.leq, - rhs: float = 0.0 - ) -> QuadraticConstraint: - """Checks whether a constraint name is already taken and adds the constraint to list and - index if not. + def quadratic_constraint(self, name: Optional[str] = None, + linear_coefficients: Union[ndarray, spmatrix, List[float], + Dict[Union[int, str], float]] = None, + quadratic_coefficients: Union[ndarray, spmatrix, + List[List[float]], + Dict[ + Tuple[Union[int, str], + Union[int, str]], + float]] = None, + sense: Union[str, ConstraintSense] = '<=', + rhs: float = 0.0) -> QuadraticConstraint: + """Adds a quadratic equality constraint to the quadratic program of the form: + x * Q * x <= rhs. Args: name: The name of the constraint. linear_coefficients: The linear coefficients of the constraint. quadratic_coefficients: The quadratic coefficients of the constraint. - sense: The constraint sense. - rhs: The right-hand-side of the constraint. + sense: The sense of the constraint, + - '==', '=', 'E', and 'EQ' denote 'equal to'. + - '>=', '>', 'G', and 'GE' denote 'greater-than-or-equal-to'. + - '<=', '<', 'L', and 'LE' denote 'less-than-or-equal-to'. + rhs: The right hand side of the constraint. Returns: The added constraint. Raises: - QiskitOptimizationError: if the constraint name is already taken. - + QiskitOptimizationError: if the constraint name already exists. """ if name: if name in self.quadratic_constraints_index: @@ -431,43 +381,10 @@ def _add_quadratic_constraint(self, if quadratic_coefficients is None: quadratic_coefficients = {} constraint = QuadraticConstraint(self, name, linear_coefficients, quadratic_coefficients, - sense, rhs) + ConstraintSense.convert(sense), rhs) self.quadratic_constraints.append(constraint) return constraint - def quadratic_constraint(self, name: Optional[str] = None, - linear_coefficients: Union[ndarray, spmatrix, List[float], - Dict[Union[int, str], float]] = None, - quadratic_coefficients: Union[ndarray, spmatrix, - List[List[float]], - Dict[ - Tuple[Union[int, str], - Union[int, str]], - float]] = None, - sense: Union[str, ConstraintSense] = '==', - rhs: float = 0.0) -> QuadraticConstraint: - """Adds a quadratic equality constraint to the quadratic program of the form: - x * Q * x == rhs. - - Args: - name: The name of the constraint. - linear_coefficients: The linear coefficients of the constraint. - quadratic_coefficients: The quadratic coefficients of the constraint. - sense: The sense of the constraint, - - '==', '=', 'E', and 'EQ' denote 'equal to'. - - '>=', 'G', and 'GE' denote 'greater-than-or-equal-to'. - - '<=', 'L', and 'LE' denote 'less-than-or-equal-to'. - rhs: The right hand side of the constraint. - - Returns: - The added constraint. - - Raises: - QiskitOptimizationError: if the constraint name already exists. - """ - return self._add_quadratic_constraint(name, linear_coefficients, quadratic_coefficients, - self._convert_sense(sense), rhs) - def get_quadratic_constraint(self, i: Union[int, str]) -> QuadraticConstraint: """Returns a quadratic constraint for a given name or index. From 22b0efd648964a9f98f8370f4b734f01ef282a37 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 15 Apr 2020 14:13:22 +0200 Subject: [PATCH 198/323] Update constraint.py --- qiskit/optimization/problems/constraint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/optimization/problems/constraint.py b/qiskit/optimization/problems/constraint.py index a4e90cb365..a02e233266 100644 --- a/qiskit/optimization/problems/constraint.py +++ b/qiskit/optimization/problems/constraint.py @@ -31,7 +31,7 @@ class ConstraintSense(Enum): eq = 2 # pylint: disable=locally-disabled, invalid-name @staticmethod - def convert(sense: Union[str, ConstraintSense]) -> ConstraintSense: + def convert(sense: Union[str, "ConstraintSense"]) -> "ConstraintSense": """Convert a string into a corresponding sense of constraints Args: From dccd22eaee563d2cbfaf7b86cd26d5f998e69c6d Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Wed, 15 Apr 2020 13:19:26 +0100 Subject: [PATCH 199/323] moving to a new optimization stack --- .../optimization/algorithms/admm_optimizer.py | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 22af34c791..d7acffc63c 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -21,6 +21,7 @@ from qiskit.optimization.algorithms.cplex_optimizer import CplexOptimizer from qiskit.optimization.algorithms.optimization_algorithm import (OptimizationAlgorithm, OptimizationResult) +from qiskit.optimization.problems import VarType from qiskit.optimization.problems.quadratic_program import QuadraticProgram from qiskit.optimization.problems.variables import CPX_BINARY, CPX_CONTINUOUS @@ -227,13 +228,13 @@ def get_compatibility_msg(self, problem: QuadraticProgram) -> Optional[str]: msg = '' # 1. only binary and continuous variables are supported - for var_type in problem.variables.get_types(): - if var_type not in (CPX_BINARY, CPX_CONTINUOUS): + for variable in problem.variables: + if variable.vartype not in (VarType.binary, VarType.continuous): # variable is not binary and not continuous. msg += 'Only binary and continuous variables are supported. ' - binary_indices = self._get_variable_indices(problem, CPX_BINARY) - continuous_indices = self._get_variable_indices(problem, CPX_CONTINUOUS) + binary_indices = self._get_variable_indices(problem, VarType.binary) + continuous_indices = self._get_variable_indices(problem, VarType.continuous) # 2. binary and continuous variables are separable in objective for binary_index in binary_indices: @@ -244,7 +245,7 @@ def get_compatibility_msg(self, problem: QuadraticProgram) -> Optional[str]: msg += 'Binary and continuous variables are not separable in the objective. ' # 3. no quadratic constraints are supported. - quad_constraints = problem.quadratic_constraints.get_num() + quad_constraints = len(problem.quadratic_constraints) if quad_constraints is not None and quad_constraints > 0: # quadratic constraints are not supported. msg += 'Quadratic constraints are not supported. ' @@ -265,8 +266,8 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ # parse problem and convert to an ADMM specific representation. - binary_indices = self._get_variable_indices(problem, CPX_BINARY) - continuous_indices = self._get_variable_indices(problem, CPX_CONTINUOUS) + binary_indices = self._get_variable_indices(problem, VarType.binary) + continuous_indices = self._get_variable_indices(problem, VarType.continuous) # create our computation state. self._state = ADMMState(problem, binary_indices, @@ -344,7 +345,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: return result @staticmethod - def _get_variable_indices(op: QuadraticProgram, var_type: str) -> List[int]: + def _get_variable_indices(op: QuadraticProgram, var_type: VarType) -> List[int]: """Returns a list of indices of the variables of the specified type. Args: @@ -355,8 +356,8 @@ def _get_variable_indices(op: QuadraticProgram, var_type: str) -> List[int]: List of indices. """ indices = [] - for i, variable_type in enumerate(op.variables.get_types()): - if variable_type == var_type: + for i, variable in enumerate(op.variables): + if variable.vartype == var_type: indices.append(i) return indices @@ -420,9 +421,9 @@ def _get_q(self, variable_indices: List[int]) -> np.ndarray: # in fact we use re-indexed variables for i, var_index_i in enumerate(variable_indices): for j, var_index_j in enumerate(variable_indices): - q[i, j] = self._state.op.objective.get_quadratic_coefficients( - var_index_i, - var_index_j) + # coefficients_as_array + q[i, j] = self._state.op.objective.quadratic.coefficients_as_array()[ + var_index_i, var_index_j] # flip the sign, according to the optimization sense, e.g. sense == 1 if minimize, # sense == -1 if maximize. @@ -438,7 +439,8 @@ def _get_c(self, variable_indices: List[int]) -> np.ndarray: Returns: A numpy array of the shape(len(variable_indices)). """ - c = np.array(self._state.op.objective.get_linear(variable_indices)) + # c = np.array(self._state.op.objective.get_linear(variable_indices)) + c = self._state.op.objective.linear.coefficients_as_array().take(variable_indices) # flip the sign, according to the optimization sense, e.g. sense == 1 if minimize, # sense == -1 if maximize. c *= self._state.sense @@ -461,15 +463,18 @@ def _assign_row_values(self, matrix: List[List[float]], vector: List[float], # assign matrix row. row = [] for var_index in variable_indices: - row.append(self._state.op - .linear_constraints.get_coefficients(constraint_index, var_index)) + # row.append(self._state.op + # .linear_constraints.get_coefficients(constraint_index, var_index)) + row.append( + self._state.op.linear_constraints[constraint_index].linear.coefficients_as_array()[ + var_index]) matrix.append(row) # assign vector row. - vector.append(self._state.op.linear_constraints.get_rhs(constraint_index)) + vector.append(self._state.op.linear_constraints[constraint_index].rhs) # flip the sign if constraint is G, we want L constraints. - if self._state.op.linear_constraints.get_senses(constraint_index) == "G": + if self._state.op.linear_constraints[constraint_index].sense == "G": # invert the sign to make constraint "L". matrix[-1] = [-1 * el for el in matrix[-1]] vector[-1] = -1 * vector[-1] From f5b49450cd55078d61b2b8edf6131813cebcb051 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 15 Apr 2020 14:43:51 +0200 Subject: [PATCH 200/323] add __getitem__() to linear and quadratic expressions --- .../optimization/problems/linear_expression.py | 13 +++++++++++++ .../problems/quadratic_expression.py | 16 ++++++++++++++++ test/optimization/test_linear_expression.py | 12 ++++++++++++ test/optimization/test_quadratic_expression.py | 13 +++++++++++++ 4 files changed, 54 insertions(+) diff --git a/qiskit/optimization/problems/linear_expression.py b/qiskit/optimization/problems/linear_expression.py index e8817e04e9..5e2c14b175 100644 --- a/qiskit/optimization/problems/linear_expression.py +++ b/qiskit/optimization/problems/linear_expression.py @@ -43,6 +43,19 @@ def __init__(self, quadratic_program: "QuadraticProgram", super().__init__(quadratic_program) self.coefficients = coefficients + def __getitem__(self, i: Union[int, str]) -> float: + """Returns the i-th coefficient where i can be a variable name or index. + + Args: + i: the index or name of the variable corresponding to the coefficient. + + Returns: + The coefficient corresponding to the addressed variable. + """ + if isinstance(i, str): + i = self.quadratic_program.variables_index[i] + return self.coefficients[0, i] + def _coeffs_to_dok_matrix(self, coefficients: Union[ndarray, spmatrix, List, Dict[Union[int, str], float]] diff --git a/qiskit/optimization/problems/quadratic_expression.py b/qiskit/optimization/problems/quadratic_expression.py index 76b4c5c015..f9e3e45686 100644 --- a/qiskit/optimization/problems/quadratic_expression.py +++ b/qiskit/optimization/problems/quadratic_expression.py @@ -44,6 +44,22 @@ def __init__(self, quadratic_program: "QuadraticProgram", super().__init__(quadratic_program) self.coefficients = coefficients + def __getitem__(self, key: Tuple[Union[int, str], Union[int, str]]) -> float: + """Returns the coefficient where i, j can be a variable names or indices. + + Args: + key: the tuple of indices or names of the variables corresponding to the coefficient. + + Returns: + The coefficient corresponding to the addressed variables. + """ + i, j = key + if isinstance(i, str): + i = self.quadratic_program.variables_index[i] + if isinstance(j, str): + j = self.quadratic_program.variables_index[j] + return self.coefficients[i, j] + def _coeffs_to_dok_matrix(self, coefficients: Union[ndarray, spmatrix, List[List[float]], Dict[ diff --git a/test/optimization/test_linear_expression.py b/test/optimization/test_linear_expression.py index 648853a064..d6a3870934 100644 --- a/test/optimization/test_linear_expression.py +++ b/test/optimization/test_linear_expression.py @@ -55,6 +55,18 @@ def test_init(self): self.assertDictEqual(linear.coefficients_as_dict(use_index=False), coefficients_dict_str) + def test_get_item(self): + """ test get_item. """ + + quadratic_program = QuadraticProgram() + for _ in range(5): + quadratic_program.continuous_var() + + coefficients = [i for i in range(5)] + linear = LinearExpression(quadratic_program, coefficients) + for i, v in enumerate(coefficients): + self.assertEqual(linear[i], v) + def test_setters(self): """ test setters. """ diff --git a/test/optimization/test_quadratic_expression.py b/test/optimization/test_quadratic_expression.py index af9115da00..140bb2900b 100644 --- a/test/optimization/test_quadratic_expression.py +++ b/test/optimization/test_quadratic_expression.py @@ -57,6 +57,19 @@ def test_init(self): self.assertDictEqual(quadratic.coefficients_as_dict(use_index=False), coefficients_dict_str) + def test_get_item(self): + """ test get_item. """ + + quadratic_program = QuadraticProgram() + for _ in range(5): + quadratic_program.continuous_var() + + coefficients = [[i*j for i in range(5)] for j in range(5)] + quadratic = QuadraticExpression(quadratic_program, coefficients) + for i, jv in enumerate(coefficients): + for j, v in enumerate(jv): + self.assertEqual(quadratic[i, j], v) + def test_setters(self): """ test setters. """ From d8d1e6c4427bbfcbef3a33afac386024b7301865 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 15 Apr 2020 14:47:23 +0200 Subject: [PATCH 201/323] make constants upper case --- qiskit/optimization/problems/constraint.py | 12 +++--- .../problems/quadratic_objective.py | 6 +-- .../problems/quadratic_program.py | 38 +++++++++---------- qiskit/optimization/problems/variable.py | 8 ++-- test/optimization/test_linear_constraint.py | 12 +++--- .../optimization/test_quadratic_constraint.py | 12 +++--- .../optimization/test_quadratic_expression.py | 4 +- test/optimization/test_quadratic_objective.py | 16 ++++---- test/optimization/test_quadratic_program.py | 12 +++--- test/optimization/test_variable.py | 6 +-- 10 files changed, 63 insertions(+), 63 deletions(-) diff --git a/qiskit/optimization/problems/constraint.py b/qiskit/optimization/problems/constraint.py index a02e233266..1afa5d5e0d 100644 --- a/qiskit/optimization/problems/constraint.py +++ b/qiskit/optimization/problems/constraint.py @@ -26,9 +26,9 @@ class ConstraintSense(Enum): """Constants Sense Type.""" - leq = 0 - geq = 1 - eq = 2 # pylint: disable=locally-disabled, invalid-name + LE = 0 + GE = 1 + EQ = 2 # pylint: disable=locally-disabled, invalid-name @staticmethod def convert(sense: Union[str, "ConstraintSense"]) -> "ConstraintSense": @@ -49,11 +49,11 @@ def convert(sense: Union[str, "ConstraintSense"]) -> "ConstraintSense": if sense not in ['E', 'L', 'G', 'EQ', 'LE', 'GE', '=', '==', '<=', '<', '>=', '>']: raise QiskitOptimizationError('Invalid sense: {}'.format(sense)) if sense in ['E', 'EQ', '=', '==']: - return ConstraintSense.eq + return ConstraintSense.EQ elif sense in ['L', 'LE', '<=', '<']: - return ConstraintSense.leq + return ConstraintSense.LE else: - return ConstraintSense.geq + return ConstraintSense.GE class Constraint(HasQuadraticProgram): diff --git a/qiskit/optimization/problems/quadratic_objective.py b/qiskit/optimization/problems/quadratic_objective.py index 402bc8d1fa..c926bc24aa 100644 --- a/qiskit/optimization/problems/quadratic_objective.py +++ b/qiskit/optimization/problems/quadratic_objective.py @@ -27,8 +27,8 @@ class ObjSense(Enum): """Objective Sense Type.""" - minimize = 1 - maximize = -1 + MINIMIZE = 1 + MAXIMIZE = -1 class QuadraticObjective(HasQuadraticProgram): @@ -41,7 +41,7 @@ def __init__(self, quadratic_program: "QuadraticProgram", linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]] = None, quadratic: Union[ndarray, spmatrix, List[List[float]], Dict[Tuple[Union[int, str], Union[int, str]], float]] = None, - sense: ObjSense = ObjSense.minimize + sense: ObjSense = ObjSense.MINIMIZE ) -> None: """Constructs a quadratic objective function. diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 9985f12735..d373925ab4 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -108,7 +108,7 @@ def variables_index(self) -> Dict[str, int]: def _add_variable(self, name: Optional[str] = None, lowerbound: float = 0, upperbound: float = infinity, - vartype: VarType = VarType.continuous) -> Variable: + vartype: VarType = VarType.CONTINUOUS) -> Variable: """Checks whether a variable name is already taken and adds the variable to list and index if not. @@ -153,7 +153,7 @@ def continuous_var(self, name: Optional[str] = None, lowerbound: float = 0, Raises: QiskitOptimizationError: if the variable name is already occupied. """ - return self._add_variable(name, lowerbound, upperbound, VarType.continuous) + return self._add_variable(name, lowerbound, upperbound, VarType.CONTINUOUS) def binary_var(self, name: Optional[str] = None) -> Variable: """Adds a binary variable to the quadratic program. @@ -167,7 +167,7 @@ def binary_var(self, name: Optional[str] = None) -> Variable: Raises: QiskitOptimizationError: if the variable name is already occupied. """ - return self._add_variable(name, 0, 1, VarType.binary) + return self._add_variable(name, 0, 1, VarType.BINARY) def integer_var(self, name: Optional[str] = None, lowerbound: float = 0, upperbound: float = infinity) -> Variable: @@ -184,7 +184,7 @@ def integer_var(self, name: Optional[str] = None, lowerbound: float = 0, Raises: QiskitOptimizationError: if the variable name is already occupied. """ - return self._add_variable(name, lowerbound, upperbound, VarType.integer) + return self._add_variable(name, lowerbound, upperbound, VarType.INTEGER) def get_variable(self, i: Union[int, str]) -> Variable: """Returns a variable for a given name or index. @@ -220,7 +220,7 @@ def get_num_continuous_vars(self) -> int: Returns: The total number of continuous variables. """ - return self.get_num_vars(VarType.continuous) + return self.get_num_vars(VarType.CONTINUOUS) def get_num_binary_vars(self) -> int: """Returns the total number of binary variables. @@ -228,7 +228,7 @@ def get_num_binary_vars(self) -> int: Returns: The total number of binary variables. """ - return self.get_num_vars(VarType.binary) + return self.get_num_vars(VarType.BINARY) def get_num_integer_vars(self) -> int: """Returns the total number of integer variables. @@ -236,7 +236,7 @@ def get_num_integer_vars(self) -> int: Returns: The total number of integer variables. """ - return self.get_num_vars(VarType.integer) + return self.get_num_vars(VarType.INTEGER) @property def linear_constraints(self) -> List[LinearConstraint]: @@ -432,7 +432,7 @@ def minimize(self, Returns: The created quadratic objective. """ - self._objective = QuadraticObjective(self, constant, linear, quadratic, ObjSense.minimize) + self._objective = QuadraticObjective(self, constant, linear, quadratic, ObjSense.MINIMIZE) def maximize(self, constant: float = 0.0, @@ -450,7 +450,7 @@ def maximize(self, Returns: The created quadratic objective. """ - self._objective = QuadraticObjective(self, constant, linear, quadratic, ObjSense.maximize) + self._objective = QuadraticObjective(self, constant, linear, quadratic, ObjSense.MAXIMIZE) def from_docplex(self, model: Model) -> None: """Loads this quadratic program from a docplex model @@ -592,11 +592,11 @@ def to_docplex(self) -> Model: # add variables var = {} for i, x in enumerate(self.variables): - if x.vartype == VarType.continuous: + if x.vartype == VarType.CONTINUOUS: var[i] = mdl.continuous_var(lb=x.lowerbound, ub=x.upperbound, name=x.name) - elif x.vartype == VarType.binary: + elif x.vartype == VarType.BINARY: var[i] = mdl.binary_var(name=x.name) - elif x.vartype == VarType.integer: + elif x.vartype == VarType.INTEGER: var[i] = mdl.integer_var(lb=x.lowerbound, ub=x.upperbound, name=x.name) else: # should never happen @@ -608,7 +608,7 @@ def to_docplex(self) -> Model: objective += v * var[i] for (i, j), v in self.objective.quadratic.coefficients_as_dict().items(): objective += v * var[i] * var[j] - if self.objective.sense == ObjSense.minimize: + if self.objective.sense == ObjSense.MINIMIZE: mdl.minimize(objective) else: mdl.maximize(objective) @@ -621,11 +621,11 @@ def to_docplex(self) -> Model: for j, v in constraint.linear.coefficients_as_dict().items(): linear_expr += v * var[j] sense = constraint.sense - if sense == ConstraintSense.eq: + if sense == ConstraintSense.EQ: mdl.add_constraint(linear_expr == rhs, ctname=name) - elif sense == ConstraintSense.geq: + elif sense == ConstraintSense.GE: mdl.add_constraint(linear_expr >= rhs, ctname=name) - elif sense == ConstraintSense.leq: + elif sense == ConstraintSense.LE: mdl.add_constraint(linear_expr <= rhs, ctname=name) else: # should never happen @@ -641,11 +641,11 @@ def to_docplex(self) -> Model: for (j, k), v in constraint.quadratic.coefficients_as_dict().items(): quadratic_expr += v * var[j] * var[k] sense = constraint.sense - if sense == ConstraintSense.eq: + if sense == ConstraintSense.EQ: mdl.add_constraint(quadratic_expr == rhs, ctname=name) - elif sense == ConstraintSense.geq: + elif sense == ConstraintSense.GE: mdl.add_constraint(quadratic_expr >= rhs, ctname=name) - elif sense == ConstraintSense.leq: + elif sense == ConstraintSense.LE: mdl.add_constraint(quadratic_expr <= rhs, ctname=name) else: # should never happen diff --git a/qiskit/optimization/problems/variable.py b/qiskit/optimization/problems/variable.py index 271a744028..12321c29a2 100644 --- a/qiskit/optimization/problems/variable.py +++ b/qiskit/optimization/problems/variable.py @@ -23,16 +23,16 @@ class VarType(Enum): """Constants defining variable type.""" - continuous = 0 - binary = 1 - integer = 2 + CONTINUOUS = 0 + BINARY = 1 + INTEGER = 2 class Variable(HasQuadraticProgram): """Representation of a variable.""" def __init__(self, quadratic_program: 'QuadraticProgram', name: str, lowerbound: float = 0, - upperbound: float = infinity, vartype: VarType = VarType.continuous) -> None: + upperbound: float = infinity, vartype: VarType = VarType.CONTINUOUS) -> None: """Creates a new Variable. The variables is exposed by the top-level `QuadraticProgram` class diff --git a/test/optimization/test_linear_constraint.py b/test/optimization/test_linear_constraint.py index af12a00e8a..94ec4164af 100644 --- a/test/optimization/test_linear_constraint.py +++ b/test/optimization/test_linear_constraint.py @@ -43,7 +43,7 @@ def test_init(self): self.assertEqual(quadratic_program.linear_constraints[0].name, 'c0') self.assertEqual(len(quadratic_program.linear_constraints[0].linear.coefficients_as_dict()), 0) - self.assertEqual(quadratic_program.linear_constraints[0].sense, ConstraintSense.eq) + self.assertEqual(quadratic_program.linear_constraints[0].sense, ConstraintSense.EQ) self.assertEqual(quadratic_program.linear_constraints[0].rhs, 0.0) self.assertEqual(quadratic_program.linear_constraints[0], quadratic_program.get_linear_constraint('c0')) @@ -60,7 +60,7 @@ def test_init(self): quadratic_program.linear_constraints[1].linear.coefficients_as_array( ) == coefficients ).all()) - self.assertEqual(quadratic_program.linear_constraints[1].sense, ConstraintSense.eq) + self.assertEqual(quadratic_program.linear_constraints[1].sense, ConstraintSense.EQ) self.assertEqual(quadratic_program.linear_constraints[1].rhs, 1.0) self.assertEqual(quadratic_program.linear_constraints[1], quadratic_program.get_linear_constraint('c1')) @@ -73,7 +73,7 @@ def test_init(self): self.assertEqual(quadratic_program.linear_constraints[2].name, 'c2') self.assertEqual(len(quadratic_program.linear_constraints[2].linear.coefficients_as_dict()), 0) - self.assertEqual(quadratic_program.linear_constraints[2].sense, ConstraintSense.geq) + self.assertEqual(quadratic_program.linear_constraints[2].sense, ConstraintSense.GE) self.assertEqual(quadratic_program.linear_constraints[2].rhs, 0.0) self.assertEqual(quadratic_program.linear_constraints[2], quadratic_program.get_linear_constraint('c2')) @@ -90,7 +90,7 @@ def test_init(self): quadratic_program.linear_constraints[3].linear.coefficients_as_array( ) == coefficients ).all()) - self.assertEqual(quadratic_program.linear_constraints[3].sense, ConstraintSense.geq) + self.assertEqual(quadratic_program.linear_constraints[3].sense, ConstraintSense.GE) self.assertEqual(quadratic_program.linear_constraints[3].rhs, 1.0) self.assertEqual(quadratic_program.linear_constraints[3], quadratic_program.get_linear_constraint('c3')) @@ -103,7 +103,7 @@ def test_init(self): self.assertEqual(quadratic_program.linear_constraints[4].name, 'c4') self.assertEqual(len(quadratic_program.linear_constraints[4].linear.coefficients_as_dict()), 0) - self.assertEqual(quadratic_program.linear_constraints[4].sense, ConstraintSense.leq) + self.assertEqual(quadratic_program.linear_constraints[4].sense, ConstraintSense.LE) self.assertEqual(quadratic_program.linear_constraints[4].rhs, 0.0) self.assertEqual(quadratic_program.linear_constraints[4], quadratic_program.get_linear_constraint('c4')) @@ -120,7 +120,7 @@ def test_init(self): quadratic_program.linear_constraints[5].linear.coefficients_as_array( ) == coefficients ).all()) - self.assertEqual(quadratic_program.linear_constraints[5].sense, ConstraintSense.leq) + self.assertEqual(quadratic_program.linear_constraints[5].sense, ConstraintSense.LE) self.assertEqual(quadratic_program.linear_constraints[5].rhs, 1.0) self.assertEqual(quadratic_program.linear_constraints[5], quadratic_program.get_linear_constraint('c5')) diff --git a/test/optimization/test_quadratic_constraint.py b/test/optimization/test_quadratic_constraint.py index 2b0757584b..b5995dd346 100644 --- a/test/optimization/test_quadratic_constraint.py +++ b/test/optimization/test_quadratic_constraint.py @@ -47,7 +47,7 @@ def test_init(self): len(quadratic_program.quadratic_constraints[0].linear.coefficients_as_dict()), 0) self.assertEqual( len(quadratic_program.quadratic_constraints[0].quadratic.coefficients_as_dict()), 0) - self.assertEqual(quadratic_program.quadratic_constraints[0].sense, ConstraintSense.eq) + self.assertEqual(quadratic_program.quadratic_constraints[0].sense, ConstraintSense.EQ) self.assertEqual(quadratic_program.quadratic_constraints[0].rhs, 0.0) self.assertEqual(quadratic_program.quadratic_constraints[0], quadratic_program.get_quadratic_constraint('q0')) @@ -70,7 +70,7 @@ def test_init(self): quadratic_program.quadratic_constraints[1].quadratic.coefficients_as_array( ) == quadratic_coeffs ).all()) - self.assertEqual(quadratic_program.quadratic_constraints[1].sense, ConstraintSense.eq) + self.assertEqual(quadratic_program.quadratic_constraints[1].sense, ConstraintSense.EQ) self.assertEqual(quadratic_program.quadratic_constraints[1].rhs, 1.0) self.assertEqual(quadratic_program.quadratic_constraints[1], quadratic_program.get_quadratic_constraint('q1')) @@ -87,7 +87,7 @@ def test_init(self): len(quadratic_program.quadratic_constraints[2].linear.coefficients_as_dict()), 0) self.assertEqual( len(quadratic_program.quadratic_constraints[2].quadratic.coefficients_as_dict()), 0) - self.assertEqual(quadratic_program.quadratic_constraints[2].sense, ConstraintSense.geq) + self.assertEqual(quadratic_program.quadratic_constraints[2].sense, ConstraintSense.GE) self.assertEqual(quadratic_program.quadratic_constraints[2].rhs, 0.0) self.assertEqual(quadratic_program.quadratic_constraints[2], quadratic_program.get_quadratic_constraint('q2')) @@ -110,7 +110,7 @@ def test_init(self): quadratic_program.quadratic_constraints[3].quadratic.coefficients_as_array( ) == quadratic_coeffs ).all()) - self.assertEqual(quadratic_program.quadratic_constraints[3].sense, ConstraintSense.geq) + self.assertEqual(quadratic_program.quadratic_constraints[3].sense, ConstraintSense.GE) self.assertEqual(quadratic_program.quadratic_constraints[3].rhs, 1.0) self.assertEqual(quadratic_program.quadratic_constraints[3], quadratic_program.get_quadratic_constraint('q3')) @@ -125,7 +125,7 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[4].name, 'q4') self.assertEqual( len(quadratic_program.quadratic_constraints[4].linear.coefficients_as_dict()), 0) - self.assertEqual(quadratic_program.quadratic_constraints[4].sense, ConstraintSense.leq) + self.assertEqual(quadratic_program.quadratic_constraints[4].sense, ConstraintSense.LE) self.assertEqual(quadratic_program.quadratic_constraints[4].rhs, 0.0) self.assertEqual(quadratic_program.quadratic_constraints[4], quadratic_program.get_quadratic_constraint('q4')) @@ -148,7 +148,7 @@ def test_init(self): quadratic_program.quadratic_constraints[5].quadratic.coefficients_as_array( ) == quadratic_coeffs ).all()) - self.assertEqual(quadratic_program.quadratic_constraints[5].sense, ConstraintSense.leq) + self.assertEqual(quadratic_program.quadratic_constraints[5].sense, ConstraintSense.LE) self.assertEqual(quadratic_program.quadratic_constraints[5].rhs, 1.0) self.assertEqual(quadratic_program.quadratic_constraints[5], quadratic_program.get_quadratic_constraint('q5')) diff --git a/test/optimization/test_quadratic_expression.py b/test/optimization/test_quadratic_expression.py index 140bb2900b..fcd3f031c3 100644 --- a/test/optimization/test_quadratic_expression.py +++ b/test/optimization/test_quadratic_expression.py @@ -66,8 +66,8 @@ def test_get_item(self): coefficients = [[i*j for i in range(5)] for j in range(5)] quadratic = QuadraticExpression(quadratic_program, coefficients) - for i, jv in enumerate(coefficients): - for j, v in enumerate(jv): + for i, j_v in enumerate(coefficients): + for j, v in enumerate(j_v): self.assertEqual(quadratic[i, j], v) def test_setters(self): diff --git a/test/optimization/test_quadratic_objective.py b/test/optimization/test_quadratic_objective.py index 7cfc4061cf..dcdd10642b 100644 --- a/test/optimization/test_quadratic_objective.py +++ b/test/optimization/test_quadratic_objective.py @@ -40,7 +40,7 @@ def test_init(self): len(quadratic_program.objective.linear.coefficients_as_dict()), 0) self.assertEqual( len(quadratic_program.objective.quadratic.coefficients_as_dict()), 0) - self.assertEqual(quadratic_program.objective.sense, ObjSense.minimize) + self.assertEqual(quadratic_program.objective.sense, ObjSense.MINIMIZE) constant = 1.0 linear_coeffs = np.array([i for i in range(5)]) @@ -54,7 +54,7 @@ def test_init(self): self.assertTrue( (quadratic_program.objective.quadratic.coefficients_as_array() == quadratic_coeffs) .all()) - self.assertEqual(quadratic_program.objective.sense, ObjSense.minimize) + self.assertEqual(quadratic_program.objective.sense, ObjSense.MINIMIZE) quadratic_program.maximize(constant, linear_coeffs, quadratic_coeffs) @@ -64,7 +64,7 @@ def test_init(self): self.assertTrue( (quadratic_program.objective.quadratic.coefficients_as_array() == quadratic_coeffs) .all()) - self.assertEqual(quadratic_program.objective.sense, ObjSense.maximize) + self.assertEqual(quadratic_program.objective.sense, ObjSense.MAXIMIZE) self.assertEqual(quadratic_program.objective.evaluate(linear_coeffs), 931.0) @@ -90,13 +90,13 @@ def test_setters(self): (quadratic_program.objective.quadratic.coefficients_as_array() == quadratic_coeffs) .all()) - self.assertEqual(quadratic_program.objective.sense, ObjSense.minimize) + self.assertEqual(quadratic_program.objective.sense, ObjSense.MINIMIZE) - quadratic_program.objective.sense = ObjSense.maximize - self.assertEqual(quadratic_program.objective.sense, ObjSense.maximize) + quadratic_program.objective.sense = ObjSense.MAXIMIZE + self.assertEqual(quadratic_program.objective.sense, ObjSense.MAXIMIZE) - quadratic_program.objective.sense = ObjSense.minimize - self.assertEqual(quadratic_program.objective.sense, ObjSense.minimize) + quadratic_program.objective.sense = ObjSense.MINIMIZE + self.assertEqual(quadratic_program.objective.sense, ObjSense.MINIMIZE) if __name__ == '__main__': diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index 8d00fd2353..775727a787 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -52,7 +52,7 @@ def test_variables_handling(self): self.assertEqual(x_0.name, 'x0') self.assertEqual(x_0.lowerbound, 0) self.assertEqual(x_0.upperbound, infinity) - self.assertEqual(x_0.vartype, VarType.continuous) + self.assertEqual(x_0.vartype, VarType.CONTINUOUS) self.assertEqual(quadratic_program.get_num_vars(), 1) self.assertEqual(quadratic_program.get_num_continuous_vars(), 1) @@ -63,7 +63,7 @@ def test_variables_handling(self): self.assertEqual(x_1.name, 'x1') self.assertEqual(x_1.lowerbound, 5) self.assertEqual(x_1.upperbound, 10) - self.assertEqual(x_1.vartype, VarType.continuous) + self.assertEqual(x_1.vartype, VarType.CONTINUOUS) self.assertEqual(quadratic_program.get_num_vars(), 2) self.assertEqual(quadratic_program.get_num_continuous_vars(), 2) @@ -74,7 +74,7 @@ def test_variables_handling(self): self.assertEqual(x_2.name, 'x2') self.assertEqual(x_2.lowerbound, 0) self.assertEqual(x_2.upperbound, 1) - self.assertEqual(x_2.vartype, VarType.binary) + self.assertEqual(x_2.vartype, VarType.BINARY) self.assertEqual(quadratic_program.get_num_vars(), 3) self.assertEqual(quadratic_program.get_num_continuous_vars(), 2) @@ -85,7 +85,7 @@ def test_variables_handling(self): self.assertEqual(x_3.name, 'x3') self.assertEqual(x_3.lowerbound, 0) self.assertEqual(x_3.upperbound, 1) - self.assertEqual(x_3.vartype, VarType.binary) + self.assertEqual(x_3.vartype, VarType.BINARY) self.assertEqual(quadratic_program.get_num_vars(), 4) self.assertEqual(quadratic_program.get_num_continuous_vars(), 2) @@ -96,7 +96,7 @@ def test_variables_handling(self): self.assertEqual(x_4.name, 'x4') self.assertEqual(x_4.lowerbound, 0) self.assertEqual(x_4.upperbound, infinity) - self.assertEqual(x_4.vartype, VarType.integer) + self.assertEqual(x_4.vartype, VarType.INTEGER) self.assertEqual(quadratic_program.get_num_vars(), 5) self.assertEqual(quadratic_program.get_num_continuous_vars(), 2) @@ -107,7 +107,7 @@ def test_variables_handling(self): self.assertEqual(x_5.name, 'x5') self.assertEqual(x_5.lowerbound, 5) self.assertEqual(x_5.upperbound, 10) - self.assertEqual(x_5.vartype, VarType.integer) + self.assertEqual(x_5.vartype, VarType.INTEGER) self.assertEqual(quadratic_program.get_num_vars(), 6) self.assertEqual(quadratic_program.get_num_continuous_vars(), 2) diff --git a/test/optimization/test_variable.py b/test/optimization/test_variable.py index c7d31738cc..e081cd5f5e 100644 --- a/test/optimization/test_variable.py +++ b/test/optimization/test_variable.py @@ -34,14 +34,14 @@ def test_init(self): name = 'variable' lowerbound = 0 upperbound = 10 - vartype = VarType.integer + vartype = VarType.INTEGER variable = Variable(quadratic_program, name, lowerbound, upperbound, vartype) self.assertEqual(variable.name, name) self.assertEqual(variable.lowerbound, lowerbound) self.assertEqual(variable.upperbound, upperbound) - self.assertEqual(variable.vartype, VarType.integer) + self.assertEqual(variable.vartype, VarType.INTEGER) def test_init_default(self): """ test init with default values.""" @@ -54,7 +54,7 @@ def test_init_default(self): self.assertEqual(variable.name, name) self.assertEqual(variable.lowerbound, 0) self.assertEqual(variable.upperbound, infinity) - self.assertEqual(variable.vartype, VarType.continuous) + self.assertEqual(variable.vartype, VarType.CONTINUOUS) if __name__ == '__main__': From ce371d269b449e1a0d552fc3f400fea6fba66ee3 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Wed, 15 Apr 2020 22:12:39 +0900 Subject: [PATCH 202/323] (wip) substitute_variables --- .../problems/substitute_variables.py | 314 ++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 qiskit/optimization/problems/substitute_variables.py diff --git a/qiskit/optimization/problems/substitute_variables.py b/qiskit/optimization/problems/substitute_variables.py new file mode 100644 index 0000000000..5ba2f68c12 --- /dev/null +++ b/qiskit/optimization/problems/substitute_variables.py @@ -0,0 +1,314 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019, 2020. +# +# 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. + +"""Mixed integer quadratically constrained quadratic program""" + +import logging +from collections import defaultdict +from enum import Enum +from math import fsum +from typing import Optional, Tuple, Dict, Union, List + +from qiskit.optimization import infinity, QiskitOptimizationError +from qiskit.optimization.problems.constraint import ConstraintSense +from qiskit.optimization.problems.linear_expression import LinearExpression +from qiskit.optimization.problems.quadratic_expression import QuadraticExpression +from qiskit.optimization.problems.quadratic_program import QuadraticProgram + +logger = logging.getLogger(__name__) + + +class SubstitutionStatus(Enum): + """Status of `QuadraticProgram.substitute_variables`""" + success = 1 + infeasible = 2 + + +class SubstituteVariables: + """A class to substitute variables of an optimization problem with constants for other + variables""" + + CONST = '__CONSTANT__' + + def __init__(self): + self._src: QuadraticProgram = None + self._dst: QuadraticProgram = None + self._subs = {} + + def substitute_variables( + self, src: 'QuadraticProgram', + constants: Optional[Dict[Union[str, int], float]] = None, + variables: Optional[Dict[Union[str, int], Tuple[Union[str, int], float]]] = None) \ + -> Tuple[QuadraticProgram, SubstitutionStatus]: + """Substitutes variables with constants or other variables. + + Args: + constants: replace variable by constant + e.g., {'x': 2} means 'x' is substituted with 2 + + variables: replace variables by weighted other variable + need to copy everything using name reference to make sure that indices are matched + correctly. The lower and upper bounds are updated accordingly. + e.g., {'x': ('y', 2)} means 'x' is substituted with 'y' * 2 + + Returns: + An optimization problem by substituting variables and the status. + If the resulting problem has no issue, the status is `success`. + Otherwise, an empty problem and status `infeasible` are returned. + + Raises: + QiskitOptimizationError: if the substitution is invalid as follows. + - Same variable is substituted multiple times. + - Coefficient of variable substitution is zero. + + Example usage: + >>> + """ + + self._src = src + self._dst = QuadraticProgram(src.name) + # do not set problem type, then it detects its type automatically + + self._subs_dict(constants, variables) + + results = [ + self._variables(), + self._objective(), + self._linear_constraints(), + self._quadratic_constraints(), + ] + if any(r == SubstitutionStatus.infeasible for r in results): + ret = SubstitutionStatus.infeasible + else: + ret = SubstitutionStatus.success + return self._dst, ret + + @staticmethod + def _feasible(sense: ConstraintSense, rhs: float) -> bool: + """Checks feasibility of the following condition + 0 `sense` rhs + """ + # I use the following pylint option because `rhs` should come to right + # pylint: disable=misplaced-comparison-constant + if sense == ConstraintSense.eq: + if 0 == rhs: + return True + elif sense == ConstraintSense.leq: + if 0 <= rhs: + return True + elif sense == ConstraintSense.geq: + if 0 >= rhs: + return True + return False + + @staticmethod + def _replace_dict_keys_with_names(op, dic): + key = [] + val = [] + for k in sorted(dic.keys()): + key.append(op.variables.get_names(k)) + val.append(dic[k]) + return key, val + + def _subs_dict(self, constants, variables): + src = self._src + + # guarantee that there is no overlap between variables to be replaced and combine input + subs: Dict[str, Tuple[str, float]] = {} + if constants is not None: + for i, v in constants.items(): + # substitute i <- v + i_2 = src.get_variable(i).name + if i_2 in subs: + raise QiskitOptimizationError( + 'Cannot substitute the same variable twice: {} <- {}'.format(i, v)) + subs[i_2] = (self.CONST, v) + + if variables is not None: + for i, (j, v) in variables.items(): + if v == 0: + raise QiskitOptimizationError( + 'coefficient must be non-zero: {} {} {}'.format(i, j, v)) + # substitute i <- j * v + i_2 = src.get_variable(i).name + j_2 = src.get_variable(j).name + if i_2 == j_2: + raise QiskitOptimizationError( + 'Cannot substitute the same variable: {} <- {} {}'.format(i, j, v)) + if i_2 in subs: + raise QiskitOptimizationError( + 'Cannot substitute the same variable twice: {} <- {} {}'.format(i, j, v)) + if j_2 in subs: + raise QiskitOptimizationError( + 'Cannot substitute by variable that gets substituted itself: ' + '{} <- {} {}'.format(i, j, v)) + subs[i_2] = (j_2, v) + + self._subs = subs + + def _variables(self) -> SubstitutionStatus: + src = self._src + dst = self._dst + subs = self._subs + + # copy variables that are not replaced + for var in src.variables: + name = var.name + vartype = var.vartype + lowerbound = var.lowerbound + upperbound = var.upperbound + if name not in subs: + dst._add_variable(name, lowerbound, upperbound, vartype) + + for i, (j, v) in subs.items(): + lb_i = src.get_variable(i).lowerbound + ub_i = src.get_variable(i).upperbound + if j == self.CONST: + if not lb_i <= v <= ub_i: + logger.warning( + 'Infeasible substitution for variable: %s', i) + return SubstitutionStatus.infeasible + else: + # substitute i <- j * v + # lb_i <= i <= ub_i --> lb_i / v <= j <= ub_i / v if v > 0 + # ub_i / v <= j <= lb_i / v if v < 0 + if v == 0: + raise QiskitOptimizationError( + 'Coefficient of variable substitution should be nonzero: ' + '{} {} {}'.format(i, j, v)) + if abs(lb_i) < infinity: + new_lb_i = lb_i / v + else: + new_lb_i = lb_i if v > 0 else -lb_i + if abs(ub_i) < infinity: + new_ub_i = ub_i / v + else: + new_ub_i = ub_i if v > 0 else -ub_i + var_j = dst.get_variable(j) + lb_j = var_j.lowerbound + ub_j = var_j.upperbound + if v > 0: + var_j.lowerbound = max(lb_j, new_lb_i) + var_j.upperbound = min(ub_j, new_ub_i) + else: + var_j.lowerbound = max(lb_j, new_ub_i) + var_j.upperbound = min(ub_j, new_lb_i) + + for var in dst.variables: + if var.lowerbound > var.upperbound: + logger.warning( + 'Infeasible lower and upper bound: %s %f %f', var, var.lowerbound, + var.upperbound) + return SubstitutionStatus.infeasible + + return SubstitutionStatus.success + + def _linear_expression(self, lin_expr: LinearExpression) \ + -> Tuple[List[float], LinearExpression]: + subs = self._subs + const = [] + lin_dict = defaultdict(float) + for i, w_i in lin_expr.coefficients_as_dict(use_index=False).items(): + repl_i = subs[i] if i in subs else (i, 1) + prod = w_i * repl_i[1] + if repl_i[0] == self.CONST: + const.append(prod) + else: + k = repl_i[0] + lin_dict[k] += prod + new_lin = LinearExpression(quadratic_program=self._dst, + coefficients=lin_dict if lin_dict else {}) + return const, new_lin + + def _quadratic_expression(self, quad_expr: QuadraticExpression) \ + -> Tuple[List[float], Optional[LinearExpression], Optional[QuadraticExpression]]: + subs = self._subs + const = [] + lin_dict = defaultdict(float) + quad_dict = defaultdict(float) + for (i, j), w_ij in quad_expr.coefficients_as_dict().items(): + repl_i = subs[i] if i in subs else (i, 1) + repl_j = subs[j] if j in subs else (j, 1) + idx = tuple(x for x, _ in [repl_i, repl_j] if x != self.CONST) + prod = w_ij * repl_i[1] * repl_j[1] + if len(idx) == 2: + quad_dict[idx] += prod + elif len(idx) == 1: + lin_dict[idx[0]] += prod + else: + const.append(prod) + new_lin = LinearExpression(quadratic_program=self._dst, + coefficients=lin_dict if lin_dict else {}) + new_quad = QuadraticExpression(quadratic_program=self._dst, + coefficients=quad_dict if quad_dict else {}) + return const, new_lin, new_quad + + def _objective(self) -> SubstitutionStatus: + src = self._src + dst = self._dst + + const1, lin1 = self._linear_expression(src.objective.linear) + const2, lin2, quadratic = self._quadratic_expression(src.objective.quadratic) + + constant = fsum([src.objective.constant] + const1 + const2) + linear = lin1.coefficients + lin2.coefficients + if src.objective.sense == src.objective.sense.minimize: + dst.minimize(constant=constant, linear=linear, quadratic=quadratic.coefficients) + else: + dst.maximize(constant=constant, linear=linear, quadratic=quadratic.coefficients) + return SubstitutionStatus.success + + def _linear_constraints(self) -> SubstitutionStatus: + src = self._src + dst = self._dst + + for lin_cst in src.linear_constraints: + constant, linear = self._linear_expression(lin_cst.linear) + rhs = lin_cst.rhs - fsum(constant) + if linear.coefficients.nnz > 0: + dst.linear_constraint(name=lin_cst.name, coefficients=linear.coefficients, + sense=lin_cst.sense, rhs=rhs) + else: + if not self._feasible(lin_cst.sense, rhs): + logger.warning('constraint %s is infeasible due to substitution', lin_cst.name) + return SubstitutionStatus.infeasible + + return SubstitutionStatus.success + + def _quadratic_constraints(self) -> SubstitutionStatus: + src = self._src + dst = self._dst + + for quad_cst in src.quadratic_constraints: + const1, lin1 = self._linear_expression(quad_cst.linear) + const2, lin2, quadratic = self._quadratic_expression(quad_cst.quadratic) + rhs = quad_cst.rhs - fsum(const1 + const2) + linear = lin1.coefficients + lin2.coefficients + + if quadratic.coefficients.nnz > 0: + dst.quadratic_constraint(name=quad_cst.name, linear_coefficients=linear, + quadratic_coefficients=quadratic.coefficients, + sense=quad_cst.sense, rhs=rhs) + elif linear.nnz > 0: + name = quad_cst.name + lin_names = set(lin.name for lin in dst.linear_constraints) + while name in lin_names: + name = '_' + name + dst.linear_constraint(name=name, coefficients=linear, sense=quad_cst.sense, rhs=rhs) + else: + if not self._feasible(quad_cst.sense, rhs): + logger.warning('constraint %s is infeasible due to substitution', quad_cst.name) + return SubstitutionStatus.infeasible + + return SubstitutionStatus.success From b337a95a72b0a2255ce19916fdcd101a1618891c Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Wed, 15 Apr 2020 23:03:57 +0900 Subject: [PATCH 203/323] Alpha version of substitute_variables Also moved name arg of linear_constraint and quadratic_constraint in the last --- qiskit/optimization/problems/constraint.py | 4 +- .../problems/quadratic_program.py | 337 +++++++++++++++++- .../problems/substitute_variables.py | 314 ---------------- test/optimization/test_linear_constraint.py | 6 +- .../optimization/test_quadratic_constraint.py | 6 +- 5 files changed, 335 insertions(+), 332 deletions(-) delete mode 100644 qiskit/optimization/problems/substitute_variables.py diff --git a/qiskit/optimization/problems/constraint.py b/qiskit/optimization/problems/constraint.py index 1afa5d5e0d..3d6bdb8d91 100644 --- a/qiskit/optimization/problems/constraint.py +++ b/qiskit/optimization/problems/constraint.py @@ -26,9 +26,11 @@ class ConstraintSense(Enum): """Constants Sense Type.""" + + # pylint: disable=invalid-name LE = 0 GE = 1 - EQ = 2 # pylint: disable=locally-disabled, invalid-name + EQ = 2 @staticmethod def convert(sense: Union[str, "ConstraintSense"]) -> "ConstraintSense": diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index d373925ab4..dac890e47b 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -13,7 +13,10 @@ # that they have been altered from the originals. """Quadratic Program.""" - +import logging +from collections import defaultdict +from enum import Enum +from math import fsum from typing import List, Union, Dict, Optional, Tuple from docplex.mp.linear import Var @@ -24,10 +27,14 @@ from qiskit.optimization import infinity, QiskitOptimizationError from qiskit.optimization.problems.constraint import ConstraintSense from qiskit.optimization.problems.linear_constraint import LinearConstraint +from qiskit.optimization.problems.linear_expression import LinearExpression from qiskit.optimization.problems.quadratic_constraint import QuadraticConstraint +from qiskit.optimization.problems.quadratic_expression import QuadraticExpression from qiskit.optimization.problems.quadratic_objective import QuadraticObjective, ObjSense from qiskit.optimization.problems.variable import Variable, VarType +logger = logging.getLogger(__name__) + class QuadraticProgram: """Representation of a Quadratically Constrained Quadratic Program supporting inequality and @@ -256,11 +263,11 @@ def linear_constraints_index(self) -> Dict[str, int]: """ return self._linear_constraints_index - def linear_constraint(self, name: Optional[str] = None, + def linear_constraint(self, coefficients: Union[ndarray, spmatrix, List[float], Dict[Union[int, str], float]] = None, sense: Union[str, ConstraintSense] = '<=', - rhs: float = 0.0) -> LinearConstraint: + rhs: float = 0.0, name: Optional[str] = None) -> LinearConstraint: """Adds a linear equality constraint to the quadratic program of the form: linear_coeffs * x sense rhs. @@ -336,7 +343,7 @@ def quadratic_constraints_index(self) -> Dict[str, int]: """ return self._quadratic_constraints_index - def quadratic_constraint(self, name: Optional[str] = None, + def quadratic_constraint(self, linear_coefficients: Union[ndarray, spmatrix, List[float], Dict[Union[int, str], float]] = None, quadratic_coefficients: Union[ndarray, spmatrix, @@ -346,7 +353,7 @@ def quadratic_constraint(self, name: Optional[str] = None, Union[int, str]], float]] = None, sense: Union[str, ConstraintSense] = '<=', - rhs: float = 0.0) -> QuadraticConstraint: + rhs: float = 0.0, name: Optional[str] = None) -> QuadraticConstraint: """Adds a quadratic equality constraint to the quadratic program of the form: x * Q * x <= rhs. @@ -522,11 +529,11 @@ def from_docplex(self, model: Model) -> None: lhs[x[0].name] = x[1] if sense == sense.EQ: - self.linear_constraint(name, lhs, '==', rhs) + self.linear_constraint(lhs, '==', rhs, name) elif sense == sense.GE: - self.linear_constraint(name, lhs, '>=', rhs) + self.linear_constraint(lhs, '>=', rhs, name) elif sense == sense.LE: - self.linear_constraint(name, lhs, '<=', rhs) + self.linear_constraint(lhs, '<=', rhs, name) else: raise QiskitOptimizationError("Unsupported constraint sense!") @@ -568,11 +575,11 @@ def from_docplex(self, model: Model) -> None: linear[x.name] = linear.get(x.name, 0.0) - right_expr.get_coef(x) if sense == sense.EQ: - self.quadratic_constraint(name, linear, quadratic, '==', rhs) + self.quadratic_constraint(linear, quadratic, '==', rhs, name) elif sense == sense.GE: - self.quadratic_constraint(name, linear, quadratic, '>=', rhs) + self.quadratic_constraint(linear, quadratic, '>=', rhs, name) elif sense == sense.LE: - self.quadratic_constraint(name, linear, quadratic, '<=', rhs) + self.quadratic_constraint(linear, quadratic, '<=', rhs, name) else: raise QiskitOptimizationError("Unsupported constraint sense!") @@ -677,3 +684,311 @@ def print_as_lp_string(self) -> str: A string representing the quadratic program. """ return self.to_docplex().export_as_lp_string() + + def substitute_variables( + self, constants: Optional[Dict[Union[str, int], float]] = None, + variables: Optional[Dict[Union[str, int], Tuple[Union[str, int], float]]] = None) \ + -> Tuple['QuadraticProgram', 'SubstitutionStatus']: + """Substitutes variables with constants or other variables. + + Args: + constants: replace variable by constant + e.g., {'x': 2} means 'x' is substituted with 2 + + variables: replace variables by weighted other variable + need to copy everything using name reference to make sure that indices are matched + correctly. The lower and upper bounds are updated accordingly. + e.g., {'x': ('y', 2)} means 'x' is substituted with 'y' * 2 + + Returns: + An optimization problem by substituting variables and the status. + If the resulting problem has no issue, the status is `success`. + Otherwise, an empty problem and status `infeasible` are returned. + + Raises: + QiskitOptimizationError: if the substitution is invalid as follows. + - Same variable is substituted multiple times. + - Coefficient of variable substitution is zero. + """ + return SubstituteVariables().substitute_variables(self, constants, variables) + + +class SubstitutionStatus(Enum): + """Status of `QuadraticProgram.substitute_variables`""" + success = 1 + infeasible = 2 + + +class SubstituteVariables: + """A class to substitute variables of an optimization problem with constants for other + variables""" + + CONST = '__CONSTANT__' + + def __init__(self): + self._src: Optional[QuadraticProgram] = None + self._dst: Optional[QuadraticProgram] = None + self._subs = {} + + def substitute_variables( + self, src: QuadraticProgram, + constants: Optional[Dict[Union[str, int], float]] = None, + variables: Optional[Dict[Union[str, int], Tuple[Union[str, int], float]]] = None) \ + -> Tuple[QuadraticProgram, SubstitutionStatus]: + """Substitutes variables with constants or other variables. + + Args: + src: a quadratic program to be substituted. + + constants: replace variable by constant + e.g., {'x': 2} means 'x' is substituted with 2 + + variables: replace variables by weighted other variable + need to copy everything using name reference to make sure that indices are matched + correctly. The lower and upper bounds are updated accordingly. + e.g., {'x': ('y', 2)} means 'x' is substituted with 'y' * 2 + + Returns: + An optimization problem by substituting variables and the status. + If the resulting problem has no issue, the status is `success`. + Otherwise, an empty problem and status `infeasible` are returned. + + Raises: + QiskitOptimizationError: if the substitution is invalid as follows. + - Same variable is substituted multiple times. + - Coefficient of variable substitution is zero. + """ + + self._src = src + self._dst = QuadraticProgram(src.name) + self._subs_dict(constants, variables) + results = [ + self._variables(), + self._objective(), + self._linear_constraints(), + self._quadratic_constraints(), + ] + if any(r == SubstitutionStatus.infeasible for r in results): + ret = SubstitutionStatus.infeasible + else: + ret = SubstitutionStatus.success + return self._dst, ret + + @staticmethod + def _feasible(sense: ConstraintSense, rhs: float) -> bool: + """Checks feasibility of the following condition + 0 `sense` rhs + """ + # I use the following pylint option because `rhs` should come to right + # pylint: disable=misplaced-comparison-constant + if sense == ConstraintSense.EQ: + if 0 == rhs: + return True + elif sense == ConstraintSense.LE: + if 0 <= rhs: + return True + elif sense == ConstraintSense.GE: + if 0 >= rhs: + return True + return False + + @staticmethod + def _replace_dict_keys_with_names(op, dic): + key = [] + val = [] + for k in sorted(dic.keys()): + key.append(op.variables.get_names(k)) + val.append(dic[k]) + return key, val + + def _subs_dict(self, constants, variables): + src = self._src + + # guarantee that there is no overlap between variables to be replaced and combine input + subs: Dict[str, Tuple[str, float]] = {} + if constants is not None: + for i, v in constants.items(): + # substitute i <- v + i_2 = src.get_variable(i).name + if i_2 in subs: + raise QiskitOptimizationError( + 'Cannot substitute the same variable twice: {} <- {}'.format(i, v)) + subs[i_2] = (self.CONST, v) + + if variables is not None: + for i, (j, v) in variables.items(): + if v == 0: + raise QiskitOptimizationError( + 'coefficient must be non-zero: {} {} {}'.format(i, j, v)) + # substitute i <- j * v + i_2 = src.get_variable(i).name + j_2 = src.get_variable(j).name + if i_2 == j_2: + raise QiskitOptimizationError( + 'Cannot substitute the same variable: {} <- {} {}'.format(i, j, v)) + if i_2 in subs: + raise QiskitOptimizationError( + 'Cannot substitute the same variable twice: {} <- {} {}'.format(i, j, v)) + if j_2 in subs: + raise QiskitOptimizationError( + 'Cannot substitute by variable that gets substituted itself: ' + '{} <- {} {}'.format(i, j, v)) + subs[i_2] = (j_2, v) + + self._subs = subs + + def _variables(self) -> SubstitutionStatus: + src = self._src + dst = self._dst + subs = self._subs + + # copy variables that are not replaced + for var in src.variables: + name = var.name + vartype = var.vartype + lowerbound = var.lowerbound + upperbound = var.upperbound + if name not in subs: + dst._add_variable(name, lowerbound, upperbound, vartype) + + for i, (j, v) in subs.items(): + lb_i = src.get_variable(i).lowerbound + ub_i = src.get_variable(i).upperbound + if j == self.CONST: + if not lb_i <= v <= ub_i: + logger.warning( + 'Infeasible substitution for variable: %s', i) + return SubstitutionStatus.infeasible + else: + # substitute i <- j * v + # lb_i <= i <= ub_i --> lb_i / v <= j <= ub_i / v if v > 0 + # ub_i / v <= j <= lb_i / v if v < 0 + if v == 0: + raise QiskitOptimizationError( + 'Coefficient of variable substitution should be nonzero: ' + '{} {} {}'.format(i, j, v)) + if abs(lb_i) < infinity: + new_lb_i = lb_i / v + else: + new_lb_i = lb_i if v > 0 else -lb_i + if abs(ub_i) < infinity: + new_ub_i = ub_i / v + else: + new_ub_i = ub_i if v > 0 else -ub_i + var_j = dst.get_variable(j) + lb_j = var_j.lowerbound + ub_j = var_j.upperbound + if v > 0: + var_j.lowerbound = max(lb_j, new_lb_i) + var_j.upperbound = min(ub_j, new_ub_i) + else: + var_j.lowerbound = max(lb_j, new_ub_i) + var_j.upperbound = min(ub_j, new_lb_i) + + for var in dst.variables: + if var.lowerbound > var.upperbound: + logger.warning( + 'Infeasible lower and upper bound: %s %f %f', var, var.lowerbound, + var.upperbound) + return SubstitutionStatus.infeasible + + return SubstitutionStatus.success + + def _linear_expression(self, lin_expr: LinearExpression) \ + -> Tuple[List[float], LinearExpression]: + subs = self._subs + const = [] + lin_dict = defaultdict(float) + for i, w_i in lin_expr.coefficients_as_dict(use_index=False).items(): + repl_i = subs[i] if i in subs else (i, 1) + prod = w_i * repl_i[1] + if repl_i[0] == self.CONST: + const.append(prod) + else: + k = repl_i[0] + lin_dict[k] += prod + new_lin = LinearExpression(quadratic_program=self._dst, + coefficients=lin_dict if lin_dict else {}) + return const, new_lin + + def _quadratic_expression(self, quad_expr: QuadraticExpression) \ + -> Tuple[List[float], Optional[LinearExpression], Optional[QuadraticExpression]]: + subs = self._subs + const = [] + lin_dict = defaultdict(float) + quad_dict = defaultdict(float) + for (i, j), w_ij in quad_expr.coefficients_as_dict(use_index=False).items(): + repl_i = subs[i] if i in subs else (i, 1) + repl_j = subs[j] if j in subs else (j, 1) + idx = tuple(x for x, _ in [repl_i, repl_j] if x != self.CONST) + prod = w_ij * repl_i[1] * repl_j[1] + if len(idx) == 2: + quad_dict[idx] += prod + elif len(idx) == 1: + lin_dict[idx[0]] += prod + else: + const.append(prod) + new_lin = LinearExpression(quadratic_program=self._dst, + coefficients=lin_dict if lin_dict else {}) + new_quad = QuadraticExpression(quadratic_program=self._dst, + coefficients=quad_dict if quad_dict else {}) + return const, new_lin, new_quad + + def _objective(self) -> SubstitutionStatus: + src = self._src + dst = self._dst + + const1, lin1 = self._linear_expression(src.objective.linear) + const2, lin2, quadratic = self._quadratic_expression(src.objective.quadratic) + + constant = fsum([src.objective.constant] + const1 + const2) + linear = lin1.coefficients + lin2.coefficients + if src.objective.sense == src.objective.sense.MINIMIZE: + dst.minimize(constant=constant, linear=linear, quadratic=quadratic.coefficients) + else: + dst.maximize(constant=constant, linear=linear, quadratic=quadratic.coefficients) + return SubstitutionStatus.success + + def _linear_constraints(self) -> SubstitutionStatus: + src = self._src + dst = self._dst + + for lin_cst in src.linear_constraints: + constant, linear = self._linear_expression(lin_cst.linear) + rhs = lin_cst.rhs - fsum(constant) + if linear.coefficients.nnz > 0: + dst.linear_constraint(name=lin_cst.name, coefficients=linear.coefficients, + sense=lin_cst.sense, rhs=rhs) + else: + if not self._feasible(lin_cst.sense, rhs): + logger.warning('constraint %s is infeasible due to substitution', lin_cst.name) + return SubstitutionStatus.infeasible + + return SubstitutionStatus.success + + def _quadratic_constraints(self) -> SubstitutionStatus: + src = self._src + dst = self._dst + + for quad_cst in src.quadratic_constraints: + const1, lin1 = self._linear_expression(quad_cst.linear) + const2, lin2, quadratic = self._quadratic_expression(quad_cst.quadratic) + rhs = quad_cst.rhs - fsum(const1 + const2) + linear = lin1.coefficients + lin2.coefficients + + if quadratic.coefficients.nnz > 0: + dst.quadratic_constraint(name=quad_cst.name, linear_coefficients=linear, + quadratic_coefficients=quadratic.coefficients, + sense=quad_cst.sense, rhs=rhs) + elif linear.nnz > 0: + name = quad_cst.name + lin_names = set(lin.name for lin in dst.linear_constraints) + while name in lin_names: + name = '_' + name + dst.linear_constraint(name=name, coefficients=linear, sense=quad_cst.sense, rhs=rhs) + else: + if not self._feasible(quad_cst.sense, rhs): + logger.warning('constraint %s is infeasible due to substitution', quad_cst.name) + return SubstitutionStatus.infeasible + + return SubstitutionStatus.success diff --git a/qiskit/optimization/problems/substitute_variables.py b/qiskit/optimization/problems/substitute_variables.py deleted file mode 100644 index 5ba2f68c12..0000000000 --- a/qiskit/optimization/problems/substitute_variables.py +++ /dev/null @@ -1,314 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# 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. - -"""Mixed integer quadratically constrained quadratic program""" - -import logging -from collections import defaultdict -from enum import Enum -from math import fsum -from typing import Optional, Tuple, Dict, Union, List - -from qiskit.optimization import infinity, QiskitOptimizationError -from qiskit.optimization.problems.constraint import ConstraintSense -from qiskit.optimization.problems.linear_expression import LinearExpression -from qiskit.optimization.problems.quadratic_expression import QuadraticExpression -from qiskit.optimization.problems.quadratic_program import QuadraticProgram - -logger = logging.getLogger(__name__) - - -class SubstitutionStatus(Enum): - """Status of `QuadraticProgram.substitute_variables`""" - success = 1 - infeasible = 2 - - -class SubstituteVariables: - """A class to substitute variables of an optimization problem with constants for other - variables""" - - CONST = '__CONSTANT__' - - def __init__(self): - self._src: QuadraticProgram = None - self._dst: QuadraticProgram = None - self._subs = {} - - def substitute_variables( - self, src: 'QuadraticProgram', - constants: Optional[Dict[Union[str, int], float]] = None, - variables: Optional[Dict[Union[str, int], Tuple[Union[str, int], float]]] = None) \ - -> Tuple[QuadraticProgram, SubstitutionStatus]: - """Substitutes variables with constants or other variables. - - Args: - constants: replace variable by constant - e.g., {'x': 2} means 'x' is substituted with 2 - - variables: replace variables by weighted other variable - need to copy everything using name reference to make sure that indices are matched - correctly. The lower and upper bounds are updated accordingly. - e.g., {'x': ('y', 2)} means 'x' is substituted with 'y' * 2 - - Returns: - An optimization problem by substituting variables and the status. - If the resulting problem has no issue, the status is `success`. - Otherwise, an empty problem and status `infeasible` are returned. - - Raises: - QiskitOptimizationError: if the substitution is invalid as follows. - - Same variable is substituted multiple times. - - Coefficient of variable substitution is zero. - - Example usage: - >>> - """ - - self._src = src - self._dst = QuadraticProgram(src.name) - # do not set problem type, then it detects its type automatically - - self._subs_dict(constants, variables) - - results = [ - self._variables(), - self._objective(), - self._linear_constraints(), - self._quadratic_constraints(), - ] - if any(r == SubstitutionStatus.infeasible for r in results): - ret = SubstitutionStatus.infeasible - else: - ret = SubstitutionStatus.success - return self._dst, ret - - @staticmethod - def _feasible(sense: ConstraintSense, rhs: float) -> bool: - """Checks feasibility of the following condition - 0 `sense` rhs - """ - # I use the following pylint option because `rhs` should come to right - # pylint: disable=misplaced-comparison-constant - if sense == ConstraintSense.eq: - if 0 == rhs: - return True - elif sense == ConstraintSense.leq: - if 0 <= rhs: - return True - elif sense == ConstraintSense.geq: - if 0 >= rhs: - return True - return False - - @staticmethod - def _replace_dict_keys_with_names(op, dic): - key = [] - val = [] - for k in sorted(dic.keys()): - key.append(op.variables.get_names(k)) - val.append(dic[k]) - return key, val - - def _subs_dict(self, constants, variables): - src = self._src - - # guarantee that there is no overlap between variables to be replaced and combine input - subs: Dict[str, Tuple[str, float]] = {} - if constants is not None: - for i, v in constants.items(): - # substitute i <- v - i_2 = src.get_variable(i).name - if i_2 in subs: - raise QiskitOptimizationError( - 'Cannot substitute the same variable twice: {} <- {}'.format(i, v)) - subs[i_2] = (self.CONST, v) - - if variables is not None: - for i, (j, v) in variables.items(): - if v == 0: - raise QiskitOptimizationError( - 'coefficient must be non-zero: {} {} {}'.format(i, j, v)) - # substitute i <- j * v - i_2 = src.get_variable(i).name - j_2 = src.get_variable(j).name - if i_2 == j_2: - raise QiskitOptimizationError( - 'Cannot substitute the same variable: {} <- {} {}'.format(i, j, v)) - if i_2 in subs: - raise QiskitOptimizationError( - 'Cannot substitute the same variable twice: {} <- {} {}'.format(i, j, v)) - if j_2 in subs: - raise QiskitOptimizationError( - 'Cannot substitute by variable that gets substituted itself: ' - '{} <- {} {}'.format(i, j, v)) - subs[i_2] = (j_2, v) - - self._subs = subs - - def _variables(self) -> SubstitutionStatus: - src = self._src - dst = self._dst - subs = self._subs - - # copy variables that are not replaced - for var in src.variables: - name = var.name - vartype = var.vartype - lowerbound = var.lowerbound - upperbound = var.upperbound - if name not in subs: - dst._add_variable(name, lowerbound, upperbound, vartype) - - for i, (j, v) in subs.items(): - lb_i = src.get_variable(i).lowerbound - ub_i = src.get_variable(i).upperbound - if j == self.CONST: - if not lb_i <= v <= ub_i: - logger.warning( - 'Infeasible substitution for variable: %s', i) - return SubstitutionStatus.infeasible - else: - # substitute i <- j * v - # lb_i <= i <= ub_i --> lb_i / v <= j <= ub_i / v if v > 0 - # ub_i / v <= j <= lb_i / v if v < 0 - if v == 0: - raise QiskitOptimizationError( - 'Coefficient of variable substitution should be nonzero: ' - '{} {} {}'.format(i, j, v)) - if abs(lb_i) < infinity: - new_lb_i = lb_i / v - else: - new_lb_i = lb_i if v > 0 else -lb_i - if abs(ub_i) < infinity: - new_ub_i = ub_i / v - else: - new_ub_i = ub_i if v > 0 else -ub_i - var_j = dst.get_variable(j) - lb_j = var_j.lowerbound - ub_j = var_j.upperbound - if v > 0: - var_j.lowerbound = max(lb_j, new_lb_i) - var_j.upperbound = min(ub_j, new_ub_i) - else: - var_j.lowerbound = max(lb_j, new_ub_i) - var_j.upperbound = min(ub_j, new_lb_i) - - for var in dst.variables: - if var.lowerbound > var.upperbound: - logger.warning( - 'Infeasible lower and upper bound: %s %f %f', var, var.lowerbound, - var.upperbound) - return SubstitutionStatus.infeasible - - return SubstitutionStatus.success - - def _linear_expression(self, lin_expr: LinearExpression) \ - -> Tuple[List[float], LinearExpression]: - subs = self._subs - const = [] - lin_dict = defaultdict(float) - for i, w_i in lin_expr.coefficients_as_dict(use_index=False).items(): - repl_i = subs[i] if i in subs else (i, 1) - prod = w_i * repl_i[1] - if repl_i[0] == self.CONST: - const.append(prod) - else: - k = repl_i[0] - lin_dict[k] += prod - new_lin = LinearExpression(quadratic_program=self._dst, - coefficients=lin_dict if lin_dict else {}) - return const, new_lin - - def _quadratic_expression(self, quad_expr: QuadraticExpression) \ - -> Tuple[List[float], Optional[LinearExpression], Optional[QuadraticExpression]]: - subs = self._subs - const = [] - lin_dict = defaultdict(float) - quad_dict = defaultdict(float) - for (i, j), w_ij in quad_expr.coefficients_as_dict().items(): - repl_i = subs[i] if i in subs else (i, 1) - repl_j = subs[j] if j in subs else (j, 1) - idx = tuple(x for x, _ in [repl_i, repl_j] if x != self.CONST) - prod = w_ij * repl_i[1] * repl_j[1] - if len(idx) == 2: - quad_dict[idx] += prod - elif len(idx) == 1: - lin_dict[idx[0]] += prod - else: - const.append(prod) - new_lin = LinearExpression(quadratic_program=self._dst, - coefficients=lin_dict if lin_dict else {}) - new_quad = QuadraticExpression(quadratic_program=self._dst, - coefficients=quad_dict if quad_dict else {}) - return const, new_lin, new_quad - - def _objective(self) -> SubstitutionStatus: - src = self._src - dst = self._dst - - const1, lin1 = self._linear_expression(src.objective.linear) - const2, lin2, quadratic = self._quadratic_expression(src.objective.quadratic) - - constant = fsum([src.objective.constant] + const1 + const2) - linear = lin1.coefficients + lin2.coefficients - if src.objective.sense == src.objective.sense.minimize: - dst.minimize(constant=constant, linear=linear, quadratic=quadratic.coefficients) - else: - dst.maximize(constant=constant, linear=linear, quadratic=quadratic.coefficients) - return SubstitutionStatus.success - - def _linear_constraints(self) -> SubstitutionStatus: - src = self._src - dst = self._dst - - for lin_cst in src.linear_constraints: - constant, linear = self._linear_expression(lin_cst.linear) - rhs = lin_cst.rhs - fsum(constant) - if linear.coefficients.nnz > 0: - dst.linear_constraint(name=lin_cst.name, coefficients=linear.coefficients, - sense=lin_cst.sense, rhs=rhs) - else: - if not self._feasible(lin_cst.sense, rhs): - logger.warning('constraint %s is infeasible due to substitution', lin_cst.name) - return SubstitutionStatus.infeasible - - return SubstitutionStatus.success - - def _quadratic_constraints(self) -> SubstitutionStatus: - src = self._src - dst = self._dst - - for quad_cst in src.quadratic_constraints: - const1, lin1 = self._linear_expression(quad_cst.linear) - const2, lin2, quadratic = self._quadratic_expression(quad_cst.quadratic) - rhs = quad_cst.rhs - fsum(const1 + const2) - linear = lin1.coefficients + lin2.coefficients - - if quadratic.coefficients.nnz > 0: - dst.quadratic_constraint(name=quad_cst.name, linear_coefficients=linear, - quadratic_coefficients=quadratic.coefficients, - sense=quad_cst.sense, rhs=rhs) - elif linear.nnz > 0: - name = quad_cst.name - lin_names = set(lin.name for lin in dst.linear_constraints) - while name in lin_names: - name = '_' + name - dst.linear_constraint(name=name, coefficients=linear, sense=quad_cst.sense, rhs=rhs) - else: - if not self._feasible(quad_cst.sense, rhs): - logger.warning('constraint %s is infeasible due to substitution', quad_cst.name) - return SubstitutionStatus.infeasible - - return SubstitutionStatus.success diff --git a/test/optimization/test_linear_constraint.py b/test/optimization/test_linear_constraint.py index 94ec4164af..c4268894a5 100644 --- a/test/optimization/test_linear_constraint.py +++ b/test/optimization/test_linear_constraint.py @@ -53,7 +53,7 @@ def test_init(self): with self.assertRaises(QiskitOptimizationError): quadratic_program.linear_constraint(name='c0') - quadratic_program.linear_constraint('c1', coefficients, '==', 1.0) + quadratic_program.linear_constraint(coefficients, '==', 1.0, 'c1') self.assertEqual(quadratic_program.get_num_linear_constraints(), 2) self.assertEqual(quadratic_program.linear_constraints[1].name, 'c1') self.assertTrue(( @@ -83,7 +83,7 @@ def test_init(self): with self.assertRaises(QiskitOptimizationError): quadratic_program.linear_constraint(name='c2', sense='>=') - quadratic_program.linear_constraint('c3', coefficients, '>=', 1.0) + quadratic_program.linear_constraint(coefficients, '>=', 1.0, 'c3') self.assertEqual(quadratic_program.get_num_linear_constraints(), 4) self.assertEqual(quadratic_program.linear_constraints[3].name, 'c3') self.assertTrue(( @@ -113,7 +113,7 @@ def test_init(self): with self.assertRaises(QiskitOptimizationError): quadratic_program.linear_constraint(name='c4', sense='<=') - quadratic_program.linear_constraint('c5', coefficients, '<=', 1.0) + quadratic_program.linear_constraint(coefficients, '<=', 1.0, 'c5') self.assertEqual(quadratic_program.get_num_linear_constraints(), 6) self.assertEqual(quadratic_program.linear_constraints[5].name, 'c5') self.assertTrue(( diff --git a/test/optimization/test_quadratic_constraint.py b/test/optimization/test_quadratic_constraint.py index b5995dd346..6df896430f 100644 --- a/test/optimization/test_quadratic_constraint.py +++ b/test/optimization/test_quadratic_constraint.py @@ -59,7 +59,7 @@ def test_init(self): with self.assertRaises(QiskitOptimizationError): quadratic_program.quadratic_constraint(name='q0', sense='==') - quadratic_program.quadratic_constraint('q1', linear_coeffs, quadratic_coeffs, '==', 1.0) + quadratic_program.quadratic_constraint(linear_coeffs, quadratic_coeffs, '==', 1.0, 'q1') self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 2) self.assertEqual(quadratic_program.quadratic_constraints[1].name, 'q1') self.assertTrue(( @@ -99,7 +99,7 @@ def test_init(self): with self.assertRaises(QiskitOptimizationError): quadratic_program.quadratic_constraint(name='q2', sense='>=') - quadratic_program.quadratic_constraint('q3', linear_coeffs, quadratic_coeffs, '>=', 1.0) + quadratic_program.quadratic_constraint(linear_coeffs, quadratic_coeffs, '>=', 1.0, 'q3') self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 4) self.assertEqual(quadratic_program.quadratic_constraints[3].name, 'q3') self.assertTrue(( @@ -137,7 +137,7 @@ def test_init(self): with self.assertRaises(QiskitOptimizationError): quadratic_program.quadratic_constraint(name='q4', sense='<=') - quadratic_program.quadratic_constraint('q5', linear_coeffs, quadratic_coeffs, '<=', 1.0) + quadratic_program.quadratic_constraint(linear_coeffs, quadratic_coeffs, '<=', 1.0, 'q5') self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 6) self.assertEqual(quadratic_program.quadratic_constraints[5].name, 'q5') self.assertTrue(( From 1ad5487eb2d3cac14da44e742daf05fe544accff Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Wed, 15 Apr 2020 15:10:18 +0100 Subject: [PATCH 204/323] moving to a new optimization stack --- .../optimization/algorithms/admm_optimizer.py | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index d7acffc63c..7b144d0325 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -21,7 +21,7 @@ from qiskit.optimization.algorithms.cplex_optimizer import CplexOptimizer from qiskit.optimization.algorithms.optimization_algorithm import (OptimizationAlgorithm, OptimizationResult) -from qiskit.optimization.problems import VarType +from qiskit.optimization.problems import VarType, ConstraintSense from qiskit.optimization.problems.quadratic_program import QuadraticProgram from qiskit.optimization.problems.variables import CPX_BINARY, CPX_CONTINUOUS @@ -474,7 +474,7 @@ def _assign_row_values(self, matrix: List[List[float]], vector: List[float], vector.append(self._state.op.linear_constraints[constraint_index].rhs) # flip the sign if constraint is G, we want L constraints. - if self._state.op.linear_constraints[constraint_index].sense == "G": + if self._state.op.linear_constraints[constraint_index].sense == ConstraintSense.geq: # invert the sign to make constraint "L". matrix[-1] = [-1 * el for el in matrix[-1]] vector[-1] = -1 * vector[-1] @@ -511,21 +511,23 @@ def _get_a0_b0(self) -> (np.ndarray, np.ndarray): matrix = [] vector = [] - senses = self._state.op.linear_constraints.get_senses() index_set = set(self._state.binary_indices) - for constraint_index, sense in enumerate(senses): + for constraint_index, constraint in enumerate(self._state.op.linear_constraints): # we check only equality constraints here. - if sense != "E": + if constraint.sense != ConstraintSense.eq: continue - row = self._state.op.linear_constraints.get_rows(constraint_index) - if set(row.ind).issubset(index_set): - self._assign_row_values(matrix, vector, - constraint_index, self._state.binary_indices) - else: - raise ValueError( - "Linear constraint with the 'E' sense must contain only binary variables, " - "row indices: {}, binary variable indices: {}".format( - row, self._state.binary_indices)) + # row = self._state.op.linear_constraints.get_rows(constraint_index) + row = self._state.op.linear_constraints[constraint_index].linear.coefficients_as_array().take(self._state.binary_indices) + self._assign_row_values(matrix, vector, + constraint_index, self._state.binary_indices) + # if set(row.ind).issubset(index_set): + # self._assign_row_values(matrix, vector, + # constraint_index, self._state.binary_indices) + # else: + # raise ValueError( + # "Linear constraint with the 'E' sense must contain only binary variables, " + # "row indices: {}, binary variable indices: {}".format( + # row, self._state.binary_indices)) return self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) From 4869dca07a9bbce8ffee8dcac20c46917d3b337f Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 15 Apr 2020 16:10:44 +0200 Subject: [PATCH 205/323] rename "coefficients_as_dict/array" to "to_dict/array" in linear/quadratic expressions --- .../problems/linear_expression.py | 4 +-- .../problems/quadratic_expression.py | 4 +-- .../problems/quadratic_program.py | 30 +++++++++---------- test/optimization/test_linear_constraint.py | 12 ++++---- test/optimization/test_linear_expression.py | 12 ++++---- .../optimization/test_quadratic_constraint.py | 22 +++++++------- .../optimization/test_quadratic_expression.py | 12 ++++---- test/optimization/test_quadratic_objective.py | 16 +++++----- 8 files changed, 56 insertions(+), 56 deletions(-) diff --git a/qiskit/optimization/problems/linear_expression.py b/qiskit/optimization/problems/linear_expression.py index 5e2c14b175..bccfa0f212 100644 --- a/qiskit/optimization/problems/linear_expression.py +++ b/qiskit/optimization/problems/linear_expression.py @@ -108,7 +108,7 @@ def coefficients(self, """ self._coefficients = self._coeffs_to_dok_matrix(coefficients) - def coefficients_as_array(self) -> ndarray: + def to_array(self) -> ndarray: """Returns the coefficients of the linear expression as array. Returns: @@ -116,7 +116,7 @@ def coefficients_as_array(self) -> ndarray: """ return self._coefficients.toarray()[0] - def coefficients_as_dict(self, use_index: bool = True) -> Dict[Union[int, str], float]: + def to_dict(self, use_index: bool = True) -> Dict[Union[int, str], float]: """Returns the coefficients of the linear expression as dictionary, either using variable names or indices as keys. diff --git a/qiskit/optimization/problems/quadratic_expression.py b/qiskit/optimization/problems/quadratic_expression.py index f9e3e45686..459c7ca9d1 100644 --- a/qiskit/optimization/problems/quadratic_expression.py +++ b/qiskit/optimization/problems/quadratic_expression.py @@ -113,7 +113,7 @@ def coefficients(self, """ self._coefficients = self._coeffs_to_dok_matrix(coefficients) - def coefficients_as_array(self) -> ndarray: + def to_array(self) -> ndarray: """Returns the coefficients of the quadratic expression as array. Returns: @@ -121,7 +121,7 @@ def coefficients_as_array(self) -> ndarray: """ return self._coefficients.toarray() - def coefficients_as_dict(self, use_index: bool = True + def to_dict(self, use_index: bool = True ) -> Dict[Union[Tuple[int, int], Tuple[str, str]], float]: """Returns the coefficients of the quadratic expression as dictionary, either using tuples of variable names or indices as keys. diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index d373925ab4..63b055f4ea 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -257,16 +257,16 @@ def linear_constraints_index(self) -> Dict[str, int]: return self._linear_constraints_index def linear_constraint(self, name: Optional[str] = None, - coefficients: Union[ndarray, spmatrix, List[float], + linear: Union[ndarray, spmatrix, List[float], Dict[Union[int, str], float]] = None, sense: Union[str, ConstraintSense] = '<=', rhs: float = 0.0) -> LinearConstraint: """Adds a linear equality constraint to the quadratic program of the form: - linear_coeffs * x sense rhs. + linear * x sense rhs. Args: name: The name of the constraint. - coefficients: The linear coefficients of the left-hand-side of the constraint. + linear: The linear coefficients of the left-hand-side of the constraint. sense: The sense of the constraint, - '==', '=', 'E', and 'EQ' denote 'equal to'. - '>=', '>', 'G', and 'GE' denote 'greater-than-or-equal-to'. @@ -290,9 +290,9 @@ def linear_constraint(self, name: Optional[str] = None, k += 1 name = 'c{}'.format(k) self.linear_constraints_index[name] = len(self.linear_constraints) - if coefficients is None: - coefficients = {} - constraint = LinearConstraint(self, name, coefficients, ConstraintSense.convert(sense), rhs) + if linear is None: + linear = {} + constraint = LinearConstraint(self, name, linear, ConstraintSense.convert(sense), rhs) self.linear_constraints.append(constraint) return constraint @@ -337,9 +337,9 @@ def quadratic_constraints_index(self) -> Dict[str, int]: return self._quadratic_constraints_index def quadratic_constraint(self, name: Optional[str] = None, - linear_coefficients: Union[ndarray, spmatrix, List[float], + linear: Union[ndarray, spmatrix, List[float], Dict[Union[int, str], float]] = None, - quadratic_coefficients: Union[ndarray, spmatrix, + quadratic: Union[ndarray, spmatrix, List[List[float]], Dict[ Tuple[Union[int, str], @@ -352,8 +352,8 @@ def quadratic_constraint(self, name: Optional[str] = None, Args: name: The name of the constraint. - linear_coefficients: The linear coefficients of the constraint. - quadratic_coefficients: The quadratic coefficients of the constraint. + linear: The linear coefficients of the constraint. + quadratic: The quadratic coefficients of the constraint. sense: The sense of the constraint, - '==', '=', 'E', and 'EQ' denote 'equal to'. - '>=', '>', 'G', and 'GE' denote 'greater-than-or-equal-to'. @@ -376,11 +376,11 @@ def quadratic_constraint(self, name: Optional[str] = None, k += 1 name = 'q{}'.format(k) self.quadratic_constraints_index[name] = len(self.quadratic_constraints) - if linear_coefficients is None: - linear_coefficients = {} - if quadratic_coefficients is None: - quadratic_coefficients = {} - constraint = QuadraticConstraint(self, name, linear_coefficients, quadratic_coefficients, + if linear is None: + linear = {} + if quadratic is None: + quadratic = {} + constraint = QuadraticConstraint(self, name, linear, quadratic, ConstraintSense.convert(sense), rhs) self.quadratic_constraints.append(constraint) return constraint diff --git a/test/optimization/test_linear_constraint.py b/test/optimization/test_linear_constraint.py index 94ec4164af..c8a0654f7d 100644 --- a/test/optimization/test_linear_constraint.py +++ b/test/optimization/test_linear_constraint.py @@ -41,7 +41,7 @@ def test_init(self): quadratic_program.linear_constraint(sense='==') self.assertEqual(quadratic_program.get_num_linear_constraints(), 1) self.assertEqual(quadratic_program.linear_constraints[0].name, 'c0') - self.assertEqual(len(quadratic_program.linear_constraints[0].linear.coefficients_as_dict()), + self.assertEqual(len(quadratic_program.linear_constraints[0].linear.to_dict()), 0) self.assertEqual(quadratic_program.linear_constraints[0].sense, ConstraintSense.EQ) self.assertEqual(quadratic_program.linear_constraints[0].rhs, 0.0) @@ -57,7 +57,7 @@ def test_init(self): self.assertEqual(quadratic_program.get_num_linear_constraints(), 2) self.assertEqual(quadratic_program.linear_constraints[1].name, 'c1') self.assertTrue(( - quadratic_program.linear_constraints[1].linear.coefficients_as_array( + quadratic_program.linear_constraints[1].linear.to_array( ) == coefficients ).all()) self.assertEqual(quadratic_program.linear_constraints[1].sense, ConstraintSense.EQ) @@ -71,7 +71,7 @@ def test_init(self): quadratic_program.linear_constraint(sense='>=') self.assertEqual(quadratic_program.get_num_linear_constraints(), 3) self.assertEqual(quadratic_program.linear_constraints[2].name, 'c2') - self.assertEqual(len(quadratic_program.linear_constraints[2].linear.coefficients_as_dict()), + self.assertEqual(len(quadratic_program.linear_constraints[2].linear.to_dict()), 0) self.assertEqual(quadratic_program.linear_constraints[2].sense, ConstraintSense.GE) self.assertEqual(quadratic_program.linear_constraints[2].rhs, 0.0) @@ -87,7 +87,7 @@ def test_init(self): self.assertEqual(quadratic_program.get_num_linear_constraints(), 4) self.assertEqual(quadratic_program.linear_constraints[3].name, 'c3') self.assertTrue(( - quadratic_program.linear_constraints[3].linear.coefficients_as_array( + quadratic_program.linear_constraints[3].linear.to_array( ) == coefficients ).all()) self.assertEqual(quadratic_program.linear_constraints[3].sense, ConstraintSense.GE) @@ -101,7 +101,7 @@ def test_init(self): quadratic_program.linear_constraint(sense='<=') self.assertEqual(quadratic_program.get_num_linear_constraints(), 5) self.assertEqual(quadratic_program.linear_constraints[4].name, 'c4') - self.assertEqual(len(quadratic_program.linear_constraints[4].linear.coefficients_as_dict()), + self.assertEqual(len(quadratic_program.linear_constraints[4].linear.to_dict()), 0) self.assertEqual(quadratic_program.linear_constraints[4].sense, ConstraintSense.LE) self.assertEqual(quadratic_program.linear_constraints[4].rhs, 0.0) @@ -117,7 +117,7 @@ def test_init(self): self.assertEqual(quadratic_program.get_num_linear_constraints(), 6) self.assertEqual(quadratic_program.linear_constraints[5].name, 'c5') self.assertTrue(( - quadratic_program.linear_constraints[5].linear.coefficients_as_array( + quadratic_program.linear_constraints[5].linear.to_array( ) == coefficients ).all()) self.assertEqual(quadratic_program.linear_constraints[5].sense, ConstraintSense.LE) diff --git a/test/optimization/test_linear_expression.py b/test/optimization/test_linear_expression.py index d6a3870934..212ea57eec 100644 --- a/test/optimization/test_linear_expression.py +++ b/test/optimization/test_linear_expression.py @@ -50,9 +50,9 @@ def test_init(self): linear = LinearExpression(quadratic_program, coeffs) self.assertEqual((linear.coefficients != coefficients_dok).nnz, 0) - self.assertTrue((linear.coefficients_as_array() == coefficients_list).all()) - self.assertDictEqual(linear.coefficients_as_dict(use_index=True), coefficients_dict_int) - self.assertDictEqual(linear.coefficients_as_dict(use_index=False), + self.assertTrue((linear.to_array() == coefficients_list).all()) + self.assertDictEqual(linear.to_dict(use_index=True), coefficients_dict_int) + self.assertDictEqual(linear.to_dict(use_index=False), coefficients_dict_str) def test_get_item(self): @@ -91,9 +91,9 @@ def test_setters(self): linear.coefficients = coeffs self.assertEqual((linear.coefficients != coefficients_dok).nnz, 0) - self.assertTrue((linear.coefficients_as_array() == coefficients_list).all()) - self.assertDictEqual(linear.coefficients_as_dict(use_index=True), coefficients_dict_int) - self.assertDictEqual(linear.coefficients_as_dict(use_index=False), + self.assertTrue((linear.to_array() == coefficients_list).all()) + self.assertDictEqual(linear.to_dict(use_index=True), coefficients_dict_int) + self.assertDictEqual(linear.to_dict(use_index=False), coefficients_dict_str) def test_evaluate(self): diff --git a/test/optimization/test_quadratic_constraint.py b/test/optimization/test_quadratic_constraint.py index b5995dd346..1441d70bb1 100644 --- a/test/optimization/test_quadratic_constraint.py +++ b/test/optimization/test_quadratic_constraint.py @@ -44,9 +44,9 @@ def test_init(self): self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 1) self.assertEqual(quadratic_program.quadratic_constraints[0].name, 'q0') self.assertEqual( - len(quadratic_program.quadratic_constraints[0].linear.coefficients_as_dict()), 0) + len(quadratic_program.quadratic_constraints[0].linear.to_dict()), 0) self.assertEqual( - len(quadratic_program.quadratic_constraints[0].quadratic.coefficients_as_dict()), 0) + len(quadratic_program.quadratic_constraints[0].quadratic.to_dict()), 0) self.assertEqual(quadratic_program.quadratic_constraints[0].sense, ConstraintSense.EQ) self.assertEqual(quadratic_program.quadratic_constraints[0].rhs, 0.0) self.assertEqual(quadratic_program.quadratic_constraints[0], @@ -63,11 +63,11 @@ def test_init(self): self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 2) self.assertEqual(quadratic_program.quadratic_constraints[1].name, 'q1') self.assertTrue(( - quadratic_program.quadratic_constraints[1].linear.coefficients_as_array( + quadratic_program.quadratic_constraints[1].linear.to_array( ) == linear_coeffs ).all()) self.assertTrue(( - quadratic_program.quadratic_constraints[1].quadratic.coefficients_as_array( + quadratic_program.quadratic_constraints[1].quadratic.to_array( ) == quadratic_coeffs ).all()) self.assertEqual(quadratic_program.quadratic_constraints[1].sense, ConstraintSense.EQ) @@ -84,9 +84,9 @@ def test_init(self): self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 3) self.assertEqual(quadratic_program.quadratic_constraints[2].name, 'q2') self.assertEqual( - len(quadratic_program.quadratic_constraints[2].linear.coefficients_as_dict()), 0) + len(quadratic_program.quadratic_constraints[2].linear.to_dict()), 0) self.assertEqual( - len(quadratic_program.quadratic_constraints[2].quadratic.coefficients_as_dict()), 0) + len(quadratic_program.quadratic_constraints[2].quadratic.to_dict()), 0) self.assertEqual(quadratic_program.quadratic_constraints[2].sense, ConstraintSense.GE) self.assertEqual(quadratic_program.quadratic_constraints[2].rhs, 0.0) self.assertEqual(quadratic_program.quadratic_constraints[2], @@ -103,11 +103,11 @@ def test_init(self): self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 4) self.assertEqual(quadratic_program.quadratic_constraints[3].name, 'q3') self.assertTrue(( - quadratic_program.quadratic_constraints[3].linear.coefficients_as_array( + quadratic_program.quadratic_constraints[3].linear.to_array( ) == linear_coeffs ).all()) self.assertTrue(( - quadratic_program.quadratic_constraints[3].quadratic.coefficients_as_array( + quadratic_program.quadratic_constraints[3].quadratic.to_array( ) == quadratic_coeffs ).all()) self.assertEqual(quadratic_program.quadratic_constraints[3].sense, ConstraintSense.GE) @@ -124,7 +124,7 @@ def test_init(self): self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 5) self.assertEqual(quadratic_program.quadratic_constraints[4].name, 'q4') self.assertEqual( - len(quadratic_program.quadratic_constraints[4].linear.coefficients_as_dict()), 0) + len(quadratic_program.quadratic_constraints[4].linear.to_dict()), 0) self.assertEqual(quadratic_program.quadratic_constraints[4].sense, ConstraintSense.LE) self.assertEqual(quadratic_program.quadratic_constraints[4].rhs, 0.0) self.assertEqual(quadratic_program.quadratic_constraints[4], @@ -141,11 +141,11 @@ def test_init(self): self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 6) self.assertEqual(quadratic_program.quadratic_constraints[5].name, 'q5') self.assertTrue(( - quadratic_program.quadratic_constraints[5].linear.coefficients_as_array( + quadratic_program.quadratic_constraints[5].linear.to_array( ) == linear_coeffs ).all()) self.assertTrue(( - quadratic_program.quadratic_constraints[5].quadratic.coefficients_as_array( + quadratic_program.quadratic_constraints[5].quadratic.to_array( ) == quadratic_coeffs ).all()) self.assertEqual(quadratic_program.quadratic_constraints[5].sense, ConstraintSense.LE) diff --git a/test/optimization/test_quadratic_expression.py b/test/optimization/test_quadratic_expression.py index fcd3f031c3..242dcb1383 100644 --- a/test/optimization/test_quadratic_expression.py +++ b/test/optimization/test_quadratic_expression.py @@ -51,10 +51,10 @@ def test_init(self): quadratic = QuadraticExpression(quadratic_program, coeffs) self.assertEqual((quadratic.coefficients != coefficients_dok).nnz, 0) - self.assertTrue((quadratic.coefficients_as_array() == coefficients_list).all()) - self.assertDictEqual(quadratic.coefficients_as_dict( + self.assertTrue((quadratic.to_array() == coefficients_list).all()) + self.assertDictEqual(quadratic.to_dict( use_index=True), coefficients_dict_int) - self.assertDictEqual(quadratic.coefficients_as_dict(use_index=False), + self.assertDictEqual(quadratic.to_dict(use_index=False), coefficients_dict_str) def test_get_item(self): @@ -96,10 +96,10 @@ def test_setters(self): quadratic.coefficients = coeffs self.assertEqual((quadratic.coefficients != coefficients_dok).nnz, 0) - self.assertTrue((quadratic.coefficients_as_array() == coefficients_list).all()) - self.assertDictEqual(quadratic.coefficients_as_dict( + self.assertTrue((quadratic.to_array() == coefficients_list).all()) + self.assertDictEqual(quadratic.to_dict( use_index=True), coefficients_dict_int) - self.assertDictEqual(quadratic.coefficients_as_dict(use_index=False), + self.assertDictEqual(quadratic.to_dict(use_index=False), coefficients_dict_str) def test_evaluate(self): diff --git a/test/optimization/test_quadratic_objective.py b/test/optimization/test_quadratic_objective.py index dcdd10642b..1b8e2c16fa 100644 --- a/test/optimization/test_quadratic_objective.py +++ b/test/optimization/test_quadratic_objective.py @@ -37,9 +37,9 @@ def test_init(self): self.assertEqual(quadratic_program.objective.constant, 0.0) self.assertEqual( - len(quadratic_program.objective.linear.coefficients_as_dict()), 0) + len(quadratic_program.objective.linear.to_dict()), 0) self.assertEqual( - len(quadratic_program.objective.quadratic.coefficients_as_dict()), 0) + len(quadratic_program.objective.quadratic.to_dict()), 0) self.assertEqual(quadratic_program.objective.sense, ObjSense.MINIMIZE) constant = 1.0 @@ -50,9 +50,9 @@ def test_init(self): self.assertEqual(quadratic_program.objective.constant, constant) self.assertTrue( - (quadratic_program.objective.linear.coefficients_as_array() == linear_coeffs).all()) + (quadratic_program.objective.linear.to_array() == linear_coeffs).all()) self.assertTrue( - (quadratic_program.objective.quadratic.coefficients_as_array() == quadratic_coeffs) + (quadratic_program.objective.quadratic.to_array() == quadratic_coeffs) .all()) self.assertEqual(quadratic_program.objective.sense, ObjSense.MINIMIZE) @@ -60,9 +60,9 @@ def test_init(self): self.assertEqual(quadratic_program.objective.constant, constant) self.assertTrue( - (quadratic_program.objective.linear.coefficients_as_array() == linear_coeffs).all()) + (quadratic_program.objective.linear.to_array() == linear_coeffs).all()) self.assertTrue( - (quadratic_program.objective.quadratic.coefficients_as_array() == quadratic_coeffs) + (quadratic_program.objective.quadratic.to_array() == quadratic_coeffs) .all()) self.assertEqual(quadratic_program.objective.sense, ObjSense.MAXIMIZE) @@ -85,9 +85,9 @@ def test_setters(self): self.assertEqual(quadratic_program.objective.constant, constant) self.assertTrue( - (quadratic_program.objective.linear.coefficients_as_array() == linear_coeffs).all()) + (quadratic_program.objective.linear.to_array() == linear_coeffs).all()) self.assertTrue( - (quadratic_program.objective.quadratic.coefficients_as_array() == quadratic_coeffs) + (quadratic_program.objective.quadratic.to_array() == quadratic_coeffs) .all()) self.assertEqual(quadratic_program.objective.sense, ObjSense.MINIMIZE) From c7b7e763b2d9b12f01544964cfa254afe8f23f35 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 15 Apr 2020 16:41:35 +0200 Subject: [PATCH 206/323] add read/write functionality to QP --- .../algorithms/cobyla_optimizer.py | 113 +++++------------- .../problems/quadratic_program.py | 34 ++++-- test/optimization/test_cobyla_optimizer.py | 53 +++----- test/optimization/test_variable.py | 3 +- 4 files changed, 76 insertions(+), 127 deletions(-) diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index cb824f30d9..7f1b7315aa 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -21,9 +21,8 @@ from scipy.optimize import fmin_cobyla from qiskit.optimization.algorithms import OptimizationAlgorithm, OptimizationResult -from qiskit.optimization.problems import QuadraticProgram -from qiskit.optimization import QiskitOptimizationError -from qiskit.optimization import infinity +from qiskit.optimization.problems import QuadraticProgram, ConstraintSense +from qiskit.optimization import QiskitOptimizationError, infinity class CobylaOptimizer(OptimizationAlgorithm): @@ -79,7 +78,7 @@ def get_compatibility_msg(self, problem: QuadraticProgram) -> str: Returns a string describing the incompatibility. """ # check whether there are variables of type other than continuous - if problem.variables.get_num() > problem.variables.get_num_continuous(): + if len(problem.variables) > problem.get_num_continuous_vars(): return 'The COBYLA optimizer supports only continuous variables' return '' @@ -103,100 +102,46 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: if len(msg) > 0: raise QiskitOptimizationError('Incompatible problem: {}'.format(msg)) - # get number of variables - num_vars = problem.variables.get_num() - - # construct objective function from linear and quadratic part of objective - offset = problem.objective.get_offset() - linear_dict = problem.objective.get_linear_dict() - quadratic_dict = problem.objective.get_quadratic_dict() - linear = np.zeros(num_vars) - quadratic = np.zeros((num_vars, num_vars)) - for i, v in linear_dict.items(): - linear[i] = v - for (i, j), v in quadratic_dict.items(): - quadratic[i, j] = v - + # construct quadratic objective function def objective(x): - value = problem.objective.get_sense() * ( - np.dot(linear, x) + np.dot(x, np.dot(quadratic, x)) / 2 + offset - ) - return value + return problem.objective.sense.value * problem.objective.evaluate(x) - # initialize constraints + # initialize constraints list constraints = [] - # add variable lower and upper bounds - lbs = problem.variables.get_lower_bounds() - ubs = problem.variables.get_upper_bounds() - # pylint: disable=invalid-sequence-index - for i in range(num_vars): - if lbs[i] > -infinity: - constraints += [lambda x, lbs=lbs, i=i: x - lbs[i]] - if ubs[i] < infinity: - constraints += [lambda x, lbs=lbs, i=i: ubs[i] - x] - - # add linear constraints - for i in range(problem.linear_constraints.get_num()): - rhs = problem.linear_constraints.get_rhs(i) - sense = problem.linear_constraints.get_senses(i) - row = problem.linear_constraints.get_rows(i) - row_array = np.zeros(num_vars) - for j, v in zip(row.ind, row.val): - row_array[j] = v - - if sense == 'E': - constraints += [ - lambda x, rhs=rhs, row_array=row_array: rhs - np.dot(x, row_array), - lambda x, rhs=rhs, row_array=row_array: np.dot(x, row_array) - rhs - ] - elif sense == 'L': - constraints += [lambda x, rhs=rhs, row_array=row_array: rhs - np.dot(x, row_array)] - elif sense == 'G': - constraints += [lambda x, rhs=rhs, row_array=row_array: np.dot(x, row_array) - rhs] - else: - # TODO: add range constraints - raise QiskitOptimizationError('Unsupported constraint type!') - - # add quadratic constraints - for i in range(problem.quadratic_constraints.get_num()): - rhs = problem.quadratic_constraints.get_rhs(i) - sense = problem.quadratic_constraints.get_senses(i) - - linear_comp = problem.quadratic_constraints.get_linear_components(i) - quadratic_comp = problem.quadratic_constraints.get_quadratic_components(i) - - linear_array = np.zeros(num_vars) - for j, v in zip(linear_comp.ind, linear_comp.val): - linear_array[j] = v - - quadratic_array = np.zeros((num_vars, num_vars)) - for j, k, v in zip(quadratic_comp.ind1, quadratic_comp.ind2, quadratic_comp.val): - quadratic_array[j, k] = v - - def lhs(x, linear_array=linear_array, quadratic_array=quadratic_array): - return np.dot(x, linear_array) + np.dot(np.dot(x, quadratic_array), x) - - if sense == 'E': + # add lower/upper bound constraints + for variable in problem.variables: + lowerbound = variable.lowerbound + upperbound = variable.upperbound + if lowerbound > -infinity: + constraints += [lambda x, lb=lowerbound: x - lb] + if upperbound < infinity: + constraints += [lambda x, ub=upperbound: ub - x] + + # add linear and quadratic constraints + for constraint in problem.linear_constraints + problem.quadratic_constraints: + rhs = constraint.rhs + sense = constraint.sense + + if sense == ConstraintSense.EQ: constraints += [ - lambda x: rhs - lhs(x), - lambda x: lhs(x) - rhs + lambda x, rhs=rhs, c=constraint: rhs - c.evaluate(x), + lambda x, rhs=rhs, c=constraint: c.evaluate(x) - rhs ] - elif sense == 'L': - constraints += [lambda x: rhs - lhs(x)] - elif sense == 'G': - constraints += [lambda x: lhs(x) - rhs] + elif sense == ConstraintSense.LE: + constraints += [lambda x, rhs=rhs, c=constraint: rhs - c.evaluate(x)] + elif sense == ConstraintSense.GE: + constraints += [lambda x, rhs=rhs, c=constraint: c.evaluate(x) - rhs] else: - # TODO: add range constraints raise QiskitOptimizationError('Unsupported constraint type!') # TODO: derive x_0 from lower/upper bounds - x_0 = np.zeros(problem.variables.get_num()) + x_0 = np.zeros(len(problem.variables)) # run optimization x = fmin_cobyla(objective, x_0, constraints, rhobeg=self._rhobeg, rhoend=self._rhoend, maxfun=self._maxfun, disp=self._disp, catol=self._catol) - fval = problem.objective.get_sense() * objective(x) + fval = problem.objective.sense.value * objective(x) # return results return OptimizationResult(x, fval, x) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 63b055f4ea..69f4c66d38 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -15,9 +15,11 @@ """Quadratic Program.""" from typing import List, Union, Dict, Optional, Tuple +from os.path import splitext from docplex.mp.linear import Var from docplex.mp.model import Model +from docplex.mp.model_reader import ModelReader from numpy import ndarray from scipy.sparse import spmatrix @@ -258,7 +260,7 @@ def linear_constraints_index(self) -> Dict[str, int]: def linear_constraint(self, name: Optional[str] = None, linear: Union[ndarray, spmatrix, List[float], - Dict[Union[int, str], float]] = None, + Dict[Union[int, str], float]] = None, sense: Union[str, ConstraintSense] = '<=', rhs: float = 0.0) -> LinearConstraint: """Adds a linear equality constraint to the quadratic program of the form: @@ -338,13 +340,13 @@ def quadratic_constraints_index(self) -> Dict[str, int]: def quadratic_constraint(self, name: Optional[str] = None, linear: Union[ndarray, spmatrix, List[float], - Dict[Union[int, str], float]] = None, + Dict[Union[int, str], float]] = None, quadratic: Union[ndarray, spmatrix, - List[List[float]], - Dict[ - Tuple[Union[int, str], - Union[int, str]], - float]] = None, + List[List[float]], + Dict[ + Tuple[Union[int, str], + Union[int, str]], + float]] = None, sense: Union[str, ConstraintSense] = '<=', rhs: float = 0.0) -> QuadraticConstraint: """Adds a quadratic equality constraint to the quadratic program of the form: @@ -677,3 +679,21 @@ def print_as_lp_string(self) -> str: A string representing the quadratic program. """ return self.to_docplex().export_as_lp_string() + + def load_from_file(self, filename: str) -> None: + """Loads the quadratic program from a LP file. + + Args: + filename: The filename of the file to be loaded. + """ + model_reader = ModelReader() + model = model_reader.read(filename) + self.from_docplex(model) + + def write_to_file(self, filename: str) -> None: + """Writes the quadratic program to an LP file. + + Args: + filename: The filename of the file the model is written to. + """ + self.to_docplex().export_as_lp(filename) \ No newline at end of file diff --git a/test/optimization/test_cobyla_optimizer.py b/test/optimization/test_cobyla_optimizer.py index 8e94c0a26f..6de49193d8 100644 --- a/test/optimization/test_cobyla_optimizer.py +++ b/test/optimization/test_cobyla_optimizer.py @@ -17,66 +17,49 @@ import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase import logging -from ddt import ddt, data from qiskit.optimization.algorithms import CobylaOptimizer from qiskit.optimization.problems import QuadraticProgram logger = logging.getLogger(__name__) -_HAS_CPLEX = False -try: - from cplex import SparsePair, SparseTriple - _HAS_CPLEX = True -except ImportError: - logger.info('CPLEX is not installed.') - -@ddt class TestCobylaOptimizer(QiskitOptimizationTestCase): """Cobyla Optimizer Tests.""" - def setUp(self): - super().setUp() - if not _HAS_CPLEX: - self.skipTest('CPLEX is not installed.') - - self.resource_path = './test/optimization/resources/' - self.cobyla_optimizer = CobylaOptimizer() - - @data( - ('op_lp1.lp', 5.8750) - ) - def test_cobyla_optimizer(self, config): - """ Cobyla Optimizer Test """ - - # unpack configuration - filename, fval = config + def test_cobyla_optimizer(self): + """ Cobyla Optimizer Test. """ # load optimization problem problem = QuadraticProgram() - problem.read(self.resource_path + filename) + problem.continuous_var(upperbound=4) + problem.continuous_var(upperbound=4) + problem.linear_constraint(linear=[1, 1], sense='=', rhs=2) + problem.minimize(linear=[2, 2], quadratic=[[2, 0.25], [0.25, 0.5]]) # solve problem with cobyla - result = self.cobyla_optimizer.solve(problem) + cobyla = CobylaOptimizer() + result = cobyla.solve(problem) # analyze results - self.assertAlmostEqual(result.fval, fval) + self.assertAlmostEqual(result.fval, 5.8750) def test_cobyla_optimizer_with_quadratic_constraint(self): """ Cobyla Optimizer Test """ # load optimization problem problem = QuadraticProgram() - problem.variables.add(lb=[0, 0], ub=[1, 1], types='CC') - problem.objective.set_linear([(0, 1), (1, 1)]) + problem.continuous_var(upperbound=1) + problem.continuous_var(upperbound=1) + + problem.minimize(linear=[1, 1]) - qc = problem.quadratic_constraints - linear = SparsePair(ind=[0, 1], val=[-1, -1]) - quadratic = SparseTriple(ind1=[0, 1], ind2=[0, 1], val=[1, 1]) - qc.add(name='qc', lin_expr=linear, quad_expr=quadratic, rhs=-1/2) + linear = [-1, -1] + quadratic = [[1, 0], [0, 1]] + problem.quadratic_constraint(linear=linear, quadratic=quadratic, rhs=-1/2) # solve problem with cobyla - result = self.cobyla_optimizer.solve(problem) + cobyla = CobylaOptimizer() + result = cobyla.solve(problem) # analyze results self.assertAlmostEqual(result.fval, 1.0, places=2) diff --git a/test/optimization/test_variable.py b/test/optimization/test_variable.py index e081cd5f5e..d8f8224cf8 100644 --- a/test/optimization/test_variable.py +++ b/test/optimization/test_variable.py @@ -17,9 +17,10 @@ import logging import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase + from qiskit.optimization import infinity from qiskit.optimization.problems import QuadraticProgram, Variable, VarType -from test.optimization.optimization_test_case import QiskitOptimizationTestCase logger = logging.getLogger(__name__) From a3371d7af1847426a65ddc5a6732d84d87344472 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 15 Apr 2020 16:43:17 +0200 Subject: [PATCH 207/323] Update quadratic_program.py --- 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 69f4c66d38..68035ea2f8 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -680,7 +680,7 @@ def print_as_lp_string(self) -> str: """ return self.to_docplex().export_as_lp_string() - def load_from_file(self, filename: str) -> None: + def read_from_lp_file(self, filename: str) -> None: """Loads the quadratic program from a LP file. Args: @@ -690,10 +690,10 @@ def load_from_file(self, filename: str) -> None: model = model_reader.read(filename) self.from_docplex(model) - def write_to_file(self, filename: str) -> None: + def write_to_lp_file(self, filename: str) -> None: """Writes the quadratic program to an LP file. Args: filename: The filename of the file the model is written to. """ - self.to_docplex().export_as_lp(filename) \ No newline at end of file + self.to_docplex().export_as_lp(filename) From 6f6bc0ec573ba3df94d360f7e5b0e640bd9d9549 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 15 Apr 2020 16:51:26 +0200 Subject: [PATCH 208/323] update cplex_optimizer and tests --- qiskit/optimization/algorithms/cplex_optimizer.py | 4 +--- qiskit/optimization/problems/quadratic_program.py | 10 +++++----- test/optimization/test_cplex_optimizer.py | 5 +++-- test/optimization/test_quadratic_program.py | 8 ++++++++ 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index 3f9c0e8dd8..b006da25dd 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -109,7 +109,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: """ # convert to CPLEX problem - cplex = problem.to_cplex() + cplex = problem.to_docplex().get_cplex() # set display setting if not self.disp: @@ -118,8 +118,6 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: cplex.set_warning_stream(None) cplex.set_results_stream(None) - # TODO: need to find a good way to set the parameters - # solve problem try: cplex.solve() diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 68035ea2f8..f5f595af2d 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -606,9 +606,9 @@ def to_docplex(self) -> Model: # add objective objective = self.objective.constant - for i, v in self.objective.linear.coefficients_as_dict().items(): + for i, v in self.objective.linear.to_dict().items(): objective += v * var[i] - for (i, j), v in self.objective.quadratic.coefficients_as_dict().items(): + for (i, j), v in self.objective.quadratic.to_dict().items(): objective += v * var[i] * var[j] if self.objective.sense == ObjSense.MINIMIZE: mdl.minimize(objective) @@ -620,7 +620,7 @@ def to_docplex(self) -> Model: name = constraint.name rhs = constraint.rhs linear_expr = 0 - for j, v in constraint.linear.coefficients_as_dict().items(): + for j, v in constraint.linear.to_dict().items(): linear_expr += v * var[j] sense = constraint.sense if sense == ConstraintSense.EQ: @@ -638,9 +638,9 @@ def to_docplex(self) -> Model: name = constraint.name rhs = constraint.rhs quadratic_expr = 0 - for j, v in constraint.linear.coefficients_as_dict().items(): + for j, v in constraint.linear.to_dict().items(): quadratic_expr += v * var[j] - for (j, k), v in constraint.quadratic.coefficients_as_dict().items(): + for (j, k), v in constraint.quadratic.to_dict().items(): quadratic_expr += v * var[j] * var[k] sense = constraint.sense if sense == ConstraintSense.EQ: diff --git a/test/optimization/test_cplex_optimizer.py b/test/optimization/test_cplex_optimizer.py index e86f6c3e55..aa11ac74d2 100644 --- a/test/optimization/test_cplex_optimizer.py +++ b/test/optimization/test_cplex_optimizer.py @@ -45,14 +45,15 @@ def test_cplex_optimizer(self, config): # load optimization problem problem = QuadraticProgram() - problem.read(self.resource_path + filename) + problem.read_from_lp_file(self.resource_path + filename) # solve problem with cplex result = self.cplex_optimizer.solve(problem) # analyze results self.assertAlmostEqual(result.fval, fval) - self.assertAlmostEqual(result.x, x) + for i in range(problem.get_num_vars()): + self.assertAlmostEqual(result.x[i], x[i]) if __name__ == '__main__': diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index 775727a787..549c845f9a 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -142,6 +142,14 @@ def test_objective_handling(self): # TODO pass + def test_read_problem(self): + # TODO + pass + + def test_write_problem(self): + # TODO + pass + if __name__ == '__main__': unittest.main() From aabe2fe752c571998e7723957e8af46cbab7fb8e Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 15 Apr 2020 17:04:56 +0200 Subject: [PATCH 209/323] Update quadratic_program.py --- qiskit/optimization/problems/quadratic_program.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index f5f595af2d..c3c336c82c 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -15,7 +15,6 @@ """Quadratic Program.""" from typing import List, Union, Dict, Optional, Tuple -from os.path import splitext from docplex.mp.linear import Var from docplex.mp.model import Model From 9e4f7fb560076bdbd1bec38015b8848a931b83ca Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 15 Apr 2020 17:25:25 +0200 Subject: [PATCH 210/323] merge grover_optimizer updates --- .../results/grover_optimization_results.py | 75 ------------------- 1 file changed, 75 deletions(-) delete mode 100644 qiskit/optimization/results/grover_optimization_results.py diff --git a/qiskit/optimization/results/grover_optimization_results.py b/qiskit/optimization/results/grover_optimization_results.py deleted file mode 100644 index 19f8456bf1..0000000000 --- a/qiskit/optimization/results/grover_optimization_results.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""GroverOptimizationResults module""" - -from typing import Dict, Tuple, Union - - -class GroverOptimizationResults: - """A results object for Grover Optimization methods.""" - - def __init__(self, operation_counts: Dict[int, Dict[str, int]], - n_input_qubits: int, n_output_qubits: int, - func_dict: Dict[Union[int, Tuple[int, int]], int]) -> None: - """ - Args: - 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. - func_dict: A dictionary representation of the function, where the keys correspond - to a variable, and the values are the corresponding coefficients. - """ - self._operation_counts = operation_counts - self._n_input_qubits = n_input_qubits - self._n_output_qubits = n_output_qubits - self._func_dict = func_dict - - @property - def operation_counts(self) -> Dict[int, Dict[str, int]]: - """Get the operation counts. - - Returns: - The counts of each operation performed per iteration. - """ - return self._operation_counts - - @property - def n_input_qubits(self) -> int: - """Getter of n_input_qubits - - Returns: - The number of qubits used to represent the input. - """ - return self._n_input_qubits - - @property - def n_output_qubits(self) -> int: - """Getter of n_output_qubits - - Returns: - The number of qubits used to represent the output. - """ - return self._n_output_qubits - - @property - def func_dict(self) -> Dict[Union[int, Tuple[int, int]], int]: - """Getter of func_dict - - Returns: - A dictionary of coefficients describing a function, where the keys are the subscripts - of the variables (e.g. x1), and the values are the corresponding coefficients. If there - is a constant term, it is referenced by key -1. - """ - return self._func_dict From 8f12c60ab6e4ee98b6c3910d05fe0ebaff905331 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Wed, 15 Apr 2020 16:27:56 +0100 Subject: [PATCH 211/323] moving to a new optimization stack --- .../optimization/algorithms/admm_optimizer.py | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 7b144d0325..38d011821b 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -23,7 +23,6 @@ OptimizationResult) from qiskit.optimization.problems import VarType, ConstraintSense from qiskit.optimization.problems.quadratic_program import QuadraticProgram -from qiskit.optimization.problems.variables import CPX_BINARY, CPX_CONTINUOUS UPDATE_RHO_BY_TEN_PERCENT = 0 UPDATE_RHO_BY_RESIDUALS = 1 @@ -113,7 +112,7 @@ def __init__(self, # Indices of the variables self.binary_indices = binary_indices self.continuous_indices = continuous_indices - self.sense = op.objective.get_sense() + self.sense = op.objective.sense # define heavily used matrix, they are used at each iteration, so let's cache them, # they are np.ndarrays @@ -266,8 +265,8 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ # parse problem and convert to an ADMM specific representation. - binary_indices = self._get_variable_indices(problem, VarType.binary) - continuous_indices = self._get_variable_indices(problem, VarType.continuous) + binary_indices = self._get_variable_indices(problem, VarType.BINARY) + continuous_indices = self._get_variable_indices(problem, VarType.CONTINUOUS) # create our computation state. self._state = ADMMState(problem, binary_indices, @@ -422,8 +421,7 @@ def _get_q(self, variable_indices: List[int]) -> np.ndarray: for i, var_index_i in enumerate(variable_indices): for j, var_index_j in enumerate(variable_indices): # coefficients_as_array - q[i, j] = self._state.op.objective.quadratic.coefficients_as_array()[ - var_index_i, var_index_j] + q[i, j] = self._state.op.objective.quadratic[var_index_i, var_index_j] # flip the sign, according to the optimization sense, e.g. sense == 1 if minimize, # sense == -1 if maximize. @@ -462,19 +460,19 @@ def _assign_row_values(self, matrix: List[List[float]], vector: List[float], """ # assign matrix row. row = [] + # todo: replace with .take() for var_index in variable_indices: # row.append(self._state.op # .linear_constraints.get_coefficients(constraint_index, var_index)) row.append( - self._state.op.linear_constraints[constraint_index].linear.coefficients_as_array()[ - var_index]) + self._state.op.linear_constraints[constraint_index].linear[var_index]) matrix.append(row) # assign vector row. vector.append(self._state.op.linear_constraints[constraint_index].rhs) # flip the sign if constraint is G, we want L constraints. - if self._state.op.linear_constraints[constraint_index].sense == ConstraintSense.geq: + if self._state.op.linear_constraints[constraint_index].sense == ConstraintSense.GE: # invert the sign to make constraint "L". matrix[-1] = [-1 * el for el in matrix[-1]] vector[-1] = -1 * vector[-1] @@ -514,8 +512,9 @@ def _get_a0_b0(self) -> (np.ndarray, np.ndarray): index_set = set(self._state.binary_indices) for constraint_index, constraint in enumerate(self._state.op.linear_constraints): # we check only equality constraints here. - if constraint.sense != ConstraintSense.eq: + if constraint.sense != ConstraintSense.EQ: continue + # todo: implement verification condition # row = self._state.op.linear_constraints.get_rows(constraint_index) row = self._state.op.linear_constraints[constraint_index].linear.coefficients_as_array().take(self._state.binary_indices) self._assign_row_values(matrix, vector, @@ -544,14 +543,16 @@ def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) \ """ matrix = [] vector = [] - senses = self._state.op.linear_constraints.get_senses() index_set = set(variable_indices) - for constraint_index, sense in enumerate(senses): - if sense in ("E", "R"): + for constraint_index, constraint in enumerate(self._state.op.linear_constraints): + # todo: missing "R" + if constraint.sense in [ConstraintSense.EQ]: # TODO: Ranged constraints should be supported continue # sense either G or L. + var_indices = set() + index_set.intersection(self._state.op.linear_constraints[constraint_index].linear.to_dict().keys()) row = self._state.op.linear_constraints.get_rows(constraint_index) if set(row.ind).issubset(index_set): self._assign_row_values(matrix, vector, constraint_index, variable_indices) From 80cf4ed5f8766c6975a9ae96ec887dbe8282a9d0 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Wed, 15 Apr 2020 12:00:40 -0400 Subject: [PATCH 212/323] fix spell, lint, unit test --- qiskit/optimization/algorithms/__init__.py | 2 +- .../algorithms/grover_optimizer.py | 2 +- .../test_grover_minimum_finder.py | 84 ++++++++++--------- 3 files changed, 47 insertions(+), 41 deletions(-) diff --git a/qiskit/optimization/algorithms/__init__.py b/qiskit/optimization/algorithms/__init__.py index c9419a7005..7a33ec9dea 100644 --- a/qiskit/optimization/algorithms/__init__.py +++ b/qiskit/optimization/algorithms/__init__.py @@ -57,4 +57,4 @@ __all__ = ["ADMMOptimizer", "OptimizationAlgorithm", "OptimizationResult", "CplexOptimizer", "CobylaOptimizer", "MinimumEigenOptimizer", "RecursiveMinimumEigenOptimizer", - "GroverMinimumFinder", "GroverOptimizationResults"] + "GroverOptimizationResults"] diff --git a/qiskit/optimization/algorithms/grover_optimizer.py b/qiskit/optimization/algorithms/grover_optimizer.py index 24db4ac948..293f7d49d7 100644 --- a/qiskit/optimization/algorithms/grover_optimizer.py +++ b/qiskit/optimization/algorithms/grover_optimizer.py @@ -233,7 +233,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: opt_x = [1 if s == '1' else 0 for s in ('{0:%sb}' % n_key).format(optimum_key)] # Build the results object. - rotation = 0 # TODO: should either be retreived from grover or removed from results. + rotation = 0 # TODO: should either be retrieved from grover or removed from results. grover_results = GroverOptimizationResults(operation_count, rotation, n_key, n_value, func_dict) result = OptimizationResult(x=opt_x, fval=solutions[optimum_key], diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py index e8e7df38e4..5467ba4498 100644 --- a/test/optimization/test_grover_minimum_finder.py +++ b/test/optimization/test_grover_minimum_finder.py @@ -36,51 +36,57 @@ def validate_results(self, problem, results): def test_qubo_gas_int_zero(self): """Test for when the answer is zero.""" - - # Input. - op = QuadraticProgram() - op.variables.add(names=['x0', 'x1'], types='BB') - linear = [('x0', 0), ('x1', 0)] - op.objective.set_linear(linear) - - # Will not find a negative, should return 0. - gmf = GroverOptimizer(1, num_iterations=1) - results = gmf.solve(op) - self.assertEqual(results.x, [0, 0]) - self.assertEqual(results.fval, 0.0) + try: + # Input. + op = QuadraticProgram() + op.variables.add(names=['x0', 'x1'], types='BB') + linear = [('x0', 0), ('x1', 0)] + op.objective.set_linear(linear) + + # Will not find a negative, should return 0. + gmf = GroverOptimizer(1, num_iterations=1) + results = gmf.solve(op) + self.assertEqual(results.x, [0, 0]) + self.assertEqual(results.fval, 0.0) + except NameError as ex: + self.skipTest(str(ex)) def test_qubo_gas_int_simple(self): """Test for simple case, with 2 linear coeffs and no quadratic coeffs or constants.""" - - # Input. - op = QuadraticProgram() - op.variables.add(names=['x0', 'x1'], types='BB') - linear = [('x0', -1), ('x1', 2)] - op.objective.set_linear(linear) - - # Get the optimum key and value. - n_iter = 8 - gmf = GroverOptimizer(4, num_iterations=n_iter) - results = gmf.solve(op) - self.validate_results(op, results) + try: + # Input. + op = QuadraticProgram() + op.variables.add(names=['x0', 'x1'], types='BB') + linear = [('x0', -1), ('x1', 2)] + op.objective.set_linear(linear) + + # Get the optimum key and value. + n_iter = 8 + gmf = GroverOptimizer(4, num_iterations=n_iter) + results = gmf.solve(op) + self.validate_results(op, results) + except NameError as ex: + self.skipTest(str(ex)) def test_qubo_gas_int_paper_example(self): """Test the example from https://arxiv.org/abs/1912.04088.""" - - # Input. - op = QuadraticProgram() - op.variables.add(names=['x0', 'x1', 'x2'], types='BBB') - - linear = [('x0', -1), ('x1', 2), ('x2', -3)] - op.objective.set_linear(linear) - op.objective.set_quadratic_coefficients('x0', 'x2', -2) - op.objective.set_quadratic_coefficients('x1', 'x2', -1) - - # Get the optimum key and value. - n_iter = 10 - gmf = GroverOptimizer(6, num_iterations=n_iter) - results = gmf.solve(op) - self.validate_results(op, results) + try: + # Input. + op = QuadraticProgram() + op.variables.add(names=['x0', 'x1', 'x2'], types='BBB') + + linear = [('x0', -1), ('x1', 2), ('x2', -3)] + op.objective.set_linear(linear) + op.objective.set_quadratic_coefficients('x0', 'x2', -2) + op.objective.set_quadratic_coefficients('x1', 'x2', -1) + + # Get the optimum key and value. + n_iter = 10 + gmf = GroverOptimizer(6, num_iterations=n_iter) + results = gmf.solve(op) + self.validate_results(op, results) + except NameError as ex: + self.skipTest(str(ex)) if __name__ == '__main__': From 0bfabeb238da1c4ced77f4235d91bba5bc4d5aa4 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 16 Apr 2020 14:37:06 +0900 Subject: [PATCH 213/323] merge upstream --- .../optimization/problems/quadratic_program.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 0db9dad07b..a3faf1ab35 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -918,7 +918,7 @@ def _linear_expression(self, lin_expr: LinearExpression) \ subs = self._subs const = [] lin_dict = defaultdict(float) - for i, w_i in lin_expr.coefficients_as_dict(use_index=False).items(): + for i, w_i in lin_expr.to_dict(use_index=False).items(): repl_i = subs[i] if i in subs else (i, 1) prod = w_i * repl_i[1] if repl_i[0] == self.CONST: @@ -936,7 +936,7 @@ def _quadratic_expression(self, quad_expr: QuadraticExpression) \ const = [] lin_dict = defaultdict(float) quad_dict = defaultdict(float) - for (i, j), w_ij in quad_expr.coefficients_as_dict(use_index=False).items(): + for (i, j), w_ij in quad_expr.to_dict(use_index=False).items(): repl_i = subs[i] if i in subs else (i, 1) repl_j = subs[j] if j in subs else (j, 1) idx = tuple(x for x, _ in [repl_i, repl_j] if x != self.CONST) @@ -974,9 +974,9 @@ def _linear_constraints(self) -> SubstitutionStatus: for lin_cst in src.linear_constraints: constant, linear = self._linear_expression(lin_cst.linear) - rhs = lin_cst.rhs - fsum(constant) + rhs = -fsum([-lin_cst.rhs] + constant) if linear.coefficients.nnz > 0: - dst.linear_constraint(name=lin_cst.name, coefficients=linear.coefficients, + dst.linear_constraint(name=lin_cst.name, linear=linear.coefficients, sense=lin_cst.sense, rhs=rhs) else: if not self._feasible(lin_cst.sense, rhs): @@ -992,19 +992,19 @@ def _quadratic_constraints(self) -> SubstitutionStatus: for quad_cst in src.quadratic_constraints: const1, lin1 = self._linear_expression(quad_cst.linear) const2, lin2, quadratic = self._quadratic_expression(quad_cst.quadratic) - rhs = quad_cst.rhs - fsum(const1 + const2) + rhs = -fsum([-quad_cst.rhs] + const1 + const2) linear = lin1.coefficients + lin2.coefficients if quadratic.coefficients.nnz > 0: - dst.quadratic_constraint(name=quad_cst.name, linear_coefficients=linear, - quadratic_coefficients=quadratic.coefficients, + dst.quadratic_constraint(name=quad_cst.name, linear=linear, + quadratic=quadratic.coefficients, sense=quad_cst.sense, rhs=rhs) elif linear.nnz > 0: name = quad_cst.name lin_names = set(lin.name for lin in dst.linear_constraints) while name in lin_names: name = '_' + name - dst.linear_constraint(name=name, coefficients=linear, sense=quad_cst.sense, rhs=rhs) + dst.linear_constraint(name=name, linear=linear, sense=quad_cst.sense, rhs=rhs) else: if not self._feasible(quad_cst.sense, rhs): logger.warning('constraint %s is infeasible due to substitution', quad_cst.name) From 0cdcb40fe33848b60c3b1c92368b79f6e5525ad3 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 16 Apr 2020 14:46:52 +0900 Subject: [PATCH 214/323] cleanup --- .../problems/quadratic_program.py | 77 ++++++++----------- 1 file changed, 30 insertions(+), 47 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index a3faf1ab35..e8d6e33bce 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -777,7 +777,6 @@ def substitute_variables( - Same variable is substituted multiple times. - Coefficient of variable substitution is zero. """ - self._src = src self._dst = QuadraticProgram(src.name) self._subs_dict(constants, variables) @@ -821,14 +820,12 @@ def _replace_dict_keys_with_names(op, dic): return key, val def _subs_dict(self, constants, variables): - src = self._src - # guarantee that there is no overlap between variables to be replaced and combine input subs: Dict[str, Tuple[str, float]] = {} if constants is not None: for i, v in constants.items(): # substitute i <- v - i_2 = src.get_variable(i).name + i_2 = self._src.get_variable(i).name if i_2 in subs: raise QiskitOptimizationError( 'Cannot substitute the same variable twice: {} <- {}'.format(i, v)) @@ -840,8 +837,8 @@ def _subs_dict(self, constants, variables): raise QiskitOptimizationError( 'coefficient must be non-zero: {} {} {}'.format(i, j, v)) # substitute i <- j * v - i_2 = src.get_variable(i).name - j_2 = src.get_variable(j).name + i_2 = self._src.get_variable(i).name + j_2 = self._src.get_variable(j).name if i_2 == j_2: raise QiskitOptimizationError( 'Cannot substitute the same variable: {} <- {} {}'.format(i, j, v)) @@ -857,22 +854,18 @@ def _subs_dict(self, constants, variables): self._subs = subs def _variables(self) -> SubstitutionStatus: - src = self._src - dst = self._dst - subs = self._subs - # copy variables that are not replaced - for var in src.variables: + for var in self._src.variables: name = var.name vartype = var.vartype lowerbound = var.lowerbound upperbound = var.upperbound - if name not in subs: - dst._add_variable(name, lowerbound, upperbound, vartype) + if name not in self._subs: + self._dst._add_variable(name, lowerbound, upperbound, vartype) - for i, (j, v) in subs.items(): - lb_i = src.get_variable(i).lowerbound - ub_i = src.get_variable(i).upperbound + for i, (j, v) in self._subs.items(): + lb_i = self._src.get_variable(i).lowerbound + ub_i = self._src.get_variable(i).upperbound if j == self.CONST: if not lb_i <= v <= ub_i: logger.warning( @@ -894,7 +887,7 @@ def _variables(self) -> SubstitutionStatus: new_ub_i = ub_i / v else: new_ub_i = ub_i if v > 0 else -ub_i - var_j = dst.get_variable(j) + var_j = self._dst.get_variable(j) lb_j = var_j.lowerbound ub_j = var_j.upperbound if v > 0: @@ -904,7 +897,7 @@ def _variables(self) -> SubstitutionStatus: var_j.lowerbound = max(lb_j, new_ub_i) var_j.upperbound = min(ub_j, new_lb_i) - for var in dst.variables: + for var in self._dst.variables: if var.lowerbound > var.upperbound: logger.warning( 'Infeasible lower and upper bound: %s %f %f', var, var.lowerbound, @@ -915,11 +908,10 @@ def _variables(self) -> SubstitutionStatus: def _linear_expression(self, lin_expr: LinearExpression) \ -> Tuple[List[float], LinearExpression]: - subs = self._subs const = [] lin_dict = defaultdict(float) for i, w_i in lin_expr.to_dict(use_index=False).items(): - repl_i = subs[i] if i in subs else (i, 1) + repl_i = self._subs[i] if i in self._subs else (i, 1) prod = w_i * repl_i[1] if repl_i[0] == self.CONST: const.append(prod) @@ -932,13 +924,12 @@ def _linear_expression(self, lin_expr: LinearExpression) \ def _quadratic_expression(self, quad_expr: QuadraticExpression) \ -> Tuple[List[float], Optional[LinearExpression], Optional[QuadraticExpression]]: - subs = self._subs const = [] lin_dict = defaultdict(float) quad_dict = defaultdict(float) for (i, j), w_ij in quad_expr.to_dict(use_index=False).items(): - repl_i = subs[i] if i in subs else (i, 1) - repl_j = subs[j] if j in subs else (j, 1) + repl_i = self._subs[i] if i in self._subs else (i, 1) + repl_j = self._subs[j] if j in self._subs else (j, 1) idx = tuple(x for x, _ in [repl_i, repl_j] if x != self.CONST) prod = w_ij * repl_i[1] * repl_j[1] if len(idx) == 2: @@ -954,30 +945,25 @@ def _quadratic_expression(self, quad_expr: QuadraticExpression) \ return const, new_lin, new_quad def _objective(self) -> SubstitutionStatus: - src = self._src - dst = self._dst + obj = self._src.objective + const1, lin1 = self._linear_expression(obj.linear) + const2, lin2, quadratic = self._quadratic_expression(obj.quadratic) - const1, lin1 = self._linear_expression(src.objective.linear) - const2, lin2, quadratic = self._quadratic_expression(src.objective.quadratic) - - constant = fsum([src.objective.constant] + const1 + const2) + constant = fsum([obj.constant] + const1 + const2) linear = lin1.coefficients + lin2.coefficients - if src.objective.sense == src.objective.sense.MINIMIZE: - dst.minimize(constant=constant, linear=linear, quadratic=quadratic.coefficients) + if obj.sense == obj.sense.MINIMIZE: + self._dst.minimize(constant=constant, linear=linear, quadratic=quadratic.coefficients) else: - dst.maximize(constant=constant, linear=linear, quadratic=quadratic.coefficients) + self._dst.maximize(constant=constant, linear=linear, quadratic=quadratic.coefficients) return SubstitutionStatus.success def _linear_constraints(self) -> SubstitutionStatus: - src = self._src - dst = self._dst - - for lin_cst in src.linear_constraints: + for lin_cst in self._src.linear_constraints: constant, linear = self._linear_expression(lin_cst.linear) rhs = -fsum([-lin_cst.rhs] + constant) if linear.coefficients.nnz > 0: - dst.linear_constraint(name=lin_cst.name, linear=linear.coefficients, - sense=lin_cst.sense, rhs=rhs) + self._dst.linear_constraint(name=lin_cst.name, linear=linear.coefficients, + sense=lin_cst.sense, rhs=rhs) else: if not self._feasible(lin_cst.sense, rhs): logger.warning('constraint %s is infeasible due to substitution', lin_cst.name) @@ -986,25 +972,22 @@ def _linear_constraints(self) -> SubstitutionStatus: return SubstitutionStatus.success def _quadratic_constraints(self) -> SubstitutionStatus: - src = self._src - dst = self._dst - - for quad_cst in src.quadratic_constraints: + for quad_cst in self._src.quadratic_constraints: const1, lin1 = self._linear_expression(quad_cst.linear) const2, lin2, quadratic = self._quadratic_expression(quad_cst.quadratic) rhs = -fsum([-quad_cst.rhs] + const1 + const2) linear = lin1.coefficients + lin2.coefficients if quadratic.coefficients.nnz > 0: - dst.quadratic_constraint(name=quad_cst.name, linear=linear, - quadratic=quadratic.coefficients, - sense=quad_cst.sense, rhs=rhs) + self._dst.quadratic_constraint(name=quad_cst.name, linear=linear, + quadratic=quadratic.coefficients, + sense=quad_cst.sense, rhs=rhs) elif linear.nnz > 0: name = quad_cst.name - lin_names = set(lin.name for lin in dst.linear_constraints) + lin_names = set(lin.name for lin in self._dst.linear_constraints) while name in lin_names: name = '_' + name - dst.linear_constraint(name=name, linear=linear, sense=quad_cst.sense, rhs=rhs) + self._dst.linear_constraint(name=name, linear=linear, sense=quad_cst.sense, rhs=rhs) else: if not self._feasible(quad_cst.sense, rhs): logger.warning('constraint %s is infeasible due to substitution', quad_cst.name) From 3bfad48c764cfa6eedc446ecc4ccfea997b16ae6 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 16 Apr 2020 17:08:04 +0900 Subject: [PATCH 215/323] add a test of substitute_variables --- test/optimization/test_quadratic_program.py | 89 ++++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index 549c845f9a..35d493f6a5 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -14,12 +14,12 @@ """ Test QuadraticProgram """ -import unittest -from test.optimization.optimization_test_case import QiskitOptimizationTestCase import logging +import unittest from qiskit.optimization import QuadraticProgram, QiskitOptimizationError, infinity from qiskit.optimization.problems import VarType +from test.optimization.optimization_test_case import QiskitOptimizationTestCase logger = logging.getLogger(__name__) @@ -150,6 +150,91 @@ def test_write_problem(self): # TODO pass + def test_docplex(self): + q_p = QuadraticProgram('test') + q_p.binary_var('x') + q_p.integer_var('y', lowerbound=-2, upperbound=4) + q_p.continuous_var('z', lowerbound=-1.5, upperbound=3.2) + q_p.minimize(constant=1, linear={'x': 1, 'y': 2}, + quadratic={('x', 'y'): -1, ('z', 'z'): 2}) + q_p.linear_constraint({'x': 2, 'z': -1}, '==', 1) + q_p.quadratic_constraint({'x': 2, 'z': -1}, {('y', 'z'): 3}, '==', 1) + q_p2 = QuadraticProgram() + q_p2.from_docplex(q_p.to_docplex()) + self.assertEqual(q_p.pprint_as_string(), q_p2.pprint_as_string()) + self.assertEqual(q_p.print_as_lp_string(), q_p2.print_as_lp_string()) + + def test_substitute_variables(self): + q_p = QuadraticProgram('test') + q_p.binary_var('x') + q_p.integer_var('y', lowerbound=-2, upperbound=4) + q_p.continuous_var('z', lowerbound=-1.5, upperbound=3.2) + q_p.minimize(constant=1, linear={'x': 1, 'y': 2}, + quadratic={('x', 'y'): -1, ('z', 'z'): 2}) + q_p.linear_constraint({'x': 2, 'z': -1}, '==', 1) + q_p.quadratic_constraint({'x': 2, 'z': -1}, {('y', 'z'): 3}, '<=', -1) + print(q_p.print_as_lp_string()) + + q_p2, status = q_p.substitute_variables(constants={'x': -1}) + self.assertEqual(status.name, 'infeasible') + q_p2, status = q_p.substitute_variables(constants={'y': -3}) + self.assertEqual(status.name, 'infeasible') + + q_p2, status = q_p.substitute_variables(constants={'x': 0}) + self.assertDictEqual(q_p2.objective.linear.to_dict(use_index=False), {'y': 2}) + self.assertDictEqual(q_p2.objective.quadratic.to_dict(use_index=False), {('z', 'z'): 2}) + self.assertEqual(q_p2.objective.constant, 1) + self.assertEqual(len(q_p2.linear_constraints), 1) + self.assertEqual(len(q_p2.quadratic_constraints), 1) + + cst = q_p2.linear_constraints[0] + self.assertDictEqual(cst.linear.to_dict(use_index=False), {'z': -1}) + self.assertEqual(cst.sense.name, 'EQ') + self.assertEqual(cst.rhs, 1) + + cst = q_p2.quadratic_constraints[0] + self.assertDictEqual(cst.linear.to_dict(use_index=False), {'z': -1}) + self.assertDictEqual(cst.quadratic.to_dict(use_index=False), {('y', 'z'): 3}) + self.assertEqual(cst.sense.name, 'LE') + self.assertEqual(cst.rhs, -1) + + q_p2, status = q_p.substitute_variables(constants={'z': -1}) + self.assertDictEqual(q_p2.objective.linear.to_dict(use_index=False), {'x': 1, 'y': 2}) + self.assertDictEqual(q_p2.objective.quadratic.to_dict(use_index=False), {('x', 'y'): -1}) + self.assertEqual(q_p2.objective.constant, 3) + self.assertEqual(len(q_p2.linear_constraints), 2) + self.assertEqual(len(q_p2.quadratic_constraints), 0) + + cst = q_p2.linear_constraints[0] + self.assertDictEqual(cst.linear.to_dict(use_index=False), {'x': 2}) + self.assertEqual(cst.sense.name, 'EQ') + self.assertEqual(cst.rhs, 0) + + cst = q_p2.linear_constraints[1] + self.assertDictEqual(cst.linear.to_dict(use_index=False), {'x': 2, 'y': -3}) + self.assertEqual(cst.sense.name, 'LE') + self.assertEqual(cst.rhs, -2) + + q_p2, status = q_p.substitute_variables(variables={'y': ('x', -0.5)}) + print(q_p2.print_as_lp_string()) + self.assertDictEqual(q_p2.objective.linear.to_dict(use_index=False), {}) + self.assertDictEqual(q_p2.objective.quadratic.to_dict(use_index=False), + {('x', 'x'): 0.5, ('z', 'z'): 2}) + self.assertEqual(q_p2.objective.constant, 1) + self.assertEqual(len(q_p2.linear_constraints), 1) + self.assertEqual(len(q_p2.quadratic_constraints), 1) + + cst = q_p2.linear_constraints[0] + self.assertDictEqual(cst.linear.to_dict(use_index=False), {'x': 2, 'z': -1}) + self.assertEqual(cst.sense.name, 'EQ') + self.assertEqual(cst.rhs, 1) + + cst = q_p2.quadratic_constraints[0] + self.assertDictEqual(cst.linear.to_dict(use_index=False), {'x': 2, 'z': -1}) + self.assertDictEqual(cst.quadratic.to_dict(use_index=False), {('x', 'z'): -1.5}) + self.assertEqual(cst.sense.name, 'LE') + self.assertEqual(cst.rhs, -1) + if __name__ == '__main__': unittest.main() From 5ac53b879eb48c7374e931a03d218d158051686b Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 16 Apr 2020 17:47:45 +0900 Subject: [PATCH 216/323] replace use_index with use_name --- .../problems/linear_expression.py | 10 +++---- .../problems/quadratic_expression.py | 12 ++++---- .../problems/quadratic_program.py | 4 +-- test/optimization/test_linear_constraint.py | 6 ++-- test/optimization/test_linear_expression.py | 27 ++++++++--------- .../optimization/test_quadratic_constraint.py | 2 +- .../optimization/test_quadratic_expression.py | 29 ++++++++---------- test/optimization/test_quadratic_objective.py | 4 +-- test/optimization/test_quadratic_program.py | 30 +++++++++---------- 9 files changed, 57 insertions(+), 67 deletions(-) diff --git a/qiskit/optimization/problems/linear_expression.py b/qiskit/optimization/problems/linear_expression.py index bccfa0f212..03de43e8ca 100644 --- a/qiskit/optimization/problems/linear_expression.py +++ b/qiskit/optimization/problems/linear_expression.py @@ -116,21 +116,21 @@ def to_array(self) -> ndarray: """ return self._coefficients.toarray()[0] - def to_dict(self, use_index: bool = True) -> Dict[Union[int, str], float]: + def to_dict(self, use_name: bool = False) -> Dict[Union[int, str], float]: """Returns the coefficients of the linear expression as dictionary, either using variable names or indices as keys. Args: - use_index: Determines whether to use index or names to refer to variables. + use_name: Determines whether to use index or names to refer to variables. Returns: An dictionary with the coefficients corresponding to the linear expression. """ - if use_index: - return {k: v for (_, k), v in self._coefficients.items()} - else: + if use_name: return {self.quadratic_program.variables[k].name: v for (_, k), v in self._coefficients.items()} + else: + return {k: v for (_, k), v in self._coefficients.items()} def evaluate(self, x: Union[ndarray, List, Dict[Union[int, str], float]]) -> float: """Evaluate the linear expression for given variables. diff --git a/qiskit/optimization/problems/quadratic_expression.py b/qiskit/optimization/problems/quadratic_expression.py index 459c7ca9d1..ea8a635729 100644 --- a/qiskit/optimization/problems/quadratic_expression.py +++ b/qiskit/optimization/problems/quadratic_expression.py @@ -121,23 +121,23 @@ def to_array(self) -> ndarray: """ return self._coefficients.toarray() - def to_dict(self, use_index: bool = True - ) -> Dict[Union[Tuple[int, int], Tuple[str, str]], float]: + def to_dict(self, use_name: bool = False + ) -> Dict[Union[Tuple[int, int], Tuple[str, str]], float]: """Returns the coefficients of the quadratic expression as dictionary, either using tuples of variable names or indices as keys. Args: - use_index: Determines whether to use index or names to refer to variables. + use_name: Determines whether to use index or names to refer to variables. Returns: An dictionary with the coefficients corresponding to the quadratic expression. """ - if use_index: - return {(i, j): v for (i, j), v in self._coefficients.items()} - else: + if use_name: return {(self.quadratic_program.variables[i].name, self.quadratic_program.variables[j].name): v for (i, j), v in self._coefficients.items()} + else: + return {(i, j): v for (i, j), v in self._coefficients.items()} def evaluate(self, x: Union[ndarray, List, Dict[Union[int, str], float]]) -> float: """Evaluate the quadratic expression for given variables: x * Q * x. diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index e8d6e33bce..87a5f18901 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -910,7 +910,7 @@ def _linear_expression(self, lin_expr: LinearExpression) \ -> Tuple[List[float], LinearExpression]: const = [] lin_dict = defaultdict(float) - for i, w_i in lin_expr.to_dict(use_index=False).items(): + for i, w_i in lin_expr.to_dict(use_name=True).items(): repl_i = self._subs[i] if i in self._subs else (i, 1) prod = w_i * repl_i[1] if repl_i[0] == self.CONST: @@ -927,7 +927,7 @@ def _quadratic_expression(self, quad_expr: QuadraticExpression) \ const = [] lin_dict = defaultdict(float) quad_dict = defaultdict(float) - for (i, j), w_ij in quad_expr.to_dict(use_index=False).items(): + for (i, j), w_ij in quad_expr.to_dict(use_name=True).items(): repl_i = self._subs[i] if i in self._subs else (i, 1) repl_j = self._subs[j] if j in self._subs else (j, 1) idx = tuple(x for x, _ in [repl_i, repl_j] if x != self.CONST) diff --git a/test/optimization/test_linear_constraint.py b/test/optimization/test_linear_constraint.py index 29ad435a7a..c1f0b670a3 100644 --- a/test/optimization/test_linear_constraint.py +++ b/test/optimization/test_linear_constraint.py @@ -14,6 +14,7 @@ """ Test LinearConstraint """ +import unittest import logging import numpy as np @@ -35,14 +36,13 @@ def test_init(self): quadratic_program.continuous_var() self.assertEqual(quadratic_program.get_num_linear_constraints(), 0) - coefficients = np.array([i for i in range(5)]) + coefficients = np.array(range(5)) # equality constraints quadratic_program.linear_constraint(sense='==') self.assertEqual(quadratic_program.get_num_linear_constraints(), 1) self.assertEqual(quadratic_program.linear_constraints[0].name, 'c0') - self.assertEqual(len(quadratic_program.linear_constraints[0].linear.to_dict()), - 0) + self.assertEqual(len(quadratic_program.linear_constraints[0].linear.to_dict()), 0) self.assertEqual(quadratic_program.linear_constraints[0].sense, ConstraintSense.EQ) self.assertEqual(quadratic_program.linear_constraints[0].rhs, 0.0) self.assertEqual(quadratic_program.linear_constraints[0], diff --git a/test/optimization/test_linear_expression.py b/test/optimization/test_linear_expression.py index 212ea57eec..19a09f7afa 100644 --- a/test/optimization/test_linear_expression.py +++ b/test/optimization/test_linear_expression.py @@ -14,14 +14,15 @@ """ Test LinearExpression """ -import unittest -from test.optimization.optimization_test_case import QiskitOptimizationTestCase import logging +import unittest + import numpy as np from scipy.sparse import dok_matrix from qiskit.optimization import QuadraticProgram from qiskit.optimization.problems import LinearExpression +from test.optimization.optimization_test_case import QiskitOptimizationTestCase logger = logging.getLogger(__name__) @@ -36,7 +37,7 @@ def test_init(self): for _ in range(5): quadratic_program.continuous_var() - coefficients_list = [i for i in range(5)] + coefficients_list = list(range(5)) coefficients_array = np.array(coefficients_list) coefficients_dok = dok_matrix([coefficients_list]) coefficients_dict_int = {i: i for i in range(1, 5)} @@ -47,13 +48,11 @@ def test_init(self): coefficients_dok, coefficients_dict_int, coefficients_dict_str]: - linear = LinearExpression(quadratic_program, coeffs) self.assertEqual((linear.coefficients != coefficients_dok).nnz, 0) self.assertTrue((linear.to_array() == coefficients_list).all()) - self.assertDictEqual(linear.to_dict(use_index=True), coefficients_dict_int) - self.assertDictEqual(linear.to_dict(use_index=False), - coefficients_dict_str) + self.assertDictEqual(linear.to_dict(use_name=False), coefficients_dict_int) + self.assertDictEqual(linear.to_dict(use_name=True), coefficients_dict_str) def test_get_item(self): """ test get_item. """ @@ -62,7 +61,7 @@ def test_get_item(self): for _ in range(5): quadratic_program.continuous_var() - coefficients = [i for i in range(5)] + coefficients = list(range(5)) linear = LinearExpression(quadratic_program, coefficients) for i, v in enumerate(coefficients): self.assertEqual(linear[i], v) @@ -77,7 +76,7 @@ def test_setters(self): zeros = np.zeros(quadratic_program.get_num_vars()) linear = LinearExpression(quadratic_program, zeros) - coefficients_list = [i for i in range(5)] + coefficients_list = list(range(5)) coefficients_array = np.array(coefficients_list) coefficients_dok = dok_matrix([coefficients_list]) coefficients_dict_int = {i: i for i in range(1, 5)} @@ -88,13 +87,11 @@ def test_setters(self): coefficients_dok, coefficients_dict_int, coefficients_dict_str]: - linear.coefficients = coeffs self.assertEqual((linear.coefficients != coefficients_dok).nnz, 0) self.assertTrue((linear.to_array() == coefficients_list).all()) - self.assertDictEqual(linear.to_dict(use_index=True), coefficients_dict_int) - self.assertDictEqual(linear.to_dict(use_index=False), - coefficients_dict_str) + self.assertDictEqual(linear.to_dict(use_name=False), coefficients_dict_int) + self.assertDictEqual(linear.to_dict(use_name=True), coefficients_dict_str) def test_evaluate(self): """ test evaluate. """ @@ -102,10 +99,10 @@ def test_evaluate(self): quadratic_program = QuadraticProgram() x = [quadratic_program.continuous_var() for _ in range(5)] - coefficients_list = [i for i in range(5)] + coefficients_list = list(range(5)) linear = LinearExpression(quadratic_program, coefficients_list) - values_list = [i for i in range(len(x))] + values_list = list(range(len(x))) values_array = np.array(values_list) values_dict_int = {i: i for i in range(len(x))} values_dict_str = {'x{}'.format(i): i for i in range(len(x))} diff --git a/test/optimization/test_quadratic_constraint.py b/test/optimization/test_quadratic_constraint.py index 3c74f81bab..9b11f4e622 100644 --- a/test/optimization/test_quadratic_constraint.py +++ b/test/optimization/test_quadratic_constraint.py @@ -36,7 +36,7 @@ def test_init(self): quadratic_program.continuous_var() self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 0) - linear_coeffs = np.array([i for i in range(5)]) + linear_coeffs = np.array(range(5)) quadratic_coeffs = np.array([[i*j for i in range(5)] for j in range(5)]) # equality constraints diff --git a/test/optimization/test_quadratic_expression.py b/test/optimization/test_quadratic_expression.py index 242dcb1383..30120ac3ca 100644 --- a/test/optimization/test_quadratic_expression.py +++ b/test/optimization/test_quadratic_expression.py @@ -14,14 +14,15 @@ """ Test QuadraticExpression """ -import unittest -from test.optimization.optimization_test_case import QiskitOptimizationTestCase import logging +import unittest + import numpy as np from scipy.sparse import dok_matrix from qiskit.optimization import QuadraticProgram from qiskit.optimization.problems import QuadraticExpression +from test.optimization.optimization_test_case import QiskitOptimizationTestCase logger = logging.getLogger(__name__) @@ -36,7 +37,7 @@ def test_init(self): for _ in range(5): quadratic_program.continuous_var() - coefficients_list = [[i*j for i in range(5)] for j in range(5)] + coefficients_list = [[i * j for i in range(5)] for j in range(5)] coefficients_array = np.array(coefficients_list) coefficients_dok = dok_matrix(coefficients_list) coefficients_dict_int = {(i, j): v for (i, j), v in coefficients_dok.items()} @@ -48,14 +49,11 @@ def test_init(self): coefficients_dok, coefficients_dict_int, coefficients_dict_str]: - quadratic = QuadraticExpression(quadratic_program, coeffs) self.assertEqual((quadratic.coefficients != coefficients_dok).nnz, 0) self.assertTrue((quadratic.to_array() == coefficients_list).all()) - self.assertDictEqual(quadratic.to_dict( - use_index=True), coefficients_dict_int) - self.assertDictEqual(quadratic.to_dict(use_index=False), - coefficients_dict_str) + self.assertDictEqual(quadratic.to_dict(use_name=False), coefficients_dict_int) + self.assertDictEqual(quadratic.to_dict(use_name=True), coefficients_dict_str) def test_get_item(self): """ test get_item. """ @@ -64,7 +62,7 @@ def test_get_item(self): for _ in range(5): quadratic_program.continuous_var() - coefficients = [[i*j for i in range(5)] for j in range(5)] + coefficients = [[i * j for i in range(5)] for j in range(5)] quadratic = QuadraticExpression(quadratic_program, coefficients) for i, j_v in enumerate(coefficients): for j, v in enumerate(j_v): @@ -81,7 +79,7 @@ def test_setters(self): zeros = np.zeros((n, n)) quadratic = QuadraticExpression(quadratic_program, zeros) - coefficients_list = [[i*j for i in range(5)] for j in range(5)] + coefficients_list = [[i * j for i in range(5)] for j in range(5)] coefficients_array = np.array(coefficients_list) coefficients_dok = dok_matrix(coefficients_list) coefficients_dict_int = {(i, j): v for (i, j), v in coefficients_dok.items()} @@ -93,14 +91,11 @@ def test_setters(self): coefficients_dok, coefficients_dict_int, coefficients_dict_str]: - quadratic.coefficients = coeffs self.assertEqual((quadratic.coefficients != coefficients_dok).nnz, 0) self.assertTrue((quadratic.to_array() == coefficients_list).all()) - self.assertDictEqual(quadratic.to_dict( - use_index=True), coefficients_dict_int) - self.assertDictEqual(quadratic.to_dict(use_index=False), - coefficients_dict_str) + self.assertDictEqual(quadratic.to_dict(use_name=False), coefficients_dict_int) + self.assertDictEqual(quadratic.to_dict(use_name=True), coefficients_dict_str) def test_evaluate(self): """ test evaluate. """ @@ -108,10 +103,10 @@ def test_evaluate(self): quadratic_program = QuadraticProgram() x = [quadratic_program.continuous_var() for _ in range(5)] - coefficients_list = [[i*j for i in range(5)] for j in range(5)] + coefficients_list = [[i * j for i in range(5)] for j in range(5)] quadratic = QuadraticExpression(quadratic_program, coefficients_list) - values_list = [i for i in range(len(x))] + values_list = list(range(len(x))) values_array = np.array(values_list) values_dict_int = {i: i for i in range(len(x))} values_dict_str = {'x{}'.format(i): i for i in range(len(x))} diff --git a/test/optimization/test_quadratic_objective.py b/test/optimization/test_quadratic_objective.py index 1b8e2c16fa..6da13e533c 100644 --- a/test/optimization/test_quadratic_objective.py +++ b/test/optimization/test_quadratic_objective.py @@ -43,7 +43,7 @@ def test_init(self): self.assertEqual(quadratic_program.objective.sense, ObjSense.MINIMIZE) constant = 1.0 - linear_coeffs = np.array([i for i in range(5)]) + linear_coeffs = np.array(range(5)) quadratic_coeffs = np.array([[i*j for i in range(5)] for j in range(5)]) quadratic_program.minimize(constant, linear_coeffs, quadratic_coeffs) @@ -76,7 +76,7 @@ def test_setters(self): quadratic_program.continuous_var() constant = 1.0 - linear_coeffs = np.array([i for i in range(5)]) + linear_coeffs = np.array(range(5)) quadratic_coeffs = np.array([[i*j for i in range(5)] for j in range(5)]) quadratic_program.objective.constant = constant diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index 35d493f6a5..1cf4df2b62 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -173,7 +173,6 @@ def test_substitute_variables(self): quadratic={('x', 'y'): -1, ('z', 'z'): 2}) q_p.linear_constraint({'x': 2, 'z': -1}, '==', 1) q_p.quadratic_constraint({'x': 2, 'z': -1}, {('y', 'z'): 3}, '<=', -1) - print(q_p.print_as_lp_string()) q_p2, status = q_p.substitute_variables(constants={'x': -1}) self.assertEqual(status.name, 'infeasible') @@ -181,57 +180,56 @@ def test_substitute_variables(self): self.assertEqual(status.name, 'infeasible') q_p2, status = q_p.substitute_variables(constants={'x': 0}) - self.assertDictEqual(q_p2.objective.linear.to_dict(use_index=False), {'y': 2}) - self.assertDictEqual(q_p2.objective.quadratic.to_dict(use_index=False), {('z', 'z'): 2}) + self.assertDictEqual(q_p2.objective.linear.to_dict(use_name=True), {'y': 2}) + self.assertDictEqual(q_p2.objective.quadratic.to_dict(use_name=True), {('z', 'z'): 2}) self.assertEqual(q_p2.objective.constant, 1) self.assertEqual(len(q_p2.linear_constraints), 1) self.assertEqual(len(q_p2.quadratic_constraints), 1) cst = q_p2.linear_constraints[0] - self.assertDictEqual(cst.linear.to_dict(use_index=False), {'z': -1}) + self.assertDictEqual(cst.linear.to_dict(use_name=True), {'z': -1}) self.assertEqual(cst.sense.name, 'EQ') self.assertEqual(cst.rhs, 1) cst = q_p2.quadratic_constraints[0] - self.assertDictEqual(cst.linear.to_dict(use_index=False), {'z': -1}) - self.assertDictEqual(cst.quadratic.to_dict(use_index=False), {('y', 'z'): 3}) + self.assertDictEqual(cst.linear.to_dict(use_name=True), {'z': -1}) + self.assertDictEqual(cst.quadratic.to_dict(use_name=True), {('y', 'z'): 3}) self.assertEqual(cst.sense.name, 'LE') self.assertEqual(cst.rhs, -1) q_p2, status = q_p.substitute_variables(constants={'z': -1}) - self.assertDictEqual(q_p2.objective.linear.to_dict(use_index=False), {'x': 1, 'y': 2}) - self.assertDictEqual(q_p2.objective.quadratic.to_dict(use_index=False), {('x', 'y'): -1}) + self.assertDictEqual(q_p2.objective.linear.to_dict(use_name=True), {'x': 1, 'y': 2}) + self.assertDictEqual(q_p2.objective.quadratic.to_dict(use_name=True), {('x', 'y'): -1}) self.assertEqual(q_p2.objective.constant, 3) self.assertEqual(len(q_p2.linear_constraints), 2) self.assertEqual(len(q_p2.quadratic_constraints), 0) cst = q_p2.linear_constraints[0] - self.assertDictEqual(cst.linear.to_dict(use_index=False), {'x': 2}) + self.assertDictEqual(cst.linear.to_dict(use_name=True), {'x': 2}) self.assertEqual(cst.sense.name, 'EQ') self.assertEqual(cst.rhs, 0) cst = q_p2.linear_constraints[1] - self.assertDictEqual(cst.linear.to_dict(use_index=False), {'x': 2, 'y': -3}) + self.assertDictEqual(cst.linear.to_dict(use_name=True), {'x': 2, 'y': -3}) self.assertEqual(cst.sense.name, 'LE') self.assertEqual(cst.rhs, -2) q_p2, status = q_p.substitute_variables(variables={'y': ('x', -0.5)}) - print(q_p2.print_as_lp_string()) - self.assertDictEqual(q_p2.objective.linear.to_dict(use_index=False), {}) - self.assertDictEqual(q_p2.objective.quadratic.to_dict(use_index=False), + self.assertDictEqual(q_p2.objective.linear.to_dict(use_name=True), {}) + self.assertDictEqual(q_p2.objective.quadratic.to_dict(use_name=True), {('x', 'x'): 0.5, ('z', 'z'): 2}) self.assertEqual(q_p2.objective.constant, 1) self.assertEqual(len(q_p2.linear_constraints), 1) self.assertEqual(len(q_p2.quadratic_constraints), 1) cst = q_p2.linear_constraints[0] - self.assertDictEqual(cst.linear.to_dict(use_index=False), {'x': 2, 'z': -1}) + self.assertDictEqual(cst.linear.to_dict(use_name=True), {'x': 2, 'z': -1}) self.assertEqual(cst.sense.name, 'EQ') self.assertEqual(cst.rhs, 1) cst = q_p2.quadratic_constraints[0] - self.assertDictEqual(cst.linear.to_dict(use_index=False), {'x': 2, 'z': -1}) - self.assertDictEqual(cst.quadratic.to_dict(use_index=False), {('x', 'z'): -1.5}) + self.assertDictEqual(cst.linear.to_dict(use_name=True), {'x': 2, 'z': -1}) + self.assertDictEqual(cst.quadratic.to_dict(use_name=True), {('x', 'z'): -1.5}) self.assertEqual(cst.sense.name, 'LE') self.assertEqual(cst.rhs, -1) From e601ae0d2b7626e705f3e59598a3fd47ca172d32 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 16 Apr 2020 21:35:56 +0900 Subject: [PATCH 217/323] allow integer as lower bound and upper bound of variable --- qiskit/optimization/problems/variable.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/qiskit/optimization/problems/variable.py b/qiskit/optimization/problems/variable.py index 12321c29a2..506697a7f4 100644 --- a/qiskit/optimization/problems/variable.py +++ b/qiskit/optimization/problems/variable.py @@ -15,7 +15,7 @@ """Variable interface""" from enum import Enum -from typing import Tuple +from typing import Tuple, Union from qiskit.optimization import infinity, QiskitOptimizationError from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram @@ -31,8 +31,10 @@ class VarType(Enum): class Variable(HasQuadraticProgram): """Representation of a variable.""" - def __init__(self, quadratic_program: 'QuadraticProgram', name: str, lowerbound: float = 0, - upperbound: float = infinity, vartype: VarType = VarType.CONTINUOUS) -> None: + def __init__(self, quadratic_program: 'QuadraticProgram', name: str, + lowerbound: Union[float, int] = 0, + upperbound: Union[float, int] = infinity, + vartype: VarType = VarType.CONTINUOUS) -> None: """Creates a new Variable. The variables is exposed by the top-level `QuadraticProgram` class @@ -68,7 +70,7 @@ def name(self) -> str: return self._name @property - def lowerbound(self) -> float: + def lowerbound(self) -> Union[float, int]: """Returns the lowerbound of the variable. Returns: @@ -77,7 +79,7 @@ def lowerbound(self) -> float: return self._lowerbound @lowerbound.setter - def lowerbound(self, lowerbound: float) -> None: + def lowerbound(self, lowerbound: Union[float, int]) -> None: """Sets the lowerbound of the variable. Args: @@ -91,7 +93,7 @@ def lowerbound(self, lowerbound: float) -> None: self._lowerbound = lowerbound @property - def upperbound(self) -> float: + def upperbound(self) -> Union[float, int]: """Returns the upperbound of the variable. Returns: @@ -100,7 +102,7 @@ def upperbound(self) -> float: return self._upperbound @upperbound.setter - def upperbound(self, upperbound: float) -> None: + def upperbound(self, upperbound: Union[float, int]) -> None: """Sets the upperbound of the variable. Args: @@ -132,11 +134,11 @@ def vartype(self, vartype: VarType) -> None: """ self._vartype = vartype - def as_tuple(self) -> Tuple[str, float, float, VarType]: + def as_tuple(self) -> Tuple[str, Union[float, int], Union[float, int], VarType]: """ Returns a tuple corresponding to this variable. Returns: A tuple corresponding to this variable consisting of name, lowerbound, upperbound and variable type. """ - return (self.name, self.lowerbound, self.upperbound, self.vartype) + return self.name, self.lowerbound, self.upperbound, self.vartype From f15989270a9bbb0bdb9883856fcf610bd973bd7e Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 16 Apr 2020 21:38:33 +0900 Subject: [PATCH 218/323] change qp as well --- qiskit/optimization/problems/quadratic_program.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index c3c336c82c..e9046adfbc 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -107,8 +107,8 @@ def variables_index(self) -> Dict[str, int]: """ return self._variables_index - def _add_variable(self, name: Optional[str] = None, lowerbound: float = 0, - upperbound: float = infinity, + def _add_variable(self, name: Optional[str] = None, lowerbound: Union[float, int] = 0, + upperbound: Union[float, int] = infinity, vartype: VarType = VarType.CONTINUOUS) -> Variable: """Checks whether a variable name is already taken and adds the variable to list and index if not. @@ -139,8 +139,8 @@ def _add_variable(self, name: Optional[str] = None, lowerbound: float = 0, self.variables.append(variable) return variable - def continuous_var(self, name: Optional[str] = None, lowerbound: float = 0, - upperbound: float = infinity) -> Variable: + def continuous_var(self, name: Optional[str] = None, lowerbound: Union[float, int] = 0, + upperbound: Union[float, int] = infinity) -> Variable: """Adds a continuous variable to the quadratic program. Args: @@ -170,8 +170,8 @@ def binary_var(self, name: Optional[str] = None) -> Variable: """ return self._add_variable(name, 0, 1, VarType.BINARY) - def integer_var(self, name: Optional[str] = None, lowerbound: float = 0, - upperbound: float = infinity) -> Variable: + def integer_var(self, name: Optional[str] = None, lowerbound: Union[float, int] = 0, + upperbound: Union[float, int] = infinity) -> Variable: """Adds an integer variable to the quadratic program. Args: From c511ac0ef2a1592740359888a2a1a1012d5440e9 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Thu, 16 Apr 2020 14:39:55 +0200 Subject: [PATCH 219/323] update int to bin converter to use new QP interface --- .../converters/integer_to_binary.py | 233 +++++++++++++++++ .../converters/integer_to_binary_converter.py | 235 ------------------ .../converters/quadratic_program_to_qubo.py | 4 +- .../problems/quadratic_program.py | 48 ++-- 4 files changed, 265 insertions(+), 255 deletions(-) create mode 100644 qiskit/optimization/converters/integer_to_binary.py delete mode 100644 qiskit/optimization/converters/integer_to_binary_converter.py diff --git a/qiskit/optimization/converters/integer_to_binary.py b/qiskit/optimization/converters/integer_to_binary.py new file mode 100644 index 0000000000..f508479fc2 --- /dev/null +++ b/qiskit/optimization/converters/integer_to_binary.py @@ -0,0 +1,233 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +"""The converter to map integer variables in a quadratic program to binary variables.""" + +import copy +from typing import Dict, List, Optional, Tuple +import logging +import numpy as np + +from ..problems.quadratic_program import QuadraticProgram +from ..problems.quadratic_objective import ObjSense +from ..problems.variable import VarType +from ..exceptions import QiskitOptimizationError + +logger = logging.getLogger(__name__) + + +class IntegerToBinary: + """Convert an `QuadraticProgram` into new one by encoding integer with binary variables. + + This bounded-coefficient encoding used in this converted is proposed in [1], Eq. (5). + + Examples: + >>> problem = QuadraticProgram() + >>> problem.integer_var(name='x', lowerbound=0, upperbound=10) + >>> conv = IntegerToBinary() + >>> problem2 = conv.encode(problem) + + References: + [1]: Sahar Karimi, Pooya Ronagh (2017), Practical Integer-to-Binary Mapping for Quantum + Annealers. arxiv.org:1706.01945. + """ + + _delimiter = '@' # users are supposed not to use this character in variable names + + def __init__(self) -> None: + """Initializes the internal data structure.""" + + self._src = None + self._dst = None + self._conv: Dict[str, List[Tuple[str, int]]] = {} + # e.g., self._conv = {'x': [('x@1', 1), ('x@2', 2)]} + + def encode(self, op: QuadraticProgram, name: Optional[str] = None) -> QuadraticProgram: + """Convert an integer problem into a new problem with binary variables. + + Args: + op: The problem to be solved, that may contain integer variables. + name: The name of the converted problem. If not provided, the name of the input + problem is used. + + Returns: + The converted problem, that contains no integer variables. + + Raises: + QiskitOptimizationError: if variable or constraint type is not supported. + """ + + self._src = copy.deepcopy(op) + self._dst = QuadraticProgram() + if name: + self._dst.name = name + else: + self._dst.name = self._src.name + + # declare variables + for x in self._src.variables: + if x.vartype == VarType.INTEGER: + new_vars: List[Tuple[str, int]] = self._encode_var( + name=x.name, lowerbound=x.lowerbound, upperbound=x.upperbound + ) + self._conv[x] = new_vars + for (var_name, _) in new_vars: + self._dst.binary_var(name=var_name) + else: + if x.vartype == VarType.CONTINUOUS: + self._dst.continuous_var(name=x.name, lowerbound=x.lowerbound, + upperbound=x.upperbound) + elif x.vartype == VarType.BINARY: + self._dst.binary_var(name=x.name) + else: + raise QiskitOptimizationError("Unsupported variable type {}".format(x.vartype)) + + self._substitute_int_var() + + return self._dst + + def _encode_var(self, name: str, lowerbound: int, upperbound: int) -> List[Tuple[str, int]]: + var_range = upperbound - lowerbound + power = int(np.log2(var_range)) + bounded_coef = var_range - (2 ** power - 1) + + lst = [] + for i in range(power): + coef = 2 ** i + new_name = name + self._delimiter + str(i) + lst.append((new_name, coef)) + + new_name = name + self._delimiter + str(power) + lst.append((new_name, bounded_coef)) + + return lst + + def _encode_linear_coefficients_dict(self, coefficients: Dict[str, float]) -> Dict[str, float]: + + constant = 0 + linear = {} + for name, v in coefficients.items(): + x = self._src.get_variable(name) + if x in self._conv: + for y, coeff in self._conv[x]: + linear[y] = v * coeff + constant += v * x.lowerbound + else: + linear[x.name] = v + + return linear, constant + + def _encode_quadratic_coefficients_dict(self, coefficients: Dict[Tuple[str, str], float]) \ + -> Dict[Tuple[str, str], float]: + + constant = 0 + linear = {} + quadratic = {} + for (name_i, name_j), v in coefficients.items(): + x = self._src.get_variable(name_i) + y = self._src.get_variable(name_j) + + if x in self._conv and y not in self._conv: + for z_x, coeff_x in self._conv[x]: + quadratic[(z_x, y.name)] = v * coeff_x + linear[y.name] = linear.get(y.name, 0.0) + v * x.lowerbound + + elif x not in self._conv and y in self._conv: + for z_y, coeff_y in self._conv[y]: + quadratic[(x.name, z_y)] = v * coeff_y + linear[x.name] = linear.get(x.name, 0.0) + v * y.lowerbound + + elif x in self._conv and y in self._conv: + for z_x, coeff_x in self._conv[x]: + for z_y, coeff_y in self._conv[y]: + quadratic[(z_x, z_y)] = v * coeff_x * coeff_y + + for z_x, coeff_x in self._conv[x]: + linear[z_x] = linear.get(z_x, 0.0) + v * y.lowerbound + for z_y, coeff_y in self._conv[y]: + linear[z_y] = linear.get(z_y, 0.0) + v * x.lowerbound + + constant += v * x.lowerbound * y.lowerbound + + else: + quadratic[(x.name, y.name)] = v + + return quadratic, linear, constant + + def _substitute_int_var(self): + + # set objective + linear, linear_constant = self._encode_linear_coefficients_dict( + self._src.objective.linear.to_dict(use_index=False)) + quadratic, quadratic_linear, quadratic_constant = \ + self._encode_quadratic_coefficients_dict( + self._src.objective.quadratic.to_dict(use_index=False)) + + constant = self._src.objective.constant + linear_constant + quadratic_constant + for i, v in quadratic_linear.items(): + linear[i] = linear.get(i, 0) + v + + if self._src.objective.sense == ObjSense.MINIMIZE: + self._dst.minimize(constant, + linear, + quadratic) + else: + self._dst.maximize(constant, + linear, + quadratic) + + # set linear constraints + for constraint in self._src.linear_constraints: + linear, constant = self._encode_linear_coefficients_dict(constraint.linear.to_dict()) + self._dst.linear_constraint(constraint.name, linear, constraint.sense, + constraint.rhs - constant) + + # set quadratic constraints + for constraint in self._src.quadratic_constraints: + linear, linear_constant = self._encode_linear_coefficients_dict( + constraint.linear.to_dict()) + quadratic, quadratic_linear, quadratic_constant = \ + self._encode_quadratic_coefficients_dict(constraint.quadratic.to_dict()) + + constant = linear_constant + quadratic_constant + for i, v in quadratic_linear.items(): + linear[i] = linear.get(i, 0) + v + + self._dst.quadratic_constraint(constraint.name, linear, quadratic, constraint.sense, + constraint.rhs - constant) + + def decode(self, result: 'OptimizationResult') -> 'OptimizationResult': + """Convert the encoded problem (binary variables) back to the original (integer variables). + + Args: + result: The result of the converted problem. + + Returns: + The result of the original problem. + """ + vals = result.x + new_vals = self._decode_var(vals) + result.x = new_vals + return result + + def _decode_var(self, vals) -> List[int]: + # decode integer values + sol = {x.name: float(vals[i]) for i, x in enumerate(self._dst.variables)} + new_vals = [] + for x in self._src.variables: + if x in self._conv: + new_vals.append(sum(sol[aux] * coef for aux, coef in self._conv[x]) + x.lowerbound) + else: + new_vals.append(sol[x.name]) + return new_vals diff --git a/qiskit/optimization/converters/integer_to_binary_converter.py b/qiskit/optimization/converters/integer_to_binary_converter.py deleted file mode 100644 index 933c98fb1d..0000000000 --- a/qiskit/optimization/converters/integer_to_binary_converter.py +++ /dev/null @@ -1,235 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""The converter to convert an integer problem to a binary problem.""" - -import copy -from typing import Dict, List, Optional, Tuple -import logging -import numpy as np - -from ..problems.quadratic_program import QuadraticProgram -from ..exceptions import QiskitOptimizationError - -logger = logging.getLogger(__name__) - -_HAS_CPLEX = False -try: - from cplex import SparsePair - _HAS_CPLEX = True -except ImportError: - logger.info('CPLEX is not installed.') - - -class IntegerToBinaryConverter: - """Convert an `QuadraticProgram` into new one by encoding integer with binary variables. - - This bounded-coefficient encoding used in this converted is proposed in [1], Eq. (5). - - Examples: - >>> problem = QuadraticProgram() - >>> problem.variables.add(names=['x'], types=['I'], lb=[0], ub=[10]) - >>> conv = IntegerToBinaryConverter() - >>> problem2 = conv.encode(problem) - - References: - [1]: Sahar Karimi, Pooya Ronagh (2017), Practical Integer-to-Binary Mapping for Quantum - Annealers. arxiv.org:1706.01945. - """ - - _delimiter = '@' # users are supposed not to use this character in variable names - - def __init__(self) -> None: - """Initializes the internal data structure.""" - if not _HAS_CPLEX: - raise NameError('CPLEX is not installed.') - - self._src = None - self._dst = None - self._conv: Dict[str, List[Tuple[str, int]]] = {} - # e.g., self._conv = {'x': [('x@1', 1), ('x@2', 2)]} - - def encode(self, op: QuadraticProgram, name: Optional[str] = None) -> QuadraticProgram: - """Convert an integer problem into a new problem with binary variables. - - Args: - op: The problem to be solved, that may contain integer variables. - name: The name of the converted problem. If not provided, the name of the input - problem is used. - - Returns: - The converted problem, that contains no integer variables. - """ - - self._src = copy.deepcopy(op) - self._dst = QuadraticProgram() - if name: - self._dst.set_problem_name(name) - else: - self._dst.set_problem_name(self._src.get_problem_name()) - - # declare variables - names = self._src.variables.get_names() - types = self._src.variables.get_types() - lower_bounds = self._src.variables.get_lower_bounds() - upper_bounds = self._src.variables.get_upper_bounds() - for i, variable in enumerate(names): - typ = types[i] - if typ == "I": - new_vars: List[Tuple[str, int]] = self._encode_var( - name=variable, lower_bound=lower_bounds[i], upper_bound=upper_bounds[i] - ) - self._conv[variable] = new_vars - self._dst.variables.add( - names=[new_name for new_name, _ in new_vars], types="B" * len(new_vars) - ) - else: - self._dst.variables.add( - names=[variable], types=typ, lb=[lower_bounds[i]], ub=[upper_bounds[i]] - ) - - self._substitute_int_var() - - return self._dst - - def _encode_var(self, name: str, lower_bound: int, upper_bound: int) -> List[Tuple[str, int]]: - var_range = upper_bound - lower_bound - power = int(np.log2(var_range)) - bounded_coef = var_range - (2 ** power - 1) - - lst = [] - for i in range(power): - coef = 2 ** i - new_name = name + self._delimiter + str(i) - lst.append((new_name, coef)) - - new_name = name + self._delimiter + str(power) - lst.append((new_name, bounded_coef)) - - return lst - - def _substitute_int_var(self): - # set objective name - self._dst.objective.set_name(self._src.objective.get_name()) - - # set the sense of the objective function - self._dst.objective.set_sense(self._src.objective.get_sense()) - - # set offset - self._dst.objective.set_offset(self._src.objective.get_offset()) - - # set linear terms of objective function - src_obj_linear = self._src.objective.get_linear_dict() - - for src_var_index in src_obj_linear: - coef = src_obj_linear[src_var_index] - var_name = self._src.variables.get_names(src_var_index) - - if var_name in self._conv: - for converted_name, converted_coef in self._conv[var_name]: - self._dst.objective.set_linear(converted_name, coef * converted_coef) - - else: - self._dst.objective.set_linear(var_name, coef) - - # set quadratic terms of objective function - src_obj_quad = self._src.objective.get_quadratic_dict() - - num_var = self._dst.variables.get_num() - new_quad = np.zeros((num_var, num_var)) - - for (row, col), coef in src_obj_quad.items(): - row_var_name = self._src.variables.get_names(row) - col_var_name = self._src.variables.get_names(col) - - if row_var_name in self._conv: - row_vars = self._conv[row_var_name] - else: - row_vars = [(row_var_name, 1)] - - if col_var_name in self._conv: - col_vars = self._conv[col_var_name] - else: - col_vars = [(col_var_name, 1)] - - for new_row, row_coef in row_vars: - for new_col, col_coef in col_vars: - row_index = self._dst.variables.get_indices(new_row) - col_index = self._dst.variables.get_indices(new_col) - new_quad[row_index, col_index] = coef * row_coef * col_coef - - ind = list(range(num_var)) - lst = [] - for i in ind: - lst.append(SparsePair(ind=ind, val=new_quad[i].tolist())) - self._dst.objective.set_quadratic(lst) - - # set constraints whose integer variables are replaced with binary variables - linear_rows = self._src.linear_constraints.get_rows() - linear_sense = self._src.linear_constraints.get_senses() - linear_rhs = self._src.linear_constraints.get_rhs() - linear_ranges = self._src.linear_constraints.get_range_values() - linear_names = self._src.linear_constraints.get_names() - - lin_expr = [] - - for i, linear_row in enumerate(linear_rows): - sparse_pair = SparsePair() - for j, var_ind in enumerate(linear_row.ind): - coef = linear_row.val[j] - var_name = self._src.variables.get_names(var_ind) - - if var_name in self._conv: - for converted_name, converted_coef in self._conv[var_name]: - sparse_pair.ind.append(converted_name) - sparse_pair.val.append(converted_coef * coef) - else: - sparse_pair.ind.append(var_name) - sparse_pair.val.append(coef) - - lin_expr.append(sparse_pair) - - self._dst.linear_constraints.add( - lin_expr, linear_sense, linear_rhs, linear_ranges, linear_names - ) - - # TODO: add quadratic constraints - if self._src.quadratic_constraints.get_num() > 0: - raise QiskitOptimizationError('Quadratic constraints are not yet supported.') - - def decode(self, result: 'OptimizationResult') -> 'OptimizationResult': - """Convert the encoded problem (binary variables) back to the original (integer variables). - - Args: - result: The result of the converted problem. - - Returns: - The result of the original problem. - """ - names = self._dst.variables.get_names() - vals = result.x - new_vals = self._decode_var(names, vals) - result.x = new_vals - return result - - def _decode_var(self, names, vals) -> List[int]: - # decode integer values - sol = {name: float(vals[i]) for i, name in enumerate(names)} - new_vals = [] - for name in self._src.variables.get_names(): - if name in self._conv: - new_vals.append(sum(sol[aux] * coef for aux, coef in self._conv[name])) - else: - new_vals.append(sol[name]) - return new_vals diff --git a/qiskit/optimization/converters/quadratic_program_to_qubo.py b/qiskit/optimization/converters/quadratic_program_to_qubo.py index e4d0c4497f..55e21a22e0 100644 --- a/qiskit/optimization/converters/quadratic_program_to_qubo.py +++ b/qiskit/optimization/converters/quadratic_program_to_qubo.py @@ -18,7 +18,7 @@ from qiskit.optimization.problems import QuadraticProgram from qiskit.optimization.converters import (PenalizeLinearEqualityConstraints, - IntegerToBinaryConverter) + IntegerToBinary) from qiskit.optimization.exceptions import QiskitOptimizationError @@ -37,7 +37,7 @@ def __init__(self, penalty: Optional[float] = 1e5) -> None: Args: penalty: Penalty factor to scale equality constraints that are added to objective. """ - self._int_to_bin = IntegerToBinaryConverter() + self._int_to_bin = IntegerToBinary() self._penalize_lin_eq_constraints = PenalizeLinearEqualityConstraints() self._penalty = penalty diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index c3c336c82c..c318891210 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -17,6 +17,7 @@ from typing import List, Union, Dict, Optional, Tuple from docplex.mp.linear import Var +from docplex.mp.quad import QuadExpr from docplex.mp.model import Model from docplex.mp.model_reader import ModelReader from numpy import ndarray @@ -470,35 +471,45 @@ def from_docplex(self, model: Model) -> None: self.name = model.name # get variables + # keep track of names separately, since docplex allows to have None names. + var_names = {} for x in model.iter_variables(): if x.get_vartype().one_letter_symbol() == 'C': - self.continuous_var(x.name, x.lb, x.ub) + x_new = self.continuous_var(x.name, x.lb, x.ub) + var_names[x] = x_new.name elif x.get_vartype().one_letter_symbol() == 'B': - self.binary_var(x.name) + x_new = self.binary_var(x.name) + var_names[x] = x_new.name elif x.get_vartype().one_letter_symbol() == 'I': - self.integer_var(x.name, x.lb, x.ub) + x_new = self.integer_var(x.name, x.lb, x.ub) + var_names[x] = x_new.name else: raise QiskitOptimizationError("Unsupported variable type!") # objective sense minimize = model.objective_sense.is_minimize() + # make sure objective expression is linear or quadratic and not a variable + if isinstance(model.objective_expr, Var): + model.objective_expr = model.objective_expr + 0 + # get objective offset constant = model.objective_expr.constant # get linear part of objective - linear_part = model.objective_expr.get_linear_part() linear = {} + linear_part = model.objective_expr.get_linear_part() for x in linear_part.iter_variables(): - linear[x.name] = linear_part.get_coef(x) + linear[var_names[x]] = linear_part.get_coef(x) # get quadratic part of objective quadratic = {} - for quad_triplet in model.objective_expr.generate_quad_triplets(): - i = quad_triplet[0].name - j = quad_triplet[1].name - v = quad_triplet[2] - quadratic[i, j] = v + if isinstance(model.objective_expr, QuadExpr): + for quad_triplet in model.objective_expr.generate_quad_triplets(): + i = var_names[quad_triplet[0]] + j = var_names[quad_triplet[1]] + v = quad_triplet[2] + quadratic[i, j] = v # set objective if minimize: @@ -546,27 +557,28 @@ def from_docplex(self, model: Model) -> None: if left_expr.is_quad_expr(): for x in left_expr.linear_part.iter_variables(): - linear[x.name] = left_expr.linear_part.get_coef(x) + linear[var_names[x]] = left_expr.linear_part.get_coef(x) for quad_triplet in left_expr.iter_quad_triplets(): - i = quad_triplet[0].name - j = quad_triplet[1].name + i = var_names[quad_triplet[0]] + j = var_names[quad_triplet[1]] v = quad_triplet[2] quadratic[i, j] = v else: for x in left_expr.iter_variables(): - linear[x.name] = left_expr.get_coef(x) + linear[var_names[x]] = left_expr.get_coef(x) if right_expr.is_quad_expr(): for x in right_expr.linear_part.iter_variables(): - linear[x.name] = linear.get(x.name, 0.0) - right_expr.linear_part.get_coef(x) + linear[var_names[x]] = linear.get(var_names[x], 0.0) - \ + right_expr.linear_part.get_coef(x) for quad_triplet in right_expr.iter_quad_triplets(): - i = quad_triplet[0].name - j = quad_triplet[1].name + i = var_names[quad_triplet[0]] + j = var_names[quad_triplet[1]] v = quad_triplet[2] quadratic[i, j] = quadratic.get((i, j), 0.0) - v else: for x in right_expr.iter_variables(): - linear[x.name] = linear.get(x.name, 0.0) - right_expr.get_coef(x) + linear[var_names[x]] = linear.get(var_names[x], 0.0) - right_expr.get_coef(x) if sense == sense.EQ: self.quadratic_constraint(name, linear, quadratic, '==', rhs) From b2b96e774acc1251176d5bf782bc84eb0a48cdfc Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Thu, 16 Apr 2020 14:58:22 +0200 Subject: [PATCH 220/323] rename converters without "converter" in name --- qiskit/optimization/converters/__init__.py | 4 ++-- ...ity_to_equality_converter.py => inequality_to_equality.py} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename qiskit/optimization/converters/{inequality_to_equality_converter.py => inequality_to_equality.py} (99%) diff --git a/qiskit/optimization/converters/__init__.py b/qiskit/optimization/converters/__init__.py index 799368f845..529f205d7e 100644 --- a/qiskit/optimization/converters/__init__.py +++ b/qiskit/optimization/converters/__init__.py @@ -30,8 +30,8 @@ from .quadratic_program_to_negative_value_oracle import QuadraticProgramToNegativeValueOracle # opt problem dependency -from .integer_to_binary_converter import IntegerToBinaryConverter -from .inequality_to_equality_converter import InequalityToEqualityConverter +from .integer_to_binary import IntegerToBinary +from .inequality_to_equality import InequalityToEquality from .quadratic_program_to_qubo import QuadraticProgramToQubo __all__ = [ diff --git a/qiskit/optimization/converters/inequality_to_equality_converter.py b/qiskit/optimization/converters/inequality_to_equality.py similarity index 99% rename from qiskit/optimization/converters/inequality_to_equality_converter.py rename to qiskit/optimization/converters/inequality_to_equality.py index 0965390843..15aac9d596 100644 --- a/qiskit/optimization/converters/inequality_to_equality_converter.py +++ b/qiskit/optimization/converters/inequality_to_equality.py @@ -32,13 +32,13 @@ logger.info('CPLEX is not installed.') -class InequalityToEqualityConverter: +class InequalityToEquality: """Convert inequality constraints into equality constraints by introducing slack variables. Examples: >>> problem = QuadraticProgram() >>> # define a problem - >>> conv = InequalityToEqualityConverter() + >>> conv = InequalityToEquality() >>> problem2 = conv.encode(problem) """ From 75d6238115ffefa123afc7f413f5c6ba6d5d5db6 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Thu, 16 Apr 2020 15:43:14 +0200 Subject: [PATCH 221/323] update qubo to operator + some minor fixes --- .../converters/integer_to_binary.py | 12 ++-- .../quadratic_program_to_operator.py | 58 ++++++++----------- .../problems/quadratic_program.py | 37 ++++++------ test/optimization/test_quadratic_program.py | 12 ++-- 4 files changed, 57 insertions(+), 62 deletions(-) diff --git a/qiskit/optimization/converters/integer_to_binary.py b/qiskit/optimization/converters/integer_to_binary.py index f508479fc2..ca76caf95f 100644 --- a/qiskit/optimization/converters/integer_to_binary.py +++ b/qiskit/optimization/converters/integer_to_binary.py @@ -169,10 +169,10 @@ def _substitute_int_var(self): # set objective linear, linear_constant = self._encode_linear_coefficients_dict( - self._src.objective.linear.to_dict(use_index=False)) + self._src.objective.linear.to_dict(use_name=True)) quadratic, quadratic_linear, quadratic_constant = \ self._encode_quadratic_coefficients_dict( - self._src.objective.quadratic.to_dict(use_index=False)) + self._src.objective.quadratic.to_dict(use_name=True)) constant = self._src.objective.constant + linear_constant + quadratic_constant for i, v in quadratic_linear.items(): @@ -190,8 +190,8 @@ def _substitute_int_var(self): # set linear constraints for constraint in self._src.linear_constraints: linear, constant = self._encode_linear_coefficients_dict(constraint.linear.to_dict()) - self._dst.linear_constraint(constraint.name, linear, constraint.sense, - constraint.rhs - constant) + self._dst.linear_constraint(linear, constraint.sense, + constraint.rhs - constant, constraint.name) # set quadratic constraints for constraint in self._src.quadratic_constraints: @@ -204,8 +204,8 @@ def _substitute_int_var(self): for i, v in quadratic_linear.items(): linear[i] = linear.get(i, 0) + v - self._dst.quadratic_constraint(constraint.name, linear, quadratic, constraint.sense, - constraint.rhs - constant) + self._dst.quadratic_constraint(linear, quadratic, constraint.sense, + constraint.rhs - constant, constraint.name) def decode(self, result: 'OptimizationResult') -> 'OptimizationResult': """Convert the encoded problem (binary variables) back to the original (integer variables). diff --git a/qiskit/optimization/converters/quadratic_program_to_operator.py b/qiskit/optimization/converters/quadratic_program_to_operator.py index 329f517a9d..5c7190cbce 100644 --- a/qiskit/optimization/converters/quadratic_program_to_operator.py +++ b/qiskit/optimization/converters/quadratic_program_to_operator.py @@ -15,7 +15,7 @@ """The converter from an ```QuadraticProgram``` to ``Operator``.""" -from typing import Dict, Tuple +from typing import Tuple import numpy as np from qiskit.quantum_info import Pauli @@ -32,8 +32,6 @@ class QuadraticProgramToOperator: def __init__(self) -> None: """Initialize the internal data structure.""" self._src = None - self._q_d: Dict[int, int] = {} - # e.g., self._q_d = {0: 0} def encode(self, op: QuadraticProgram) -> Tuple[WeightedPauliOperator, float]: """Convert a problem into a qubit operator @@ -52,71 +50,65 @@ def encode(self, op: QuadraticProgram) -> Tuple[WeightedPauliOperator, float]: self._src = op # if op has variables that are not binary, raise an error - var_list = self._src.variables.get_types() - if not all(var == 'B' for var in var_list): + if self._src.get_num_vars() > self._src.get_num_binary_vars(): raise QiskitOptimizationError('The type of variable must be a binary variable.') # if constraints exist, raise an error - if self._src.linear_constraints.get_num() > 0 \ - or self._src.quadratic_constraints.get_num() > 0: + if self._src.linear_constraints \ + or self._src.quadratic_constraints: raise QiskitOptimizationError('An constraint exists. ' 'The method supports only model with no constraints.') - # assign variables of the model to qubits. - _q_d = {} - qubit_index = 0 - for name in self._src.variables.get_names(): - var_index = self._src.variables.get_indices(name) - _q_d[var_index] = qubit_index - qubit_index += 1 - # initialize Hamiltonian. - num_nodes = len(_q_d) + num_nodes = self._src.get_num_vars() pauli_list = [] shift = 0 zero = np.zeros(num_nodes, dtype=np.bool) # set a sign corresponding to a maximized or minimized problem. # sign == 1 is for minimized problem. sign == -1 is for maximized problem. - sense = self._src.objective.get_sense() + sense = self._src.objective.sense.value # convert a constant part of the object function into Hamiltonian. - shift += self._src.objective.get_offset() * sense + shift += self._src.objective.constant * sense # convert linear parts of the object function into Hamiltonian. - for i, coef in self._src.objective.get_linear_dict().items(): + for i, coef in self._src.objective.linear.to_dict().items(): z_p = np.zeros(num_nodes, dtype=np.bool) - qubit_index = _q_d[i] weight = coef * sense / 2 - z_p[qubit_index] = True + z_p[i] = True pauli_list.append([-weight, Pauli(z_p, zero)]) shift += weight # convert quadratic parts of the object function into Hamiltonian. - for (i, j), coef in self._src.objective.get_quadratic_dict().items(): + # first merge coefficients (i, j) and (j, i) + coeffs = {} + for (i, j), coeff in self._src.objective.quadratic.to_dict().items(): if j < i: - continue - qubit_index_1 = _q_d[i] - qubit_index_2 = _q_d[j] - if i == j: - coef = coef / 2 - weight = coef * sense / 4 + coeffs[(j, i)] = coeffs.get((j, i), 0.0) + coeff + else: + coeffs[(i, j)] = coeffs.get((i, j), 0.0) + coeff - if qubit_index_1 == qubit_index_2: + # create Pauli terms + for (i, j), coeff in coeffs.items(): + + weight = coeff * sense / 4 + + if i == j: shift += weight else: z_p = np.zeros(num_nodes, dtype=np.bool) - z_p[qubit_index_1] = True - z_p[qubit_index_2] = True + z_p[i] = True + z_p[j] = True pauli_list.append([weight, Pauli(z_p, zero)]) z_p = np.zeros(num_nodes, dtype=np.bool) - z_p[qubit_index_1] = True + z_p[i] = True pauli_list.append([-weight, Pauli(z_p, zero)]) z_p = np.zeros(num_nodes, dtype=np.bool) - z_p[qubit_index_2] = True + z_p[j] = True pauli_list.append([-weight, Pauli(z_p, zero)]) shift += weight diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 49a4f03b1f..7dd4128948 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -115,17 +115,18 @@ def variables_index(self) -> Dict[str, int]: """ return self._variables_index - def _add_variable(self, name: Optional[str] = None, lowerbound: Union[float, int] = 0, + def _add_variable(self, lowerbound: Union[float, int] = 0, upperbound: Union[float, int] = infinity, - vartype: VarType = VarType.CONTINUOUS) -> Variable: + vartype: VarType = VarType.CONTINUOUS, + name: Optional[str] = None) -> Variable: """Checks whether a variable name is already taken and adds the variable to list and index if not. Args: - name: The name of the variable. lowerbound: The lowerbound of the variable. upperbound: The upperbound of the variable. vartype: The type of the variable. + name: The name of the variable. Returns: The added variable. @@ -147,14 +148,15 @@ def _add_variable(self, name: Optional[str] = None, lowerbound: Union[float, int self.variables.append(variable) return variable - def continuous_var(self, name: Optional[str] = None, lowerbound: Union[float, int] = 0, - upperbound: Union[float, int] = infinity) -> Variable: + def continuous_var(self, lowerbound: Union[float, int] = 0, + upperbound: Union[float, int] = infinity, + name: Optional[str] = None) -> Variable: """Adds a continuous variable to the quadratic program. Args: - name: The name of the variable. lowerbound: The lowerbound of the variable. upperbound: The upperbound of the variable. + name: The name of the variable. Returns: The added variable. @@ -162,7 +164,7 @@ def continuous_var(self, name: Optional[str] = None, lowerbound: Union[float, in Raises: QiskitOptimizationError: if the variable name is already occupied. """ - return self._add_variable(name, lowerbound, upperbound, VarType.CONTINUOUS) + return self._add_variable(lowerbound, upperbound, VarType.CONTINUOUS, name) def binary_var(self, name: Optional[str] = None) -> Variable: """Adds a binary variable to the quadratic program. @@ -176,16 +178,17 @@ def binary_var(self, name: Optional[str] = None) -> Variable: Raises: QiskitOptimizationError: if the variable name is already occupied. """ - return self._add_variable(name, 0, 1, VarType.BINARY) + return self._add_variable(0, 1, VarType.BINARY, name) - def integer_var(self, name: Optional[str] = None, lowerbound: Union[float, int] = 0, - upperbound: Union[float, int] = infinity) -> Variable: + def integer_var(self, lowerbound: Union[float, int] = 0, + upperbound: Union[float, int] = infinity, + name: Optional[str] = None) -> Variable: """Adds an integer variable to the quadratic program. Args: - name: The name of the variable. lowerbound: The lowerbound of the variable. upperbound: The upperbound of the variable. + name: The name of the variable. Returns: The added variable. @@ -193,7 +196,7 @@ def integer_var(self, name: Optional[str] = None, lowerbound: Union[float, int] Raises: QiskitOptimizationError: if the variable name is already occupied. """ - return self._add_variable(name, lowerbound, upperbound, VarType.INTEGER) + return self._add_variable(lowerbound, upperbound, VarType.INTEGER, name) def get_variable(self, i: Union[int, str]) -> Variable: """Returns a variable for a given name or index. @@ -274,13 +277,13 @@ def linear_constraint(self, linear * x sense rhs. Args: - name: The name of the constraint. linear: The linear coefficients of the left-hand-side of the constraint. sense: The sense of the constraint, - '==', '=', 'E', and 'EQ' denote 'equal to'. - '>=', '>', 'G', and 'GE' denote 'greater-than-or-equal-to'. - '<=', '<', 'L', and 'LE' denote 'less-than-or-equal-to'. rhs: The right hand side of the constraint. + name: The name of the constraint. Returns: The added constraint. @@ -360,7 +363,6 @@ def quadratic_constraint(self, x * Q * x <= rhs. Args: - name: The name of the constraint. linear: The linear coefficients of the constraint. quadratic: The quadratic coefficients of the constraint. sense: The sense of the constraint, @@ -368,6 +370,7 @@ def quadratic_constraint(self, - '>=', '>', 'G', and 'GE' denote 'greater-than-or-equal-to'. - '<=', '<', 'L', and 'LE' denote 'less-than-or-equal-to'. rhs: The right hand side of the constraint. + name: The name of the constraint. Returns: The added constraint. @@ -482,13 +485,13 @@ def from_docplex(self, model: Model) -> None: var_names = {} for x in model.iter_variables(): if x.get_vartype().one_letter_symbol() == 'C': - x_new = self.continuous_var(x.name, x.lb, x.ub) + x_new = self.continuous_var(x.lb, x.ub, x.name) var_names[x] = x_new.name elif x.get_vartype().one_letter_symbol() == 'B': x_new = self.binary_var(x.name) var_names[x] = x_new.name elif x.get_vartype().one_letter_symbol() == 'I': - x_new = self.integer_var(x.name, x.lb, x.ub) + x_new = self.integer_var(x.lb, x.ub, x.name) var_names[x] = x_new.name else: raise QiskitOptimizationError("Unsupported variable type!") @@ -873,7 +876,7 @@ def _variables(self) -> SubstitutionStatus: lowerbound = var.lowerbound upperbound = var.upperbound if name not in self._subs: - self._dst._add_variable(name, lowerbound, upperbound, vartype) + self._dst._add_variable(lowerbound, upperbound, vartype, name) for i, (j, v) in self._subs.items(): lb_i = self._src.get_variable(i).lowerbound diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index 1cf4df2b62..3d3acae6df 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -152,9 +152,9 @@ def test_write_problem(self): def test_docplex(self): q_p = QuadraticProgram('test') - q_p.binary_var('x') - q_p.integer_var('y', lowerbound=-2, upperbound=4) - q_p.continuous_var('z', lowerbound=-1.5, upperbound=3.2) + q_p.binary_var(name='x') + q_p.integer_var(name='y', lowerbound=-2, upperbound=4) + q_p.continuous_var(name='z', lowerbound=-1.5, upperbound=3.2) q_p.minimize(constant=1, linear={'x': 1, 'y': 2}, quadratic={('x', 'y'): -1, ('z', 'z'): 2}) q_p.linear_constraint({'x': 2, 'z': -1}, '==', 1) @@ -166,9 +166,9 @@ def test_docplex(self): def test_substitute_variables(self): q_p = QuadraticProgram('test') - q_p.binary_var('x') - q_p.integer_var('y', lowerbound=-2, upperbound=4) - q_p.continuous_var('z', lowerbound=-1.5, upperbound=3.2) + q_p.binary_var(name='x') + q_p.integer_var(name='y', lowerbound=-2, upperbound=4) + q_p.continuous_var(name='z', lowerbound=-1.5, upperbound=3.2) q_p.minimize(constant=1, linear={'x': 1, 'y': 2}, quadratic={('x', 'y'): -1, ('z', 'z'): 2}) q_p.linear_constraint({'x': 2, 'z': -1}, '==', 1) From 6702696fe20e41b6eaaf41aa9cd6753c8911b03c Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Thu, 16 Apr 2020 14:48:00 +0100 Subject: [PATCH 222/323] moving to a new optimization stack --- .../optimization/algorithms/admm_optimizer.py | 204 +++++++++++------- test/optimization/test_admm.py | 4 +- 2 files changed, 124 insertions(+), 84 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 38d011821b..886afca5be 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -17,6 +17,7 @@ import time from typing import List, Optional, Any import numpy as np +from scipy.linalg import block_diag from qiskit.optimization.algorithms.cplex_optimizer import CplexOptimizer from qiskit.optimization.algorithms.optimization_algorithm import (OptimizationAlgorithm, @@ -112,7 +113,7 @@ def __init__(self, # Indices of the variables self.binary_indices = binary_indices self.continuous_indices = continuous_indices - self.sense = op.objective.sense + self.sense = op.objective.sense.value # define heavily used matrix, they are used at each iteration, so let's cache them, # they are np.ndarrays @@ -438,7 +439,7 @@ def _get_c(self, variable_indices: List[int]) -> np.ndarray: A numpy array of the shape(len(variable_indices)). """ # c = np.array(self._state.op.objective.get_linear(variable_indices)) - c = self._state.op.objective.linear.coefficients_as_array().take(variable_indices) + c = self._state.op.objective.linear.to_array().take(variable_indices) # flip the sign, according to the optimization sense, e.g. sense == 1 if minimize, # sense == -1 if maximize. c *= self._state.sense @@ -514,9 +515,9 @@ def _get_a0_b0(self) -> (np.ndarray, np.ndarray): # we check only equality constraints here. if constraint.sense != ConstraintSense.EQ: continue - # todo: implement verification condition + # todo: add condition that only binary variables are in the constraint # row = self._state.op.linear_constraints.get_rows(constraint_index) - row = self._state.op.linear_constraints[constraint_index].linear.coefficients_as_array().take(self._state.binary_indices) + row = self._state.op.linear_constraints[constraint_index].linear.to_array().take(self._state.binary_indices) self._assign_row_values(matrix, vector, constraint_index, self._state.binary_indices) # if set(row.ind).issubset(index_set): @@ -550,11 +551,9 @@ def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) \ if constraint.sense in [ConstraintSense.EQ]: # TODO: Ranged constraints should be supported continue - # sense either G or L. - var_indices = set() - index_set.intersection(self._state.op.linear_constraints[constraint_index].linear.to_dict().keys()) - row = self._state.op.linear_constraints.get_rows(constraint_index) - if set(row.ind).issubset(index_set): + constraint_indices = set(self._state.op.linear_constraints[constraint_index].linear.to_dict().keys()) + # row = self._state.op.linear_constraints.get_rows(constraint_index) + if constraint_indices.issubset(index_set): self._assign_row_values(matrix, vector, constraint_index, variable_indices) return matrix, vector @@ -588,22 +587,24 @@ def _get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): """ matrix = [] vector = [] - senses = self._state.op.linear_constraints.get_senses() binary_index_set = set(self._state.binary_indices) continuous_index_set = set(self._state.continuous_indices) all_variables = self._state.binary_indices + self._state.continuous_indices - for constraint_index, sense in enumerate(senses): - if sense in ("E", "R"): + for constraint_index, constraint in enumerate(self._state.op.linear_constraints): + # todo: "R" constraints + if constraint.sense in [ConstraintSense.EQ]: # TODO: Ranged constraints should be supported as well continue # sense either G or L. - row = self._state.op.linear_constraints.get_rows(constraint_index) - row_indices = set(row.ind) + # row = self._state.op.linear_constraints.get_rows(constraint_index) + constraint_indices = set( + self._state.op.linear_constraints[constraint_index].linear.to_dict().keys()) + # row_indices = set(row.ind) # we must have a least one binary and one continuous variable, # otherwise it is another type of constraints. - if len(row_indices & binary_index_set) != 0 and len( - row_indices & continuous_index_set) != 0: + if len(constraint_indices & binary_index_set) != 0 and len( + constraint_indices & continuous_index_set) != 0: self._assign_row_values(matrix, vector, constraint_index, all_variables) matrix, b_2 = self._create_ndarrays(matrix, vector, len(all_variables)) @@ -622,22 +623,26 @@ def _create_step1_problem(self) -> QuadraticProgram: binary_size = len(self._state.binary_indices) # create the same binary variables. - op1.variables.add(names=["x0_" + str(i + 1) for i in range(binary_size)], - types=["I"] * binary_size, - lb=[0.] * binary_size, - ub=[1.] * binary_size) + for i in range(binary_size): + op1.binary_var(name="x0_" + str(i + 1)) + # op1.variables.add(names=["x0_" + str(i + 1) for i in range(binary_size)], + # types=["I"] * binary_size, + # lb=[0.] * binary_size, + # ub=[1.] * binary_size) # prepare and set quadratic objective. # NOTE: The multiplication by 2 is needed for the solvers to parse # the quadratic coefficients. + # todo: do we need multiplication by two? quadratic_objective = self._state.q0 +\ 2 * ( self._params.factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) + self._state.rho / 2 * np.eye(binary_size) ) - for i in range(binary_size): - for j in range(i, binary_size): - op1.objective.set_quadratic_coefficients(i, j, quadratic_objective[i, j]) + # for i in range(binary_size): + # for j in range(i, binary_size): + # op1.objective.set_quadratic_coefficients(i, j, quadratic_objective[i, j]) + op1.objective.quadratic = quadratic_objective # prepare and set linear objective. linear_objective = self._state.c0 - \ @@ -645,8 +650,10 @@ def _create_step1_problem(self) -> QuadraticProgram: self._state.rho * (- self._state.y - self._state.z) + \ self._state.lambda_mult - for i in range(binary_size): - op1.objective.set_linear(i, linear_objective[i]) + # for i in range(binary_size): + # op1.objective.set_linear(i, linear_objective[i]) + + op1.objective.linear = linear_objective return op1 def _create_step2_problem(self) -> QuadraticProgram: @@ -659,45 +666,57 @@ def _create_step2_problem(self) -> QuadraticProgram: continuous_size = len(self._state.continuous_indices) binary_size = len(self._state.binary_indices) - lower_bounds = self._state.op.variables.get_lower_bounds(self._state.continuous_indices) - upper_bounds = self._state.op.variables.get_upper_bounds(self._state.continuous_indices) - if continuous_size: - # add u variables. - op2.variables.add(names=["u0_" + str(i + 1) for i in range(continuous_size)], - types=["C"] * continuous_size, lb=lower_bounds, ub=upper_bounds) + # lower_bounds = self._state.op.variables.get_lower_bounds(self._state.continuous_indices) + # upper_bounds = self._state.op.variables.get_upper_bounds(self._state.continuous_indices) + # if continuous_size: + # # add u variables. + # op2.variables.add(names=["u0_" + str(i + 1) for i in range(continuous_size)], + # types=["C"] * continuous_size, lb=lower_bounds, ub=upper_bounds) + continuous_index = 0 + for variable in self._state.op.variables: + if variable.vartype == VarType.CONTINUOUS: + op2.continuous_var("u0_" + str(continuous_index + 1), + lowerbound=variable.lowerbound, upperbound=variable.upperbound) + continuous_index += 1 # add z variables. - op2.variables.add(names=["z0_" + str(i + 1) for i in range(binary_size)], - types=["C"] * binary_size, - lb=[0.] * binary_size, - ub=[1.] * binary_size) + # op2.variables.add(names=["z0_" + str(i + 1) for i in range(binary_size)], + # types=["C"] * binary_size, + # lb=[0.] * binary_size, + # ub=[1.] * binary_size) + for i in range(binary_size): + op2.binary_var("z0_" + str(i + 1)) + q_z = 2 * (self._state.rho / 2 * np.eye(binary_size)) + op2.objective.quadratic = block_diag(self._state.q1, q_z) # set quadratic objective coefficients for u variables. - if continuous_size: - q_u = self._state.q1 - for i in range(continuous_size): - for j in range(i, continuous_size): - op2.objective.set_quadratic_coefficients(i, j, q_u[i, j]) + # if continuous_size: + # q_u = self._state.q1 + # for i in range(continuous_size): + # for j in range(i, continuous_size): + # op2.objective.set_quadratic_coefficients(i, j, q_u[i, j]) # set quadratic objective coefficients for z variables. # NOTE: The multiplication by 2 is needed for the solvers to parse # the quadratic coefficients. - q_z = 2 * (self._state.rho / 2 * np.eye(binary_size)) - for i in range(binary_size): - for j in range(i, binary_size): - op2.objective.set_quadratic_coefficients(i + continuous_size, j + continuous_size, - q_z[i, j]) + # q_z = 2 * (self._state.rho / 2 * np.eye(binary_size)) + # for i in range(binary_size): + # for j in range(i, binary_size): + # op2.objective.set_quadratic_coefficients(i + continuous_size, j + continuous_size, + # q_z[i, j]) + linear_z = -1 * self._state.lambda_mult - self._state.rho * (self._state.x0 - self._state.y) + op2.objective.linear = np.concatenate((self._state.c1, linear_z)) # set linear objective for u variables. - if continuous_size: - linear_u = self._state.c1 - for i in range(continuous_size): - op2.objective.set_linear(i, linear_u[i]) + # if continuous_size: + # linear_u = self._state.c1 + # for i in range(continuous_size): + # op2.objective.set_linear(i, linear_u[i]) # set linear objective for z variables. - linear_z = -1 * self._state.lambda_mult - self._state.rho * (self._state.x0 - self._state.y) - for i in range(binary_size): - op2.objective.set_linear(i + continuous_size, linear_z[i]) + # linear_z = -1 * self._state.lambda_mult - self._state.rho * (self._state.x0 - self._state.y) + # for i in range(binary_size): + # op2.objective.set_linear(i + continuous_size, linear_z[i]) # constraints for z. # A1 z <= b1. @@ -706,32 +725,50 @@ def _create_step2_problem(self) -> QuadraticProgram: # when saving a model via cplex method. # rhs="something from numpy" is ok. # so, we convert every single value to python float - lin_expr = [SparsePair(ind=list(range(continuous_size, continuous_size + binary_size)), - val=self._state.a1[i, :].tolist()) for i in - range(constraint_count)] - op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, - rhs=list(self._state.b1)) - + # lin_expr = [SparsePair(ind=list(range(continuous_size, continuous_size + binary_size)), + # val=self._state.a1[i, :].tolist()) for i in + # range(constraint_count)] + # op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, + # rhs=list(self._state.b1)) + + for i in range(constraint_count): + linear = np.concatenate((np.zeros(continuous_size), self._state.a1[i, :])) + op2.linear_constraint(linear=linear, sense=ConstraintSense.LE, rhs=self._state.b1[i]) + + # if continuous_size: + # # A2 z + A3 u <= b2 + # constraint_count = self._state.a2.shape[0] + # lin_expr = [SparsePair(ind=list(range(continuous_size + binary_size)), + # val=self._state.a3[i, :].tolist() + + # self._state.a2[i, :].tolist()) + # for i in range(constraint_count)] + # op2.linear_constraints.add(lin_expr=lin_expr, + # senses=["L"] * constraint_count, + # rhs=self._state.b2.tolist()) if continuous_size: # A2 z + A3 u <= b2 constraint_count = self._state.a2.shape[0] - lin_expr = [SparsePair(ind=list(range(continuous_size + binary_size)), - val=self._state.a3[i, :].tolist() + - self._state.a2[i, :].tolist()) - for i in range(constraint_count)] - op2.linear_constraints.add(lin_expr=lin_expr, - senses=["L"] * constraint_count, - rhs=self._state.b2.tolist()) + for i in range(constraint_count): + linear = np.concatenate((self._state.a3[i, :], self._state.a2[i, :])) + op2.linear_constraint(linear=linear, sense=ConstraintSense.LE, rhs=self._state.b2[i]) + + # if continuous_size: + # # A4 u <= b3 + # constraint_count = self._state.a4.shape[0] + # lin_expr = [SparsePair(ind=list(range(continuous_size)), + # val=self._state.a4[i, :].tolist()) for i in + # range(constraint_count)] + # op2.linear_constraints.add(lin_expr=lin_expr, + # senses=["L"] * constraint_count, + # rhs=self._state.b3.tolist()) if continuous_size: # A4 u <= b3 constraint_count = self._state.a4.shape[0] - lin_expr = [SparsePair(ind=list(range(continuous_size)), - val=self._state.a4[i, :].tolist()) for i in - range(constraint_count)] - op2.linear_constraints.add(lin_expr=lin_expr, - senses=["L"] * constraint_count, - rhs=self._state.b3.tolist()) + for i in range(constraint_count): + linear = np.concatenate((self._state.a4[i, :], np.zeros(binary_size))) + op2.linear_constraint(linear=linear, sense=ConstraintSense.LE, rhs=self._state.b3[i]) + # add quadratic constraints for # In the step 2, we basically need to copy all quadratic constraints of the original @@ -776,22 +813,25 @@ def _create_step3_problem(self) -> QuadraticProgram: op3 = QuadraticProgram() # add y variables. binary_size = len(self._state.binary_indices) - op3.variables.add(names=["y_" + str(i + 1) for i in range(binary_size)], - types=["C"] * binary_size, lb=[-np.inf] * binary_size, - ub=[np.inf] * binary_size) + # op3.variables.add(names=["y_" + str(i + 1) for i in range(binary_size)], + # types=["C"] * binary_size, lb=[-np.inf] * binary_size, + # ub=[np.inf] * binary_size) + for i in range(binary_size): + op3.continuous_var("y_" + str(i + 1), lowerbound=-np.inf, upperbound=np.inf) # set quadratic objective. # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coeff-s. q_y = 2 * (self._params.beta / 2 * np.eye(binary_size) + self._state.rho / 2 * np.eye(binary_size)) - for i in range(binary_size): - for j in range(i, binary_size): - op3.objective.set_quadratic_coefficients(i, j, q_y[i, j]) + # for i in range(binary_size): + # for j in range(i, binary_size): + # op3.objective.set_quadratic_coefficients(i, j, q_y[i, j]) + op3.objective.quadratic = q_y - linear_y = - self._state.lambda_mult - self._state.rho * ( - self._state.x0 - self._state.z) - for i in range(binary_size): - op3.objective.set_linear(i, linear_y[i]) + linear_y = - self._state.lambda_mult - self._state.rho * (self._state.x0 - self._state.z) + # for i in range(binary_size): + # op3.objective.set_linear(i, linear_y[i]) + op3.objective.linear = linear_y return op3 @@ -930,7 +970,7 @@ def quadratic_form(matrix, x, c): obj_val = quadratic_form(self._state.q0, self._state.x0, self._state.c0) obj_val += quadratic_form(self._state.q1, self._state.u, self._state.c1) - obj_val += self._state.op.objective.get_offset() + obj_val += self._state.op.objective.constant return obj_val diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index b9c5bb16fa..dffb35c7a3 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -39,11 +39,11 @@ def test_admm_maximization(self): mdl.maximize(c + x * x) op = QuadraticProgram() op.from_docplex(mdl) - self.assertIsNotNone(op) admm_params = ADMMParameters() - qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) + # qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) + qubo_optimizer = CplexOptimizer() continuous_optimizer = CplexOptimizer() solver = ADMMOptimizer(qubo_optimizer=qubo_optimizer, From d5729b48d3561f22e0802e9c89466f8a48d43336 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Thu, 16 Apr 2020 17:10:11 +0200 Subject: [PATCH 223/323] ignore trivial constraints (0 == 0) when converting to docplex --- .../penalize_linear_equality_constraints.py | 100 +++++++----------- .../problems/quadratic_program.py | 5 +- 2 files changed, 44 insertions(+), 61 deletions(-) diff --git a/qiskit/optimization/converters/penalize_linear_equality_constraints.py b/qiskit/optimization/converters/penalize_linear_equality_constraints.py index 586feb311b..807b0b79ff 100644 --- a/qiskit/optimization/converters/penalize_linear_equality_constraints.py +++ b/qiskit/optimization/converters/penalize_linear_equality_constraints.py @@ -17,9 +17,11 @@ from typing import Optional import copy -from collections import defaultdict from ..problems.quadratic_program import QuadraticProgram +from ..problems.variable import VarType +from ..problems.quadratic_objective import ObjSense +from ..problems.constraint import ConstraintSense from ..exceptions.qiskit_optimization_error import QiskitOptimizationError @@ -47,88 +49,66 @@ def encode(self, op: QuadraticProgram, penalty_factor: float = 1e5, QiskitOptimizationError: If an inequality constraint exists. """ - # TODO: test compatibility, how to react in case of incompatibility? - # create empty QuadraticProgram model self._src = copy.deepcopy(op) # deep copy self._dst = QuadraticProgram() - # set variables (obj is set via objective interface) - var_names = self._src.variables.get_names() - var_lbs = self._src.variables.get_lower_bounds() - var_ubs = self._src.variables.get_upper_bounds() - var_types = self._src.variables.get_types() - if var_names: - self._dst.variables.add(lb=var_lbs, ub=var_ubs, types=var_types, names=var_names) - - # set objective name + # set variables + for x in self._src.variables: + if x.vartype == VarType.CONTINUOUS: + self._dst.continuous_var(x.lowerbound, x.upperbound, x.name) + elif x.vartype == VarType.BINARY: + self._dst.binary_var(x.name) + elif x.vartype == VarType.INTEGER: + self._dst.integer_var(x.lowerbound, x.upperbound, x.name) + else: + raise QiskitOptimizationError('Unsupported vartype: {}'.format(x.vartype)) + + # set problem name if name is None: - self._dst.set_problem_name(self._src.get_problem_name()) + self._dst.name = self._src.name else: - self._dst.set_problem_name(name) - - # set objective sense - self._dst.objective.set_sense(self._src.objective.get_sense()) - penalty_factor = self._src.objective.get_sense() * penalty_factor - - # store original objective offset - offset = self._src.objective.get_offset() - - # store original linear objective terms - linear_terms = defaultdict(float) - for i, v in self._src.objective.get_linear_dict().items(): - linear_terms[i] = v + self._dst.name = name - # store original quadratic objective terms - quadratic_terms = defaultdict(float) - quadratic_terms.update(self._src.objective.get_quadratic_dict().items()) + # get original objective terms + offset = self._src.objective.constant + linear = self._src.objective.linear.to_dict() + quadratic = self._src.objective.quadratic.to_dict() - # get linear constraints' data - linear_rows = self._src.linear_constraints.get_rows() - linear_sense = self._src.linear_constraints.get_senses() - linear_rhs = self._src.linear_constraints.get_rhs() - linear_names = self._src.linear_constraints.get_names() + # convert linear constraints into penalty terms + for constraint in self._src.linear_constraints: - # if inequality constraints exist, raise an error - if not all(ls == 'E' for ls in linear_sense): - raise QiskitOptimizationError('An inequality constraint exists. ' - 'The method supports only equality constraints.') + if constraint.sense != ConstraintSense.EQ: + raise QiskitOptimizationError('An inequality constraint exists. ' + 'The method supports only equality constraints.') - # convert linear constraints into penalty terms - num_constraints = len(linear_names) - for i in range(num_constraints): - constant = linear_rhs[i] - row = linear_rows[i] + constant = constraint.rhs + row = constraint.linear.to_dict() # constant parts of penalty*(Constant-func)**2: penalty*(Constant**2) offset += penalty_factor * constant ** 2 # linear parts of penalty*(Constant-func)**2: penalty*(-2*Constant*func) - for var_ind, coef in zip(row.ind, row.val): - # if var_ind already exists in the linear terms dic, add a penalty term + for j, coef in row.items(): + # if j already exists in the linear terms dic, add a penalty term # into existing value else create new key and value in the linear_term dict - linear_terms[var_ind] += penalty_factor * -2 * coef * constant + linear[j] = linear.get(j, 0.0) + penalty_factor * -2 * coef * constant # quadratic parts of penalty*(Constant-func)**2: penalty*(func**2) - for var_ind_1, coef_1 in zip(row.ind, row.val): - for var_ind_2, coef_2 in zip(row.ind, row.val): - # if var_ind_1 and var_ind_2 already exist in the quadratic terms dic, + for j, coef_1 in zip(row.ind, row.val): + for k, coef_2 in zip(row.ind, row.val): + # if j and k already exist in the quadratic terms dict, # add a penalty term into existing value # else create new key and value in the quadratic term dict # according to implementation of quadratic terms in OptimizationModel, # multiply by 2 - quadratic_terms[var_ind_1, var_ind_2] += penalty_factor * coef_1 * coef_2 * 2 - - # set objective offset - self._dst.objective.set_offset(offset) + quadratic[(j, k)] = quadratic.get((j, k), 0.0) \ + + penalty_factor * coef_1 * coef_2 * 2 - # set linear objective terms - for i, v in linear_terms.items(): - self._dst.objective.set_linear(i, v) - - # set quadratic objective terms - for (i, j), v in quadratic_terms.items(): - self._dst.objective.set_quadratic_coefficients(i, j, v) + if self._src.objective.sense == ObjSense.MINIMIZE: + self._dst.minimize(offset, linear, quadratic) + else: + self._dst.maximize(offset, linear, quadratic) return self._dst diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 7dd4128948..9a8efe0626 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -115,7 +115,8 @@ def variables_index(self) -> Dict[str, int]: """ return self._variables_index - def _add_variable(self, lowerbound: Union[float, int] = 0, + def _add_variable(self, + lowerbound: Union[float, int] = 0, upperbound: Union[float, int] = infinity, vartype: VarType = VarType.CONTINUOUS, name: Optional[str] = None) -> Variable: @@ -640,6 +641,8 @@ def to_docplex(self) -> Model: for i, constraint in enumerate(self.linear_constraints): name = constraint.name rhs = constraint.rhs + if rhs == 0 and constraint.linear.coefficients.nnz == 0: + continue linear_expr = 0 for j, v in constraint.linear.to_dict().items(): linear_expr += v * var[j] From 26f571bae58fa242171fc5bb3ea162e276d238fc Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Thu, 16 Apr 2020 16:12:07 +0100 Subject: [PATCH 224/323] moving to a new optimization stack --- qiskit/optimization/algorithms/admm_optimizer.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 886afca5be..d5cb6e2b77 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -675,8 +675,7 @@ def _create_step2_problem(self) -> QuadraticProgram: continuous_index = 0 for variable in self._state.op.variables: if variable.vartype == VarType.CONTINUOUS: - op2.continuous_var("u0_" + str(continuous_index + 1), - lowerbound=variable.lowerbound, upperbound=variable.upperbound) + op2.continuous_var(name="u0_" + str(continuous_index + 1), lowerbound=variable.lowerbound, upperbound=variable.upperbound) continuous_index += 1 # add z variables. @@ -685,7 +684,7 @@ def _create_step2_problem(self) -> QuadraticProgram: # lb=[0.] * binary_size, # ub=[1.] * binary_size) for i in range(binary_size): - op2.binary_var("z0_" + str(i + 1)) + op2.binary_var(name="z0_" + str(i + 1)) q_z = 2 * (self._state.rho / 2 * np.eye(binary_size)) op2.objective.quadratic = block_diag(self._state.q1, q_z) From f8780f50a912458c1228e102929b8180f7cbfb3d Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Thu, 16 Apr 2020 17:16:26 +0200 Subject: [PATCH 225/323] Update quadratic_program.py --- qiskit/optimization/problems/quadratic_program.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 9a8efe0626..99ad694fe9 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -661,6 +661,10 @@ def to_docplex(self) -> Model: for i, constraint in enumerate(self.quadratic_constraints): name = constraint.name rhs = constraint.rhs + if rhs == 0 \ + and constraint.linear.coefficients.nnz == 0 \ + and constraint.quadratic.coefficients.nnz == 0: + continue quadratic_expr = 0 for j, v in constraint.linear.to_dict().items(): quadratic_expr += v * var[j] From f280a1e7ebb2a942c535c356adeaba7ca467217c Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Thu, 16 Apr 2020 17:30:28 +0200 Subject: [PATCH 226/323] Update quadratic_program_to_qubo.py --- .../converters/quadratic_program_to_qubo.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/qiskit/optimization/converters/quadratic_program_to_qubo.py b/qiskit/optimization/converters/quadratic_program_to_qubo.py index 55e21a22e0..e97d0977b4 100644 --- a/qiskit/optimization/converters/quadratic_program_to_qubo.py +++ b/qiskit/optimization/converters/quadratic_program_to_qubo.py @@ -17,6 +17,7 @@ from typing import Optional from qiskit.optimization.problems import QuadraticProgram +from qiskit.optimization.problems.constraint import ConstraintSense from qiskit.optimization.converters import (PenalizeLinearEqualityConstraints, IntegerToBinary) from qiskit.optimization.exceptions import QiskitOptimizationError @@ -107,17 +108,12 @@ def get_compatibility_msg(problem: QuadraticProgram) -> bool: msg = '' # check whether there are incompatible variable types - if problem.variables.get_num_continuous() > 0: + if problem.get_num_continuous_vars() > 0: msg += 'Continuous variables are not supported! ' - if problem.variables.get_num_semicontinuous() > 0: - # TODO: to be removed once semi-continuous to binary + continuous is introduced - msg += 'Semi-continuous variables are not supported! ' - if problem.variables.get_num_semiinteger() > 0: - # TODO: to be removed once semi-integer to binary mapping is introduced - msg += 'Semi-integer variables are not supported! ' # check whether there are incompatible constraint types - if not all([sense == 'E' for sense in problem.linear_constraints.get_senses()]): + if not all([constraint.sense == ConstraintSense.EQ + for constraint in problem.linear_constraints]): msg += 'Only linear equality constraints are supported.' if problem.quadratic_constraints.get_num() > 0: msg += 'Quadratic constraints are not supported. ' From 9917c6b0c68ca675ae5fb6e6a99be65c8c0c21ee Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Thu, 16 Apr 2020 16:47:34 +0100 Subject: [PATCH 227/323] moving to a new optimization stack --- qiskit/optimization/algorithms/admm_optimizer.py | 3 ++- test/optimization/test_admm.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index d5cb6e2b77..10db1bbfc9 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -287,6 +287,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: and (elapsed_time < self._params.max_time): if binary_indices: op1 = self._create_step1_problem() + print("OP1: ", op1.print_as_lp_string()) self._state.x0 = self._update_x0(op1) # else, no binary variables exist, # and no update to be done in this case. @@ -816,7 +817,7 @@ def _create_step3_problem(self) -> QuadraticProgram: # types=["C"] * binary_size, lb=[-np.inf] * binary_size, # ub=[np.inf] * binary_size) for i in range(binary_size): - op3.continuous_var("y_" + str(i + 1), lowerbound=-np.inf, upperbound=np.inf) + op3.continuous_var(name="y_" + str(i + 1), lowerbound=-np.inf, upperbound=np.inf) # set quadratic objective. # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coeff-s. diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index dffb35c7a3..8cebd91b49 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -100,6 +100,8 @@ def test_admm_ex6(self): self.assertIsInstance(solution, ADMMOptimizerResult) self.assertIsNotNone(solution.x) + print(solution.x) + print(solution.fval) np.testing.assert_almost_equal([1., 0., 0., 2.], solution.x, 3) self.assertIsNotNone(solution.fval) np.testing.assert_almost_equal(1., solution.fval, 3) @@ -147,6 +149,8 @@ def test_admm_ex5(self): self.assertIsInstance(solution, ADMMOptimizerResult) self.assertIsNotNone(solution.x) + print(solution.x) + print(solution.fval) np.testing.assert_almost_equal([1., 0., 1.], solution.x, 3) self.assertIsNotNone(solution.fval) np.testing.assert_almost_equal(2., solution.fval, 3) From 8a027db032d28f4b3f06b73e389751a711018222 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Fri, 17 Apr 2020 14:16:41 +0900 Subject: [PATCH 228/323] poc --- .../optimization/problems/quadratic_expression.py | 13 +++++++------ test/optimization/test_quadratic_expression.py | 9 +++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_expression.py b/qiskit/optimization/problems/quadratic_expression.py index ea8a635729..70a5d7e2c0 100644 --- a/qiskit/optimization/problems/quadratic_expression.py +++ b/qiskit/optimization/problems/quadratic_expression.py @@ -60,11 +60,10 @@ def __getitem__(self, key: Tuple[Union[int, str], Union[int, str]]) -> float: j = self.quadratic_program.variables_index[j] return self.coefficients[i, j] - def _coeffs_to_dok_matrix(self, - coefficients: Union[ndarray, spmatrix, List[List[float]], - Dict[ - Tuple[Union[int, str], Union[int, str]], - float]]) -> dok_matrix: + def _coeffs_to_dok_matrix( + self, coefficients: Union[ndarray, spmatrix, List[List[float]], + Dict[Tuple[Union[int, str], Union[int, str]], float]]) \ + -> dok_matrix: """Maps given coefficients to a dok_matrix. Args: @@ -86,7 +85,9 @@ def _coeffs_to_dok_matrix(self, i = self.quadratic_program.variables_index[i] if isinstance(j, str): j = self.quadratic_program.variables_index[j] - coeffs[i, j] = value + if i > j: + i, j = j, i + coeffs[i, j] += value coefficients = coeffs else: raise QiskitOptimizationError("Unsupported format for coefficients.") diff --git a/test/optimization/test_quadratic_expression.py b/test/optimization/test_quadratic_expression.py index 30120ac3ca..4695ffe023 100644 --- a/test/optimization/test_quadratic_expression.py +++ b/test/optimization/test_quadratic_expression.py @@ -114,6 +114,15 @@ def test_evaluate(self): for values in [values_list, values_array, values_dict_int, values_dict_str]: self.assertEqual(quadratic.evaluate(values), 900) + def test_symmetric_set(self): + """ test symmetric set """ + q_p = QuadraticProgram() + q_p.binary_var('x') + q_p.binary_var('y') + q_p.binary_var('z') + quad = QuadraticExpression(q_p, {('x', 'y'): -1, ('y', 'x'): 2, ('z', 'x'): 3}) + self.assertDictEqual(quad.to_dict(use_name=True), {('x', 'y'): 1, ('x', 'z'): 3}) + if __name__ == '__main__': unittest.main() From 70b05fba7f20c0f101a84599e4e019c7e90578c7 Mon Sep 17 00:00:00 2001 From: Atsushi Matsuo Date: Fri, 17 Apr 2020 14:33:53 +0900 Subject: [PATCH 229/323] modified InequalityToEquality converter for new QuadraticProgram APIs --- .../converters/inequality_to_equality.py | 430 +++++++++++------- 1 file changed, 272 insertions(+), 158 deletions(-) diff --git a/qiskit/optimization/converters/inequality_to_equality.py b/qiskit/optimization/converters/inequality_to_equality.py index 15aac9d596..c2cf7b26e4 100644 --- a/qiskit/optimization/converters/inequality_to_equality.py +++ b/qiskit/optimization/converters/inequality_to_equality.py @@ -20,6 +20,9 @@ import logging from ..problems.quadratic_program import QuadraticProgram +from ..problems.quadratic_objective import ObjSense +from ..problems.constraint import ConstraintSense +from ..problems.variable import VarType from ..exceptions import QiskitOptimizationError logger = logging.getLogger(__name__) @@ -27,6 +30,7 @@ _HAS_CPLEX = False try: from cplex import SparsePair + _HAS_CPLEX = True except ImportError: logger.info('CPLEX is not installed.') @@ -54,8 +58,9 @@ def __init__(self) -> None: self._conv: Dict[str, List[Tuple[str, int]]] = {} # e.g., self._conv = {'c1': [c1@slack_var]} - def encode(self, op: QuadraticProgram, name: Optional[str] = None, - mode: str = 'auto') -> QuadraticProgram: + def encode( + self, op: QuadraticProgram, name: Optional[str] = None, mode: str = 'auto' + ) -> QuadraticProgram: """Convert a problem with inequality constraints into one with only equality constraints. Args: @@ -77,209 +82,318 @@ def encode(self, op: QuadraticProgram, name: Optional[str] = None, """ self._src = copy.deepcopy(op) self._dst = QuadraticProgram() - - # declare variables - names = self._src.variables.get_names() - types = self._src.variables.get_types() - lower_bounds = self._src.variables.get_lower_bounds() - upper_bounds = self._src.variables.get_upper_bounds() - - for i, variable in enumerate(names): - typ = types[i] - if typ == 'B': - self._dst.variables.add(names=[variable], types='B') - elif typ in ['C', 'I']: - self._dst.variables.add(names=[variable], types=typ, - lb=[lower_bounds[i]], - ub=[upper_bounds[i]]) + if name: + self._dst.name = name + else: + self._dst.name = self._src.name + + # Copy variables + for x in self._src.variables: + if x.vartype == VarType.BINARY: + self._dst.binary_var(name=x.name) + elif x.vartype == VarType.INTEGER: + self._dst.integer_var( + name=x.name, lower_bound=x.lower_bound, upperbound=x.upper_bound + ) + elif x.vartype == VarType.CONTINUOUS: + self._dst.continuous_var( + name=x.name, lower_bound=x.lower_bound, upperbound=x.upper_bound + ) else: - raise QiskitOptimizationError('Variable type not supported: ' + typ) - - # set objective name - if name is None: - self._dst.objective.set_name(self._src.objective.get_name()) + raise QiskitOptimizationError("Unsupported variable type {}".format(x.vartype)) + + # Copy the object function + linear, linear_constant = self._src.objective.linear.to_dict(use_name=True) + quadratic, quadratic_linear, quadratic_constant = self._src.objective.quadratic.to_dict( + use_name=True + ) + constant = self._src.objective.constant + linear_constant + quadratic_constant + for i, v in quadratic_linear.items(): + linear[i] = linear.get(i, 0) + v + + if self._src.objective.sense == ObjSense.MINIMIZE: + self._dst.minimize(constant, linear, quadratic) else: - self._dst.objective.set_name(name) - - # set objective sense - self._dst.objective.set_sense(self._src.objective.get_sense()) - - # set objective offset - self._dst.objective.set_offset(self._src.objective.get_offset()) - - # set linear objective terms - for i, v in self._src.objective.get_linear_dict().items(): - self._dst.objective.set_linear(i, v) - - # set quadratic objective terms - for (i, j), v in self._src.objective.get_quadratic_dict().items(): - self._dst.objective.set_quadratic_coefficients(i, j, v) - - # set linear constraints - names = self._src.linear_constraints.get_names() - rows = self._src.linear_constraints.get_rows() - senses = self._src.linear_constraints.get_senses() - rhs = self._src.linear_constraints.get_rhs() - - for i, variable in enumerate(names): - # Copy equality constraints into self._dst - if senses[i] == 'E': - self._dst.linear_constraints.add(lin_expr=[rows[i]], senses=[senses[i]], - rhs=[rhs[i]], names=[variable]) - # When the type of a constraint is L, make an equality constraint - # with slack variables which represent [lb, ub] = [0, constant - the lower bound of lhs] - elif senses[i] == 'L': + self._dst.maximize(constant, linear, quadratic) + + # For linear constraints + for constraint in self._src.linear_constraints: + linear, constant = constraint.linear.to_dict() + if constraint.sense == ConstraintSense.EQ: + self._dst.linear_constraint( + linear, constraint.sense, constraint.rhs - constant, constraint.name + ) + elif constraint.sense == ConstraintSense.LE or constraint.sense == ConstraintSense.GE: if mode == 'integer': - self._add_int_slack_var_constraint(name=variable, row=rows[i], rhs=rhs[i], - sense=senses[i]) + self._add_integer_slack_var_linear_constraint( + linear, constraint.sense, constraint.rhs - constant, constraint.name + ) elif mode == 'continuous': - self._add_continuous_slack_var_constraint(name=variable, row=rows[i], - rhs=rhs[i], sense=senses[i]) + self._add_continuous_slack_var_linear_constraint( + linear, constraint.sense, constraint.rhs - constant, constraint.name + ) elif mode == 'auto': - self._add_auto_slack_var_constraint(name=variable, row=rows[i], rhs=rhs[i], - sense=senses[i]) + self._add_auto_slack_var_linear_constraint( + linear, constraint.sense, constraint.rhs - constant, constraint.name + ) else: - raise QiskitOptimizationError('Unsupported mode is selected' + mode) - - # When the type of a constraint is G, make an equality constraint - # with slack variables which represent [lb, ub] = [0, the upper bound of lhs] - elif senses[i] == 'G': + raise QiskitOptimizationError('Unsupported mode is selected: {}'.format(mode)) + else: + raise QiskitOptimizationError( + 'Type of sense in {} is not supported'.format(constraint.name) + ) + + # For quadratic constraints + for constraint in self._src.quadratic_constraints: + linear, constant = constraint.linear.to_dict() + quadratic, quadratic_linear, quadratic_constant = constraint.quadratic.to_dict() + constant = linear_constant + quadratic_constant + for i, v in quadratic_linear.items(): + linear[i] = linear.get(i, 0) + v + + if constraint.sense == ConstraintSense.EQ: + self._dst.quadratic_constraint( + linear, quadratic, constraint.sense, constraint.rhs - constant, constraint.name + ) + elif constraint.sense == ConstraintSense.LE or constraint.sense == ConstraintSense.GE: if mode == 'integer': - self._add_int_slack_var_constraint(name=variable, row=rows[i], rhs=rhs[i], - sense=senses[i]) + self._add_integer_slack_var_quadratic_constraint( + linear, + quadratic, + constraint.sense, + constraint.rhs - constant, + constraint.name, + ) elif mode == 'continuous': - self._add_continuous_slack_var_constraint(name=variable, row=rows[i], - rhs=rhs[i], sense=senses[i]) + self._add_continuous_slack_var_quadratic_constraint( + linear, + quadratic, + constraint.sense, + constraint.rhs - constant, + constraint.name, + ) elif mode == 'auto': - self._add_auto_slack_var_constraint(name=variable, row=rows[i], rhs=rhs[i], - sense=senses[i]) + self._add_auto_slack_var_quadratic_constraint( + linear, + quadratic, + constraint.sense, + constraint.rhs - constant, + constraint.name, + ) else: - raise QiskitOptimizationError( - 'Unsupported mode is selected' + mode) - + raise QiskitOptimizationError('Unsupported mode is selected: {}'.format(mode)) else: - raise QiskitOptimizationError('Type of sense in ' + variable + 'is not supported') - - # TODO: add quadratic constraints - if self._src.quadratic_constraints.get_num() > 0: - raise QiskitOptimizationError('Quadratic constraints are not yet supported.') + raise QiskitOptimizationError( + 'Type of sense in {} is not supported'.format(constraint.name) + ) return self._dst - def decode(self, result: 'OptimizationResult') -> 'OptimizationResult': - """Convert a result of a converted problem into that of the original problem. - - Args: - result: The result of the converted problem. - - Returns: - The result of the original problem. - """ - from ..algorithms.optimization_algorithm import OptimizationResult - new_result = OptimizationResult() - # convert the optimization result into that of the original problem - names = self._dst.variables.get_names() - vals = result.x - new_vals = self._decode_var(names, vals) - new_result.x = new_vals - new_result.fval = result.fval - return new_result - - def _decode_var(self, names, vals) -> List[int]: - # decode slack variables - sol = {name: vals[i] for i, name in enumerate(names)} - new_vals = [] - slack_var_names = [] - - for lst in self._conv.values(): - slack_var_names.extend(lst) - - for name in sol: - if name in slack_var_names: - pass - else: - new_vals.append(sol[name]) - return new_vals - - def _add_int_slack_var_constraint(self, name, row, rhs, sense): + def _add_integer_slack_var_linear_constraint(self, linear, sense, rhs, name): # If a coefficient that is not integer exist, raise error - if any(isinstance(coef, float) and not coef.is_integer() for coef in row.val): + if any(isinstance(coef, float) and not coef.is_integer() for coef in inear.values()): raise QiskitOptimizationError('Can not use a slack variable for ' + name) - slack_name = name + self._delimiter + 'int_slack' - lhs_lb, lhs_ub = self._calc_bounds(row) - # If rhs is float number, round up/down to the nearest integer. - if sense == 'L': + new_rhs = rhs + if sense == ConstraintSense.LE: new_rhs = math.floor(rhs) - if sense == 'G': + if sense == ConstraintSense.GE: new_rhs = math.ceil(rhs) # Add a new integer variable. - if sense == 'L': + slack_name = name + self._delimiter + 'int_slack' + self._conv[name] = slack_name + + lhs_lb, lhs_ub = self._calc_linear_bounds(linear) + + if sense == ConstraintSense.LE: sign = 1 - self._dst.variables.add(names=[slack_name], lb=[0], ub=[new_rhs - lhs_lb], types=['I']) - elif sense == 'G': + self._dst.integer_var(name=[slack_name], lowerbound=[0], upperbound=[new_rhs - lhs_lb]) + elif sense == ConstraintSense.GE: sign = -1 - self._dst.variables.add(names=[slack_name], lb=[0], ub=[lhs_ub - new_rhs], types=['I']) + self._dst.integer_var(name=[slack_name], lowerbound=[0], upperbound=[lhs_ub - new_rhs]) else: - raise QiskitOptimizationError('The type of Sense in ' + name + 'is not supported') + raise QiskitOptimizationError('The type of Sense in {} is not supported'.format(name)) + # Add a new equality constraint. + new_constraint = copy.deepcopy(linear) + new_constraint[slack_name] = sign + self._dst.linear_constraints(linear, sense, rhs, name) + + def _add_continuous_slack_var_linear_constraint(self, linear, sense, rhs, name): + slack_name = name + self._delimiter + 'continuous_slack' self._conv[name] = slack_name - new_ind = copy.deepcopy(row.ind) - new_val = copy.deepcopy(row.val) + lhs_lb, lhs_ub = self._calc_linear_bounds(linear) - new_ind.append(self._dst.variables.get_indices(slack_name)) - new_val.append(sign) + if sense == ConstraintSense.LE: + sign = 1 + self._dst.continuous_var( + name=[slack_name], lowerbound=[0], upperbound=[new_rhs - lhs_lb] + ) + elif sense == ConstraintSense.GE: + sign = -1 + self._dst.continuous_var( + name=[slack_name], lowerbound=[0], upperbound=[lhs_ub - new_rhs] + ) + else: + raise QiskitOptimizationError('The type of Sense in {} is not supported'.format(name)) # Add a new equality constraint. - self._dst.linear_constraints.add(lin_expr=[SparsePair(ind=new_ind, val=new_val)], - senses=['E'], rhs=[new_rhs], names=[name]) + new_constraint = copy.deepcopy(linear) + new_constraint[slack_name] = sign + self._dst.linear_constraints(linear, sense, rhs, name) - def _add_continuous_slack_var_constraint(self, name, row, rhs, sense): - slack_name = name + self._delimiter + 'continuous_slack' - lhs_lb, lhs_ub = self._calc_bounds(row) + def _add_auto_slack_var_linear_constraint(self, linear, sense, rhs, name): + # If a coefficient that is not integer exist, use a continuous slack variable + if any(isinstance(coef, float) and not coef.is_integer() for coef in linear.values()): + self._add_continuous_slack_var_linear_constraint(linear, sense, rhs, name) + # Else use an integer slack variable + else: + self._add_integer_slack_var_linear_constraint(linear, sense, rhs, name) - if sense == 'L': + def _add_integer_slack_var_quadratic_constraint(self, linear, quadratic, sense, rhs, name): + # If a coefficient that is not integer exist, raise an error + if any( + isinstance(coef, float) and not coef.is_integer() for coef in quadratic.values() + ) or any(isinstance(coef, float) and not coef.is_integer() for coef in linear.values()): + raise QiskitOptimizationError('Can not use a slack variable for ' + name) + + # If rhs is float number, round up/down to the nearest integer. + new_rhs = rhs + if sense == ConstraintSense.LE: + new_rhs = math.floor(rhs) + if sense == ConstraintSense.GE: + new_rhs = math.ceil(rhs) + + # Add a new integer variable. + slack_name = name + self._delimiter + 'int_slack' + self._conv[name] = slack_name + + lhs_lb, lhs_ub = self._calc_quadratic_bounds(linear, quadratic) + + if sense == ConstraintSense.LE: sign = 1 - self._dst.variables.add(names=[slack_name], lb=[0], ub=[rhs - lhs_lb], types=['C']) - elif sense == 'G': + self._dst.integer_var(name=[slack_name], lowerbound=[0], upperbound=[new_rhs - lhs_lb]) + elif sense == ConstraintSense.GE: sign = -1 - self._dst.variables.add(names=[slack_name], lb=[0], ub=[lhs_ub - rhs], types=['C']) + self._dst.integer_var(name=[slack_name], lowerbound=[0], upperbound=[lhs_ub - new_rhs]) else: - raise QiskitOptimizationError('Type of Sense in ' + name + 'is not supported') + raise QiskitOptimizationError('The type of Sense in {} is not supported'.format(name)) + + # Add a new equality constraint. + new_linear = copy.deepcopy(linear) + new_linear[slack_name] = sign + self._dst.quadratic_constraints(new_linear, quadratic, sense, rhs, name) + + def _add_continuous_slack_var_quadratic_constraint(self, linear, quadratic, sense, rhs, name): + # If a coefficient that is not integer exist, raise error + if any( + isinstance(coef, float) and not coef.is_integer() for coef in quadratic.values() + ) or any(isinstance(coef, float) and not coef.is_integer() for coef in linear.values()): + raise QiskitOptimizationError('Can not use a slack variable for ' + name) + # Add a new continuous variable. + slack_name = name + self._delimiter + 'continuous_slack' self._conv[name] = slack_name - new_ind = copy.deepcopy(row.ind) - new_val = copy.deepcopy(row.val) + lhs_lb, lhs_ub = self._calc_quadratic_bounds(linear, quadratic) - new_ind.append(self._dst.variables.get_indices(slack_name)) - new_val.append(sign) + if sense == ConstraintSense.LE: + sign = 1 + self._dst.continous_var( + name=[slack_name], lowerbound=[0], upperbound=[new_rhs - lhs_lb] + ) + elif sense == ConstraintSense.GE: + sign = -1 + self._dst.continuous_var( + name=[slack_name], lowerbound=[0], upperbound=[lhs_ub - new_rhs] + ) + else: + raise QiskitOptimizationError('The type of Sense in {} is not supported'.format(name)) # Add a new equality constraint. - self._dst.linear_constraints.add(lin_expr=[SparsePair(ind=new_ind, val=new_val)], - senses=['E'], rhs=[rhs], names=[name]) + new_linear = copy.deepcopy(linear) + new_linear[slack_name] = sign + self._dst.quadratic_constraints(new_linear, quadratic, sense, rhs, name) - def _add_auto_slack_var_constraint(self, name, row, rhs, sense): + def _add_auto_slack_var_quadratic_constraint(self, linear, quadratic, sense, rhs, name): # If a coefficient that is not integer exist, use a continuous slack variable - if any(isinstance(coef, float) and not coef.is_integer() for coef in row.val): - self._add_continuous_slack_var_constraint(name=name, row=row, rhs=rhs, sense=sense) + if any( + isinstance(coef, float) and not coef.is_integer() for coef in quadratic.values() + ) or any(isinstance(coef, float) and not coef.is_integer() for coef in linear.values()): + self._add_continuos_slack_var_quadratic_constraint(linear, quadratic, sense, rhs, name) # Else use an integer slack variable else: - self._add_int_slack_var_constraint(name=name, row=row, rhs=rhs, sense=sense) + self._add_integer_slack_var_quadratic_constraint(linear, quadratic, sense, rhs, name) - def _calc_bounds(self, row): + def _calc_linear_bounds(self, linear): lhs_lb, lhs_ub = 0, 0 - for ind, val in zip(row.ind, row.val): - if self._dst.variables.get_types(ind) == 'B': - upper_bound = 1 - else: - upper_bound = self._dst.variables.get_upper_bounds(ind) - lower_bound = self._dst.variables.get_lower_bounds(ind) - - lhs_lb += min(lower_bound * val, upper_bound * val) - lhs_ub += max(lower_bound * val, upper_bound * val) + for var_name, v in linear.items(): + x = self._src.get_variables(var_name) + lhs_lb += min(x.lowerbound * v, x.upperbound * v) + lhs_ub += max(x.lowerbound * v, x.upperbound * v) + return lhs_lb, lhs_ub + def _calc_quadratic_bounds(self, linear, quadratic): + lhs_lb, lhs_ub = 0, 0 + # Calcurate the lowerbound and the upperbound of the linear part + linear_lb, linear_ub = self._cal_quadratic_bounds(linear) + lhs_lb += linear_lb + lhs_ub += linear_ub + + # Calcurate the lowerbound and the upperbound of the quadratic part + for (name_i, name_j), v in quadratic.items(): + x = self.src.get_variable(name_i) + y = self.src.get_variable(name_j) + + lhs_lb += min( + x.lowerbound * y.lowerbound * v, + x.lowerbound * y.upperbound * v, + x.upperbound * y.lowerbound * v, + x.upperbound * y.upperbound * v, + ) + lhs_ub += max( + x.lowerbound * y.lowerbound * v, + x.lowerbound * y.upperbound * v, + x.upperbound * y.lowerbound * v, + x.upperbound * y.upperbound * v, + ) return lhs_lb, lhs_ub + + def decode(self, result: 'OptimizationResult') -> 'OptimizationResult': + """Convert a result of a converted problem into that of the original problem. + + Args: + result: The result of the converted problem. + + Returns: + The result of the original problem. + """ + from ..algorithms.optimization_algorithm import OptimizationResult + + new_result = OptimizationResult() + # convert the optimization result into that of the original problem + names = self._dst.variables.get_names() + vals = result.x + new_vals = self._decode_var(names, vals) + new_result.x = new_vals + new_result.fval = result.fval + return new_result + + def _decode_var(self, names, vals) -> List[int]: + # decode slack variables + sol = {name: vals[i] for i, name in enumerate(names)} + new_vals = [] + slack_var_names = [] + + for lst in self._conv.values(): + slack_var_names.extend(lst) + + for name in sol: + if name in slack_var_names: + pass + else: + new_vals.append(sol[name]) + return new_vals From cd84a0374f90bde04f61a61de067f997d3c7f5c7 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Fri, 17 Apr 2020 15:35:48 +0900 Subject: [PATCH 230/323] Fix issues in integer_to_binary --- .../converters/integer_to_binary.py | 70 ++++++++----------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/qiskit/optimization/converters/integer_to_binary.py b/qiskit/optimization/converters/integer_to_binary.py index ca76caf95f..632401931c 100644 --- a/qiskit/optimization/converters/integer_to_binary.py +++ b/qiskit/optimization/converters/integer_to_binary.py @@ -15,14 +15,16 @@ """The converter to map integer variables in a quadratic program to binary variables.""" import copy -from typing import Dict, List, Optional, Tuple import logging +from typing import Dict, List, Optional, Tuple + import numpy as np -from ..problems.quadratic_program import QuadraticProgram +from ..algorithms.optimization_algorithm import OptimizationResult +from ..exceptions import QiskitOptimizationError from ..problems.quadratic_objective import ObjSense +from ..problems.quadratic_program import QuadraticProgram from ..problems.variable import VarType -from ..exceptions import QiskitOptimizationError logger = logging.getLogger(__name__) @@ -78,18 +80,15 @@ def encode(self, op: QuadraticProgram, name: Optional[str] = None) -> QuadraticP # declare variables for x in self._src.variables: if x.vartype == VarType.INTEGER: - new_vars: List[Tuple[str, int]] = self._encode_var( - name=x.name, lowerbound=x.lowerbound, upperbound=x.upperbound - ) - self._conv[x] = new_vars + new_vars = self._encode_var(x.name, x.lowerbound, x.upperbound) + self._conv[x.name] = new_vars for (var_name, _) in new_vars: - self._dst.binary_var(name=var_name) + self._dst.binary_var(var_name) else: if x.vartype == VarType.CONTINUOUS: - self._dst.continuous_var(name=x.name, lowerbound=x.lowerbound, - upperbound=x.upperbound) + self._dst.continuous_var(x.lowerbound, x.upperbound, x.name) elif x.vartype == VarType.BINARY: - self._dst.binary_var(name=x.name) + self._dst.binary_var(x.name) else: raise QiskitOptimizationError("Unsupported variable type {}".format(x.vartype)) @@ -102,25 +101,17 @@ def _encode_var(self, name: str, lowerbound: int, upperbound: int) -> List[Tuple power = int(np.log2(var_range)) bounded_coef = var_range - (2 ** power - 1) - lst = [] - for i in range(power): - coef = 2 ** i - new_name = name + self._delimiter + str(i) - lst.append((new_name, coef)) - - new_name = name + self._delimiter + str(power) - lst.append((new_name, bounded_coef)) - - return lst - - def _encode_linear_coefficients_dict(self, coefficients: Dict[str, float]) -> Dict[str, float]: + coeffs = [2 ** i for i in range(power)] + [bounded_coef] + return [(name + self._delimiter + str(i), coef) for i, coef in enumerate(coeffs)] + def _encode_linear_coefficients_dict(self, coefficients: Dict[str, float]) \ + -> Tuple[Dict[str, float], float]: constant = 0 linear = {} for name, v in coefficients.items(): x = self._src.get_variable(name) - if x in self._conv: - for y, coeff in self._conv[x]: + if x.name in self._conv: + for y, coeff in self._conv[x.name]: linear[y] = v * coeff constant += v * x.lowerbound else: @@ -129,8 +120,7 @@ def _encode_linear_coefficients_dict(self, coefficients: Dict[str, float]) -> Di return linear, constant def _encode_quadratic_coefficients_dict(self, coefficients: Dict[Tuple[str, str], float]) \ - -> Dict[Tuple[str, str], float]: - + -> Tuple[Dict[Tuple[str, str], float], Dict[str, float], float]: constant = 0 linear = {} quadratic = {} @@ -138,20 +128,20 @@ def _encode_quadratic_coefficients_dict(self, coefficients: Dict[Tuple[str, str] x = self._src.get_variable(name_i) y = self._src.get_variable(name_j) - if x in self._conv and y not in self._conv: - for z_x, coeff_x in self._conv[x]: - quadratic[(z_x, y.name)] = v * coeff_x + if x.name in self._conv and y.name not in self._conv: + for z_x, coeff_x in self._conv[x.name]: + quadratic[z_x, y.name] = v * coeff_x linear[y.name] = linear.get(y.name, 0.0) + v * x.lowerbound - elif x not in self._conv and y in self._conv: + elif x.name not in self._conv and y.name in self._conv: for z_y, coeff_y in self._conv[y]: - quadratic[(x.name, z_y)] = v * coeff_y + quadratic[x.name, z_y] = v * coeff_y linear[x.name] = linear.get(x.name, 0.0) + v * y.lowerbound - elif x in self._conv and y in self._conv: + elif x.name in self._conv and y.name in self._conv: for z_x, coeff_x in self._conv[x]: for z_y, coeff_y in self._conv[y]: - quadratic[(z_x, z_y)] = v * coeff_x * coeff_y + quadratic[z_x, z_y] = v * coeff_x * coeff_y for z_x, coeff_x in self._conv[x]: linear[z_x] = linear.get(z_x, 0.0) + v * y.lowerbound @@ -161,7 +151,7 @@ def _encode_quadratic_coefficients_dict(self, coefficients: Dict[Tuple[str, str] constant += v * x.lowerbound * y.lowerbound else: - quadratic[(x.name, y.name)] = v + quadratic[x.name, y.name] = v return quadratic, linear, constant @@ -179,13 +169,9 @@ def _substitute_int_var(self): linear[i] = linear.get(i, 0) + v if self._src.objective.sense == ObjSense.MINIMIZE: - self._dst.minimize(constant, - linear, - quadratic) + self._dst.minimize(constant, linear, quadratic) else: - self._dst.maximize(constant, - linear, - quadratic) + self._dst.maximize(constant, linear, quadratic) # set linear constraints for constraint in self._src.linear_constraints: @@ -207,7 +193,7 @@ def _substitute_int_var(self): self._dst.quadratic_constraint(linear, quadratic, constraint.sense, constraint.rhs - constant, constraint.name) - def decode(self, result: 'OptimizationResult') -> 'OptimizationResult': + def decode(self, result: OptimizationResult) -> OptimizationResult: """Convert the encoded problem (binary variables) back to the original (integer variables). Args: From cacc87fdf20bedbd025a8fe2aa69637f3bc6df31 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Fri, 17 Apr 2020 08:47:26 +0200 Subject: [PATCH 231/323] bug fixes --- qiskit/optimization/algorithms/minimum_eigen_optimizer.py | 4 ++-- .../converters/penalize_linear_equality_constraints.py | 4 ++-- qiskit/optimization/converters/quadratic_program_to_qubo.py | 2 +- test/optimization/test_min_eigen_optimizer.py | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 706f1f50f6..1e4fa8c1a7 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -141,9 +141,9 @@ def solve(self, problem: QuadraticProgram) -> MinimumEigenOptimizerResult: # analyze results samples = eigenvector_to_solutions(eigen_results.eigenstate, operator) - samples = [(res[0], problem_.objective.get_sense() * (res[1] + offset), res[2]) + samples = [(res[0], problem_.objective.sense.value * (res[1] + offset), res[2]) for res in samples] - samples.sort(key=lambda x: problem_.objective.get_sense() * x[1]) + samples.sort(key=lambda x: problem_.objective.sense.value * x[1]) # translate result back to integers opt_res = MinimumEigenOptimizerResult(samples[0][0], samples[0][1], samples, qubo_converter) diff --git a/qiskit/optimization/converters/penalize_linear_equality_constraints.py b/qiskit/optimization/converters/penalize_linear_equality_constraints.py index 807b0b79ff..e0b2f92db8 100644 --- a/qiskit/optimization/converters/penalize_linear_equality_constraints.py +++ b/qiskit/optimization/converters/penalize_linear_equality_constraints.py @@ -95,8 +95,8 @@ def encode(self, op: QuadraticProgram, penalty_factor: float = 1e5, linear[j] = linear.get(j, 0.0) + penalty_factor * -2 * coef * constant # quadratic parts of penalty*(Constant-func)**2: penalty*(func**2) - for j, coef_1 in zip(row.ind, row.val): - for k, coef_2 in zip(row.ind, row.val): + for j, coef_1 in row.items(): + for k, coef_2 in row.items(): # if j and k already exist in the quadratic terms dict, # add a penalty term into existing value # else create new key and value in the quadratic term dict diff --git a/qiskit/optimization/converters/quadratic_program_to_qubo.py b/qiskit/optimization/converters/quadratic_program_to_qubo.py index e97d0977b4..d3fa426a84 100644 --- a/qiskit/optimization/converters/quadratic_program_to_qubo.py +++ b/qiskit/optimization/converters/quadratic_program_to_qubo.py @@ -115,7 +115,7 @@ def get_compatibility_msg(problem: QuadraticProgram) -> bool: if not all([constraint.sense == ConstraintSense.EQ for constraint in problem.linear_constraints]): msg += 'Only linear equality constraints are supported.' - if problem.quadratic_constraints.get_num() > 0: + if len(problem.quadratic_constraints) > 0: msg += 'Quadratic constraints are not supported. ' # if an error occurred, return error message, otherwise, return None diff --git a/test/optimization/test_min_eigen_optimizer.py b/test/optimization/test_min_eigen_optimizer.py index d163278692..ab9d33ddeb 100644 --- a/test/optimization/test_min_eigen_optimizer.py +++ b/test/optimization/test_min_eigen_optimizer.py @@ -49,8 +49,8 @@ def setUp(self): @data( ('exact', None, 'op_ip1.lp'), - ('qaoa', 'statevector_simulator', 'op_ip1.lp'), - ('qaoa', 'qasm_simulator', 'op_ip1.lp') + # ('qaoa', 'statevector_simulator', 'op_ip1.lp'), + # ('qaoa', 'qasm_simulator', 'op_ip1.lp') ) def test_min_eigen_optimizer(self, config): """ Min Eigen Optimizer Test """ @@ -68,7 +68,7 @@ def test_min_eigen_optimizer(self, config): # load optimization problem problem = QuadraticProgram() - problem.read(self.resource_path + filename) + problem.read_from_lp_file(self.resource_path + filename) # solve problem with cplex cplex = CplexOptimizer() From 76c6930858c48fa2991006ba38acdca22fc8b553 Mon Sep 17 00:00:00 2001 From: Atsushi Matsuo Date: Fri, 17 Apr 2020 16:47:55 +0900 Subject: [PATCH 232/323] removed importing cplex and fixied linting --- .../converters/inequality_to_equality.py | 45 +++++++------------ 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/qiskit/optimization/converters/inequality_to_equality.py b/qiskit/optimization/converters/inequality_to_equality.py index c2cf7b26e4..c4feb64fd0 100644 --- a/qiskit/optimization/converters/inequality_to_equality.py +++ b/qiskit/optimization/converters/inequality_to_equality.py @@ -11,7 +11,6 @@ # 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. - """The inequality to equality converter.""" import copy @@ -27,14 +26,6 @@ logger = logging.getLogger(__name__) -_HAS_CPLEX = False -try: - from cplex import SparsePair - - _HAS_CPLEX = True -except ImportError: - logger.info('CPLEX is not installed.') - class InequalityToEquality: """Convert inequality constraints into equality constraints by introducing slack variables. @@ -50,8 +41,6 @@ class InequalityToEquality: def __init__(self) -> None: """Initialize the inequality to equality variable converter.""" - if not _HAS_CPLEX: - raise NameError('CPLEX is not installed.') self._src = None self._dst = None @@ -59,7 +48,7 @@ def __init__(self) -> None: # e.g., self._conv = {'c1': [c1@slack_var]} def encode( - self, op: QuadraticProgram, name: Optional[str] = None, mode: str = 'auto' + self, op: QuadraticProgram, name: Optional[str] = None, mode: str = 'auto' ) -> QuadraticProgram: """Convert a problem with inequality constraints into one with only equality constraints. @@ -93,11 +82,11 @@ def encode( self._dst.binary_var(name=x.name) elif x.vartype == VarType.INTEGER: self._dst.integer_var( - name=x.name, lower_bound=x.lower_bound, upperbound=x.upper_bound + name=x.name, lowerbound=x.lowerbound, upperbound=x.upperbound ) elif x.vartype == VarType.CONTINUOUS: self._dst.continuous_var( - name=x.name, lower_bound=x.lower_bound, upperbound=x.upper_bound + name=x.name, lowerbound=x.lowerbound, upperbound=x.upperbound ) else: raise QiskitOptimizationError("Unsupported variable type {}".format(x.vartype)) @@ -191,7 +180,7 @@ def encode( def _add_integer_slack_var_linear_constraint(self, linear, sense, rhs, name): # If a coefficient that is not integer exist, raise error - if any(isinstance(coef, float) and not coef.is_integer() for coef in inear.values()): + if any(isinstance(coef, float) and not coef.is_integer() for coef in linear.values()): raise QiskitOptimizationError('Can not use a slack variable for ' + name) # If rhs is float number, round up/down to the nearest integer. @@ -230,12 +219,12 @@ def _add_continuous_slack_var_linear_constraint(self, linear, sense, rhs, name): if sense == ConstraintSense.LE: sign = 1 self._dst.continuous_var( - name=[slack_name], lowerbound=[0], upperbound=[new_rhs - lhs_lb] + name=[slack_name], lowerbound=[0], upperbound=[rhs - lhs_lb] ) elif sense == ConstraintSense.GE: sign = -1 self._dst.continuous_var( - name=[slack_name], lowerbound=[0], upperbound=[lhs_ub - new_rhs] + name=[slack_name], lowerbound=[0], upperbound=[lhs_ub - rhs] ) else: raise QiskitOptimizationError('The type of Sense in {} is not supported'.format(name)) @@ -256,7 +245,7 @@ def _add_auto_slack_var_linear_constraint(self, linear, sense, rhs, name): def _add_integer_slack_var_quadratic_constraint(self, linear, quadratic, sense, rhs, name): # If a coefficient that is not integer exist, raise an error if any( - isinstance(coef, float) and not coef.is_integer() for coef in quadratic.values() + isinstance(coef, float) and not coef.is_integer() for coef in quadratic.values() ) or any(isinstance(coef, float) and not coef.is_integer() for coef in linear.values()): raise QiskitOptimizationError('Can not use a slack variable for ' + name) @@ -290,7 +279,7 @@ def _add_integer_slack_var_quadratic_constraint(self, linear, quadratic, sense, def _add_continuous_slack_var_quadratic_constraint(self, linear, quadratic, sense, rhs, name): # If a coefficient that is not integer exist, raise error if any( - isinstance(coef, float) and not coef.is_integer() for coef in quadratic.values() + isinstance(coef, float) and not coef.is_integer() for coef in quadratic.values() ) or any(isinstance(coef, float) and not coef.is_integer() for coef in linear.values()): raise QiskitOptimizationError('Can not use a slack variable for ' + name) @@ -302,13 +291,13 @@ def _add_continuous_slack_var_quadratic_constraint(self, linear, quadratic, sens if sense == ConstraintSense.LE: sign = 1 - self._dst.continous_var( - name=[slack_name], lowerbound=[0], upperbound=[new_rhs - lhs_lb] + self._dst.continuous_var( + name=[slack_name], lowerbound=[0], upperbound=[rhs - lhs_lb] ) elif sense == ConstraintSense.GE: sign = -1 self._dst.continuous_var( - name=[slack_name], lowerbound=[0], upperbound=[lhs_ub - new_rhs] + name=[slack_name], lowerbound=[0], upperbound=[lhs_ub - rhs] ) else: raise QiskitOptimizationError('The type of Sense in {} is not supported'.format(name)) @@ -321,9 +310,9 @@ def _add_continuous_slack_var_quadratic_constraint(self, linear, quadratic, sens def _add_auto_slack_var_quadratic_constraint(self, linear, quadratic, sense, rhs, name): # If a coefficient that is not integer exist, use a continuous slack variable if any( - isinstance(coef, float) and not coef.is_integer() for coef in quadratic.values() + isinstance(coef, float) and not coef.is_integer() for coef in quadratic.values() ) or any(isinstance(coef, float) and not coef.is_integer() for coef in linear.values()): - self._add_continuos_slack_var_quadratic_constraint(linear, quadratic, sense, rhs, name) + self._add_continuous_slack_var_quadratic_constraint(linear, quadratic, sense, rhs, name) # Else use an integer slack variable else: self._add_integer_slack_var_quadratic_constraint(linear, quadratic, sense, rhs, name) @@ -339,14 +328,14 @@ def _calc_linear_bounds(self, linear): def _calc_quadratic_bounds(self, linear, quadratic): lhs_lb, lhs_ub = 0, 0 # Calcurate the lowerbound and the upperbound of the linear part - linear_lb, linear_ub = self._cal_quadratic_bounds(linear) + linear_lb, linear_ub = self._calc_linear_bounds(linear) lhs_lb += linear_lb lhs_ub += linear_ub # Calcurate the lowerbound and the upperbound of the quadratic part for (name_i, name_j), v in quadratic.items(): - x = self.src.get_variable(name_i) - y = self.src.get_variable(name_j) + x = self._src.get_variable(name_i) + y = self._src.get_variable(name_j) lhs_lb += min( x.lowerbound * y.lowerbound * v, @@ -375,7 +364,7 @@ def decode(self, result: 'OptimizationResult') -> 'OptimizationResult': new_result = OptimizationResult() # convert the optimization result into that of the original problem - names = self._dst.variables.get_names() + names = self._dst.variables() vals = result.x new_vals = self._decode_var(names, vals) new_result.x = new_vals From e61e2e0c3661e2ead3fea6903098286a9ac7c3cf Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Fri, 17 Apr 2020 11:21:52 +0200 Subject: [PATCH 233/323] update QP and tests --- .../converters/integer_to_binary.py | 20 +++++++++---------- .../penalize_linear_equality_constraints.py | 4 ++-- .../converters/quadratic_program_to_qubo.py | 2 +- test/optimization/test_min_eigen_optimizer.py | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/qiskit/optimization/converters/integer_to_binary.py b/qiskit/optimization/converters/integer_to_binary.py index 632401931c..2d9367c24d 100644 --- a/qiskit/optimization/converters/integer_to_binary.py +++ b/qiskit/optimization/converters/integer_to_binary.py @@ -24,7 +24,7 @@ from ..exceptions import QiskitOptimizationError from ..problems.quadratic_objective import ObjSense from ..problems.quadratic_program import QuadraticProgram -from ..problems.variable import VarType +from ..problems.variable import Variable, VarType logger = logging.getLogger(__name__) @@ -52,8 +52,8 @@ def __init__(self) -> None: self._src = None self._dst = None - self._conv: Dict[str, List[Tuple[str, int]]] = {} - # e.g., self._conv = {'x': [('x@1', 1), ('x@2', 2)]} + self._conv: Dict[Variable, List[Tuple[str, int]]] = {} + # e.g., self._conv = {x: [('x@1', 1), ('x@2', 2)]} def encode(self, op: QuadraticProgram, name: Optional[str] = None) -> QuadraticProgram: """Convert an integer problem into a new problem with binary variables. @@ -81,7 +81,7 @@ def encode(self, op: QuadraticProgram, name: Optional[str] = None) -> QuadraticP for x in self._src.variables: if x.vartype == VarType.INTEGER: new_vars = self._encode_var(x.name, x.lowerbound, x.upperbound) - self._conv[x.name] = new_vars + self._conv[x] = new_vars for (var_name, _) in new_vars: self._dst.binary_var(var_name) else: @@ -110,8 +110,8 @@ def _encode_linear_coefficients_dict(self, coefficients: Dict[str, float]) \ linear = {} for name, v in coefficients.items(): x = self._src.get_variable(name) - if x.name in self._conv: - for y, coeff in self._conv[x.name]: + if x in self._conv: + for y, coeff in self._conv[x]: linear[y] = v * coeff constant += v * x.lowerbound else: @@ -128,17 +128,17 @@ def _encode_quadratic_coefficients_dict(self, coefficients: Dict[Tuple[str, str] x = self._src.get_variable(name_i) y = self._src.get_variable(name_j) - if x.name in self._conv and y.name not in self._conv: - for z_x, coeff_x in self._conv[x.name]: + if x in self._conv and y not in self._conv: + for z_x, coeff_x in self._conv[x]: quadratic[z_x, y.name] = v * coeff_x linear[y.name] = linear.get(y.name, 0.0) + v * x.lowerbound - elif x.name not in self._conv and y.name in self._conv: + elif x not in self._conv and y in self._conv: for z_y, coeff_y in self._conv[y]: quadratic[x.name, z_y] = v * coeff_y linear[x.name] = linear.get(x.name, 0.0) + v * y.lowerbound - elif x.name in self._conv and y.name in self._conv: + elif x in self._conv and y in self._conv: for z_x, coeff_x in self._conv[x]: for z_y, coeff_y in self._conv[y]: quadratic[z_x, z_y] = v * coeff_x * coeff_y diff --git a/qiskit/optimization/converters/penalize_linear_equality_constraints.py b/qiskit/optimization/converters/penalize_linear_equality_constraints.py index e0b2f92db8..67bb22ec15 100644 --- a/qiskit/optimization/converters/penalize_linear_equality_constraints.py +++ b/qiskit/optimization/converters/penalize_linear_equality_constraints.py @@ -102,9 +102,9 @@ def encode(self, op: QuadraticProgram, penalty_factor: float = 1e5, # else create new key and value in the quadratic term dict # according to implementation of quadratic terms in OptimizationModel, - # multiply by 2 + # don't need to multiply by 2, since loops run over (x, y) and (y, x). quadratic[(j, k)] = quadratic.get((j, k), 0.0) \ - + penalty_factor * coef_1 * coef_2 * 2 + + penalty_factor * coef_1 * coef_2 if self._src.objective.sense == ObjSense.MINIMIZE: self._dst.minimize(offset, linear, quadratic) diff --git a/qiskit/optimization/converters/quadratic_program_to_qubo.py b/qiskit/optimization/converters/quadratic_program_to_qubo.py index d3fa426a84..3d46b016e1 100644 --- a/qiskit/optimization/converters/quadratic_program_to_qubo.py +++ b/qiskit/optimization/converters/quadratic_program_to_qubo.py @@ -33,7 +33,7 @@ class QuadraticProgramToQubo: >>> problem2 = conv.encode(problem) """ - def __init__(self, penalty: Optional[float] = 1e5) -> None: + def __init__(self, penalty: Optional[float] = None) -> None: """ Args: penalty: Penalty factor to scale equality constraints that are added to objective. diff --git a/test/optimization/test_min_eigen_optimizer.py b/test/optimization/test_min_eigen_optimizer.py index ab9d33ddeb..52767f117e 100644 --- a/test/optimization/test_min_eigen_optimizer.py +++ b/test/optimization/test_min_eigen_optimizer.py @@ -49,8 +49,8 @@ def setUp(self): @data( ('exact', None, 'op_ip1.lp'), - # ('qaoa', 'statevector_simulator', 'op_ip1.lp'), - # ('qaoa', 'qasm_simulator', 'op_ip1.lp') + ('qaoa', 'statevector_simulator', 'op_ip1.lp'), + ('qaoa', 'qasm_simulator', 'op_ip1.lp') ) def test_min_eigen_optimizer(self, config): """ Min Eigen Optimizer Test """ From 20990fadaabce3e35a7867f5b7bbb2c93214e80b Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Fri, 17 Apr 2020 14:31:38 +0200 Subject: [PATCH 234/323] add __setitem__ to linear/quadratic expression --- qiskit/optimization/problems/linear_expression.py | 5 +++++ qiskit/optimization/problems/quadratic_expression.py | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/qiskit/optimization/problems/linear_expression.py b/qiskit/optimization/problems/linear_expression.py index 03de43e8ca..a243e3d852 100644 --- a/qiskit/optimization/problems/linear_expression.py +++ b/qiskit/optimization/problems/linear_expression.py @@ -56,6 +56,11 @@ def __getitem__(self, i: Union[int, str]) -> float: i = self.quadratic_program.variables_index[i] return self.coefficients[0, i] + def __setitem__(self, i: Union[int, str], value: float) -> None: + if isinstance(i, str): + i = self.quadratic_program.variables_index[i] + self._coefficients[0, i] = value + def _coeffs_to_dok_matrix(self, coefficients: Union[ndarray, spmatrix, List, Dict[Union[int, str], float]] diff --git a/qiskit/optimization/problems/quadratic_expression.py b/qiskit/optimization/problems/quadratic_expression.py index ea8a635729..a29d0042ea 100644 --- a/qiskit/optimization/problems/quadratic_expression.py +++ b/qiskit/optimization/problems/quadratic_expression.py @@ -60,6 +60,14 @@ def __getitem__(self, key: Tuple[Union[int, str], Union[int, str]]) -> float: j = self.quadratic_program.variables_index[j] return self.coefficients[i, j] + def __setitem__(self, key: Tuple[Union[int, str], Union[int, str]], value: float) -> None: + i, j = key + if isinstance(i, str): + i = self.quadratic_program.variables_index[i] + if isinstance(j, str): + j = self.quadratic_program.variables_index[j] + self.coefficients[i, j] = value + def _coeffs_to_dok_matrix(self, coefficients: Union[ndarray, spmatrix, List[List[float]], Dict[ From add5c1ce90cf0ac7d5b01ea62c2988ac5a825862 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Fri, 17 Apr 2020 14:37:55 +0200 Subject: [PATCH 235/323] update recursive minimum eigen optimizer --- .../recursive_minimum_eigen_optimizer.py | 61 +++++++------------ .../test_recursive_optimization.py | 6 +- 2 files changed, 26 insertions(+), 41 deletions(-) diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 7a72607428..983d8dd607 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -25,18 +25,11 @@ from .optimization_algorithm import OptimizationAlgorithm, OptimizationResult from .minimum_eigen_optimizer import MinimumEigenOptimizer from ..exceptions.qiskit_optimization_error import QiskitOptimizationError -from ..problems.quadratic_program import QuadraticProgram +from ..problems.quadratic_program import QuadraticProgram, SubstitutionStatus from ..converters.quadratic_program_to_qubo import QuadraticProgramToQubo logger = logging.getLogger(__name__) -_HAS_CPLEX = False -try: - from cplex import SparseTriple - _HAS_CPLEX = True -except ImportError: - logger.info('CPLEX is not installed.') - class RecursiveMinimumEigenOptimizer(OptimizationAlgorithm): """A meta-algorithm that applies a recursive optimization. @@ -80,15 +73,8 @@ def __init__(self, min_eigen_optimizer: MinimumEigenOptimizer, min_num_vars: int Raises: QiskitOptimizationError: In case of invalid parameters (num_min_vars < 1). - NameError: CPLEX is not installed. """ - # TODO: should also allow function that maps problem to -correlators? - # --> would support efficient classical implementation for QAOA with depth p=1 - # --> add results class for MinimumEigenSolver that contains enough info to do so. - if not _HAS_CPLEX: - raise NameError('CPLEX is not installed.') - validate_min('min_num_vars', min_num_vars, 1) self._min_eigen_optimizer = min_eigen_optimizer @@ -134,7 +120,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: # run recursive optimization until the resulting problem is small enough replacements = {} - while problem_.variables.get_num() > self._min_num_vars: + while problem_.get_num_vars() > self._min_num_vars: # solve current problem with optimizer result = self._min_eigen_optimizer.solve(problem_) @@ -143,13 +129,13 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: correlations = result.get_correlations() i, j = self._find_strongest_correlation(correlations) - x_i = problem_.variables.get_names(i) - x_j = problem_.variables.get_names(j) + x_i = problem_.variables[i].name + x_j = problem_.variables[j].name if correlations[i, j] > 0: # set x_i = x_j - problem_, status = problem_.substitute_variables( - variables=SparseTriple([i], [j], [1])) - if status == problem_.substitution_status.infeasible: + problem_.substitute_variables() + problem_, status = problem_.substitute_variables(variables={i: (j, 1)}) + if status == SubstitutionStatus.infeasible: raise QiskitOptimizationError('Infeasible due to variable substitution') replacements[x_i] = (x_j, 1) else: @@ -158,22 +144,21 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: # 2. set x_i = -x_j # 1a. get additional offset - offset = problem_.objective.get_offset() - offset += problem_.objective.get_quadratic_coefficients(i, i) / 2 - offset += problem_.objective.get_linear(i) - problem_.objective.set_offset(offset) + constant = problem_.objective.constant + constant += problem_.objective.quadratic[i, i] / 2 + constant += problem_.objective.linear[i] + problem_.objective.constant = constant # 1b. get additional linear part - for k in range(problem_.variables.get_num()): - coeff = problem_.objective.get_quadratic_coefficients(i, k) + for k in range(problem_.get_num_vars()): + coeff = problem_.objective.quadratic[i, k] if np.abs(coeff) > 1e-10: - coeff += problem_.objective.get_linear(k) - problem_.objective.set_linear(k, coeff) + coeff += problem_.objective.linear[k] + problem_.objective.linear[k] = coeff # 2. replace x_i by -x_j - problem_, status = problem_.substitute_variables( - variables=SparseTriple([i], [j], [-1])) - if status == problem_.substitution_status.infeasible: + problem_, status = problem_.substitute_variables(variables={i: (j, -1)}) + if status == SubstitutionStatus.infeasible: raise QiskitOptimizationError('Infeasible due to variable substitution') replacements[x_i] = (x_j, -1) @@ -182,8 +167,8 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: # unroll replacements var_values = {} - for i, name in enumerate(problem_.variables.get_names()): - var_values[name] = result.x[i] + for i, x in enumerate(problem_.variables): + var_values[x.name] = result.x[i] def find_value(x, replacements, var_values): if x in var_values: @@ -201,12 +186,12 @@ def find_value(x, replacements, var_values): raise QiskitOptimizationError('Invalid values!') # loop over all variables to set their values - for x_i in problem_ref.variables.get_names(): - if x_i not in var_values: - find_value(x_i, replacements, var_values) + for x_i in problem_ref.variables: + if x_i.name not in var_values: + find_value(x_i.name, replacements, var_values) # construct result - x = [var_values[name] for name in problem_ref.variables.get_names()] + x = [var_values[x_aux.name] for x_aux in problem_ref.variables] fval = result.fval results = OptimizationResult(x, fval, (replacements, qubo_converter)) results = qubo_converter.decode(results) diff --git a/test/optimization/test_recursive_optimization.py b/test/optimization/test_recursive_optimization.py index 3114e0054a..6c7b2b9d82 100755 --- a/test/optimization/test_recursive_optimization.py +++ b/test/optimization/test_recursive_optimization.py @@ -37,8 +37,8 @@ def setUp(self): super().setUp() # fix random seed for reproducible results - np.random.seed = 109 - aqua_globals.random_seed = 89 + np.random.seed = 1 + aqua_globals.random_seed = 2 self.resource_path = './test/optimization/resources/' @@ -82,7 +82,7 @@ def test_recursive_min_eigen_optimizer(self, solver, simulator, filename): # load optimization problem problem = QuadraticProgram() - problem.read(self.resource_path + filename) + problem.read_from_lp_file(self.resource_path + filename) # solve problem with cplex cplex = CplexOptimizer() From 6ebd54e82e91c290692bb0b7330aeaa6963ceff0 Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Fri, 17 Apr 2020 14:21:47 +0100 Subject: [PATCH 236/323] removing 2* from quad objective --- .../optimization/algorithms/admm_optimizer.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 10db1bbfc9..f63f656fb4 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -634,12 +634,10 @@ def _create_step1_problem(self) -> QuadraticProgram: # prepare and set quadratic objective. # NOTE: The multiplication by 2 is needed for the solvers to parse # the quadratic coefficients. - # todo: do we need multiplication by two? + # todo: do we need multiplication by two? For anything other than self._state.q0 quadratic_objective = self._state.q0 +\ - 2 * ( - self._params.factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) + + self._params.factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) +\ self._state.rho / 2 * np.eye(binary_size) - ) # for i in range(binary_size): # for j in range(i, binary_size): # op1.objective.set_quadratic_coefficients(i, j, quadratic_objective[i, j]) @@ -676,7 +674,8 @@ def _create_step2_problem(self) -> QuadraticProgram: continuous_index = 0 for variable in self._state.op.variables: if variable.vartype == VarType.CONTINUOUS: - op2.continuous_var(name="u0_" + str(continuous_index + 1), lowerbound=variable.lowerbound, upperbound=variable.upperbound) + op2.continuous_var(name="u0_" + str(continuous_index + 1), + lowerbound=variable.lowerbound, upperbound=variable.upperbound) continuous_index += 1 # add z variables. @@ -687,7 +686,8 @@ def _create_step2_problem(self) -> QuadraticProgram: for i in range(binary_size): op2.binary_var(name="z0_" + str(i + 1)) - q_z = 2 * (self._state.rho / 2 * np.eye(binary_size)) + # todo: do we need the 2* + q_z = self._state.rho / 2 * np.eye(binary_size) op2.objective.quadratic = block_diag(self._state.q1, q_z) # set quadratic objective coefficients for u variables. # if continuous_size: @@ -820,9 +820,9 @@ def _create_step3_problem(self) -> QuadraticProgram: op3.continuous_var(name="y_" + str(i + 1), lowerbound=-np.inf, upperbound=np.inf) # set quadratic objective. - # NOTE: The multiplication by 2 is needed for the solvers to parse the quadratic coeff-s. - q_y = 2 * (self._params.beta / 2 * np.eye(binary_size) + - self._state.rho / 2 * np.eye(binary_size)) + # todo: do we need 2*? + q_y = self._params.beta / 2 * np.eye(binary_size) + \ + self._state.rho / 2 * np.eye(binary_size) # for i in range(binary_size): # for j in range(i, binary_size): # op3.objective.set_quadratic_coefficients(i, j, q_y[i, j]) From 704a0ab1ccbb311431824ab09ff3f983677905e7 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Fri, 17 Apr 2020 15:19:29 +0100 Subject: [PATCH 237/323] moving to a new optimization stack --- 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 f63f656fb4..58c685d86f 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -162,7 +162,7 @@ class ADMMOptimizerResult(OptimizationResult): def __init__(self, x: Optional[Any] = None, fval: Optional[Any] = None, state: Optional[ADMMState] = None, results: Optional[Any] = None) -> None: - super().__init__(x, fval, results) + super().__init__(x, fval, results or state) self._state = state @property From 14768532589e4f309ed2a1015200503053e54c1a Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Fri, 17 Apr 2020 16:17:24 +0100 Subject: [PATCH 238/323] removing 2* from quad objective --- qiskit/optimization/algorithms/admm_optimizer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index f63f656fb4..fe45c1430f 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -297,6 +297,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: op2 = self._create_step2_problem() self._state.u, self._state.z = self._update_x1(op2) # debug + print("OP2: ", op2.print_as_lp_string()) self._log.debug("u=%s", self._state.u) self._log.debug("z=%s", self._state.z) @@ -305,6 +306,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: op3 = self._create_step3_problem() self._state.y = self._update_y(op3) # debug + print("OP3: ", op3.print_as_lp_string()) self._log.debug("y=%s", self._state.y) self._state.lambda_mult = self._update_lambda_mult() @@ -684,7 +686,7 @@ def _create_step2_problem(self) -> QuadraticProgram: # lb=[0.] * binary_size, # ub=[1.] * binary_size) for i in range(binary_size): - op2.binary_var(name="z0_" + str(i + 1)) + op2.continuous_var(name="z0_" + str(i + 1), lowerbound=0, upperbound=1.) # todo: do we need the 2* q_z = self._state.rho / 2 * np.eye(binary_size) From 38839fc734a20c3f8c699b029924339ceb63b70a Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Fri, 17 Apr 2020 16:18:34 +0100 Subject: [PATCH 239/323] fix in step2 --- qiskit/optimization/algorithms/admm_optimizer.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index fe45c1430f..25255ecff6 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -681,10 +681,6 @@ def _create_step2_problem(self) -> QuadraticProgram: continuous_index += 1 # add z variables. - # op2.variables.add(names=["z0_" + str(i + 1) for i in range(binary_size)], - # types=["C"] * binary_size, - # lb=[0.] * binary_size, - # ub=[1.] * binary_size) for i in range(binary_size): op2.continuous_var(name="z0_" + str(i + 1), lowerbound=0, upperbound=1.) From 1779131a3b15bbb52ffcd6141409c6a5795b6f69 Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Fri, 17 Apr 2020 16:42:58 +0100 Subject: [PATCH 240/323] fix in get_obj_val --- 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 25255ecff6..054fca7502 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -963,7 +963,7 @@ def _get_objective_value(self) -> float: """ def quadratic_form(matrix, x, c): - return np.dot(x.T, np.dot(matrix / 2, x)) + np.dot(c.T, x) + return np.dot(x.T, np.dot(matrix, x)) + np.dot(c.T, x) obj_val = quadratic_form(self._state.q0, self._state.x0, self._state.c0) obj_val += quadratic_form(self._state.q1, self._state.u, self._state.c1) From a687f3a2111fbc94d9045c3b7ddfd71d6975d2e5 Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Fri, 17 Apr 2020 16:54:05 +0100 Subject: [PATCH 241/323] fix in get_obj_val --- qiskit/optimization/algorithms/admm_optimizer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 054fca7502..f5eaecc02d 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -297,7 +297,6 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: op2 = self._create_step2_problem() self._state.u, self._state.z = self._update_x1(op2) # debug - print("OP2: ", op2.print_as_lp_string()) self._log.debug("u=%s", self._state.u) self._log.debug("z=%s", self._state.z) From 490da574111e8fe2e1fc9b918bcfd8b27daf1de9 Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Fri, 17 Apr 2020 16:57:29 +0100 Subject: [PATCH 242/323] fix in get_obj_val --- 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 f5eaecc02d..2fa427d038 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -287,7 +287,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: and (elapsed_time < self._params.max_time): if binary_indices: op1 = self._create_step1_problem() - print("OP1: ", op1.print_as_lp_string()) + self._state.x0 = self._update_x0(op1) # else, no binary variables exist, # and no update to be done in this case. From aa2bb983eab974d4114d9003e916fb7b9aaf3f81 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Mon, 20 Apr 2020 19:57:28 +0900 Subject: [PATCH 243/323] Use compressed dictionary as the internal of quadratic expression --- .../problems/quadratic_expression.py | 57 +++++++++++++------ .../optimization/test_quadratic_expression.py | 29 ++++++++-- 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_expression.py b/qiskit/optimization/problems/quadratic_expression.py index a29d0042ea..1428728c1b 100644 --- a/qiskit/optimization/problems/quadratic_expression.py +++ b/qiskit/optimization/problems/quadratic_expression.py @@ -18,7 +18,7 @@ import numpy as np from numpy import ndarray -from scipy.sparse import spmatrix, dok_matrix +from scipy.sparse import spmatrix, dok_matrix, tril, triu from qiskit.optimization import QiskitOptimizationError from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram @@ -34,7 +34,8 @@ def __init__(self, quadratic_program: "QuadraticProgram", The quadratic expression can be defined via an array, a list, a sparse matrix, or a dictionary that uses variable names or indices as keys and stores the values internally as a - dok_matrix. + dok_matrix. We stores values in a compressed way, i.e., values at symmetric positions are + summed up in the upper triangle. For example, {(0, 1): 1, (1, 0): 2} -> {(0, 1): 3}. Args: quadratic_program: The parent QuadraticProgram. @@ -48,7 +49,7 @@ def __getitem__(self, key: Tuple[Union[int, str], Union[int, str]]) -> float: """Returns the coefficient where i, j can be a variable names or indices. Args: - key: the tuple of indices or names of the variables corresponding to the coefficient. + key: The tuple of indices or names of the variables corresponding to the coefficient. Returns: The coefficient corresponding to the addressed variables. @@ -58,21 +59,26 @@ def __getitem__(self, key: Tuple[Union[int, str], Union[int, str]]) -> float: i = self.quadratic_program.variables_index[i] if isinstance(j, str): j = self.quadratic_program.variables_index[j] - return self.coefficients[i, j] + return self.coefficients[min(i, j), max(i, j)] def __setitem__(self, key: Tuple[Union[int, str], Union[int, str]], value: float) -> None: + """Sets the coefficient where i, j can be a variable names or indices. + + Args: + key: The tuple of indices or names of the variables corresponding to the coefficient. + value: The coefficient corresponding to the addressed variables. + """ i, j = key if isinstance(i, str): i = self.quadratic_program.variables_index[i] if isinstance(j, str): j = self.quadratic_program.variables_index[j] - self.coefficients[i, j] = value + self.coefficients[min(i, j), max(i, j)] = value def _coeffs_to_dok_matrix(self, coefficients: Union[ndarray, spmatrix, List[List[float]], - Dict[ - Tuple[Union[int, str], Union[int, str]], - float]]) -> dok_matrix: + Dict[Tuple[Union[int, str], Union[int, str]], + float]]) -> dok_matrix: """Maps given coefficients to a dok_matrix. Args: @@ -97,8 +103,21 @@ def _coeffs_to_dok_matrix(self, coeffs[i, j] = value coefficients = coeffs else: - raise QiskitOptimizationError("Unsupported format for coefficients.") - return coefficients + raise QiskitOptimizationError( + "Unsupported format for coefficients: {}".format(coefficients)) + return self._triangle_matrix(coefficients) + + @staticmethod + def _triangle_matrix(mat: dok_matrix) -> dok_matrix: + lower = tril(mat, -1, format='dok') + # `todok` is necessary because subtraction results in other format + return (mat + lower.transpose() - lower).todok() + + @staticmethod + def _symmetric_matrix(mat: dok_matrix) -> dok_matrix: + upper = triu(mat, 1, format='dok') / 2 + # `todok` is necessary because subtraction results in other format + return (mat + upper.transpose() - upper).todok() @property def coefficients(self) -> dok_matrix: @@ -121,31 +140,37 @@ def coefficients(self, """ self._coefficients = self._coeffs_to_dok_matrix(coefficients) - def to_array(self) -> ndarray: + def to_array(self, symmetric: bool = False) -> ndarray: """Returns the coefficients of the quadratic expression as array. + Args: + symmetric: Determines whether the output is in a symmetric form or not. + Returns: An array with the coefficients corresponding to the quadratic expression. """ - return self._coefficients.toarray() + coeffs = self._symmetric_matrix(self._coefficients) if symmetric else self._coefficients + return coeffs.toarray() - def to_dict(self, use_name: bool = False - ) -> Dict[Union[Tuple[int, int], Tuple[str, str]], float]: + def to_dict(self, symmetric: bool = False, use_name: bool = False) \ + -> Dict[Union[Tuple[int, int], Tuple[str, str]], float]: """Returns the coefficients of the quadratic expression as dictionary, either using tuples of variable names or indices as keys. Args: + symmetric: Determines whether the output is in a symmetric form or not. use_name: Determines whether to use index or names to refer to variables. Returns: An dictionary with the coefficients corresponding to the quadratic expression. """ + coeffs = self._symmetric_matrix(self._coefficients) if symmetric else self._coefficients if use_name: return {(self.quadratic_program.variables[i].name, self.quadratic_program.variables[j].name): v - for (i, j), v in self._coefficients.items()} + for (i, j), v in coeffs.items()} else: - return {(i, j): v for (i, j), v in self._coefficients.items()} + return dict(coeffs.items()) def evaluate(self, x: Union[ndarray, List, Dict[Union[int, str], float]]) -> float: """Evaluate the quadratic expression for given variables: x * Q * x. diff --git a/test/optimization/test_quadratic_expression.py b/test/optimization/test_quadratic_expression.py index 4695ffe023..e659cbac51 100644 --- a/test/optimization/test_quadratic_expression.py +++ b/test/optimization/test_quadratic_expression.py @@ -37,7 +37,10 @@ def test_init(self): for _ in range(5): quadratic_program.continuous_var() - coefficients_list = [[i * j for i in range(5)] for j in range(5)] + coefficients_list = [[0 for _ in range(5)] for _ in range(5)] + for i, v in enumerate(coefficients_list): + for j, _ in enumerate(v): + coefficients_list[min(i, j)][max(i, j)] += i * j coefficients_array = np.array(coefficients_list) coefficients_dok = dok_matrix(coefficients_list) coefficients_dict_int = {(i, j): v for (i, j), v in coefficients_dok.items()} @@ -62,11 +65,17 @@ def test_get_item(self): for _ in range(5): quadratic_program.continuous_var() - coefficients = [[i * j for i in range(5)] for j in range(5)] + coefficients = [[0 for _ in range(5)] for _ in range(5)] + for i, v in enumerate(coefficients): + for j, _ in enumerate(v): + coefficients[min(i, j)][max(i, j)] += i * j quadratic = QuadraticExpression(quadratic_program, coefficients) for i, j_v in enumerate(coefficients): - for j, v in enumerate(j_v): - self.assertEqual(quadratic[i, j], v) + for j, _ in enumerate(j_v): + if i == j: + self.assertEqual(quadratic[i, j], coefficients[i][j]) + else: + self.assertEqual(quadratic[i, j], coefficients[i][j] + coefficients[j][i]) def test_setters(self): """ test setters. """ @@ -79,7 +88,10 @@ def test_setters(self): zeros = np.zeros((n, n)) quadratic = QuadraticExpression(quadratic_program, zeros) - coefficients_list = [[i * j for i in range(5)] for j in range(5)] + coefficients_list = [[0 for _ in range(5)] for _ in range(5)] + for i, v in enumerate(coefficients_list): + for j, _ in enumerate(v): + coefficients_list[min(i, j)][max(i, j)] += i * j coefficients_array = np.array(coefficients_list) coefficients_dok = dok_matrix(coefficients_list) coefficients_dict_int = {(i, j): v for (i, j), v in coefficients_dok.items()} @@ -103,7 +115,10 @@ def test_evaluate(self): quadratic_program = QuadraticProgram() x = [quadratic_program.continuous_var() for _ in range(5)] - coefficients_list = [[i * j for i in range(5)] for j in range(5)] + coefficients_list = [[0 for _ in range(5)] for _ in range(5)] + for i, v in enumerate(coefficients_list): + for j, _ in enumerate(v): + coefficients_list[min(i, j)][max(i, j)] += i * j quadratic = QuadraticExpression(quadratic_program, coefficients_list) values_list = list(range(len(x))) @@ -122,6 +137,8 @@ def test_symmetric_set(self): q_p.binary_var('z') quad = QuadraticExpression(q_p, {('x', 'y'): -1, ('y', 'x'): 2, ('z', 'x'): 3}) self.assertDictEqual(quad.to_dict(use_name=True), {('x', 'y'): 1, ('x', 'z'): 3}) + self.assertDictEqual(quad.to_dict(symmetric=True, use_name=True), + {('x', 'y'): 0.5, ('y', 'x'): 0.5, ('x', 'z'): 1.5, ('z', 'x'): 1.5}) if __name__ == '__main__': From fa63fae89e50f10e8955574c485142f385a45beb Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Mon, 20 Apr 2020 12:52:05 +0100 Subject: [PATCH 244/323] minor --- qiskit/optimization/algorithms/admm_optimizer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 59a896fb7c..207804f072 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -305,7 +305,6 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: op3 = self._create_step3_problem() self._state.y = self._update_y(op3) # debug - print("OP3: ", op3.print_as_lp_string()) self._log.debug("y=%s", self._state.y) self._state.lambda_mult = self._update_lambda_mult() From 0dbb5a5cc3288e887577fdad7b389921d9c87252 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Mon, 20 Apr 2020 13:55:46 +0200 Subject: [PATCH 245/323] Update optimization_algorithm.py --- .../algorithms/optimization_algorithm.py | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index eeb6a4ac34..73983f367b 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -17,6 +17,7 @@ from typing import Any, Optional from abc import ABC, abstractmethod +from enum import Enum from ..problems.quadratic_program import QuadraticProgram @@ -75,14 +76,23 @@ class OptimizationResult: fval: The function value corresponding to the optimal value. results: The original results object returned from the optimization algorithm. This can contain more information than only the optimal value and function value. + status: The termination status of the algorithm. """ + class Status(Enum): + """Feasible values for the termination status of an optimization algorithm. + """ + SUCCESS = 0 + FAILURE = 1 + INFEASIBLE = 2 + def __init__(self, x: Optional[Any] = None, fval: Optional[Any] = None, - results: Optional[Any] = None) -> None: + results: Optional[Any] = None, status: Status = Status.SUCCESS) -> None: """Initialize the optimization result.""" self._val = x self._fval = fval self._results = results + self._status = status def __repr__(self): return '([%s] / %s)' % (','.join([str(x_) for x_ in self.x]), self.fval) @@ -116,6 +126,15 @@ def results(self) -> Any: """ return self._results + @property + def status(self) -> OptimizationResult.Status: + """Return the termination status of the algorithm. + + Returns: + The termination status of the algorithm. + """ + return self._status + @x.setter def x(self, x: Any) -> None: """Set a new optimal value. @@ -142,3 +161,12 @@ def results(self, results: Any) -> None: results: The new additional results of the optimization. """ self._results = results + + @status.setter + def status(self, status: Status) -> None: + """Set a new termination status. + + Args: + status: The new termination status. + """ + self._status = status From e700a4de8dbb77a94539817f57c0c25fcbaa7620 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Mon, 20 Apr 2020 22:24:39 +0900 Subject: [PATCH 246/323] fix a type hint --- qiskit/optimization/algorithms/optimization_algorithm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index 73983f367b..2c770aabca 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -127,7 +127,7 @@ def results(self) -> Any: return self._results @property - def status(self) -> OptimizationResult.Status: + def status(self) -> 'OptimizationResult.Status': """Return the termination status of the algorithm. Returns: From 034dcaa98e57ebce514211dc41099217c68ddc71 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Mon, 20 Apr 2020 14:45:06 +0100 Subject: [PATCH 247/323] moving to a new optimization stack --- .../optimization/algorithms/admm_optimizer.py | 123 +++--------------- test/optimization/test_admm.py | 81 ++++++++---- 2 files changed, 74 insertions(+), 130 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 207804f072..8446ea89be 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -229,7 +229,7 @@ def get_compatibility_msg(self, problem: QuadraticProgram) -> Optional[str]: # 1. only binary and continuous variables are supported for variable in problem.variables: - if variable.vartype not in (VarType.binary, VarType.continuous): + if variable.vartype not in (VarType.BINARY, VarType.CONTINUOUS): # variable is not binary and not continuous. msg += 'Only binary and continuous variables are supported. ' @@ -239,6 +239,7 @@ def get_compatibility_msg(self, problem: QuadraticProgram) -> Optional[str]: # 2. binary and continuous variables are separable in objective for binary_index in binary_indices: for continuous_index in continuous_indices: + # todo: implement coeff = problem.objective.get_quadratic_coefficients(binary_index, continuous_index) if coeff != 0: # binary and continuous vars are mixed. @@ -287,7 +288,6 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: and (elapsed_time < self._params.max_time): if binary_indices: op1 = self._create_step1_problem() - self._state.x0 = self._update_x0(op1) # else, no binary variables exist, # and no update to be done in this case. @@ -439,7 +439,6 @@ def _get_c(self, variable_indices: List[int]) -> np.ndarray: Returns: A numpy array of the shape(len(variable_indices)). """ - # c = np.array(self._state.op.objective.get_linear(variable_indices)) c = self._state.op.objective.linear.to_array().take(variable_indices) # flip the sign, according to the optimization sense, e.g. sense == 1 if minimize, # sense == -1 if maximize. @@ -460,14 +459,11 @@ def _assign_row_values(self, matrix: List[List[float]], vector: List[float], Returns: None """ - # assign matrix row. - row = [] - # todo: replace with .take() - for var_index in variable_indices: - # row.append(self._state.op - # .linear_constraints.get_coefficients(constraint_index, var_index)) - row.append( - self._state.op.linear_constraints[constraint_index].linear[var_index]) + # assign matrix row, actually pick coefficients at the positions specified in + # the variable_indices list + row = self._state.op.linear_constraints[constraint_index].linear.to_array().take( + variable_indices).tolist() + matrix.append(row) # assign vector row. @@ -476,6 +472,7 @@ def _assign_row_values(self, matrix: List[List[float]], vector: List[float], # flip the sign if constraint is G, we want L constraints. if self._state.op.linear_constraints[constraint_index].sense == ConstraintSense.GE: # invert the sign to make constraint "L". + # we invert only last row/last element matrix[-1] = [-1 * el for el in matrix[-1]] vector[-1] = -1 * vector[-1] @@ -548,12 +545,11 @@ def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) \ index_set = set(variable_indices) for constraint_index, constraint in enumerate(self._state.op.linear_constraints): - # todo: missing "R" if constraint.sense in [ConstraintSense.EQ]: # TODO: Ranged constraints should be supported continue - constraint_indices = set(self._state.op.linear_constraints[constraint_index].linear.to_dict().keys()) - # row = self._state.op.linear_constraints.get_rows(constraint_index) + constraint_indices = set( + self._state.op.linear_constraints[constraint_index].linear.to_dict().keys()) if constraint_indices.issubset(index_set): self._assign_row_values(matrix, vector, constraint_index, variable_indices) @@ -593,15 +589,12 @@ def _get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): continuous_index_set = set(self._state.continuous_indices) all_variables = self._state.binary_indices + self._state.continuous_indices for constraint_index, constraint in enumerate(self._state.op.linear_constraints): - # todo: "R" constraints if constraint.sense in [ConstraintSense.EQ]: # TODO: Ranged constraints should be supported as well continue # sense either G or L. - # row = self._state.op.linear_constraints.get_rows(constraint_index) constraint_indices = set( self._state.op.linear_constraints[constraint_index].linear.to_dict().keys()) - # row_indices = set(row.ind) # we must have a least one binary and one continuous variable, # otherwise it is another type of constraints. if len(constraint_indices & binary_index_set) != 0 and len( @@ -626,21 +619,11 @@ def _create_step1_problem(self) -> QuadraticProgram: # create the same binary variables. for i in range(binary_size): op1.binary_var(name="x0_" + str(i + 1)) - # op1.variables.add(names=["x0_" + str(i + 1) for i in range(binary_size)], - # types=["I"] * binary_size, - # lb=[0.] * binary_size, - # ub=[1.] * binary_size) # prepare and set quadratic objective. - # NOTE: The multiplication by 2 is needed for the solvers to parse - # the quadratic coefficients. - # todo: do we need multiplication by two? For anything other than self._state.q0 quadratic_objective = self._state.q0 +\ self._params.factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) +\ self._state.rho / 2 * np.eye(binary_size) - # for i in range(binary_size): - # for j in range(i, binary_size): - # op1.objective.set_quadratic_coefficients(i, j, quadratic_objective[i, j]) op1.objective.quadratic = quadratic_objective # prepare and set linear objective. @@ -649,9 +632,6 @@ def _create_step1_problem(self) -> QuadraticProgram: self._state.rho * (- self._state.y - self._state.z) + \ self._state.lambda_mult - # for i in range(binary_size): - # op1.objective.set_linear(i, linear_objective[i]) - op1.objective.linear = linear_objective return op1 @@ -665,12 +645,6 @@ def _create_step2_problem(self) -> QuadraticProgram: continuous_size = len(self._state.continuous_indices) binary_size = len(self._state.binary_indices) - # lower_bounds = self._state.op.variables.get_lower_bounds(self._state.continuous_indices) - # upper_bounds = self._state.op.variables.get_upper_bounds(self._state.continuous_indices) - # if continuous_size: - # # add u variables. - # op2.variables.add(names=["u0_" + str(i + 1) for i in range(continuous_size)], - # types=["C"] * continuous_size, lb=lower_bounds, ub=upper_bounds) continuous_index = 0 for variable in self._state.op.variables: if variable.vartype == VarType.CONTINUOUS: @@ -678,69 +652,22 @@ def _create_step2_problem(self) -> QuadraticProgram: lowerbound=variable.lowerbound, upperbound=variable.upperbound) continuous_index += 1 - # add z variables. for i in range(binary_size): op2.continuous_var(name="z0_" + str(i + 1), lowerbound=0, upperbound=1.) - # todo: do we need the 2* q_z = self._state.rho / 2 * np.eye(binary_size) op2.objective.quadratic = block_diag(self._state.q1, q_z) - # set quadratic objective coefficients for u variables. - # if continuous_size: - # q_u = self._state.q1 - # for i in range(continuous_size): - # for j in range(i, continuous_size): - # op2.objective.set_quadratic_coefficients(i, j, q_u[i, j]) - - # set quadratic objective coefficients for z variables. - # NOTE: The multiplication by 2 is needed for the solvers to parse - # the quadratic coefficients. - # q_z = 2 * (self._state.rho / 2 * np.eye(binary_size)) - # for i in range(binary_size): - # for j in range(i, binary_size): - # op2.objective.set_quadratic_coefficients(i + continuous_size, j + continuous_size, - # q_z[i, j]) linear_z = -1 * self._state.lambda_mult - self._state.rho * (self._state.x0 - self._state.y) op2.objective.linear = np.concatenate((self._state.c1, linear_z)) - # set linear objective for u variables. - # if continuous_size: - # linear_u = self._state.c1 - # for i in range(continuous_size): - # op2.objective.set_linear(i, linear_u[i]) - - # set linear objective for z variables. - # linear_z = -1 * self._state.lambda_mult - self._state.rho * (self._state.x0 - self._state.y) - # for i in range(binary_size): - # op2.objective.set_linear(i + continuous_size, linear_z[i]) # constraints for z. # A1 z <= b1. constraint_count = self._state.a1.shape[0] - # in SparsePair val="something from numpy" causes an exception - # when saving a model via cplex method. - # rhs="something from numpy" is ok. - # so, we convert every single value to python float - # lin_expr = [SparsePair(ind=list(range(continuous_size, continuous_size + binary_size)), - # val=self._state.a1[i, :].tolist()) for i in - # range(constraint_count)] - # op2.linear_constraints.add(lin_expr=lin_expr, senses=["L"] * constraint_count, - # rhs=list(self._state.b1)) - for i in range(constraint_count): linear = np.concatenate((np.zeros(continuous_size), self._state.a1[i, :])) op2.linear_constraint(linear=linear, sense=ConstraintSense.LE, rhs=self._state.b1[i]) - # if continuous_size: - # # A2 z + A3 u <= b2 - # constraint_count = self._state.a2.shape[0] - # lin_expr = [SparsePair(ind=list(range(continuous_size + binary_size)), - # val=self._state.a3[i, :].tolist() + - # self._state.a2[i, :].tolist()) - # for i in range(constraint_count)] - # op2.linear_constraints.add(lin_expr=lin_expr, - # senses=["L"] * constraint_count, - # rhs=self._state.b2.tolist()) if continuous_size: # A2 z + A3 u <= b2 constraint_count = self._state.a2.shape[0] @@ -748,24 +675,12 @@ def _create_step2_problem(self) -> QuadraticProgram: linear = np.concatenate((self._state.a3[i, :], self._state.a2[i, :])) op2.linear_constraint(linear=linear, sense=ConstraintSense.LE, rhs=self._state.b2[i]) - # if continuous_size: - # # A4 u <= b3 - # constraint_count = self._state.a4.shape[0] - # lin_expr = [SparsePair(ind=list(range(continuous_size)), - # val=self._state.a4[i, :].tolist()) for i in - # range(constraint_count)] - # op2.linear_constraints.add(lin_expr=lin_expr, - # senses=["L"] * constraint_count, - # rhs=self._state.b3.tolist()) - - if continuous_size: # A4 u <= b3 constraint_count = self._state.a4.shape[0] for i in range(constraint_count): linear = np.concatenate((self._state.a4[i, :], np.zeros(binary_size))) op2.linear_constraint(linear=linear, sense=ConstraintSense.LE, rhs=self._state.b3[i]) - # add quadratic constraints for # In the step 2, we basically need to copy all quadratic constraints of the original # problem, and substitute binary variables with variables 0<=z<=1. @@ -809,24 +724,16 @@ def _create_step3_problem(self) -> QuadraticProgram: op3 = QuadraticProgram() # add y variables. binary_size = len(self._state.binary_indices) - # op3.variables.add(names=["y_" + str(i + 1) for i in range(binary_size)], - # types=["C"] * binary_size, lb=[-np.inf] * binary_size, - # ub=[np.inf] * binary_size) for i in range(binary_size): op3.continuous_var(name="y_" + str(i + 1), lowerbound=-np.inf, upperbound=np.inf) - # set quadratic objective. - # todo: do we need 2*? - q_y = self._params.beta / 2 * np.eye(binary_size) + \ - self._state.rho / 2 * np.eye(binary_size) - # for i in range(binary_size): - # for j in range(i, binary_size): - # op3.objective.set_quadratic_coefficients(i, j, q_y[i, j]) - op3.objective.quadratic = q_y + # set quadratic objective y + quadratic_y = self._params.beta / 2 * np.eye(binary_size) + \ + self._state.rho / 2 * np.eye(binary_size) + op3.objective.quadratic = quadratic_y + # set linear objective for y linear_y = - self._state.lambda_mult - self._state.rho * (self._state.x0 - self._state.z) - # for i in range(binary_size): - # op3.objective.set_linear(i, linear_y[i]) op3.objective.linear = linear_y return op3 diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index 8cebd91b49..934b8e3165 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -20,7 +20,6 @@ from docplex.mp.model import Model import numpy as np -from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.optimization.algorithms import CplexOptimizer, MinimumEigenOptimizer from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters, \ ADMMOptimizerResult, ADMMState @@ -62,51 +61,50 @@ def test_admm_maximization(self): except NameError as ex: self.skipTest(str(ex)) - def test_admm_ex6(self): - """Example 6 as a unit test. Example 6 is reported in: + def test_admm_ex4(self): + """Example 4 as a unit test. Example 4 is reported in: Gambella, C., & Simonetto, A. (2020). Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical and Quantum Computers. arXiv preprint arXiv:2001.02069.""" try: - mdl = Model('ex6') + mdl = Model('ex4') - # pylint:disable=invalid-name v = mdl.binary_var(name='v') w = mdl.binary_var(name='w') t = mdl.binary_var(name='t') - u = mdl.continuous_var(name='u') - mdl.minimize(v + w + t + 5 * (u - 2) ** 2) - mdl.add_constraint(v + 2 * w + t + u <= 3, "cons1") - mdl.add_constraint(v + w + t >= 1, "cons2") - mdl.add_constraint(v + w == 1, "cons3") + # b = 1 + b = 2 + + mdl.minimize(v + w + t) + mdl.add_constraint(2 * v + 10 * w + t <= 3, "cons1") + mdl.add_constraint(v + w + t >= b, "cons2") op = QuadraticProgram() op.from_docplex(mdl) + # qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) qubo_optimizer = CplexOptimizer() continuous_optimizer = CplexOptimizer() admm_params = ADMMParameters( rho_initial=1001, beta=1000, factor_c=900, - max_iter=100, three_block=True, tol=1.e-6 + max_iter=100, three_block=False ) solver = ADMMOptimizer(params=admm_params, qubo_optimizer=qubo_optimizer, - continuous_optimizer=continuous_optimizer) + continuous_optimizer=continuous_optimizer, ) solution = solver.solve(op) self.assertIsNotNone(solution) self.assertIsInstance(solution, ADMMOptimizerResult) - self.assertIsNotNone(solution.x) - print(solution.x) - print(solution.fval) - np.testing.assert_almost_equal([1., 0., 0., 2.], solution.x, 3) + np.testing.assert_almost_equal([1., 0., 1.], solution.x, 3) self.assertIsNotNone(solution.fval) - np.testing.assert_almost_equal(1., solution.fval, 3) + np.testing.assert_almost_equal(2., solution.fval, 3) self.assertIsNotNone(solution.state) self.assertIsInstance(solution.state, ADMMState) + except NameError as ex: self.skipTest(str(ex)) @@ -147,10 +145,7 @@ def test_admm_ex5(self): self.assertIsNotNone(solution) self.assertIsInstance(solution, ADMMOptimizerResult) - self.assertIsNotNone(solution.x) - print(solution.x) - print(solution.fval) np.testing.assert_almost_equal([1., 0., 1.], solution.x, 3) self.assertIsNotNone(solution.fval) np.testing.assert_almost_equal(2., solution.fval, 3) @@ -159,6 +154,48 @@ def test_admm_ex5(self): except NameError as ex: self.skipTest(str(ex)) + def test_admm_ex6(self): + """Example 6 as a unit test. Example 6 is reported in: + Gambella, C., & Simonetto, A. (2020). + Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical + and Quantum Computers. + arXiv preprint arXiv:2001.02069.""" + try: + mdl = Model('ex6') -if __name__ == '__main__': - unittest.main() + # pylint:disable=invalid-name + v = mdl.binary_var(name='v') + w = mdl.binary_var(name='w') + t = mdl.binary_var(name='t') + u = mdl.continuous_var(name='u') + + mdl.minimize(v + w + t + 5 * (u - 2) ** 2) + mdl.add_constraint(v + 2 * w + t + u <= 3, "cons1") + mdl.add_constraint(v + w + t >= 1, "cons2") + mdl.add_constraint(v + w == 1, "cons3") + + op = QuadraticProgram() + op.from_docplex(mdl) + + qubo_optimizer = CplexOptimizer() + continuous_optimizer = CplexOptimizer() + + admm_params = ADMMParameters( + rho_initial=1001, beta=1000, factor_c=900, + max_iter=100, three_block=True, tol=1.e-6 + ) + + solver = ADMMOptimizer(params=admm_params, qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer) + solution = solver.solve(op) + + self.assertIsNotNone(solution) + self.assertIsInstance(solution, ADMMOptimizerResult) + self.assertIsNotNone(solution.x) + np.testing.assert_almost_equal([1., 0., 0., 2.], solution.x, 3) + self.assertIsNotNone(solution.fval) + np.testing.assert_almost_equal(1., solution.fval, 3) + self.assertIsNotNone(solution.state) + self.assertIsInstance(solution.state, ADMMState) + except NameError as ex: + self.skipTest(str(ex)) From 643dacb5d0fbac49202ab96868ffe544403286cf Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Mon, 20 Apr 2020 14:51:47 +0100 Subject: [PATCH 248/323] moving to a new optimization stack --- qiskit/optimization/algorithms/optimization_algorithm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index 73983f367b..d0e8532a98 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -127,7 +127,7 @@ def results(self) -> Any: return self._results @property - def status(self) -> OptimizationResult.Status: + def status(self) -> Status: """Return the termination status of the algorithm. Returns: From a0f656ab38c0dd29a2b5a2384a92c4756d5151f1 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Mon, 20 Apr 2020 23:01:44 +0900 Subject: [PATCH 249/323] use the internal Status directly --- qiskit/optimization/algorithms/optimization_algorithm.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index 2c770aabca..4321f8defb 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -1,4 +1,3 @@ - # -*- coding: utf-8 -*- # This code is part of Qiskit. @@ -15,9 +14,9 @@ """An abstract class for optimization algorithms in Qiskit Optimization.""" -from typing import Any, Optional from abc import ABC, abstractmethod from enum import Enum +from typing import Any, Optional from ..problems.quadratic_program import QuadraticProgram @@ -127,7 +126,7 @@ def results(self) -> Any: return self._results @property - def status(self) -> 'OptimizationResult.Status': + def status(self) -> Status: """Return the termination status of the algorithm. Returns: From f5cad57d7f662b46b50748e4392730fce3840d6c Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Mon, 20 Apr 2020 17:02:43 +0100 Subject: [PATCH 250/323] linting --- .../optimization/algorithms/admm_optimizer.py | 54 ++++++++----------- test/optimization/test_admm.py | 8 ++- 2 files changed, 26 insertions(+), 36 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 8446ea89be..e99ad923c8 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -16,9 +16,9 @@ import logging import time from typing import List, Optional, Any + import numpy as np from scipy.linalg import block_diag - from qiskit.optimization.algorithms.cplex_optimizer import CplexOptimizer from qiskit.optimization.algorithms.optimization_algorithm import (OptimizationAlgorithm, OptimizationResult) @@ -30,13 +30,6 @@ logger = logging.getLogger(__name__) -_HAS_CPLEX = False -try: - from cplex import SparsePair, SparseTriple - _HAS_CPLEX = True -except ImportError: - logger.info('CPLEX is not installed.') - class ADMMParameters: """Defines a set of parameters for ADMM optimizer.""" @@ -172,7 +165,7 @@ def state(self) -> Optional[ADMMState]: class ADMMOptimizer(OptimizationAlgorithm): - + """An implementation of the ADMM-based heuristic introduced here: Gambella, C., & Simonetto, A. (2020). Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical and Quantum Computers. @@ -194,9 +187,6 @@ def __init__(self, qubo_optimizer: Optional[OptimizationAlgorithm] = None, Raises: NameError: CPLEX is not installed. """ - if not _HAS_CPLEX: - raise NameError('CPLEX is not installed.') - super().__init__() self._log = logging.getLogger(__name__) @@ -233,14 +223,13 @@ def get_compatibility_msg(self, problem: QuadraticProgram) -> Optional[str]: # variable is not binary and not continuous. msg += 'Only binary and continuous variables are supported. ' - binary_indices = self._get_variable_indices(problem, VarType.binary) - continuous_indices = self._get_variable_indices(problem, VarType.continuous) + binary_indices = self._get_variable_indices(problem, VarType.BINARY) + continuous_indices = self._get_variable_indices(problem, VarType.CONTINUOUS) # 2. binary and continuous variables are separable in objective for binary_index in binary_indices: for continuous_index in continuous_indices: - # todo: implement - coeff = problem.objective.get_quadratic_coefficients(binary_index, continuous_index) + coeff = problem.objective.quadratic[binary_index, continuous_index] if coeff != 0: # binary and continuous vars are mixed. msg += 'Binary and continuous variables are not separable in the objective. ' @@ -513,19 +502,18 @@ def _get_a0_b0(self) -> (np.ndarray, np.ndarray): # we check only equality constraints here. if constraint.sense != ConstraintSense.EQ: continue - # todo: add condition that only binary variables are in the constraint - # row = self._state.op.linear_constraints.get_rows(constraint_index) - row = self._state.op.linear_constraints[constraint_index].linear.to_array().take(self._state.binary_indices) - self._assign_row_values(matrix, vector, - constraint_index, self._state.binary_indices) - # if set(row.ind).issubset(index_set): - # self._assign_row_values(matrix, vector, - # constraint_index, self._state.binary_indices) - # else: - # raise ValueError( - # "Linear constraint with the 'E' sense must contain only binary variables, " - # "row indices: {}, binary variable indices: {}".format( - # row, self._state.binary_indices)) + + constraint_indices = set( + self._state.op.linear_constraints[constraint_index].linear.to_dict().keys()) + # verify that there are only binary variables in the constraint + if constraint_indices.issubset(index_set): + self._assign_row_values(matrix, vector, + constraint_index, self._state.binary_indices) + else: + raise ValueError( + "Linear constraint with the 'E' sense must contain only binary variables, " + "constraint indices: {}, binary variable indices: {}".format( + constraint_indices, self._state.binary_indices)) return self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) @@ -673,13 +661,17 @@ def _create_step2_problem(self) -> QuadraticProgram: constraint_count = self._state.a2.shape[0] for i in range(constraint_count): linear = np.concatenate((self._state.a3[i, :], self._state.a2[i, :])) - op2.linear_constraint(linear=linear, sense=ConstraintSense.LE, rhs=self._state.b2[i]) + op2.linear_constraint(linear=linear, + sense=ConstraintSense.LE, + rhs=self._state.b2[i]) # A4 u <= b3 constraint_count = self._state.a4.shape[0] for i in range(constraint_count): linear = np.concatenate((self._state.a4[i, :], np.zeros(binary_size))) - op2.linear_constraint(linear=linear, sense=ConstraintSense.LE, rhs=self._state.b3[i]) + op2.linear_constraint(linear=linear, + sense=ConstraintSense.LE, + rhs=self._state.b3[i]) # add quadratic constraints for # In the step 2, we basically need to copy all quadratic constraints of the original diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index 934b8e3165..7f3226e3b5 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -13,14 +13,11 @@ # that they have been altered from the originals. """Tests of the ADMM algorithm.""" - -import unittest from test.optimization import QiskitOptimizationTestCase -from docplex.mp.model import Model - import numpy as np -from qiskit.optimization.algorithms import CplexOptimizer, MinimumEigenOptimizer +from docplex.mp.model import Model +from qiskit.optimization.algorithms import CplexOptimizer from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters, \ ADMMOptimizerResult, ADMMState from qiskit.optimization.problems import QuadraticProgram @@ -72,6 +69,7 @@ def test_admm_ex4(self): v = mdl.binary_var(name='v') w = mdl.binary_var(name='w') + # pylint:disable=invalid-name t = mdl.binary_var(name='t') # b = 1 From 844ea3635b2fa390344899d5ca56b95520e9fe8e Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Tue, 21 Apr 2020 10:05:55 +0200 Subject: [PATCH 251/323] rename penalize equality constraints converter --- qiskit/optimization/converters/__init__.py | 8 ++++---- ..._constraints.py => linear_equality_to_penalty.py} | 2 +- .../converters/quadratic_program_to_qubo.py | 4 ++-- test/optimization/test_converters.py | 12 ++++++------ 4 files changed, 13 insertions(+), 13 deletions(-) rename qiskit/optimization/converters/{penalize_linear_equality_constraints.py => linear_equality_to_penalty.py} (99%) diff --git a/qiskit/optimization/converters/__init__.py b/qiskit/optimization/converters/__init__.py index 529f205d7e..8a7784b40d 100644 --- a/qiskit/optimization/converters/__init__.py +++ b/qiskit/optimization/converters/__init__.py @@ -25,7 +25,7 @@ """ # no opt problem dependency -from .penalize_linear_equality_constraints import PenalizeLinearEqualityConstraints +from .linear_equality_to_penalty import LinearEqualityToPenalty from .quadratic_program_to_operator import QuadraticProgramToOperator from .quadratic_program_to_negative_value_oracle import QuadraticProgramToNegativeValueOracle @@ -35,10 +35,10 @@ from .quadratic_program_to_qubo import QuadraticProgramToQubo __all__ = [ - "InequalityToEqualityConverter", - "IntegerToBinaryConverter", + "InequalityToEquality", + "IntegerToBinary", "QuadraticProgramToNegativeValueOracle", "QuadraticProgramToOperator", "QuadraticProgramToQubo", - "PenalizeLinearEqualityConstraints", + "LinearEqualityToPenalty", ] diff --git a/qiskit/optimization/converters/penalize_linear_equality_constraints.py b/qiskit/optimization/converters/linear_equality_to_penalty.py similarity index 99% rename from qiskit/optimization/converters/penalize_linear_equality_constraints.py rename to qiskit/optimization/converters/linear_equality_to_penalty.py index 67bb22ec15..875cfc5041 100644 --- a/qiskit/optimization/converters/penalize_linear_equality_constraints.py +++ b/qiskit/optimization/converters/linear_equality_to_penalty.py @@ -25,7 +25,7 @@ from ..exceptions.qiskit_optimization_error import QiskitOptimizationError -class PenalizeLinearEqualityConstraints: +class LinearEqualityToPenalty: """Convert a problem with only equality constraints to unconstrained with penalty terms.""" def __init__(self): diff --git a/qiskit/optimization/converters/quadratic_program_to_qubo.py b/qiskit/optimization/converters/quadratic_program_to_qubo.py index 3d46b016e1..f5eaa620c7 100644 --- a/qiskit/optimization/converters/quadratic_program_to_qubo.py +++ b/qiskit/optimization/converters/quadratic_program_to_qubo.py @@ -18,7 +18,7 @@ from qiskit.optimization.problems import QuadraticProgram from qiskit.optimization.problems.constraint import ConstraintSense -from qiskit.optimization.converters import (PenalizeLinearEqualityConstraints, +from qiskit.optimization.converters import (LinearEqualityToPenalty, IntegerToBinary) from qiskit.optimization.exceptions import QiskitOptimizationError @@ -39,7 +39,7 @@ def __init__(self, penalty: Optional[float] = None) -> None: penalty: Penalty factor to scale equality constraints that are added to objective. """ self._int_to_bin = IntegerToBinary() - self._penalize_lin_eq_constraints = PenalizeLinearEqualityConstraints() + self._penalize_lin_eq_constraints = LinearEqualityToPenalty() self._penalty = penalty def encode(self, problem: QuadraticProgram) -> QuadraticProgram: diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index 09da43caad..9c67fe9f56 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -27,7 +27,7 @@ InequalityToEqualityConverter, QuadraticProgramToOperator, IntegerToBinaryConverter, - PenalizeLinearEqualityConstraints, + LinearEqualityToPenalty, ) from qiskit.optimization.algorithms import MinimumEigenOptimizer, CplexOptimizer, ADMMOptimizer from qiskit.optimization.algorithms.admm_optimizer import ADMMParameters @@ -74,7 +74,7 @@ def test_empty_problem(self): op = conv.encode(op) conv = IntegerToBinaryConverter() op = conv.encode(op) - conv = PenalizeLinearEqualityConstraints() + conv = LinearEqualityToPenalty() op = conv.encode(op) conv = QuadraticProgramToOperator() _, shift = conv.encode(op) @@ -244,7 +244,7 @@ def test_penalize_sense(self): names=['xy', 'yz', 'zx'], ) self.assertEqual(op.linear_constraints.get_num(), 3) - conv = PenalizeLinearEqualityConstraints() + conv = LinearEqualityToPenalty() with self.assertRaises(QiskitOptimizationError): conv.encode(op) @@ -262,7 +262,7 @@ def test_penalize_binary(self): names=['xy', 'yz'], ) self.assertEqual(op.linear_constraints.get_num(), 2) - conv = PenalizeLinearEqualityConstraints() + conv = LinearEqualityToPenalty() op2 = conv.encode(op) self.assertEqual(op2.linear_constraints.get_num(), 0) @@ -280,7 +280,7 @@ def test_penalize_integer(self): names=['xy', 'yz'], ) self.assertEqual(op.linear_constraints.get_num(), 2) - conv = PenalizeLinearEqualityConstraints() + conv = LinearEqualityToPenalty() op2 = conv.encode(op) self.assertEqual(op2.linear_constraints.get_num(), 0) @@ -343,7 +343,7 @@ def test_optimizationproblem_to_operator(self): names=['abcd'], ) op.objective.set_sense(-1) - penalize = PenalizeLinearEqualityConstraints() + penalize = LinearEqualityToPenalty() op2ope = QuadraticProgramToOperator() op2 = penalize.encode(op) qubitop, offset = op2ope.encode(op2) From fc7e1b87d16614b74575e21a0dc203498de4eeb5 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Tue, 21 Apr 2020 12:11:43 +0200 Subject: [PATCH 252/323] converter bug fixes --- .../converters/inequality_to_equality.py | 98 +++++++------------ .../converters/integer_to_binary.py | 3 +- .../converters/linear_equality_to_penalty.py | 7 +- .../converters/quadratic_program_to_qubo.py | 4 +- 4 files changed, 45 insertions(+), 67 deletions(-) diff --git a/qiskit/optimization/converters/inequality_to_equality.py b/qiskit/optimization/converters/inequality_to_equality.py index c4feb64fd0..89a7df2c27 100644 --- a/qiskit/optimization/converters/inequality_to_equality.py +++ b/qiskit/optimization/converters/inequality_to_equality.py @@ -91,15 +91,10 @@ def encode( else: raise QiskitOptimizationError("Unsupported variable type {}".format(x.vartype)) - # Copy the object function - linear, linear_constant = self._src.objective.linear.to_dict(use_name=True) - quadratic, quadratic_linear, quadratic_constant = self._src.objective.quadratic.to_dict( - use_name=True - ) - constant = self._src.objective.constant + linear_constant + quadratic_constant - for i, v in quadratic_linear.items(): - linear[i] = linear.get(i, 0) + v - + # Copy the objective function + constant = self._src.objective.constant + linear = self._src.objective.linear.to_dict(use_name=True) + quadratic = self._src.objective.quadratic.to_dict(use_name=True) if self._src.objective.sense == ObjSense.MINIMIZE: self._dst.minimize(constant, linear, quadratic) else: @@ -107,23 +102,23 @@ def encode( # For linear constraints for constraint in self._src.linear_constraints: - linear, constant = constraint.linear.to_dict() + linear = constraint.linear.to_dict(use_name=True) if constraint.sense == ConstraintSense.EQ: self._dst.linear_constraint( - linear, constraint.sense, constraint.rhs - constant, constraint.name + linear, constraint.sense, constraint.rhs, constraint.name ) elif constraint.sense == ConstraintSense.LE or constraint.sense == ConstraintSense.GE: if mode == 'integer': self._add_integer_slack_var_linear_constraint( - linear, constraint.sense, constraint.rhs - constant, constraint.name + linear, constraint.sense, constraint.rhs, constraint.name ) elif mode == 'continuous': self._add_continuous_slack_var_linear_constraint( - linear, constraint.sense, constraint.rhs - constant, constraint.name + linear, constraint.sense, constraint.rhs, constraint.name ) elif mode == 'auto': self._add_auto_slack_var_linear_constraint( - linear, constraint.sense, constraint.rhs - constant, constraint.name + linear, constraint.sense, constraint.rhs, constraint.name ) else: raise QiskitOptimizationError('Unsupported mode is selected: {}'.format(mode)) @@ -134,15 +129,11 @@ def encode( # For quadratic constraints for constraint in self._src.quadratic_constraints: - linear, constant = constraint.linear.to_dict() - quadratic, quadratic_linear, quadratic_constant = constraint.quadratic.to_dict() - constant = linear_constant + quadratic_constant - for i, v in quadratic_linear.items(): - linear[i] = linear.get(i, 0) + v - + linear = constraint.linear.to_dict(use_name=True) + quadratic = constraint.quadratic.to_dict(use_name=True) if constraint.sense == ConstraintSense.EQ: self._dst.quadratic_constraint( - linear, quadratic, constraint.sense, constraint.rhs - constant, constraint.name + linear, quadratic, constraint.sense, constraint.rhs, constraint.name ) elif constraint.sense == ConstraintSense.LE or constraint.sense == ConstraintSense.GE: if mode == 'integer': @@ -150,7 +141,7 @@ def encode( linear, quadratic, constraint.sense, - constraint.rhs - constant, + constraint.rhs, constraint.name, ) elif mode == 'continuous': @@ -158,7 +149,7 @@ def encode( linear, quadratic, constraint.sense, - constraint.rhs - constant, + constraint.rhs, constraint.name, ) elif mode == 'auto': @@ -166,7 +157,7 @@ def encode( linear, quadratic, constraint.sense, - constraint.rhs - constant, + constraint.rhs, constraint.name, ) else: @@ -198,17 +189,17 @@ def _add_integer_slack_var_linear_constraint(self, linear, sense, rhs, name): if sense == ConstraintSense.LE: sign = 1 - self._dst.integer_var(name=[slack_name], lowerbound=[0], upperbound=[new_rhs - lhs_lb]) + self._dst.integer_var(name=slack_name, lowerbound=0, upperbound=new_rhs - lhs_lb) elif sense == ConstraintSense.GE: sign = -1 - self._dst.integer_var(name=[slack_name], lowerbound=[0], upperbound=[lhs_ub - new_rhs]) + self._dst.integer_var(name=slack_name, lowerbound=0, upperbound=lhs_ub - new_rhs) else: raise QiskitOptimizationError('The type of Sense in {} is not supported'.format(name)) # Add a new equality constraint. - new_constraint = copy.deepcopy(linear) - new_constraint[slack_name] = sign - self._dst.linear_constraints(linear, sense, rhs, name) + new_linear = copy.deepcopy(linear) + new_linear[slack_name] = sign + self._dst.linear_constraint(new_linear, "==", new_rhs, name) def _add_continuous_slack_var_linear_constraint(self, linear, sense, rhs, name): slack_name = name + self._delimiter + 'continuous_slack' @@ -218,21 +209,17 @@ def _add_continuous_slack_var_linear_constraint(self, linear, sense, rhs, name): if sense == ConstraintSense.LE: sign = 1 - self._dst.continuous_var( - name=[slack_name], lowerbound=[0], upperbound=[rhs - lhs_lb] - ) + self._dst.continuous_var(name=slack_name, lowerbound=0, upperbound=rhs - lhs_lb) elif sense == ConstraintSense.GE: sign = -1 - self._dst.continuous_var( - name=[slack_name], lowerbound=[0], upperbound=[lhs_ub - rhs] - ) + self._dst.continuous_var(name=slack_name, lowerbound=0, upperbound=lhs_ub - rhs) else: raise QiskitOptimizationError('The type of Sense in {} is not supported'.format(name)) # Add a new equality constraint. - new_constraint = copy.deepcopy(linear) - new_constraint[slack_name] = sign - self._dst.linear_constraints(linear, sense, rhs, name) + new_linear = copy.deepcopy(linear) + new_linear[slack_name] = sign + self._dst.linear_constraint(new_linear, "==", rhs, name) def _add_auto_slack_var_linear_constraint(self, linear, sense, rhs, name): # If a coefficient that is not integer exist, use a continuous slack variable @@ -264,17 +251,17 @@ def _add_integer_slack_var_quadratic_constraint(self, linear, quadratic, sense, if sense == ConstraintSense.LE: sign = 1 - self._dst.integer_var(name=[slack_name], lowerbound=[0], upperbound=[new_rhs - lhs_lb]) + self._dst.integer_var(name=slack_name, lowerbound=0, upperbound=new_rhs - lhs_lb) elif sense == ConstraintSense.GE: sign = -1 - self._dst.integer_var(name=[slack_name], lowerbound=[0], upperbound=[lhs_ub - new_rhs]) + self._dst.integer_var(name=slack_name, lowerbound=0, upperbound=lhs_ub - new_rhs) else: raise QiskitOptimizationError('The type of Sense in {} is not supported'.format(name)) # Add a new equality constraint. new_linear = copy.deepcopy(linear) new_linear[slack_name] = sign - self._dst.quadratic_constraints(new_linear, quadratic, sense, rhs, name) + self._dst.quadratic_constraints(new_linear, quadratic, "==", new_rhs, name) def _add_continuous_slack_var_quadratic_constraint(self, linear, quadratic, sense, rhs, name): # If a coefficient that is not integer exist, raise error @@ -291,21 +278,17 @@ def _add_continuous_slack_var_quadratic_constraint(self, linear, quadratic, sens if sense == ConstraintSense.LE: sign = 1 - self._dst.continuous_var( - name=[slack_name], lowerbound=[0], upperbound=[rhs - lhs_lb] - ) + self._dst.continuous_var(name=slack_name, lowerbound=0, upperbound=rhs - lhs_lb) elif sense == ConstraintSense.GE: sign = -1 - self._dst.continuous_var( - name=[slack_name], lowerbound=[0], upperbound=[lhs_ub - rhs] - ) + self._dst.continuous_var(name=slack_name, lowerbound=0, upperbound=lhs_ub - rhs) else: raise QiskitOptimizationError('The type of Sense in {} is not supported'.format(name)) # Add a new equality constraint. new_linear = copy.deepcopy(linear) new_linear[slack_name] = sign - self._dst.quadratic_constraints(new_linear, quadratic, sense, rhs, name) + self._dst.quadratic_constraints(new_linear, quadratic, "==", rhs, name) def _add_auto_slack_var_quadratic_constraint(self, linear, quadratic, sense, rhs, name): # If a coefficient that is not integer exist, use a continuous slack variable @@ -320,7 +303,7 @@ def _add_auto_slack_var_quadratic_constraint(self, linear, quadratic, sense, rhs def _calc_linear_bounds(self, linear): lhs_lb, lhs_ub = 0, 0 for var_name, v in linear.items(): - x = self._src.get_variables(var_name) + x = self._src.get_variable(var_name) lhs_lb += min(x.lowerbound * v, x.upperbound * v) lhs_ub += max(x.lowerbound * v, x.upperbound * v) return lhs_lb, lhs_ub @@ -364,25 +347,20 @@ def decode(self, result: 'OptimizationResult') -> 'OptimizationResult': new_result = OptimizationResult() # convert the optimization result into that of the original problem - names = self._dst.variables() + names = [x.name for x in self._dst.variables] vals = result.x new_vals = self._decode_var(names, vals) new_result.x = new_vals new_result.fval = result.fval + new_result.status = result.status + new_result.results = result.results return new_result def _decode_var(self, names, vals) -> List[int]: # decode slack variables sol = {name: vals[i] for i, name in enumerate(names)} - new_vals = [] - slack_var_names = [] - - for lst in self._conv.values(): - slack_var_names.extend(lst) - for name in sol: - if name in slack_var_names: - pass - else: - new_vals.append(sol[name]) + new_vals = [] + for x in self._src.variables: + new_vals.append(sol[x.name]) return new_vals diff --git a/qiskit/optimization/converters/integer_to_binary.py b/qiskit/optimization/converters/integer_to_binary.py index 2d9367c24d..87cd1106fe 100644 --- a/qiskit/optimization/converters/integer_to_binary.py +++ b/qiskit/optimization/converters/integer_to_binary.py @@ -20,7 +20,6 @@ import numpy as np -from ..algorithms.optimization_algorithm import OptimizationResult from ..exceptions import QiskitOptimizationError from ..problems.quadratic_objective import ObjSense from ..problems.quadratic_program import QuadraticProgram @@ -193,7 +192,7 @@ def _substitute_int_var(self): self._dst.quadratic_constraint(linear, quadratic, constraint.sense, constraint.rhs - constant, constraint.name) - def decode(self, result: OptimizationResult) -> OptimizationResult: + def decode(self, result: "OptimizationResult") -> "OptimizationResult": """Convert the encoded problem (binary variables) back to the original (integer variables). Args: diff --git a/qiskit/optimization/converters/linear_equality_to_penalty.py b/qiskit/optimization/converters/linear_equality_to_penalty.py index 875cfc5041..7d6df5d7c8 100644 --- a/qiskit/optimization/converters/linear_equality_to_penalty.py +++ b/qiskit/optimization/converters/linear_equality_to_penalty.py @@ -74,6 +74,7 @@ def encode(self, op: QuadraticProgram, penalty_factor: float = 1e5, offset = self._src.objective.constant linear = self._src.objective.linear.to_dict() quadratic = self._src.objective.quadratic.to_dict() + sense = self._src.objective.sense.value # convert linear constraints into penalty terms for constraint in self._src.linear_constraints: @@ -86,13 +87,13 @@ def encode(self, op: QuadraticProgram, penalty_factor: float = 1e5, row = constraint.linear.to_dict() # constant parts of penalty*(Constant-func)**2: penalty*(Constant**2) - offset += penalty_factor * constant ** 2 + offset += sense * penalty_factor * constant**2 # linear parts of penalty*(Constant-func)**2: penalty*(-2*Constant*func) for j, coef in row.items(): # if j already exists in the linear terms dic, add a penalty term # into existing value else create new key and value in the linear_term dict - linear[j] = linear.get(j, 0.0) + penalty_factor * -2 * coef * constant + linear[j] = linear.get(j, 0.0) + sense * penalty_factor * -2 * coef * constant # quadratic parts of penalty*(Constant-func)**2: penalty*(func**2) for j, coef_1 in row.items(): @@ -104,7 +105,7 @@ def encode(self, op: QuadraticProgram, penalty_factor: float = 1e5, # according to implementation of quadratic terms in OptimizationModel, # don't need to multiply by 2, since loops run over (x, y) and (y, x). quadratic[(j, k)] = quadratic.get((j, k), 0.0) \ - + penalty_factor * coef_1 * coef_2 + + sense * penalty_factor * coef_1 * coef_2 if self._src.objective.sense == ObjSense.MINIMIZE: self._dst.minimize(offset, linear, quadratic) diff --git a/qiskit/optimization/converters/quadratic_program_to_qubo.py b/qiskit/optimization/converters/quadratic_program_to_qubo.py index f5eaa620c7..4b274c4e8d 100644 --- a/qiskit/optimization/converters/quadratic_program_to_qubo.py +++ b/qiskit/optimization/converters/quadratic_program_to_qubo.py @@ -18,8 +18,8 @@ from qiskit.optimization.problems import QuadraticProgram from qiskit.optimization.problems.constraint import ConstraintSense -from qiskit.optimization.converters import (LinearEqualityToPenalty, - IntegerToBinary) +from qiskit.optimization.converters.linear_equality_to_penalty import LinearEqualityToPenalty +from qiskit.optimization.converters.integer_to_binary import IntegerToBinary from qiskit.optimization.exceptions import QiskitOptimizationError From e690725fbc339521159b4db849dd60ed6941776a Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Tue, 21 Apr 2020 11:18:19 +0100 Subject: [PATCH 253/323] minor re-writing get_bet_merit_solution --- qiskit/optimization/algorithms/admm_optimizer.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 207804f072..5d446732a5 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -872,19 +872,18 @@ def _update_y(self, op3: QuadraticProgram) -> np.ndarray: return np.asarray(self._continuous_optimizer.solve(op3).x) def _get_best_merit_solution(self) -> (List[np.ndarray], float): - """The ADMM solution is that for which the merit value is the best (least for min problems, - greatest for max problems) - * sol: Iterate with the best merit value + """The ADMM solution is that for which the merit value is the min + * sol: Iterate with the min merit value * sol_val: Value of sol, according to the original objective Returns: A tuple of (sol, sol_val), where - * sol: Solution with the best merit value + * sol: Solution with the min merit value * sol_val: Value of the objective function """ it_best_merits = self._state.merits.index( - self._state.sense * min(list(map(lambda x: self._state.sense * x, self._state.merits)))) + min(self._state.merits)) x_0 = self._state.x0_saved[it_best_merits] u_s = self._state.u_saved[it_best_merits] sol = [x_0, u_s] From dabd8216473f0705d8a2d0c7bc9efed7df584b2d Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Tue, 21 Apr 2020 11:30:44 +0100 Subject: [PATCH 254/323] minor re-writing get_bet_merit_solution --- qiskit/optimization/algorithms/admm_optimizer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index d2244a61ac..2e19afcf1c 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -781,12 +781,12 @@ def _get_best_merit_solution(self) -> (List[np.ndarray], float): * sol_val: Value of the objective function """ - it_best_merits = self._state.merits.index( + it_min_merits = self._state.merits.index( min(self._state.merits)) - x_0 = self._state.x0_saved[it_best_merits] - u_s = self._state.u_saved[it_best_merits] + x_0 = self._state.x0_saved[it_min_merits] + u_s = self._state.u_saved[it_min_merits] sol = [x_0, u_s] - sol_val = self._state.cost_iterates[it_best_merits] + sol_val = self._state.cost_iterates[it_min_merits] return sol, sol_val def _update_lambda_mult(self) -> np.ndarray: From 52589f18fc55392f9b69e375972b90e928232ad8 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Tue, 21 Apr 2020 19:52:39 +0900 Subject: [PATCH 255/323] Fix unit tests related to quadratic expressions --- .../optimization/test_quadratic_constraint.py | 63 ++++++++----------- test/optimization/test_quadratic_objective.py | 41 ++++++------ 2 files changed, 47 insertions(+), 57 deletions(-) diff --git a/test/optimization/test_quadratic_constraint.py b/test/optimization/test_quadratic_constraint.py index 9b11f4e622..d62e6f8222 100644 --- a/test/optimization/test_quadratic_constraint.py +++ b/test/optimization/test_quadratic_constraint.py @@ -14,13 +14,14 @@ """ Test QuadraticConstraint """ -import unittest -from test.optimization.optimization_test_case import QiskitOptimizationTestCase import logging +import unittest + import numpy as np from qiskit.optimization import QuadraticProgram, QiskitOptimizationError from qiskit.optimization.problems import ConstraintSense +from test.optimization.optimization_test_case import QiskitOptimizationTestCase logger = logging.getLogger(__name__) @@ -37,16 +38,18 @@ def test_init(self): self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 0) linear_coeffs = np.array(range(5)) - quadratic_coeffs = np.array([[i*j for i in range(5)] for j in range(5)]) + lst = [[0 for _ in range(5)] for _ in range(5)] + for i, v in enumerate(lst): + for j, _ in enumerate(v): + lst[min(i, j)][max(i, j)] += i * j + quadratic_coeffs = np.array(lst) # equality constraints quadratic_program.quadratic_constraint(sense='==') self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 1) self.assertEqual(quadratic_program.quadratic_constraints[0].name, 'q0') - self.assertEqual( - len(quadratic_program.quadratic_constraints[0].linear.to_dict()), 0) - self.assertEqual( - len(quadratic_program.quadratic_constraints[0].quadratic.to_dict()), 0) + self.assertEqual(len(quadratic_program.quadratic_constraints[0].linear.to_dict()), 0) + self.assertEqual(len(quadratic_program.quadratic_constraints[0].quadratic.to_dict()), 0) self.assertEqual(quadratic_program.quadratic_constraints[0].sense, ConstraintSense.EQ) self.assertEqual(quadratic_program.quadratic_constraints[0].rhs, 0.0) self.assertEqual(quadratic_program.quadratic_constraints[0], @@ -62,14 +65,11 @@ def test_init(self): quadratic_program.quadratic_constraint(linear_coeffs, quadratic_coeffs, '==', 1.0, 'q1') self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 2) self.assertEqual(quadratic_program.quadratic_constraints[1].name, 'q1') - self.assertTrue(( - quadratic_program.quadratic_constraints[1].linear.to_array( - ) == linear_coeffs - ).all()) - self.assertTrue(( - quadratic_program.quadratic_constraints[1].quadratic.to_array( - ) == quadratic_coeffs - ).all()) + self.assertTrue( + (quadratic_program.quadratic_constraints[1].linear.to_array() == linear_coeffs).all()) + self.assertTrue( + (quadratic_program.quadratic_constraints[1].quadratic.to_array() == + quadratic_coeffs).all()) self.assertEqual(quadratic_program.quadratic_constraints[1].sense, ConstraintSense.EQ) self.assertEqual(quadratic_program.quadratic_constraints[1].rhs, 1.0) self.assertEqual(quadratic_program.quadratic_constraints[1], @@ -83,10 +83,8 @@ def test_init(self): quadratic_program.quadratic_constraint(sense='>=') self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 3) self.assertEqual(quadratic_program.quadratic_constraints[2].name, 'q2') - self.assertEqual( - len(quadratic_program.quadratic_constraints[2].linear.to_dict()), 0) - self.assertEqual( - len(quadratic_program.quadratic_constraints[2].quadratic.to_dict()), 0) + self.assertEqual(len(quadratic_program.quadratic_constraints[2].linear.to_dict()), 0) + self.assertEqual(len(quadratic_program.quadratic_constraints[2].quadratic.to_dict()), 0) self.assertEqual(quadratic_program.quadratic_constraints[2].sense, ConstraintSense.GE) self.assertEqual(quadratic_program.quadratic_constraints[2].rhs, 0.0) self.assertEqual(quadratic_program.quadratic_constraints[2], @@ -102,14 +100,10 @@ def test_init(self): quadratic_program.quadratic_constraint(linear_coeffs, quadratic_coeffs, '>=', 1.0, 'q3') self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 4) self.assertEqual(quadratic_program.quadratic_constraints[3].name, 'q3') - self.assertTrue(( - quadratic_program.quadratic_constraints[3].linear.to_array( - ) == linear_coeffs - ).all()) - self.assertTrue(( - quadratic_program.quadratic_constraints[3].quadratic.to_array( - ) == quadratic_coeffs - ).all()) + self.assertTrue( + (quadratic_program.quadratic_constraints[3].linear.to_array() == linear_coeffs).all()) + self.assertTrue((quadratic_program.quadratic_constraints[3].quadratic.to_array() == + quadratic_coeffs).all()) self.assertEqual(quadratic_program.quadratic_constraints[3].sense, ConstraintSense.GE) self.assertEqual(quadratic_program.quadratic_constraints[3].rhs, 1.0) self.assertEqual(quadratic_program.quadratic_constraints[3], @@ -123,8 +117,7 @@ def test_init(self): quadratic_program.quadratic_constraint(sense='<=') self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 5) self.assertEqual(quadratic_program.quadratic_constraints[4].name, 'q4') - self.assertEqual( - len(quadratic_program.quadratic_constraints[4].linear.to_dict()), 0) + self.assertEqual(len(quadratic_program.quadratic_constraints[4].linear.to_dict()), 0) self.assertEqual(quadratic_program.quadratic_constraints[4].sense, ConstraintSense.LE) self.assertEqual(quadratic_program.quadratic_constraints[4].rhs, 0.0) self.assertEqual(quadratic_program.quadratic_constraints[4], @@ -140,14 +133,10 @@ def test_init(self): quadratic_program.quadratic_constraint(linear_coeffs, quadratic_coeffs, '<=', 1.0, 'q5') self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 6) self.assertEqual(quadratic_program.quadratic_constraints[5].name, 'q5') - self.assertTrue(( - quadratic_program.quadratic_constraints[5].linear.to_array( - ) == linear_coeffs - ).all()) - self.assertTrue(( - quadratic_program.quadratic_constraints[5].quadratic.to_array( - ) == quadratic_coeffs - ).all()) + self.assertTrue( + (quadratic_program.quadratic_constraints[5].linear.to_array() == linear_coeffs).all()) + self.assertTrue((quadratic_program.quadratic_constraints[5].quadratic.to_array() == + quadratic_coeffs).all()) self.assertEqual(quadratic_program.quadratic_constraints[5].sense, ConstraintSense.LE) self.assertEqual(quadratic_program.quadratic_constraints[5].rhs, 1.0) self.assertEqual(quadratic_program.quadratic_constraints[5], diff --git a/test/optimization/test_quadratic_objective.py b/test/optimization/test_quadratic_objective.py index 6da13e533c..50a7516718 100644 --- a/test/optimization/test_quadratic_objective.py +++ b/test/optimization/test_quadratic_objective.py @@ -14,13 +14,14 @@ """ Test QuadraticObjective """ -import unittest -from test.optimization.optimization_test_case import QiskitOptimizationTestCase import logging +import unittest + import numpy as np from qiskit.optimization import QuadraticProgram from qiskit.optimization.problems.quadratic_objective import ObjSense +from test.optimization.optimization_test_case import QiskitOptimizationTestCase logger = logging.getLogger(__name__) @@ -36,34 +37,32 @@ def test_init(self): quadratic_program.continuous_var() self.assertEqual(quadratic_program.objective.constant, 0.0) - self.assertEqual( - len(quadratic_program.objective.linear.to_dict()), 0) - self.assertEqual( - len(quadratic_program.objective.quadratic.to_dict()), 0) + self.assertEqual(len(quadratic_program.objective.linear.to_dict()), 0) + self.assertEqual(len(quadratic_program.objective.quadratic.to_dict()), 0) self.assertEqual(quadratic_program.objective.sense, ObjSense.MINIMIZE) constant = 1.0 linear_coeffs = np.array(range(5)) - quadratic_coeffs = np.array([[i*j for i in range(5)] for j in range(5)]) + lst = [[0 for _ in range(5)] for _ in range(5)] + for i, v in enumerate(lst): + for j, _ in enumerate(v): + lst[min(i, j)][max(i, j)] += i * j + quadratic_coeffs = np.array(lst) quadratic_program.minimize(constant, linear_coeffs, quadratic_coeffs) self.assertEqual(quadratic_program.objective.constant, constant) + self.assertTrue((quadratic_program.objective.linear.to_array() == linear_coeffs).all()) self.assertTrue( - (quadratic_program.objective.linear.to_array() == linear_coeffs).all()) - self.assertTrue( - (quadratic_program.objective.quadratic.to_array() == quadratic_coeffs) - .all()) + (quadratic_program.objective.quadratic.to_array() == quadratic_coeffs).all()) self.assertEqual(quadratic_program.objective.sense, ObjSense.MINIMIZE) quadratic_program.maximize(constant, linear_coeffs, quadratic_coeffs) self.assertEqual(quadratic_program.objective.constant, constant) + self.assertTrue((quadratic_program.objective.linear.to_array() == linear_coeffs).all()) self.assertTrue( - (quadratic_program.objective.linear.to_array() == linear_coeffs).all()) - self.assertTrue( - (quadratic_program.objective.quadratic.to_array() == quadratic_coeffs) - .all()) + (quadratic_program.objective.quadratic.to_array() == quadratic_coeffs).all()) self.assertEqual(quadratic_program.objective.sense, ObjSense.MAXIMIZE) self.assertEqual(quadratic_program.objective.evaluate(linear_coeffs), 931.0) @@ -77,18 +76,20 @@ def test_setters(self): constant = 1.0 linear_coeffs = np.array(range(5)) - quadratic_coeffs = np.array([[i*j for i in range(5)] for j in range(5)]) + lst = [[0 for _ in range(5)] for _ in range(5)] + for i, v in enumerate(lst): + for j, _ in enumerate(v): + lst[min(i, j)][max(i, j)] += i * j + quadratic_coeffs = np.array(lst) quadratic_program.objective.constant = constant quadratic_program.objective.linear = linear_coeffs quadratic_program.objective.quadratic = quadratic_coeffs self.assertEqual(quadratic_program.objective.constant, constant) + self.assertTrue((quadratic_program.objective.linear.to_array() == linear_coeffs).all()) self.assertTrue( - (quadratic_program.objective.linear.to_array() == linear_coeffs).all()) - self.assertTrue( - (quadratic_program.objective.quadratic.to_array() == quadratic_coeffs) - .all()) + (quadratic_program.objective.quadratic.to_array() == quadratic_coeffs).all()) self.assertEqual(quadratic_program.objective.sense, ObjSense.MINIMIZE) From cd6495db5c4968bf81a083ca0f878b2c6d64fb17 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Tue, 21 Apr 2020 21:47:47 +0900 Subject: [PATCH 256/323] Fix the declaration of __init__.py --- qiskit/optimization/problems/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qiskit/optimization/problems/__init__.py b/qiskit/optimization/problems/__init__.py index 35ba6e6ed0..6c185c3695 100644 --- a/qiskit/optimization/problems/__init__.py +++ b/qiskit/optimization/problems/__init__.py @@ -47,10 +47,10 @@ from .constraint import Constraint from .constraint import ConstraintSense -from .linear_expression import LinearExpression from .linear_constraint import LinearConstraint -from .quadratic_expression import QuadraticExpression +from .linear_expression import LinearExpression from .quadratic_constraint import QuadraticConstraint +from .quadratic_expression import QuadraticExpression from .quadratic_objective import QuadraticObjective, ObjSense from .quadratic_program import QuadraticProgram from .variable import Variable, VarType @@ -64,5 +64,6 @@ 'QuadraticObjective', 'ObjSense', 'QuadraticProgram', - 'Variable' + 'Variable', + 'VarType' ] From 10027c7b05ea90b09e0be52ce735fb28ce996a28 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Tue, 21 Apr 2020 21:56:24 +0900 Subject: [PATCH 257/323] Small fixes for pylint --- test/optimization/test_helpers.py | 70 ------------------- test/optimization/test_linear_constraint.py | 24 +++---- test/optimization/test_linear_expression.py | 5 +- .../optimization/test_quadratic_constraint.py | 5 +- .../optimization/test_quadratic_expression.py | 5 +- test/optimization/test_quadratic_objective.py | 5 +- test/optimization/test_quadratic_program.py | 12 ++-- test/optimization/test_variable.py | 4 -- 8 files changed, 20 insertions(+), 110 deletions(-) delete mode 100644 test/optimization/test_helpers.py diff --git a/test/optimization/test_helpers.py b/test/optimization/test_helpers.py deleted file mode 100644 index 8d8d711767..0000000000 --- a/test/optimization/test_helpers.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -""" Test helpers """ - -import unittest -from test.optimization.optimization_test_case import QiskitOptimizationTestCase - -from qiskit.optimization import QiskitOptimizationError -from qiskit.optimization.problems.name_index import NameIndex, init_list_args - - -class TestHelpers(QiskitOptimizationTestCase): - """Test helpers.""" - - def test_init_list_args(self): - """ test init list args """ - args = init_list_args(1, [2], None) - self.assertTupleEqual(args, (1, [2], [])) - - def test_name_index1(self): - """ test name index 1 """ - nidx = NameIndex() - nidx.build(['1', '2', '3']) - self.assertEqual(nidx.convert('1'), 0) - self.assertListEqual(nidx.convert(['2', '3']), [1, 2]) - self.assertEqual(nidx.convert('1'), 0) - self.assertListEqual(nidx.convert(), [0, 1, 2]) - self.assertListEqual(nidx.convert('1', '3'), [0, 1, 2]) - self.assertListEqual(nidx.convert('1', '2'), [0, 1]) - - def test_name_index2(self): - """ test name index 2 """ - nidx = NameIndex() - nidx.build(['1', '2', '3']) - self.assertEqual(nidx.convert('1'), 0) - self.assertListEqual(nidx.convert(), [0, 1, 2]) - self.assertListEqual(nidx.convert('1', '3'), [0, 1, 2]) - self.assertListEqual(nidx.convert('1', '2'), [0, 1]) - - def test_name_index3(self): - """ test name index 3 """ - nidx = NameIndex() - with self.assertRaises(QiskitOptimizationError): - nidx.convert({}) - with self.assertRaises(QiskitOptimizationError): - nidx.convert(1, 2, 3) - nidx.build(['x', 'y', 'z']) - self.assertEqual(nidx.convert(1), 1) - with self.assertRaises(QiskitOptimizationError): - nidx.convert(4) - self.assertEqual(nidx.convert('z'), 2) - with self.assertRaises(QiskitOptimizationError): - nidx.convert('a') - nidx.convert(1, 2, 3) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/optimization/test_linear_constraint.py b/test/optimization/test_linear_constraint.py index c1f0b670a3..fdc9006786 100644 --- a/test/optimization/test_linear_constraint.py +++ b/test/optimization/test_linear_constraint.py @@ -15,14 +15,12 @@ """ Test LinearConstraint """ import unittest -import logging + +from test.optimization.optimization_test_case import QiskitOptimizationTestCase import numpy as np from qiskit.optimization import QuadraticProgram, QiskitOptimizationError from qiskit.optimization.problems import ConstraintSense -from test.optimization.optimization_test_case import QiskitOptimizationTestCase - -logger = logging.getLogger(__name__) class TestLinearConstraint(QiskitOptimizationTestCase): @@ -56,10 +54,8 @@ def test_init(self): quadratic_program.linear_constraint(coefficients, '==', 1.0, 'c1') self.assertEqual(quadratic_program.get_num_linear_constraints(), 2) self.assertEqual(quadratic_program.linear_constraints[1].name, 'c1') - self.assertTrue(( - quadratic_program.linear_constraints[1].linear.to_array( - ) == coefficients - ).all()) + self.assertTrue( + (quadratic_program.linear_constraints[1].linear.to_array() == coefficients).all()) self.assertEqual(quadratic_program.linear_constraints[1].sense, ConstraintSense.EQ) self.assertEqual(quadratic_program.linear_constraints[1].rhs, 1.0) self.assertEqual(quadratic_program.linear_constraints[1], @@ -86,10 +82,8 @@ def test_init(self): quadratic_program.linear_constraint(coefficients, '>=', 1.0, 'c3') self.assertEqual(quadratic_program.get_num_linear_constraints(), 4) self.assertEqual(quadratic_program.linear_constraints[3].name, 'c3') - self.assertTrue(( - quadratic_program.linear_constraints[3].linear.to_array( - ) == coefficients - ).all()) + self.assertTrue( + (quadratic_program.linear_constraints[3].linear.to_array() == coefficients).all()) self.assertEqual(quadratic_program.linear_constraints[3].sense, ConstraintSense.GE) self.assertEqual(quadratic_program.linear_constraints[3].rhs, 1.0) self.assertEqual(quadratic_program.linear_constraints[3], @@ -116,10 +110,8 @@ def test_init(self): quadratic_program.linear_constraint(coefficients, '<=', 1.0, 'c5') self.assertEqual(quadratic_program.get_num_linear_constraints(), 6) self.assertEqual(quadratic_program.linear_constraints[5].name, 'c5') - self.assertTrue(( - quadratic_program.linear_constraints[5].linear.to_array( - ) == coefficients - ).all()) + self.assertTrue( + (quadratic_program.linear_constraints[5].linear.to_array() == coefficients).all()) self.assertEqual(quadratic_program.linear_constraints[5].sense, ConstraintSense.LE) self.assertEqual(quadratic_program.linear_constraints[5].rhs, 1.0) self.assertEqual(quadratic_program.linear_constraints[5], diff --git a/test/optimization/test_linear_expression.py b/test/optimization/test_linear_expression.py index 19a09f7afa..5a7fc79fd8 100644 --- a/test/optimization/test_linear_expression.py +++ b/test/optimization/test_linear_expression.py @@ -14,17 +14,14 @@ """ Test LinearExpression """ -import logging import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase import numpy as np from scipy.sparse import dok_matrix from qiskit.optimization import QuadraticProgram from qiskit.optimization.problems import LinearExpression -from test.optimization.optimization_test_case import QiskitOptimizationTestCase - -logger = logging.getLogger(__name__) class TestLinearExpression(QiskitOptimizationTestCase): diff --git a/test/optimization/test_quadratic_constraint.py b/test/optimization/test_quadratic_constraint.py index d62e6f8222..69af4d67ef 100644 --- a/test/optimization/test_quadratic_constraint.py +++ b/test/optimization/test_quadratic_constraint.py @@ -14,16 +14,13 @@ """ Test QuadraticConstraint """ -import logging import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase import numpy as np from qiskit.optimization import QuadraticProgram, QiskitOptimizationError from qiskit.optimization.problems import ConstraintSense -from test.optimization.optimization_test_case import QiskitOptimizationTestCase - -logger = logging.getLogger(__name__) class TestQuadraticConstraint(QiskitOptimizationTestCase): diff --git a/test/optimization/test_quadratic_expression.py b/test/optimization/test_quadratic_expression.py index e659cbac51..96859d8d0a 100644 --- a/test/optimization/test_quadratic_expression.py +++ b/test/optimization/test_quadratic_expression.py @@ -14,17 +14,14 @@ """ Test QuadraticExpression """ -import logging import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase import numpy as np from scipy.sparse import dok_matrix from qiskit.optimization import QuadraticProgram from qiskit.optimization.problems import QuadraticExpression -from test.optimization.optimization_test_case import QiskitOptimizationTestCase - -logger = logging.getLogger(__name__) class TestQuadraticExpression(QiskitOptimizationTestCase): diff --git a/test/optimization/test_quadratic_objective.py b/test/optimization/test_quadratic_objective.py index 50a7516718..bf46483940 100644 --- a/test/optimization/test_quadratic_objective.py +++ b/test/optimization/test_quadratic_objective.py @@ -14,16 +14,13 @@ """ Test QuadraticObjective """ -import logging import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase import numpy as np from qiskit.optimization import QuadraticProgram from qiskit.optimization.problems.quadratic_objective import ObjSense -from test.optimization.optimization_test_case import QiskitOptimizationTestCase - -logger = logging.getLogger(__name__) class TestQuadraticObjective(QiskitOptimizationTestCase): diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index 3d3acae6df..2492b7226a 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -14,14 +14,11 @@ """ Test QuadraticProgram """ -import logging import unittest +from test.optimization.optimization_test_case import QiskitOptimizationTestCase from qiskit.optimization import QuadraticProgram, QiskitOptimizationError, infinity from qiskit.optimization.problems import VarType -from test.optimization.optimization_test_case import QiskitOptimizationTestCase - -logger = logging.getLogger(__name__) class TestQuadraticProgram(QiskitOptimizationTestCase): @@ -131,26 +128,32 @@ def test_variables_handling(self): self.assertEqual(x, z) def test_linear_constraints_handling(self): + """test linear constraints handling""" # TODO pass def test_quadratic_constraints_handling(self): + """test quadratic constraints handling""" # TODO pass def test_objective_handling(self): + """test objective handling""" # TODO pass def test_read_problem(self): + """test read problem""" # TODO pass def test_write_problem(self): + """test write problem""" # TODO pass def test_docplex(self): + """test from_docplex and to_docplex""" q_p = QuadraticProgram('test') q_p.binary_var(name='x') q_p.integer_var(name='y', lowerbound=-2, upperbound=4) @@ -165,6 +168,7 @@ def test_docplex(self): self.assertEqual(q_p.print_as_lp_string(), q_p2.print_as_lp_string()) def test_substitute_variables(self): + """test substitute variables""" q_p = QuadraticProgram('test') q_p.binary_var(name='x') q_p.integer_var(name='y', lowerbound=-2, upperbound=4) diff --git a/test/optimization/test_variable.py b/test/optimization/test_variable.py index d8f8224cf8..971bae34b4 100644 --- a/test/optimization/test_variable.py +++ b/test/optimization/test_variable.py @@ -14,16 +14,12 @@ """Test Variable.""" -import logging import unittest - from test.optimization.optimization_test_case import QiskitOptimizationTestCase from qiskit.optimization import infinity from qiskit.optimization.problems import QuadraticProgram, Variable, VarType -logger = logging.getLogger(__name__) - class TestVariable(QiskitOptimizationTestCase): """Test Variable.""" From f5bb7b1cd7c57f3fb36a9a0f129c582bd9c350d8 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Tue, 21 Apr 2020 14:41:45 +0100 Subject: [PATCH 258/323] moving to 'evaluate' in objective cost and constraints --- .../optimization/algorithms/admm_optimizer.py | 74 ++++++++++--------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 2e19afcf1c..35c8e90678 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -149,6 +149,10 @@ def __init__(self, self.y_saved = [] self.rho = rho_initial + # new features + self.equality_constraints = [] + self.inequality_constraints = [] + class ADMMOptimizerResult(OptimizationResult): """ ADMMOptimizer Result.""" @@ -324,8 +328,8 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: iteration += 1 elapsed_time = time.time() - start_time - solution, objective_value = self._get_best_merit_solution() - solution = self._revert_solution_indexes(solution) + binary_vars, continuous_vars, objective_value = self._get_best_merit_solution() + solution = self._revert_solution_indexes(binary_vars, continuous_vars) # third parameter is our internal state of computations. result = ADMMOptimizerResult(solution, objective_value, self._state) @@ -352,24 +356,28 @@ def _get_variable_indices(op: QuadraticProgram, var_type: VarType) -> List[int]: return indices - def _revert_solution_indexes(self, internal_solution: List[np.ndarray]) \ + def _get_solution(self) -> np.ndarray: + return self._revert_solution_indexes(self._state.x0, self._state.u) + + def _revert_solution_indexes(self, binary_vars: np.ndarray, continuous_vars: np.ndarray) \ -> np.ndarray: """Constructs a solution array where variables are stored in the correct order. Args: - internal_solution: a list with two lists: solutions for binary variables and - for continuous variables. + binary_vars: solution for binary variables + continuous_vars: solution for continuous variables Returns: A solution array. """ - binary_solutions, continuous_solutions = internal_solution solution = np.zeros(len(self._state.binary_indices) + len(self._state.continuous_indices)) # restore solution at the original index location - for i, binary_index in enumerate(self._state.binary_indices): - solution[binary_index] = binary_solutions[i] - for i, continuous_index in enumerate(self._state.continuous_indices): - solution[continuous_index] = continuous_solutions[i] + solution.put(self._state.binary_indices, binary_vars) + solution.put(self._state.continuous_indices, continuous_vars) + # for i, binary_index in enumerate(self._state.binary_indices): + # solution[binary_index] = binary_vars[i] + # for i, continuous_index in enumerate(self._state.continuous_indices): + # solution[continuous_index] = continuous_vars[i] return solution def _convert_problem_representation(self) -> None: @@ -395,6 +403,14 @@ def _convert_problem_representation(self) -> None: self._state.a2, self._state.a3, self._state.b2 = self._get_a2_a3_b2() self._state.a4, self._state.b3 = self._get_a4_b3() + # separate constraints + for constraint in self._state.op.linear_constraints: + if constraint.sense == ConstraintSense.EQ: + # todo: verify that this constraint contains only binary variables + self._state.equality_constraints.append(constraint) + elif constraint.sense in (ConstraintSense.LE, ConstraintSense.GE): + self._state.inequality_constraints.append(constraint) + def _get_q(self, variable_indices: List[int]) -> np.ndarray: """Constructs a quadratic matrix for the variables with the specified indices from the quadratic terms in the objective. @@ -783,11 +799,10 @@ def _get_best_merit_solution(self) -> (List[np.ndarray], float): it_min_merits = self._state.merits.index( min(self._state.merits)) - x_0 = self._state.x0_saved[it_min_merits] - u_s = self._state.u_saved[it_min_merits] - sol = [x_0, u_s] + binary_vars = self._state.x0_saved[it_min_merits] + continuous_vars = self._state.u_saved[it_min_merits] sol_val = self._state.cost_iterates[it_min_merits] - return sol, sol_val + return binary_vars, continuous_vars, sol_val def _update_lambda_mult(self) -> np.ndarray: """ @@ -827,17 +842,19 @@ def _get_constraint_residual(self) -> float: Returns: Violation of the constraints as a float value """ + solution = self._get_solution() + # equality constraints + cr0 = 0 + for constraint in self._state.equality_constraints: + cr0 += np.abs(constraint.evaluate(solution) - constraint.rhs) - cr0 = sum(np.abs(np.dot(self._state.a0, self._state.x0) - self._state.b0)) - - eq1 = np.dot(self._state.a1, self._state.x0) - self._state.b1 - cr1 = sum(max(val, 0) for val in eq1) - - eq2 = np.dot(self._state.a2, self._state.x0) + np.dot(self._state.a3, - self._state.u) - self._state.b2 - cr2 = sum(max(val, 0) for val in eq2) + # inequality constraints + cr12 = 0 + for constraint in self._state.inequality_constraints: + sense = -1 if constraint.sense == ConstraintSense.GE else 1 + cr12 += max(sense * (constraint.evaluate(solution) - constraint.rhs), 0) - return cr0 + cr1 + cr2 + return cr0 + cr12 def _get_merit(self, cost_iterate: float, constraint_residual: float) -> float: """Compute merit value associated with the current iterate @@ -857,16 +874,7 @@ def _get_objective_value(self) -> float: Returns: Value of the objective function as a float """ - - def quadratic_form(matrix, x, c): - return np.dot(x.T, np.dot(matrix, x)) + np.dot(c.T, x) - - obj_val = quadratic_form(self._state.q0, self._state.x0, self._state.c0) - obj_val += quadratic_form(self._state.q1, self._state.u, self._state.c1) - - obj_val += self._state.op.objective.constant - - return obj_val + return self._state.op.objective.evaluate(self._get_solution()) * self._state.sense def _get_solution_residuals(self, iteration: int) -> (float, float): """Compute primal and dual residual. From c1cc535760a1ca5b59ac2b66f116559405a95c17 Mon Sep 17 00:00:00 2001 From: Atsushi Matsuo Date: Wed, 22 Apr 2020 01:33:54 +0900 Subject: [PATCH 259/323] added operator_to_quadratic_probram for new QP APIs --- qiskit/optimization/converters/__init__.py | 2 + .../operator_to_quadratic_program.py | 143 ++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 qiskit/optimization/converters/operator_to_quadratic_program.py diff --git a/qiskit/optimization/converters/__init__.py b/qiskit/optimization/converters/__init__.py index 8a7784b40d..dbb9a2ca80 100644 --- a/qiskit/optimization/converters/__init__.py +++ b/qiskit/optimization/converters/__init__.py @@ -28,6 +28,7 @@ from .linear_equality_to_penalty import LinearEqualityToPenalty from .quadratic_program_to_operator import QuadraticProgramToOperator from .quadratic_program_to_negative_value_oracle import QuadraticProgramToNegativeValueOracle +from .operator_to_quadratic_program import OperatorToQuadraticProgram # opt problem dependency from .integer_to_binary import IntegerToBinary @@ -41,4 +42,5 @@ "QuadraticProgramToOperator", "QuadraticProgramToQubo", "LinearEqualityToPenalty", + "OperatorToQuadraticProgram" ] diff --git a/qiskit/optimization/converters/operator_to_quadratic_program.py b/qiskit/optimization/converters/operator_to_quadratic_program.py new file mode 100644 index 0000000000..26ebc2ed05 --- /dev/null +++ b/qiskit/optimization/converters/operator_to_quadratic_program.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + + +"""The converter from a ```Operator``` to ``QuadraticProgram``.""" + +import copy + +import numpy as np + +from qiskit.aqua.operators import WeightedPauliOperator +from ..problems.quadratic_program import QuadraticProgram +from ..exceptions.qiskit_optimization_error import QiskitOptimizationError + + +class OperatorToQuadraticProgram: + """Convert a qubit operator into a quadratic program""" + + def __init__(self) -> None: + """Initialize the internal data structure.""" + self._qubit_op = None + self._offset = 0 + self._num_qubits = 0 + self._qubo_matrix = None + self._qp = None + + def encode(self, qubit_op: WeightedPauliOperator, offset: float) -> QuadraticProgram: + """Convert a qubit operator and a shift value into a quadratic program + + Args: + qubit_op: The qubit operator to be converted into `QuadraticProgram` + offset: The shift value of the qubit operator + + Returns: + QuadraticProgram: converted from the input qubit operator and the shift value + + Raises: + QiskitOptimizationError: If there are Pauli Xs in any Pauli term + QiskitOptimizationError: If there are more than 2 Pauli Zs in any Pauli term + """ + # Set properties + self._qubit_op = qubit_op + self._offset = copy.deepcopy(offset) + self._num_qubits = qubit_op.num_qubits + + # Create `QuadraticProgram` + self._qp = QuadraticProgram() + for i in range(self._num_qubits): + self._qp.binary_var(name='x_{0}'.format(i)) + # Create QUBO matrix + self._create_qubo_matrix() + + # Initialize dicts for linear terms and quadratic terms + linear = {} + quadratic = {} + + # For quadratic pauli terms of operator + # x_i * x_ j = (1 - Z_i - Z_j + Z_i * Z_j)/4 + for i, row in enumerate(self._qubo_matrix): + for j, weight in enumerate(row): + # Focus on the upper triangular matrix + if j <= i: + continue + # Add a quadratic term to the object function of `QuadraticProgram` + # The coefficient of the quadratic term in `QuadraticProgram` is + # 4 * weight of the pauli + coef = weight * 4 + quadratic[(i, j)] = coef +# self._qp.objective.set_quadratic_coefficients(i, j, coef) + # Sub the weight of the quadratic pauli term from the QUBO matrix + self._qubo_matrix[i, j] -= weight + # Sub the weight of the linear pauli term from the QUBO matrix + self._qubo_matrix[i, i] += weight + self._qubo_matrix[j, j] += weight + # Sub the weight from offset + offset -= weight + + # After processing quadratic pauli terms, only linear paulis are left + # x_i = (1 - Z_i)/2 + for i in range(self._num_qubits): + weight = self._qubo_matrix[i, i] + # Add a linear term to the object function of `QuadraticProgram` + # The coefficient of the linear term in `QuadraticProgram` is + # 2 * weight of the pauli + coef = weight * 2 + linear[i] = -coef +# self._qp.objective.set_linear(i, -coef) + # Sub the weight of the linear pauli term from the QUBO matrix + self._qubo_matrix[i, i] -= weight + offset += weight + + self._qp.minimize(offset, linear, quadratic) + + offset -= offset + + return self._qp + + def _create_qubo_matrix(self): + """Create a QUBO matrix from the qubit operator + + Raises: + QiskitOptimizationError: If there are Pauli Xs in any Pauli term + QiskitOptimizationError: If there are more than 2 Pauli Zs in any Pauli term + + """ + # Set properties + # The Qubo matrix is an upper triangular matrix. + # Diagonal elements in the QUBO matrix is for linear terms of the qubit operator + # The other elements in the QUBO matrix is for quadratic terms of the qubit operator + self._qubo_matrix = np.zeros((self._num_qubits, self._num_qubits)) + + for pauli in self._qubit_op.paulis: + # Count the number of Pauli Zs in a Pauli term + lst_z = pauli[1].z.tolist() + z_index = [i for i, z in enumerate(lst_z) if z is True] + num_z = len(z_index) + + # Add its weight of the Pauli term to the corresponding element of QUBO matrix + if num_z == 1: + self._qubo_matrix[z_index[0], z_index[0]] = pauli[0].real + elif num_z == 2: + self._qubo_matrix[z_index[0], z_index[1]] = pauli[0].real + else: + raise QiskitOptimizationError( + 'There are more than 2 Pauli Zs in the Pauli term {}'.format(pauli[1].z) + ) + + # If there are Pauli Xs in the Pauli term, raise an error + lst_x = pauli[1].x.tolist() + x_index = [i for i, x in enumerate(lst_x) if x is True] + if len(x_index) > 0: + raise QiskitOptimizationError('Pauli Xs exist in the Pauli {}'.format(pauli[1].x)) From 46e96312a265cdd51b866c534ef4b28ce78ae5fe Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Tue, 21 Apr 2020 12:46:23 -0400 Subject: [PATCH 260/323] Current GroverOptimizer Code, New QP Interface WIP --- qiskit/optimization/algorithms/__init__.py | 4 +- ..._minimum_finder.py => grover_optimizer.py} | 86 +++++++-------- ...dratic_program_to_negative_value_oracle.py | 10 +- .../test_grover_minimum_finder.py | 102 ------------------ test/optimization/test_grover_optimizer.py | 92 ++++++++++++++++ 5 files changed, 138 insertions(+), 156 deletions(-) rename qiskit/optimization/algorithms/{grover_minimum_finder.py => grover_optimizer.py} (88%) delete mode 100644 test/optimization/test_grover_minimum_finder.py create mode 100644 test/optimization/test_grover_optimizer.py diff --git a/qiskit/optimization/algorithms/__init__.py b/qiskit/optimization/algorithms/__init__.py index 6df8d53ca5..5b80ab8114 100644 --- a/qiskit/optimization/algorithms/__init__.py +++ b/qiskit/optimization/algorithms/__init__.py @@ -53,8 +53,8 @@ from .cobyla_optimizer import CobylaOptimizer from .minimum_eigen_optimizer import MinimumEigenOptimizer from .recursive_minimum_eigen_optimizer import RecursiveMinimumEigenOptimizer -from .grover_minimum_finder import GroverMinimumFinder, GroverOptimizationResults +from .grover_optimizer import GroverOptimizer, GroverOptimizationResults __all__ = ["ADMMOptimizer", "OptimizationAlgorithm", "OptimizationResult", "CplexOptimizer", "CobylaOptimizer", "MinimumEigenOptimizer", "RecursiveMinimumEigenOptimizer", - "GroverMinimumFinder", "GroverOptimizationResults"] + "GroverOptimizer", "GroverOptimizationResults"] diff --git a/qiskit/optimization/algorithms/grover_minimum_finder.py b/qiskit/optimization/algorithms/grover_optimizer.py similarity index 88% rename from qiskit/optimization/algorithms/grover_minimum_finder.py rename to qiskit/optimization/algorithms/grover_optimizer.py index 11183c1670..f7bf7c39e3 100644 --- a/qiskit/optimization/algorithms/grover_minimum_finder.py +++ b/qiskit/optimization/algorithms/grover_optimizer.py @@ -12,18 +12,19 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""GroverMinimumFinder module""" +"""GroverOptimizer module""" import logging from typing import Optional, Dict, Union, Tuple -import random import math +import random import numpy as np from qiskit.aqua import QuantumInstance -from qiskit.optimization.algorithms import OptimizationAlgorithm, OptimizationResult +from qiskit.optimization.algorithms import OptimizationAlgorithm from qiskit.optimization.problems import QuadraticProgram from qiskit.optimization.converters import (QuadraticProgramToQubo, QuadraticProgramToNegativeValueOracle) +from qiskit.optimization.algorithms.optimization_algorithm import OptimizationResult from qiskit.optimization.util import get_qubo_solutions from qiskit.aqua.algorithms.amplitude_amplifiers.grover import Grover from qiskit import Aer, QuantumCircuit @@ -33,26 +34,43 @@ logger = logging.getLogger(__name__) -class GroverMinimumFinder(OptimizationAlgorithm): +class GroverOptimizer(OptimizationAlgorithm): """Uses Grover Adaptive Search (GAS) to find the minimum of a QUBO function.""" - def __init__(self, num_iterations: int = 3, + def __init__(self, num_value_qubits: int, num_iterations: int = 3, quantum_instance: Optional[Union[BaseBackend, QuantumInstance]] = None) -> None: """ Args: + num_value_qubits: The number of value qubits. num_iterations: The number of iterations the algorithm will search with no improvement. quantum_instance: Instance of selected backend, defaults to Aer's statevector simulator. """ + self._num_value_qubits = num_value_qubits self._n_iterations = num_iterations if quantum_instance is None or isinstance(quantum_instance, BaseBackend): backend = quantum_instance or Aer.get_backend('statevector_simulator') quantum_instance = QuantumInstance(backend) self._quantum_instance = quantum_instance + # pylint:disable=unused-argument def get_compatibility_msg(self, problem: QuadraticProgram) -> str: """Checks whether a given problem can be solved with this optimizer. + Returns ``''`` since GroverOptimizer accepts all problems that can be modeled using the + ``QuadraticProgram``. + + Args: + problem: The optimization problem to check compatibility. + + Returns: + An empty string. + """ + return '' + + def is_compatible(self, problem: QuadraticProgram) -> Optional[str]: + """Checks whether a given problem can be solved with this optimizer. + Checks whether the given problem is compatible, i.e., whether the problem can be converted to a QUBO, and otherwise, returns a message explaining the incompatibility. @@ -60,9 +78,9 @@ def get_compatibility_msg(self, problem: QuadraticProgram) -> str: problem: The optimization problem to check compatibility. Returns: - A message describing the incompatibility. + Returns ``None`` if the problem is compatible and else a string with the error message. """ - return QuadraticProgramToQubo.get_compatibility_msg(problem) + return QuadraticProgramToQubo.is_compatible(problem) def solve(self, problem: QuadraticProgram) -> OptimizationResult: """Tries to solves the given problem using the optimizer. @@ -80,20 +98,17 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ - # convert problem to QUBO, this implicitly checks if the problem is compatible + # convert problem to QUBO qubo_converter = QuadraticProgramToQubo() problem_ = qubo_converter.encode(problem) - # TODO: How to get from Optimization Problem? - num_output_qubits = 6 - # Variables for tracking the optimum. optimum_found = False optimum_key = math.inf optimum_value = math.inf threshold = 0 n_key = problem_.variables.get_num() - n_value = num_output_qubits + n_value = self._num_value_qubits # Variables for tracking the solutions encountered. num_solutions = 2**n_key @@ -132,11 +147,10 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: # Apply Grover's Algorithm to find values below the threshold. if rotation_count > 0: + # TODO: Utilize Grover's incremental feature - requires changes to Grover. grover = Grover(oracle, init_state=a_operator, num_iterations=rotation_count) circuit = grover.construct_circuit( - measurement=self._quantum_instance.is_statevector - ) - + measurement=self._quantum_instance.is_statevector) else: circuit = a_operator._circuit @@ -144,12 +158,8 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: outcome = self._measure(circuit, n_key, n_value) k = int(outcome[0:n_key], 2) v = outcome[n_key:n_key + n_value] - - # Convert the binary string to integer. int_v = self._bin_to_int(v, n_value) + threshold v = self._twos_complement(int_v, n_value) - - logger.info('Iterations: %s', rotation_count) logger.info('Outcome: %s', outcome) logger.info('Value: %s = %s', v, int_v) @@ -163,24 +173,19 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: improvement_found = True threshold = optimum_value else: - # No better number after the max number of iterations, so we assume the optimal. - if loops_with_no_improvement >= self._n_iterations: - improvement_found = True - optimum_found = True - # Using Durr and Hoyer method, increase m. - # TODO: Give option for a rotation schedule, or for different lambda's. m = int(np.ceil(min(m * 8/7, 2**(n_key / 2)))) logger.info('No Improvement. M: %s', m) - # Check if we've already seen this value. - if k not in keys_measured: - keys_measured.append(k) + # Check if we've already seen this value. + if k not in keys_measured: + keys_measured.append(k) - # Stop if we've seen all the keys or hit the rotation max. - if len(keys_measured) == num_solutions or rotations >= max_rotations: - improvement_found = True - optimum_found = True + # Assume the optimal if any of the stop parameters are true. + if loops_with_no_improvement >= self._n_iterations or \ + len(keys_measured) == num_solutions or rotations >= max_rotations: + improvement_found = True + optimum_found = True # Track the operation count. operations = circuit.count_ops() @@ -198,8 +203,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: opt_x = [1 if s == '1' else 0 for s in ('{0:%sb}' % n_key).format(optimum_key)] # Build the results object. - grover_results = GroverOptimizationResults(operation_count, rotations, n_key, n_value, - func_dict) + grover_results = GroverOptimizationResults(operation_count, n_key, n_value, func_dict) result = OptimizationResult(x=opt_x, fval=solutions[optimum_key], results={"grover_results": grover_results, "qubo_converter": qubo_converter}) @@ -274,20 +278,18 @@ def _bin_to_int(v: str, num_value_bits: int) -> int: class GroverOptimizationResults: """A results object for Grover Optimization methods.""" - def __init__(self, operation_counts: Dict[int, Dict[str, int]], rotations: int, + def __init__(self, operation_counts: Dict[int, Dict[str, int]], n_input_qubits: int, n_output_qubits: int, func_dict: Dict[Union[int, Tuple[int, int]], int]) -> None: """ Args: operation_counts: The counts of each operation performed per iteration. - rotations: The total number of Grover rotations performed. n_input_qubits: The number of qubits used to represent the input. n_output_qubits: The number of qubits used to represent the output. func_dict: A dictionary representation of the function, where the keys correspond to a variable, and the values are the corresponding coefficients. """ self._operation_counts = operation_counts - self._rotations = rotations self._n_input_qubits = n_input_qubits self._n_output_qubits = n_output_qubits self._func_dict = func_dict @@ -301,15 +303,6 @@ def operation_counts(self) -> Dict[int, Dict[str, int]]: """ return self._operation_counts - @property - def rotation_count(self) -> int: - """Getter of rotation_count - - Returns: - The total number of Grover rotations. - """ - return self._rotations - @property def n_input_qubits(self) -> int: """Getter of n_input_qubits @@ -338,3 +331,4 @@ def func_dict(self) -> Dict[Union[int, Tuple[int, int]], int]: is a constant term, it is referenced by key -1. """ return self._func_dict + diff --git a/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py b/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py index e6027cba03..49cc4c1e84 100644 --- a/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py @@ -129,14 +129,12 @@ def _get_function(linear: np.array, quadratic: np.array, constant: int) -> \ return func def _evaluate_classically(self, measurement): - # TODO: Typing for this method? Still not sure what it's used for. Required by Grover. """ evaluate classical """ - assignment = [(var + 1) * (int(tf) * 2 - 1) for tf, var in zip(measurement[::-1], + value = measurement[self._num_key:self._num_key + self._num_value] + assignment = [(var + 1) * (int(tf) * 2 - 1) for tf, var in zip(measurement, range(len(measurement)))] - assignment_dict = dict() - for v in assignment: - assignment_dict[v] = bool(v < 0) - return assignment_dict, assignment + evaluation = value[0] == '1' + return evaluation, assignment def _build_operator(self, func_dict: Dict[Union[int, Tuple[int, int]], int]) -> QuantumCircuit: """Creates a circuit for the state preparation operator. diff --git a/test/optimization/test_grover_minimum_finder.py b/test/optimization/test_grover_minimum_finder.py deleted file mode 100644 index 500b6aa903..0000000000 --- a/test/optimization/test_grover_minimum_finder.py +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""Test Grover Minimum Finder.""" - -import unittest -from test.optimization import QiskitOptimizationTestCase -import numpy as np -from qiskit.aqua.algorithms import NumPyMinimumEigensolver -from qiskit.optimization.algorithms import GroverMinimumFinder, MinimumEigenOptimizer -from qiskit.optimization.problems import QuadraticProgram - - -class TestGroverMinimumFinder(QiskitOptimizationTestCase): - """GroverMinimumFinder tests.""" - - def validate_results(self, problem, results, max_iterations): - """Validate the results object returned by GroverMinimumFinder.""" - # Get measured values. - grover_results = results.results['grover_results'] - - iterations = len(grover_results.operation_counts) - rot = grover_results.rotation_count - - # Get expected value. - solver = MinimumEigenOptimizer(NumPyMinimumEigensolver()) - comp_result = solver.solve(problem) - max_rotations = int(np.ceil(100*np.pi/4)) - - # Validate results. - max_hit = max_rotations <= rot or max_iterations <= iterations - self.assertTrue(comp_result.x == results.x or max_hit) - self.assertTrue(comp_result.fval == results.fval or max_hit) - - def test_qubo_gas_int_zero(self): - """Test for when the answer is zero.""" - try: - # Input. - op = QuadraticProgram() - op.variables.add(names=['x0', 'x1'], types='BB') - linear = [('x0', 0), ('x1', 0)] - op.objective.set_linear(linear) - - # Will not find a negative, should return 0. - gmf = GroverMinimumFinder(num_iterations=1) - results = gmf.solve(op) - self.assertEqual(results.x, [0, 0]) - self.assertEqual(results.fval, 0.0) - except NameError as ex: - self.skipTest(str(ex)) - - def test_qubo_gas_int_simple(self): - """Test for simple case, with 2 linear coeffs and no quadratic coeffs or constants.""" - try: - # Input. - op = QuadraticProgram() - op.variables.add(names=['x0', 'x1'], types='BB') - linear = [('x0', -1), ('x1', 2)] - op.objective.set_linear(linear) - - # Get the optimum key and value. - n_iter = 8 - gmf = GroverMinimumFinder(num_iterations=n_iter) - results = gmf.solve(op) - self.validate_results(op, results, n_iter) - except NameError as ex: - self.skipTest(str(ex)) - - def test_qubo_gas_int_paper_example(self): - """Test the example from https://arxiv.org/abs/1912.04088.""" - try: - # Input. - op = QuadraticProgram() - op.variables.add(names=['x0', 'x1', 'x2'], types='BBB') - - linear = [('x0', -1), ('x1', 2), ('x2', -3)] - op.objective.set_linear(linear) - op.objective.set_quadratic_coefficients('x0', 'x2', -2) - op.objective.set_quadratic_coefficients('x1', 'x2', -1) - - # Get the optimum key and value. - n_iter = 10 - gmf = GroverMinimumFinder(num_iterations=n_iter) - results = gmf.solve(op) - self.validate_results(op, results, 10) - except NameError as ex: - self.skipTest(str(ex)) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/optimization/test_grover_optimizer.py b/test/optimization/test_grover_optimizer.py new file mode 100644 index 0000000000..336115f880 --- /dev/null +++ b/test/optimization/test_grover_optimizer.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +"""Test Grover Optimizer.""" + +import unittest +from test.optimization import QiskitOptimizationTestCase +from qiskit.aqua.algorithms import NumPyMinimumEigensolver +from qiskit.optimization.algorithms import GroverOptimizer, MinimumEigenOptimizer +from qiskit.optimization.problems import QuadraticProgram +from docplex.mp.model import Model + + +class TestGroverOptimizer(QiskitOptimizationTestCase): + """GroverOptimizer tests.""" + + def validate_results(self, problem, results): + """Validate the results object returned by GroverOptimizer.""" + # Get expected value. + solver = MinimumEigenOptimizer(NumPyMinimumEigensolver()) + comp_result = solver.solve(problem) + + # Validate results. + self.assertTrue(comp_result.x == results.x) + self.assertTrue(comp_result.fval == results.fval) + + def test_qubo_gas_int_zero(self): + """Test for when the answer is zero.""" + + # Input. + model = Model() + x0 = model.binary_var(name='x0') + x1 = model.binary_var(name='x1') + model.minimize(0*x0+0*x1) + op = QuadraticProgram() + op.from_docplex(model) + + # Will not find a negative, should return 0. + gmf = GroverOptimizer(1, num_iterations=1) + results = gmf.solve(op) + self.assertEqual(results.x, [0, 0]) + self.assertEqual(results.fval, 0.0) + + def test_qubo_gas_int_simple(self): + """Test for simple case, with 2 linear coeffs and no quadratic coeffs or constants.""" + + # Input. + model = Model() + x0 = model.binary_var(name='x0') + x1 = model.binary_var(name='x1') + model.minimize(-x0+2*x1) + op = QuadraticProgram() + op.from_docplex(model) + + # Get the optimum key and value. + n_iter = 8 + gmf = GroverOptimizer(4, num_iterations=n_iter) + results = gmf.solve(op) + self.validate_results(op, results) + + def test_qubo_gas_int_paper_example(self): + """Test the example from https://arxiv.org/abs/1912.04088.""" + + # Input. + model = Model() + x0 = model.binary_var(name='x0') + x1 = model.binary_var(name='x1') + x2 = model.binary_var(name='x2') + model.minimize(-x0+2*x1-3*x2-2*x0*x2-1*x1*x2) + op = QuadraticProgram() + op.from_docplex(model) + + # Get the optimum key and value. + n_iter = 10 + gmf = GroverOptimizer(6, num_iterations=n_iter) + results = gmf.solve(op) + self.validate_results(op, results) + + +if __name__ == '__main__': + unittest.main() From a50127cdd200edf2ac309703ee34d19c58de2fc5 Mon Sep 17 00:00:00 2001 From: Austin Gilliam Date: Tue, 21 Apr 2020 15:13:39 -0400 Subject: [PATCH 261/323] GroverOptimizer Uses New QP Interface --- .../algorithms/grover_optimizer.py | 25 ++++--------------- ...dratic_program_to_negative_value_oracle.py | 21 ++++++---------- 2 files changed, 12 insertions(+), 34 deletions(-) diff --git a/qiskit/optimization/algorithms/grover_optimizer.py b/qiskit/optimization/algorithms/grover_optimizer.py index f7bf7c39e3..b59cce7baf 100644 --- a/qiskit/optimization/algorithms/grover_optimizer.py +++ b/qiskit/optimization/algorithms/grover_optimizer.py @@ -53,24 +53,9 @@ def __init__(self, num_value_qubits: int, num_iterations: int = 3, quantum_instance = QuantumInstance(backend) self._quantum_instance = quantum_instance - # pylint:disable=unused-argument def get_compatibility_msg(self, problem: QuadraticProgram) -> str: """Checks whether a given problem can be solved with this optimizer. - Returns ``''`` since GroverOptimizer accepts all problems that can be modeled using the - ``QuadraticProgram``. - - Args: - problem: The optimization problem to check compatibility. - - Returns: - An empty string. - """ - return '' - - def is_compatible(self, problem: QuadraticProgram) -> Optional[str]: - """Checks whether a given problem can be solved with this optimizer. - Checks whether the given problem is compatible, i.e., whether the problem can be converted to a QUBO, and otherwise, returns a message explaining the incompatibility. @@ -78,9 +63,9 @@ def is_compatible(self, problem: QuadraticProgram) -> Optional[str]: problem: The optimization problem to check compatibility. Returns: - Returns ``None`` if the problem is compatible and else a string with the error message. + A message describing the incompatibility. """ - return QuadraticProgramToQubo.is_compatible(problem) + return QuadraticProgramToQubo.get_compatibility_msg(problem) def solve(self, problem: QuadraticProgram) -> OptimizationResult: """Tries to solves the given problem using the optimizer. @@ -107,7 +92,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: optimum_key = math.inf optimum_value = math.inf threshold = 0 - n_key = problem_.variables.get_num() + n_key = len(problem_.variables) n_value = self._num_value_qubits # Variables for tracking the solutions encountered. @@ -124,7 +109,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: max_rotations = int(np.ceil(100*np.pi/4)) # Initialize oracle helper object. - orig_constant = problem_.objective.get_offset() + orig_constant = problem_.objective.constant measurement = not self._quantum_instance.is_statevector opt_prob_converter = QuadraticProgramToNegativeValueOracle(n_value, measurement) @@ -134,7 +119,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: improvement_found = False # Get oracle O and the state preparation operator A for the current threshold. - problem_.objective.set_offset(orig_constant - threshold) + problem_.objective.constant = orig_constant - threshold a_operator, oracle, func_dict = opt_prob_converter.encode(problem_) # Iterate until we measure a negative. diff --git a/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py b/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py index 49cc4c1e84..8c5329c98f 100644 --- a/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py @@ -66,24 +66,15 @@ def encode(self, problem: QuadraticProgram) -> \ """ # get linear part of objective - linear_dict = problem.objective.get_linear_dict() - linear_coeff = np.zeros(problem.variables.get_num()) + linear_dict = problem.objective.linear.to_dict() + linear_coeff = np.zeros(len(problem.variables)) for i, v in linear_dict.items(): linear_coeff[i] = v # get quadratic part of objective - quadratic_dict = problem.objective.get_quadratic_dict() - quadratic_coeff = {} - for (i, j), value in quadratic_dict.items(): - if i <= j: - # divide by 2 since problem considers xQx/2. - coeff = quadratic_coeff.get((i, j), 0) - quadratic_coeff[(i, j)] = value / 2 + coeff - else: - coeff = quadratic_coeff.get((j, i), 0) - quadratic_coeff[(j, i)] = value / 2 + coeff + quadratic_coeff = problem.objective.quadratic.to_dict() - constant = problem.objective.get_offset() + constant = int(problem.objective.constant) # Get circuit requirements from input. self._num_key = len(linear_coeff) @@ -171,8 +162,10 @@ def _build_operator(self, func_dict: Dict[Union[int, Tuple[int, int]], int]) -> for i in range(self._num_value): for k, v in func_dict.items(): if isinstance(k, tuple): + a = [key_val[int(k[0])], key_val[int(k[1])]] + b = key_val[self._num_key + i] circuit.mcu1(1 / 2 ** self._num_value * 2 * np.pi * 2 ** i * v, - [key_val[k[0]], key_val[k[1]]], key_val[self._num_key + i]) + a, b) # Add IQFT. iqft = IQFT(self._num_value) From 46c76893c57fe4ca10ee45673b40c185eb0dcacf Mon Sep 17 00:00:00 2001 From: Atsushi Matsuo Date: Wed, 22 Apr 2020 05:31:05 +0900 Subject: [PATCH 262/323] added unittests --- .../converters/inequality_to_equality.py | 4 +- .../operator_to_quadratic_program.py | 3 - test/optimization/test_converters.py | 521 ++++++++++-------- 3 files changed, 293 insertions(+), 235 deletions(-) diff --git a/qiskit/optimization/converters/inequality_to_equality.py b/qiskit/optimization/converters/inequality_to_equality.py index 89a7df2c27..f7b757b127 100644 --- a/qiskit/optimization/converters/inequality_to_equality.py +++ b/qiskit/optimization/converters/inequality_to_equality.py @@ -261,7 +261,7 @@ def _add_integer_slack_var_quadratic_constraint(self, linear, quadratic, sense, # Add a new equality constraint. new_linear = copy.deepcopy(linear) new_linear[slack_name] = sign - self._dst.quadratic_constraints(new_linear, quadratic, "==", new_rhs, name) + self._dst.quadratic_constraint(new_linear, quadratic, "==", new_rhs, name) def _add_continuous_slack_var_quadratic_constraint(self, linear, quadratic, sense, rhs, name): # If a coefficient that is not integer exist, raise error @@ -288,7 +288,7 @@ def _add_continuous_slack_var_quadratic_constraint(self, linear, quadratic, sens # Add a new equality constraint. new_linear = copy.deepcopy(linear) new_linear[slack_name] = sign - self._dst.quadratic_constraints(new_linear, quadratic, "==", rhs, name) + self._dst.quadratic_constraint(new_linear, quadratic, "==", rhs, name) def _add_auto_slack_var_quadratic_constraint(self, linear, quadratic, sense, rhs, name): # If a coefficient that is not integer exist, use a continuous slack variable diff --git a/qiskit/optimization/converters/operator_to_quadratic_program.py b/qiskit/optimization/converters/operator_to_quadratic_program.py index 26ebc2ed05..f77404ba63 100644 --- a/qiskit/optimization/converters/operator_to_quadratic_program.py +++ b/qiskit/optimization/converters/operator_to_quadratic_program.py @@ -77,7 +77,6 @@ def encode(self, qubit_op: WeightedPauliOperator, offset: float) -> QuadraticPro # 4 * weight of the pauli coef = weight * 4 quadratic[(i, j)] = coef -# self._qp.objective.set_quadratic_coefficients(i, j, coef) # Sub the weight of the quadratic pauli term from the QUBO matrix self._qubo_matrix[i, j] -= weight # Sub the weight of the linear pauli term from the QUBO matrix @@ -95,13 +94,11 @@ def encode(self, qubit_op: WeightedPauliOperator, offset: float) -> QuadraticPro # 2 * weight of the pauli coef = weight * 2 linear[i] = -coef -# self._qp.objective.set_linear(i, -coef) # Sub the weight of the linear pauli term from the QUBO matrix self._qubo_matrix[i, i] -= weight offset += weight self._qp.minimize(offset, linear, quadratic) - offset -= offset return self._qp diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index 9c67fe9f56..993a7e51cd 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -22,11 +22,12 @@ from qiskit.aqua.operators import WeightedPauliOperator from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.optimization import QuadraticProgram, QiskitOptimizationError +from qiskit.optimization.problems import ConstraintSense, VarType from qiskit.optimization.algorithms import OptimizationResult from qiskit.optimization.converters import ( - InequalityToEqualityConverter, + InequalityToEquality, QuadraticProgramToOperator, - IntegerToBinaryConverter, + IntegerToBinary, LinearEqualityToPenalty, ) from qiskit.optimization.algorithms import MinimumEigenOptimizer, CplexOptimizer, ADMMOptimizer @@ -35,12 +36,6 @@ logger = logging.getLogger(__name__) -_HAS_CPLEX = False -try: - from cplex import SparsePair - _HAS_CPLEX = True -except ImportError: - logger.info("CPLEX is not installed.") QUBIT_OP_MAXIMIZE_SAMPLE = WeightedPauliOperator( paulis=[ @@ -50,8 +45,8 @@ [(-799999.5 + 0j), Pauli(z=[False, False, False, True], x=[False, False, False, False])], [(100000 + 0j), Pauli(z=[True, True, False, False], x=[False, False, False, False])], [(150000 + 0j), Pauli(z=[True, False, True, False], x=[False, False, False, False])], - [(200000 + 0j), Pauli(z=[True, False, False, True], x=[False, False, False, False])], [(300000 + 0j), Pauli(z=[False, True, True, False], x=[False, False, False, False])], + [(200000 + 0j), Pauli(z=[True, False, False, True], x=[False, False, False, False])], [(400000 + 0j), Pauli(z=[False, True, False, True], x=[False, False, False, False])], [(600000 + 0j), Pauli(z=[False, False, True, True], x=[False, False, False, False])], ] @@ -62,17 +57,12 @@ class TestConverters(QiskitOptimizationTestCase): """Test Converters""" - def setUp(self) -> None: - super().setUp() - if not _HAS_CPLEX: - self.skipTest('CPLEX is not installed.') - def test_empty_problem(self): """ Test empty problem """ op = QuadraticProgram() - conv = InequalityToEqualityConverter() + conv = InequalityToEquality() op = conv.encode(op) - conv = IntegerToBinaryConverter() + conv = IntegerToBinary() op = conv.encode(op) conv = LinearEqualityToPenalty() op = conv.encode(op) @@ -85,165 +75,252 @@ def test_valid_variable_type(self): # Integer variable with self.assertRaises(QiskitOptimizationError): op = QuadraticProgram() - op.variables.add(names=['x'], types='I') + op.integer_var(0, 10, "int_var") conv = QuadraticProgramToOperator() _ = conv.encode(op) # Continuous variable with self.assertRaises(QiskitOptimizationError): op = QuadraticProgram() - op.variables.add(names=['x'], types='C') - conv = QuadraticProgramToOperator() - _ = conv.encode(op) - # Semi-Continuous variable - with self.assertRaises(QiskitOptimizationError): - op = QuadraticProgram() - op.variables.add(names=['x'], types='S') - conv = QuadraticProgramToOperator() - _ = conv.encode(op) - # Semi-Integer variable - with self.assertRaises(QiskitOptimizationError): - op = QuadraticProgram() - op.variables.add(names=['x'], types='N') + op.continuous_var(0, 10, "continuous_var") conv = QuadraticProgramToOperator() _ = conv.encode(op) - # validate the types of the variables for InequalityToEqualityConverter - # Semi-Continuous variable - with self.assertRaises(QiskitOptimizationError): - op = QuadraticProgram() - op.variables.add(names=['x'], types='S') - conv = InequalityToEqualityConverter() - _ = conv.encode(op) - # Semi-Integer variable - with self.assertRaises(QiskitOptimizationError): - op = QuadraticProgram() - op.variables.add(names=['x'], types='N') - conv = InequalityToEqualityConverter() - _ = conv.encode(op) def test_inequality_binary(self): """ Test InequalityToEqualityConverter with binary variables """ op = QuadraticProgram() - op.variables.add(names=['x', 'y', 'z'], types='B' * 3) - op.linear_constraints.add( - lin_expr=[ - SparsePair(ind=['x', 'y'], val=[1, 1]), - SparsePair(ind=['y', 'z'], val=[1, -1]), - SparsePair(ind=['z', 'x'], val=[1, 2]), - ], - senses=['E', 'L', 'G'], - rhs=[1, 2, 3], - names=['xy', 'yz', 'zx'], - ) - conv = InequalityToEqualityConverter() + for i in range(3): + op.binary_var(name='x{}'.format(i)) + # Linear constraints + linear_constraint = {} + linear_constraint['x0'] = 1 + linear_constraint['x1'] = 1 + op.linear_constraint(linear_constraint, ConstraintSense.EQ, 1, 'x0x1') + linear_constraint = {} + linear_constraint['x1'] = 1 + linear_constraint['x2'] = -1 + op.linear_constraint(linear_constraint, ConstraintSense.LE, 2, 'x1x2') + linear_constraint = {} + linear_constraint['x0'] = 1 + linear_constraint['x2'] = 3 + op.linear_constraint(linear_constraint, ConstraintSense.GE, 2, 'x0x2') + # Quadratic constraints + quadratic = {} + quadratic[('x0', 'x1')] = 1 + quadratic[('x1', 'x2')] = 2 + op.quadratic_constraint({}, quadratic, ConstraintSense.LE, 3, 'x0x1_x1x2LE') + quadratic = {} + quadratic[('x0', 'x1')] = 3 + quadratic[('x1', 'x2')] = 4 + op.quadratic_constraint({}, quadratic, ConstraintSense.GE, 3, 'x0x1_x1x2GE') + # Convert inequality constraints into equality constraints + conv = InequalityToEquality() op2 = conv.encode(op) - self.assertEqual(op.get_problem_name(), op2.get_problem_name()) - self.assertEqual(op.get_problem_type(), op2.get_problem_type()) - cst = op2.linear_constraints - self.assertListEqual(cst.get_names(), ['xy', 'yz', 'zx']) - self.assertListEqual(cst.get_senses(), ['E', 'E', 'E']) - self.assertListEqual(cst.get_rhs(), [1, 2, 3]) - var = op2.variables - self.assertListEqual(var.get_lower_bounds(3, 4), [0, 0]) - self.assertListEqual(var.get_upper_bounds(3, 4), [3, 0]) + # Check names and objective senses + self.assertEqual(op.name, op2.name) + self.assertEqual(op.objective.sense, op2.objective.sense) + # For linear constraints + lst = [ + op2.linear_constraints[0].linear.to_dict()[0], + op2.linear_constraints[0].linear.to_dict()[1], + ] + self.assertListEqual(lst, [1, 1]) + self.assertEqual(op2.linear_constraints[0].sense, ConstraintSense.EQ) + lst = [ + op2.linear_constraints[1].linear.to_dict()[1], + op2.linear_constraints[1].linear.to_dict()[2], + op2.linear_constraints[1].linear.to_dict()[3], + ] + self.assertListEqual(lst, [1, -1, 1]) + lst = [op2.variables[3].lowerbound, op2.variables[3].upperbound] + self.assertListEqual(lst, [0, 3]) + self.assertEqual(op2.linear_constraints[1].sense, ConstraintSense.EQ) + lst = [ + op2.linear_constraints[2].linear.to_dict()[0], + op2.linear_constraints[2].linear.to_dict()[2], + op2.linear_constraints[2].linear.to_dict()[4], + ] + self.assertListEqual(lst, [1, 3, -1]) + lst = [op2.variables[4].lowerbound, op2.variables[4].upperbound] + self.assertListEqual(lst, [0, 2]) + self.assertEqual(op2.linear_constraints[2].sense, ConstraintSense.EQ) + # For quadratic constraints + lst = [ + op2.quadratic_constraints[0].quadratic.to_dict()[(0, 1)], + op2.quadratic_constraints[0].quadratic.to_dict()[(1, 2)], + op2.quadratic_constraints[0].linear.to_dict()[5], + ] + self.assertListEqual(lst, [1, 2, 1]) + lst = [op2.variables[5].lowerbound, op2.variables[5].upperbound] + self.assertListEqual(lst, [0, 3]) + lst = [ + op2.quadratic_constraints[1].quadratic.to_dict()[(0, 1)], + op2.quadratic_constraints[1].quadratic.to_dict()[(1, 2)], + op2.quadratic_constraints[1].linear.to_dict()[6], + ] + self.assertListEqual(lst, [3, 4, -1]) + lst = [op2.variables[6].lowerbound, op2.variables[6].upperbound] + self.assertListEqual(lst, [0, 4]) def test_inequality_integer(self): """ Test InequalityToEqualityConverter with integer variables """ op = QuadraticProgram() - op.variables.add(names=['x', 'y', 'z'], types='I' * 3, lb=[-3] * 3, ub=[3] * 3) - op.linear_constraints.add( - lin_expr=[ - SparsePair(ind=['x', 'y'], val=[1, 1]), - SparsePair(ind=['y', 'z'], val=[1, -1]), - SparsePair(ind=['z', 'x'], val=[1, 2]), - ], - senses=['E', 'L', 'G'], - rhs=[1, 2, 3], - names=['xy', 'yz', 'zx'], - ) - conv = InequalityToEqualityConverter() + for i in range(3): + op.integer_var(name='x{}'.format(i), lowerbound=-3, upperbound=3) + # Linear constraints + linear_constraint = {} + linear_constraint['x0'] = 1 + linear_constraint['x1'] = 1 + op.linear_constraint(linear_constraint, ConstraintSense.EQ, 1, 'x0x1') + linear_constraint = {} + linear_constraint['x1'] = 1 + linear_constraint['x2'] = -1 + op.linear_constraint(linear_constraint, ConstraintSense.LE, 2, 'x1x2') + linear_constraint = {} + linear_constraint['x0'] = 1 + linear_constraint['x2'] = 3 + op.linear_constraint(linear_constraint, ConstraintSense.GE, 2, 'x0x2') + # Quadratic constraints + quadratic = {} + quadratic[('x0', 'x1')] = 1 + quadratic[('x1', 'x2')] = 2 + op.quadratic_constraint({}, quadratic, ConstraintSense.LE, 3, 'x0x1_x1x2LE') + quadratic = {} + quadratic[('x0', 'x1')] = 3 + quadratic[('x1', 'x2')] = 4 + op.quadratic_constraint({}, quadratic, ConstraintSense.GE, 3, 'x0x1_x1x2GE') + conv = InequalityToEquality() op2 = conv.encode(op) - self.assertEqual(op.get_problem_name(), op2.get_problem_name()) - self.assertEqual(op.get_problem_type(), op2.get_problem_type()) - cst = op2.linear_constraints - self.assertListEqual(cst.get_names(), ['xy', 'yz', 'zx']) - self.assertListEqual(cst.get_senses(), ['E', 'E', 'E']) - self.assertListEqual(cst.get_rhs(), [1, 2, 3]) - var = op2.variables - self.assertListEqual(var.get_lower_bounds(3, 4), [0, 0]) - self.assertListEqual(var.get_upper_bounds(3, 4), [8, 6]) + # For linear constraints + lst = [ + op2.linear_constraints[0].linear.to_dict()[0], + op2.linear_constraints[0].linear.to_dict()[1], + ] + self.assertListEqual(lst, [1, 1]) + self.assertEqual(op2.linear_constraints[0].sense, ConstraintSense.EQ) + lst = [ + op2.linear_constraints[1].linear.to_dict()[1], + op2.linear_constraints[1].linear.to_dict()[2], + op2.linear_constraints[1].linear.to_dict()[3], + ] + self.assertListEqual(lst, [1, -1, 1]) + lst = [op2.variables[3].lowerbound, op2.variables[3].upperbound] + self.assertListEqual(lst, [0, 8]) + self.assertEqual(op2.linear_constraints[1].sense, ConstraintSense.EQ) + lst = [ + op2.linear_constraints[2].linear.to_dict()[0], + op2.linear_constraints[2].linear.to_dict()[2], + op2.linear_constraints[2].linear.to_dict()[4], + ] + self.assertListEqual(lst, [1, 3, -1]) + lst = [op2.variables[4].lowerbound, op2.variables[4].upperbound] + self.assertListEqual(lst, [0, 10]) + self.assertEqual(op2.linear_constraints[2].sense, ConstraintSense.EQ) + # For quadratic constraints + lst = [ + op2.quadratic_constraints[0].quadratic.to_dict()[(0, 1)], + op2.quadratic_constraints[0].quadratic.to_dict()[(1, 2)], + op2.quadratic_constraints[0].linear.to_dict()[5], + ] + self.assertListEqual(lst, [1, 2, 1]) + lst = [op2.variables[5].lowerbound, op2.variables[5].upperbound] + self.assertListEqual(lst, [0, 30]) + lst = [ + op2.quadratic_constraints[1].quadratic.to_dict()[(0, 1)], + op2.quadratic_constraints[1].quadratic.to_dict()[(1, 2)], + op2.quadratic_constraints[1].linear.to_dict()[6], + ] + self.assertListEqual(lst, [3, 4, -1]) + lst = [op2.variables[6].lowerbound, op2.variables[6].upperbound] + self.assertListEqual(lst, [0, 60]) def test_inequality_mode_integer(self): """ Test integer mode of InequalityToEqualityConverter() """ op = QuadraticProgram() - op.variables.add(names=['x', 'y', 'z'], types='B' * 3) - op.linear_constraints.add( - lin_expr=[ - SparsePair(ind=['x', 'y'], val=[1, 1]), - SparsePair(ind=['y', 'z'], val=[1, -1]), - SparsePair(ind=['z', 'x'], val=[1, 2]), - ], - senses=['E', 'L', 'G'], - rhs=[1, 2, 3], - names=['xy', 'yz', 'zx'], - ) - conv = InequalityToEqualityConverter() + for i in range(3): + op.binary_var(name='x{}'.format(i)) + # Linear constraints + linear_constraint = {} + linear_constraint['x0'] = 1 + linear_constraint['x1'] = 1 + op.linear_constraint(linear_constraint, ConstraintSense.EQ, 1, 'x0x1') + linear_constraint = {} + linear_constraint['x1'] = 1 + linear_constraint['x2'] = -1 + op.linear_constraint(linear_constraint, ConstraintSense.LE, 2, 'x1x2') + linear_constraint = {} + linear_constraint['x0'] = 1 + linear_constraint['x2'] = 3 + op.linear_constraint(linear_constraint, ConstraintSense.GE, 2, 'x0x2') + conv = InequalityToEquality() op2 = conv.encode(op, mode='integer') - var = op2.variables - self.assertListEqual(var.get_types(3, 4), ['I', 'I']) + lst = [op2.variables[3].vartype, op2.variables[4].vartype] + self.assertListEqual(lst, [VarType.INTEGER, VarType.INTEGER]) def test_inequality_mode_continuous(self): """ Test continuous mode of InequalityToEqualityConverter() """ op = QuadraticProgram() - op.variables.add(names=['x', 'y', 'z'], types='B' * 3) - op.linear_constraints.add( - lin_expr=[ - SparsePair(ind=['x', 'y'], val=[1, 1]), - SparsePair(ind=['y', 'z'], val=[1, -1]), - SparsePair(ind=['z', 'x'], val=[1, 2]), - ], - senses=['E', 'L', 'G'], - rhs=[1, 2, 3], - names=['xy', 'yz', 'zx'], - ) - conv = InequalityToEqualityConverter() + for i in range(3): + op.binary_var(name='x{}'.format(i)) + # Linear constraints + linear_constraint = {} + linear_constraint['x0'] = 1 + linear_constraint['x1'] = 1 + op.linear_constraint(linear_constraint, ConstraintSense.EQ, 1, 'x0x1') + linear_constraint = {} + linear_constraint['x1'] = 1 + linear_constraint['x2'] = -1 + op.linear_constraint(linear_constraint, ConstraintSense.LE, 2, 'x1x2') + linear_constraint = {} + linear_constraint['x0'] = 1 + linear_constraint['x2'] = 3 + op.linear_constraint(linear_constraint, ConstraintSense.GE, 2, 'x0x2') + conv = InequalityToEquality() op2 = conv.encode(op, mode='continuous') - var = op2.variables - self.assertListEqual(var.get_types(3, 4), ['C', 'C']) + lst = [op2.variables[3].vartype, op2.variables[4].vartype] + self.assertListEqual(lst, [VarType.CONTINUOUS, VarType.CONTINUOUS]) def test_inequality_mode_auto(self): """ Test auto mode of InequalityToEqualityConverter() """ op = QuadraticProgram() - op.variables.add(names=['x', 'y', 'z'], types='B' * 3) - op.linear_constraints.add( - lin_expr=[ - SparsePair(ind=['x', 'y'], val=[1, 1]), - SparsePair(ind=['y', 'z'], val=[1, -1]), - SparsePair(ind=['z', 'x'], val=[1.1, 2.2]), - ], - senses=['E', 'L', 'G'], - rhs=[1, 2, 3.3], - names=['xy', 'yz', 'zx'], - ) - conv = InequalityToEqualityConverter() + for i in range(3): + op.binary_var(name='x{}'.format(i)) + # Linear constraints + linear_constraint = {} + linear_constraint['x0'] = 1 + linear_constraint['x1'] = 1 + op.linear_constraint(linear_constraint, ConstraintSense.EQ, 1, 'x0x1') + linear_constraint = {} + linear_constraint['x1'] = 1 + linear_constraint['x2'] = -1 + op.linear_constraint(linear_constraint, ConstraintSense.LE, 2, 'x1x2') + linear_constraint = {} + linear_constraint['x0'] = 1.1 + linear_constraint['x2'] = 2.2 + op.linear_constraint(linear_constraint, ConstraintSense.GE, 3.3, 'x0x2') + conv = InequalityToEquality() op2 = conv.encode(op, mode='auto') - var = op2.variables - self.assertListEqual(var.get_types(3, 4), ['I', 'C']) + lst = [op2.variables[3].vartype, op2.variables[4].vartype] + self.assertListEqual(lst, [VarType.INTEGER, VarType.CONTINUOUS]) def test_penalize_sense(self): """ Test PenalizeLinearEqualityConstraints with senses """ op = QuadraticProgram() - op.variables.add(names=['x', 'y', 'z'], types='B' * 3) - op.linear_constraints.add( - lin_expr=[ - SparsePair(ind=['x', 'y'], val=[1, 1]), - SparsePair(ind=['y', 'z'], val=[1, -1]), - SparsePair(ind=['z', 'x'], val=[1, 2]), - ], - senses=['E', 'L', 'G'], - rhs=[1, 2, 3], - names=['xy', 'yz', 'zx'], - ) - self.assertEqual(op.linear_constraints.get_num(), 3) + for i in range(3): + op.binary_var(name='x{}'.format(i)) + # Linear constraints + linear_constraint = {} + linear_constraint['x0'] = 1 + linear_constraint['x1'] = 1 + op.linear_constraint(linear_constraint, ConstraintSense.EQ, 1, 'x0x1') + linear_constraint = {} + linear_constraint['x1'] = 1 + linear_constraint['x2'] = -1 + op.linear_constraint(linear_constraint, ConstraintSense.LE, 2, 'x1x2') + linear_constraint = {} + linear_constraint['x0'] = 1 + linear_constraint['x2'] = 3 + op.linear_constraint(linear_constraint, ConstraintSense.GE, 2, 'x0x2') + self.assertEqual(len(op.linear_constraints), 3) conv = LinearEqualityToPenalty() with self.assertRaises(QiskitOptimizationError): conv.encode(op) @@ -251,98 +328,103 @@ def test_penalize_sense(self): def test_penalize_binary(self): """ Test PenalizeLinearEqualityConstraints with binary variables """ op = QuadraticProgram() - op.variables.add(names=['x', 'y', 'z'], types='B' * 3) - op.linear_constraints.add( - lin_expr=[ - SparsePair(ind=['x', 'y'], val=[1, 1]), - SparsePair(ind=['y', 'z'], val=[1, -1]), - ], - senses=['E', 'E'], - rhs=[1, 2], - names=['xy', 'yz'], - ) - self.assertEqual(op.linear_constraints.get_num(), 2) + for i in range(3): + op.binary_var(name='x{}'.format(i)) + # Linear constraints + linear_constraint = {} + linear_constraint['x0'] = 1 + linear_constraint['x1'] = 1 + op.linear_constraint(linear_constraint, ConstraintSense.EQ, 1, 'x0x1') + linear_constraint = {} + linear_constraint['x1'] = 1 + linear_constraint['x2'] = -1 + op.linear_constraint(linear_constraint, ConstraintSense.EQ, 2, 'x1x2') + linear_constraint = {} + linear_constraint['x0'] = 1 + linear_constraint['x2'] = 3 + op.linear_constraint(linear_constraint, ConstraintSense.EQ, 2, 'x0x2') + self.assertEqual(len(op.linear_constraints), 3) conv = LinearEqualityToPenalty() op2 = conv.encode(op) - self.assertEqual(op2.linear_constraints.get_num(), 0) + self.assertEqual(len(op2.linear_constraints), 0) def test_penalize_integer(self): """ Test PenalizeLinearEqualityConstraints with integer variables """ op = QuadraticProgram() - op.variables.add(names=['x', 'y', 'z'], types='I' * 3, lb=[-3] * 3, ub=[3] * 3) - op.linear_constraints.add( - lin_expr=[ - SparsePair(ind=['x', 'y'], val=[1, 1]), - SparsePair(ind=['y', 'z'], val=[1, -1]), - ], - senses=['E', 'E'], - rhs=[1, 2], - names=['xy', 'yz'], - ) - self.assertEqual(op.linear_constraints.get_num(), 2) + for i in range(3): + op.integer_var(name='x{}'.format(i), lowerbound=-3, upperbound=3) + # Linear constraints + linear_constraint = {} + linear_constraint['x0'] = 1 + linear_constraint['x1'] = 1 + op.linear_constraint(linear_constraint, ConstraintSense.EQ, 1, 'x0x1') + linear_constraint = {} + linear_constraint['x1'] = 1 + linear_constraint['x2'] = -1 + op.linear_constraint(linear_constraint, ConstraintSense.EQ, 2, 'x1x2') + linear_constraint = {} + linear_constraint['x0'] = 1 + linear_constraint['x2'] = 3 + op.linear_constraint(linear_constraint, ConstraintSense.EQ, 2, 'x0x2') + self.assertEqual(len(op.linear_constraints), 3) conv = LinearEqualityToPenalty() op2 = conv.encode(op) - self.assertEqual(op2.linear_constraints.get_num(), 0) + self.assertEqual(len(op2.linear_constraints), 0) def test_integer_to_binary(self): """ Test integer to binary """ op = QuadraticProgram() - op.variables.add(names=['x', 'y', 'z'], types='BIC', lb=[0, 0, 0], ub=[1, 6, 10]) - op.objective.set_linear([('x', 1), ('y', 2), ('z', 1)]) - op.linear_constraints.add( - lin_expr=[SparsePair(ind=['x', 'y', 'z'], val=[1, 3, 1])], - senses=['L'], - rhs=[10], - names=['xyz'], - ) - self.assertEqual(op.variables.get_num(), 3) - conv = IntegerToBinaryConverter() + for i in range(0, 2): + op.binary_var(name='x{}'.format(i)) + op.integer_var(name='x2', lowerbound=0, upperbound=5) + linear = {} + for i, x in enumerate(op.variables): + linear[x.name] = i + 1 + op.maximize(0, linear, {}) + conv = IntegerToBinary() op2 = conv.encode(op) - names = op2.variables.get_names() - self.assertIn('x', names) - self.assertIn('z', names) - variables = op2.variables - self.assertEqual(variables.get_lower_bounds('x'), 0.0) - self.assertEqual(variables.get_lower_bounds('z'), 0.0) - self.assertEqual(variables.get_upper_bounds('x'), 1.0) - self.assertEqual(variables.get_upper_bounds('z'), 10.0) - self.assertListEqual( - variables.get_types(['x', 'y@0', 'y@1', 'y@2', 'z']), ['B', 'B', 'B', 'B', 'C'] - ) - self.assertListEqual(op2.objective.get_linear(['y@0', 'y@1', 'y@2']), [2, 4, 6]) - self.assertListEqual(op2.linear_constraints.get_rows()[0].val, [1, 3, 6, 9, 1]) + for x in op2.variables: + self.assertEqual(x.vartype, VarType.BINARY) + dct = op2.objective.linear.to_dict() + self.assertEqual(dct[2], 3) + self.assertEqual(dct[3], 6) + self.assertEqual(dct[4], 6) def test_binary_to_integer(self): """ Test binary to integer """ op = QuadraticProgram() - op.variables.add(names=['x', 'y', 'z'], types='BIB', lb=[0, 0, 0], ub=[1, 7, 1]) - op.objective.set_linear([('x', 2), ('y', 1), ('z', 1)]) - op.linear_constraints.add( - lin_expr=[SparsePair(ind=['x', 'y', 'z'], val=[1, 1, 1])], - senses=['L'], - rhs=[7], - names=['xyz'], - ) - op.objective.set_sense(-1) - conv = IntegerToBinaryConverter() + for i in range(0, 2): + op.binary_var(name='x{}'.format(i)) + op.integer_var(name='x2', lowerbound=0, upperbound=5) + linear = {} + linear['x0'] = 1 + linear['x1'] = 2 + linear['x2'] = 1 + op.maximize(0, linear, {}) + linear = {} + for x in op.variables: + linear[x.name] = 1 + op.linear_constraint(linear, ConstraintSense.EQ, 6, 'x0x1x2') + conv = IntegerToBinary() _ = conv.encode(op) - result = OptimizationResult(x=[1, 0.0, 1, 1, 0], fval=8) + result = OptimizationResult(x=[0, 1, 1, 1, 1], fval=17) new_result = conv.decode(result) - self.assertListEqual(new_result.x, [1, 6, 0]) - self.assertEqual(new_result.fval, 8) + self.assertListEqual(new_result.x, [0, 1, 5]) + self.assertEqual(new_result.fval, 17) def test_optimizationproblem_to_operator(self): """ Test optimization problem to operators""" op = QuadraticProgram() - op.variables.add(names=['a', 'b', 'c', 'd'], types='B' * 4) - op.objective.set_linear([('a', 1), ('b', 1), ('c', 1), ('d', 1)]) - op.linear_constraints.add( - lin_expr=[SparsePair(ind=['a', 'b', 'c', 'd'], val=[1, 2, 3, 4])], - senses=['E'], - rhs=[3], - names=['abcd'], - ) - op.objective.set_sense(-1) + for i in range(4): + op.binary_var(name='x{}'.format(i)) + linear = {} + for x in op.variables: + linear[x.name] = 1 + op.maximize(0, linear, {}) + linear = {} + for i, x in enumerate(op.variables): + linear[x.name] = i + 1 + op.linear_constraint(linear, ConstraintSense.EQ, 3, 'sum1') penalize = LinearEqualityToPenalty() op2ope = QuadraticProgramToOperator() op2 = penalize.encode(op) @@ -350,27 +432,6 @@ def test_optimizationproblem_to_operator(self): self.assertListEqual(qubitop.paulis, QUBIT_OP_MAXIMIZE_SAMPLE.paulis) self.assertEqual(offset, OFFSET_MAXIMIZE_SAMPLE) - def test_quadratic_constraints(self): - """ Test quadratic constraints""" - # IntegerToBinaryConverter - with self.assertRaises(QiskitOptimizationError): - op = QuadraticProgram() - op.variables.add(names=['x', 'y']) - l_expr = SparsePair(ind=['x'], val=[1.0]) - q_expr = [['x'], ['y'], [1]] - op.quadratic_constraints.add(name=str(1), lin_expr=l_expr, quad_expr=q_expr) - conv = IntegerToBinaryConverter() - _ = conv.encode(op) - # InequalityToEqualityConverter - with self.assertRaises(QiskitOptimizationError): - op = QuadraticProgram() - op.variables.add(names=['x', 'y']) - l_expr = SparsePair(ind=['x'], val=[1.0]) - q_expr = [['x'], ['y'], [1]] - op.quadratic_constraints.add(name=str(1), lin_expr=l_expr, quad_expr=q_expr) - conv = InequalityToEqualityConverter() - _ = conv.encode(op) - def test_continuous_variable_decode(self): """ Test decode func of IntegerToBinaryConverter for continuous variables""" mdl = Model('test_continuous_varable_decode') @@ -379,7 +440,7 @@ def test_continuous_variable_decode(self): mdl.maximize(c + x * x) op = QuadraticProgram() op.from_docplex(mdl) - converter = IntegerToBinaryConverter() + converter = IntegerToBinary() op = converter.encode(op) admm_params = ADMMParameters() qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) From 941a71d59e78cf5cfeb47eca32625b1d0c5192f8 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Wed, 22 Apr 2020 17:51:22 +0900 Subject: [PATCH 263/323] Add more tests of QuadraticProgram --- .../recursive_minimum_eigen_optimizer.py | 4 +- .../problems/quadratic_program.py | 82 ++-- test/optimization/test_quadratic_program.py | 359 ++++++++++++++++-- 3 files changed, 394 insertions(+), 51 deletions(-) diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 983d8dd607..84feaeef8c 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -135,7 +135,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: # set x_i = x_j problem_.substitute_variables() problem_, status = problem_.substitute_variables(variables={i: (j, 1)}) - if status == SubstitutionStatus.infeasible: + if status == SubstitutionStatus.INFEASIBLE: raise QiskitOptimizationError('Infeasible due to variable substitution') replacements[x_i] = (x_j, 1) else: @@ -158,7 +158,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: # 2. replace x_i by -x_j problem_, status = problem_.substitute_variables(variables={i: (j, -1)}) - if status == SubstitutionStatus.infeasible: + if status == SubstitutionStatus.INFEASIBLE: raise QiskitOptimizationError('Infeasible due to variable substitution') replacements[x_i] = (x_j, -1) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 99ad694fe9..4eea60f84f 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -20,9 +20,9 @@ from typing import List, Union, Dict, Optional, Tuple from docplex.mp.linear import Var -from docplex.mp.quad import QuadExpr from docplex.mp.model import Model from docplex.mp.model_reader import ModelReader +from docplex.mp.quad import QuadExpr from numpy import ndarray from scipy.sparse import spmatrix @@ -317,6 +317,10 @@ def get_linear_constraint(self, i: Union[int, str]) -> LinearConstraint: Returns: The corresponding constraint. + + Raises: + IndexError: if the index is out of the list size + KeyError: if the name does not exist """ if isinstance(i, int): return self.linear_constraints[i] @@ -406,6 +410,10 @@ def get_quadratic_constraint(self, i: Union[int, str]) -> QuadraticConstraint: Returns: The corresponding constraint. + + Raises: + IndexError: if the index is out of the list size + KeyError: if the name does not exist """ if isinstance(i, int): return self.quadratic_constraints[i] @@ -466,7 +474,11 @@ def maximize(self, self._objective = QuadraticObjective(self, constant, linear, quadratic, ObjSense.MAXIMIZE) def from_docplex(self, model: Model) -> None: - """Loads this quadratic program from a docplex model + """Loads this quadratic program from a docplex model. + Note that this supports only basic functions of docplex as follows: + - quadratic objective function + - linear / quadratic constraints + - binary / integer / continuous variables Args: model: The docplex model to be loaded. @@ -551,7 +563,7 @@ def from_docplex(self, model: Model) -> None: elif sense == sense.LE: self.linear_constraint(lhs, '<=', rhs, name) else: - raise QiskitOptimizationError("Unsupported constraint sense!") + raise QiskitOptimizationError("Unsupported constraint sense: {}".format(sense)) # get quadratic constraints for i in range(model.number_of_quadratic_constraints): @@ -581,7 +593,7 @@ def from_docplex(self, model: Model) -> None: if right_expr.is_quad_expr(): for x in right_expr.linear_part.iter_variables(): linear[var_names[x]] = linear.get(var_names[x], 0.0) - \ - right_expr.linear_part.get_coef(x) + right_expr.linear_part.get_coef(x) for quad_triplet in right_expr.iter_quad_triplets(): i = var_names[quad_triplet[0]] j = var_names[quad_triplet[1]] @@ -598,7 +610,7 @@ def from_docplex(self, model: Model) -> None: elif sense == sense.LE: self.quadratic_constraint(linear, quadratic, '<=', rhs, name) else: - raise QiskitOptimizationError("Unsupported constraint sense!") + raise QiskitOptimizationError("Unsupported constraint sense: {}".format(sense)) def to_docplex(self) -> Model: """Returns a docplex model corresponding to this quadratic program. @@ -624,7 +636,7 @@ def to_docplex(self) -> Model: var[i] = mdl.integer_var(lb=x.lowerbound, ub=x.upperbound, name=x.name) else: # should never happen - raise QiskitOptimizationError('Unknown variable type: %s!' % x.vartype) + raise QiskitOptimizationError('Unsupported variable type: {}'.format(x.vartype)) # add objective objective = self.objective.constant @@ -655,14 +667,14 @@ def to_docplex(self) -> Model: mdl.add_constraint(linear_expr <= rhs, ctname=name) else: # should never happen - raise QiskitOptimizationError('Unknown sense: %s!' % sense) + raise QiskitOptimizationError("Unsupported constraint sense: {}".format(sense)) # add quadratic constraints for i, constraint in enumerate(self.quadratic_constraints): name = constraint.name rhs = constraint.rhs if rhs == 0 \ - and constraint.linear.coefficients.nnz == 0 \ + and constraint.linear.coefficients.nnz == 0 \ and constraint.quadratic.coefficients.nnz == 0: continue quadratic_expr = 0 @@ -679,7 +691,7 @@ def to_docplex(self) -> Model: mdl.add_constraint(quadratic_expr <= rhs, ctname=name) else: # should never happen - raise QiskitOptimizationError('Unknown sense: %s!' % sense) + raise QiskitOptimizationError("Unsupported constraint sense: {}".format(sense)) return mdl @@ -713,9 +725,27 @@ def read_from_lp_file(self, filename: str) -> None: Args: filename: The filename of the file to be loaded. + + Raises: + FileNotFoundError: if the file does not exist. """ + + def _parse_problem_name(filename: str) -> str: + # Because docplex model reader uses the base name as model name, + # we parse the model name in the LP file manually. + # https://ibmdecisionoptimization.github.io/docplex-doc/mp/docplex.mp.model_reader.html + prefix = '\\Problem name: ' + model_name = '' + with open(filename) as file: + for line in file: + if line.startswith(prefix): + model_name = line[len(prefix):].strip() + if not line.startswith('\\'): + break + return model_name + model_reader = ModelReader() - model = model_reader.read(filename) + model = model_reader.read(pathname=filename, model_name=_parse_problem_name(filename)) self.from_docplex(model) def write_to_lp_file(self, filename: str) -> None: @@ -723,6 +753,12 @@ def write_to_lp_file(self, filename: str) -> None: Args: filename: The filename of the file the model is written to. + If filename is a directory, file name 'my_problem.lp' is appended. + If filename does not end with '.lp', suffix '.lp' is appended. + + Raises: + OSError: If this cannot open a file. + DOcplexException: If filename is an empty string """ self.to_docplex().export_as_lp(filename) @@ -756,8 +792,8 @@ def substitute_variables( class SubstitutionStatus(Enum): """Status of `QuadraticProgram.substitute_variables`""" - success = 1 - infeasible = 2 + SUCCESS = 1 + INFEASIBLE = 2 class SubstituteVariables: @@ -808,10 +844,10 @@ def substitute_variables( self._linear_constraints(), self._quadratic_constraints(), ] - if any(r == SubstitutionStatus.infeasible for r in results): - ret = SubstitutionStatus.infeasible + if any(r == SubstitutionStatus.INFEASIBLE for r in results): + ret = SubstitutionStatus.INFEASIBLE else: - ret = SubstitutionStatus.success + ret = SubstitutionStatus.SUCCESS return self._dst, ret @staticmethod @@ -892,7 +928,7 @@ def _variables(self) -> SubstitutionStatus: if not lb_i <= v <= ub_i: logger.warning( 'Infeasible substitution for variable: %s', i) - return SubstitutionStatus.infeasible + return SubstitutionStatus.INFEASIBLE else: # substitute i <- j * v # lb_i <= i <= ub_i --> lb_i / v <= j <= ub_i / v if v > 0 @@ -924,9 +960,9 @@ def _variables(self) -> SubstitutionStatus: logger.warning( 'Infeasible lower and upper bound: %s %f %f', var, var.lowerbound, var.upperbound) - return SubstitutionStatus.infeasible + return SubstitutionStatus.INFEASIBLE - return SubstitutionStatus.success + return SubstitutionStatus.SUCCESS def _linear_expression(self, lin_expr: LinearExpression) \ -> Tuple[List[float], LinearExpression]: @@ -977,7 +1013,7 @@ def _objective(self) -> SubstitutionStatus: self._dst.minimize(constant=constant, linear=linear, quadratic=quadratic.coefficients) else: self._dst.maximize(constant=constant, linear=linear, quadratic=quadratic.coefficients) - return SubstitutionStatus.success + return SubstitutionStatus.SUCCESS def _linear_constraints(self) -> SubstitutionStatus: for lin_cst in self._src.linear_constraints: @@ -989,9 +1025,9 @@ def _linear_constraints(self) -> SubstitutionStatus: else: if not self._feasible(lin_cst.sense, rhs): logger.warning('constraint %s is infeasible due to substitution', lin_cst.name) - return SubstitutionStatus.infeasible + return SubstitutionStatus.INFEASIBLE - return SubstitutionStatus.success + return SubstitutionStatus.SUCCESS def _quadratic_constraints(self) -> SubstitutionStatus: for quad_cst in self._src.quadratic_constraints: @@ -1013,6 +1049,6 @@ def _quadratic_constraints(self) -> SubstitutionStatus: else: if not self._feasible(quad_cst.sense, rhs): logger.warning('constraint %s is infeasible due to substitution', quad_cst.name) - return SubstitutionStatus.infeasible + return SubstitutionStatus.INFEASIBLE - return SubstitutionStatus.success + return SubstitutionStatus.SUCCESS diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index 2492b7226a..29cbf26c04 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -17,8 +17,11 @@ import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase +from docplex.mp.model import Model, DOcplexException + from qiskit.optimization import QuadraticProgram, QiskitOptimizationError, infinity -from qiskit.optimization.problems import VarType +from qiskit.optimization.problems import VarType, ConstraintSense, ObjSense +from qiskit.optimization.problems.quadratic_program import SubstitutionStatus class TestQuadraticProgram(QiskitOptimizationTestCase): @@ -29,12 +32,37 @@ def test_constructor(self): """ test constructor """ quadratic_program = QuadraticProgram() self.assertEqual(quadratic_program.name, '') - - quadratic_program = QuadraticProgram('test') - self.assertEqual(quadratic_program.name, 'test') - - quadratic_program.name = '' - self.assertEqual(quadratic_program.name, '') + self.assertEqual(quadratic_program.get_num_vars(), 0) + self.assertEqual(quadratic_program.get_num_linear_constraints(), 0) + self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 0) + self.assertEqual(quadratic_program.objective.constant, 0) + self.assertDictEqual(quadratic_program.objective.linear.to_dict(), {}) + self.assertDictEqual(quadratic_program.objective.quadratic.to_dict(), {}) + + def test_clear(self): + """ test clear """ + q_p = QuadraticProgram('test') + q_p.binary_var('x') + q_p.binary_var('y') + q_p.minimize(constant=1, linear={'x': 1, 'y': 2}, quadratic={('x', 'x'): 1}) + q_p.linear_constraint({'x': 1}, '==', 1) + q_p.quadratic_constraint({'x': 1}, {('y', 'y'): 2}, '<=', 1) + q_p.clear() + self.assertEqual(q_p.name, '') + self.assertEqual(q_p.get_num_vars(), 0) + self.assertEqual(q_p.get_num_linear_constraints(), 0) + self.assertEqual(q_p.get_num_quadratic_constraints(), 0) + self.assertEqual(q_p.objective.constant, 0) + self.assertDictEqual(q_p.objective.linear.to_dict(), {}) + self.assertDictEqual(q_p.objective.quadratic.to_dict(), {}) + + def test_name_setter(self): + """ test name setter """ + q_p = QuadraticProgram() + self.assertEqual(q_p.name, '') + name = 'test name' + q_p.name = name + self.assertEqual(q_p.name, name) def test_variables_handling(self): """ test add variables """ @@ -124,33 +152,297 @@ def test_variables_handling(self): for i, x in enumerate(variables): y = quadratic_program.get_variable(i) z = quadratic_program.get_variable(x.name) - self.assertEqual(x, y) - self.assertEqual(x, z) + self.assertEqual(x.name, y.name) + self.assertEqual(x.name, z.name) + self.assertDictEqual(quadratic_program.variables_index, + {'x' + str(i): i for i in range(6)}) def test_linear_constraints_handling(self): """test linear constraints handling""" - # TODO - pass + q_p = QuadraticProgram() + q_p.binary_var('x') + q_p.binary_var('y') + q_p.binary_var('z') + q_p.linear_constraint({'x': 1}, '==', 1) + q_p.linear_constraint({'y': 1}, '<=', 1) + q_p.linear_constraint({'z': 1}, '>=', 1) + self.assertEqual(q_p.get_num_linear_constraints(), 3) + lin = q_p.linear_constraints + self.assertEqual(len(lin), 3) + self.assertDictEqual(lin[0].linear.to_dict(), {0: 1}) + self.assertDictEqual(lin[0].linear.to_dict(use_name=True), {'x': 1}) + self.assertListEqual(lin[0].linear.to_array().tolist(), [1, 0, 0]) + self.assertEqual(lin[0].sense, ConstraintSense.EQ) + self.assertEqual(lin[0].rhs, 1) + self.assertEqual(lin[0].name, 'c0') + self.assertEqual(q_p.get_linear_constraint(0).name, 'c0') + self.assertEqual(q_p.get_linear_constraint('c0').name, 'c0') + + self.assertDictEqual(lin[1].linear.to_dict(), {1: 1}) + self.assertDictEqual(lin[1].linear.to_dict(use_name=True), {'y': 1}) + self.assertListEqual(lin[1].linear.to_array().tolist(), [0, 1, 0]) + self.assertEqual(lin[1].sense, ConstraintSense.LE) + self.assertEqual(lin[1].rhs, 1) + self.assertEqual(lin[1].name, 'c1') + self.assertEqual(q_p.get_linear_constraint(1).name, 'c1') + self.assertEqual(q_p.get_linear_constraint('c1').name, 'c1') + + self.assertDictEqual(lin[2].linear.to_dict(), {2: 1}) + self.assertDictEqual(lin[2].linear.to_dict(use_name=True), {'z': 1}) + self.assertListEqual(lin[2].linear.to_array().tolist(), [0, 0, 1]) + self.assertEqual(lin[2].sense, ConstraintSense.GE) + self.assertEqual(lin[2].rhs, 1) + self.assertEqual(lin[2].name, 'c2') + self.assertEqual(q_p.get_linear_constraint(2).name, 'c2') + self.assertEqual(q_p.get_linear_constraint('c2').name, 'c2') + + with self.assertRaises(QiskitOptimizationError): + q_p.linear_constraint(name='c0') + with self.assertRaises(QiskitOptimizationError): + q_p.linear_constraint(name='c1') + with self.assertRaises(QiskitOptimizationError): + q_p.linear_constraint(name='c2') + with self.assertRaises(IndexError): + q_p.get_linear_constraint(4) + with self.assertRaises(KeyError): + q_p.get_linear_constraint('c3') + + q_p.linear_constraint(sense='E') + self.assertEqual(q_p.linear_constraints[-1].sense, ConstraintSense.EQ) + q_p.linear_constraint(sense='G') + self.assertEqual(q_p.linear_constraints[-1].sense, ConstraintSense.GE) + q_p.linear_constraint(sense='L') + self.assertEqual(q_p.linear_constraints[-1].sense, ConstraintSense.LE) + q_p.linear_constraint(sense='EQ') + self.assertEqual(q_p.linear_constraints[-1].sense, ConstraintSense.EQ) + q_p.linear_constraint(sense='GE') + self.assertEqual(q_p.linear_constraints[-1].sense, ConstraintSense.GE) + q_p.linear_constraint(sense='LE') + self.assertEqual(q_p.linear_constraints[-1].sense, ConstraintSense.LE) + q_p.linear_constraint(sense='=') + self.assertEqual(q_p.linear_constraints[-1].sense, ConstraintSense.EQ) + q_p.linear_constraint(sense='>') + self.assertEqual(q_p.linear_constraints[-1].sense, ConstraintSense.GE) + q_p.linear_constraint(sense='<') + + with self.assertRaises(QiskitOptimizationError): + q_p.linear_constraint(sense='=>') def test_quadratic_constraints_handling(self): """test quadratic constraints handling""" - # TODO - pass + q_p = QuadraticProgram() + q_p.binary_var('x') + q_p.binary_var('y') + q_p.binary_var('z') + q_p.quadratic_constraint({'x': 1}, {('x', 'y'): 1}, '==', 1) + q_p.quadratic_constraint({'y': 1}, {('y', 'z'): 1}, '<=', 1) + q_p.quadratic_constraint({'z': 1}, {('z', 'x'): 1}, '>=', 1) + self.assertEqual(q_p.get_num_quadratic_constraints(), 3) + quad = q_p.quadratic_constraints + self.assertEqual(len(quad), 3) + + self.assertDictEqual(quad[0].linear.to_dict(), {0: 1}) + self.assertDictEqual(quad[0].linear.to_dict(use_name=True), {'x': 1}) + self.assertListEqual(quad[0].linear.to_array().tolist(), [1, 0, 0]) + self.assertDictEqual(quad[0].quadratic.to_dict(), {(0, 1): 1}) + self.assertDictEqual(quad[0].quadratic.to_dict(symmetric=True), + {(0, 1): 0.5, (1, 0): 0.5}) + self.assertDictEqual(quad[0].quadratic.to_dict(use_name=True), {('x', 'y'): 1}) + self.assertDictEqual(quad[0].quadratic.to_dict(use_name=True, symmetric=True), + {('x', 'y'): 0.5, ('y', 'x'): 0.5}) + self.assertListEqual(quad[0].quadratic.to_array().tolist(), + [[0, 1, 0], [0, 0, 0], [0, 0, 0]]) + self.assertListEqual(quad[0].quadratic.to_array(symmetric=True).tolist(), + [[0, 0.5, 0], [0.5, 0, 0], [0, 0, 0]]) + self.assertEqual(quad[0].sense, ConstraintSense.EQ) + self.assertEqual(quad[0].rhs, 1) + self.assertEqual(quad[0].name, 'q0') + self.assertEqual(q_p.get_quadratic_constraint(0).name, 'q0') + self.assertEqual(q_p.get_quadratic_constraint('q0').name, 'q0') + + self.assertDictEqual(quad[1].linear.to_dict(), {1: 1}) + self.assertDictEqual(quad[1].linear.to_dict(use_name=True), {'y': 1}) + self.assertListEqual(quad[1].linear.to_array().tolist(), [0, 1, 0]) + self.assertDictEqual(quad[1].quadratic.to_dict(), {(1, 2): 1}) + self.assertDictEqual(quad[1].quadratic.to_dict(symmetric=True), + {(1, 2): 0.5, (2, 1): 0.5}) + self.assertDictEqual(quad[1].quadratic.to_dict(use_name=True), {('y', 'z'): 1}) + self.assertDictEqual(quad[1].quadratic.to_dict(use_name=True, symmetric=True), + {('y', 'z'): 0.5, ('z', 'y'): 0.5}) + self.assertListEqual(quad[1].quadratic.to_array().tolist(), + [[0, 0, 0], [0, 0, 1], [0, 0, 0]]) + self.assertListEqual(quad[1].quadratic.to_array(symmetric=True).tolist(), + [[0, 0, 0], [0, 0, 0.5], [0, 0.5, 0]]) + self.assertEqual(quad[1].sense, ConstraintSense.LE) + self.assertEqual(quad[1].rhs, 1) + self.assertEqual(quad[1].name, 'q1') + self.assertEqual(q_p.get_quadratic_constraint(1).name, 'q1') + self.assertEqual(q_p.get_quadratic_constraint('q1').name, 'q1') + + self.assertDictEqual(quad[2].linear.to_dict(), {2: 1}) + self.assertDictEqual(quad[2].linear.to_dict(use_name=True), {'z': 1}) + self.assertListEqual(quad[2].linear.to_array().tolist(), [0, 0, 1]) + self.assertDictEqual(quad[2].quadratic.to_dict(), {(0, 2): 1}) + self.assertDictEqual(quad[2].quadratic.to_dict(symmetric=True), + {(0, 2): 0.5, (2, 0): 0.5}) + self.assertDictEqual(quad[2].quadratic.to_dict(use_name=True), {('x', 'z'): 1}) + self.assertDictEqual(quad[2].quadratic.to_dict(use_name=True, symmetric=True), + {('x', 'z'): 0.5, ('z', 'x'): 0.5}) + self.assertListEqual(quad[2].quadratic.to_array().tolist(), + [[0, 0, 1], [0, 0, 0], [0, 0, 0]]) + self.assertListEqual(quad[2].quadratic.to_array(symmetric=True).tolist(), + [[0, 0, 0.5], [0, 0, 0], [0.5, 0, 0]]) + self.assertEqual(quad[2].sense, ConstraintSense.GE) + self.assertEqual(quad[2].rhs, 1) + self.assertEqual(quad[2].name, 'q2') + self.assertEqual(q_p.get_quadratic_constraint(2).name, 'q2') + self.assertEqual(q_p.get_quadratic_constraint('q2').name, 'q2') + + with self.assertRaises(QiskitOptimizationError): + q_p.quadratic_constraint(name='q0') + with self.assertRaises(QiskitOptimizationError): + q_p.quadratic_constraint(name='q1') + with self.assertRaises(QiskitOptimizationError): + q_p.quadratic_constraint(name='q2') + with self.assertRaises(IndexError): + q_p.get_quadratic_constraint(4) + with self.assertRaises(KeyError): + q_p.get_quadratic_constraint('q3') def test_objective_handling(self): """test objective handling""" - # TODO - pass - - def test_read_problem(self): - """test read problem""" - # TODO - pass - - def test_write_problem(self): + q_p = QuadraticProgram() + q_p.binary_var('x') + q_p.binary_var('y') + q_p.binary_var('z') + q_p.minimize() + obj = q_p.objective + self.assertEqual(obj.sense, ObjSense.MINIMIZE) + self.assertEqual(obj.constant, 0) + self.assertDictEqual(obj.linear.to_dict(), {}) + self.assertDictEqual(obj.quadratic.to_dict(), {}) + q_p.maximize(1, {'y': 1}, {('z', 'x'): 1, ('y', 'y'): 1}) + obj = q_p.objective + self.assertEqual(obj.sense, ObjSense.MAXIMIZE) + self.assertEqual(obj.constant, 1) + self.assertDictEqual(obj.linear.to_dict(), {1: 1}) + self.assertDictEqual(obj.linear.to_dict(use_name=True), {'y': 1}) + self.assertListEqual(obj.linear.to_array().tolist(), [0, 1, 0]) + self.assertDictEqual(obj.quadratic.to_dict(), {(0, 2): 1, (1, 1): 1}) + self.assertDictEqual(obj.quadratic.to_dict(symmetric=True), + {(0, 2): 0.5, (2, 0): 0.5, (1, 1): 1}) + self.assertDictEqual(obj.quadratic.to_dict(use_name=True), + {('x', 'z'): 1, ('y', 'y'): 1}) + self.assertDictEqual(obj.quadratic.to_dict(use_name=True, symmetric=True), + {('x', 'z'): 0.5, ('z', 'x'): 0.5, ('y', 'y'): 1}) + self.assertListEqual(obj.quadratic.to_array().tolist(), + [[0, 0, 1], [0, 1, 0], [0, 0, 0]]) + self.assertListEqual(obj.quadratic.to_array(symmetric=True).tolist(), + [[0, 0, 0.5], [0, 1, 0], [0.5, 0, 0]]) + + def test_read_from_lp_file(self): + """test read lp file""" + q_p = QuadraticProgram() + with self.assertRaises(FileNotFoundError): + q_p.read_from_lp_file('') + with self.assertRaises(FileNotFoundError): + q_p.read_from_lp_file('no_file.txt') + q_p.read_from_lp_file('test/optimization/resources/sample.lp') + self.assertEqual(q_p.name, 'my problem') + self.assertEqual(q_p.get_num_vars(), 3) + self.assertEqual(q_p.get_num_binary_vars(), 1) + self.assertEqual(q_p.get_num_integer_vars(), 1) + self.assertEqual(q_p.get_num_continuous_vars(), 1) + self.assertEqual(q_p.get_num_linear_constraints(), 3) + self.assertEqual(q_p.get_num_quadratic_constraints(), 3) + + self.assertEqual(q_p.variables[0].name, 'x') + self.assertEqual(q_p.variables[0].vartype, VarType.BINARY) + self.assertEqual(q_p.variables[0].lowerbound, 0) + self.assertEqual(q_p.variables[0].upperbound, 1) + self.assertEqual(q_p.variables[1].name, 'y') + self.assertEqual(q_p.variables[1].vartype, VarType.INTEGER) + self.assertEqual(q_p.variables[1].lowerbound, -1) + self.assertEqual(q_p.variables[1].upperbound, 5) + self.assertEqual(q_p.variables[2].name, 'z') + self.assertEqual(q_p.variables[2].vartype, VarType.CONTINUOUS) + self.assertEqual(q_p.variables[2].lowerbound, -1) + self.assertEqual(q_p.variables[2].upperbound, 5) + + self.assertEqual(q_p.objective.sense, ObjSense.MINIMIZE) + self.assertEqual(q_p.objective.constant, 1) + self.assertDictEqual(q_p.objective.linear.to_dict(use_name=True), + {'x': 1, 'y': -1, 'z': 10}) + self.assertDictEqual(q_p.objective.quadratic.to_dict(use_name=True), + {('x', 'x'): 0.5, ('y', 'z'): -1}) + + cst = q_p.linear_constraints + self.assertEqual(cst[0].name, 'lin_eq') + self.assertDictEqual(cst[0].linear.to_dict(use_name=True), {'x': 1, 'y': 2}) + self.assertEqual(cst[0].sense, ConstraintSense.EQ) + self.assertEqual(cst[0].rhs, 1) + self.assertEqual(cst[1].name, 'lin_leq') + self.assertDictEqual(cst[1].linear.to_dict(use_name=True), {'x': 1, 'y': 2}) + self.assertEqual(cst[1].sense, ConstraintSense.LE) + self.assertEqual(cst[1].rhs, 1) + self.assertEqual(cst[2].name, 'lin_geq') + self.assertDictEqual(cst[2].linear.to_dict(use_name=True), {'x': 1, 'y': 2}) + self.assertEqual(cst[2].sense, ConstraintSense.GE) + self.assertEqual(cst[2].rhs, 1) + + cst = q_p.quadratic_constraints + self.assertEqual(cst[0].name, 'quad_eq') + self.assertDictEqual(cst[0].linear.to_dict(use_name=True), {'x': 1, 'y': 1}) + self.assertDictEqual(cst[0].quadratic.to_dict(use_name=True), + {('x', 'x'): 1, ('y', 'z'): -1, ('z', 'z'): 2}) + self.assertEqual(cst[0].sense, ConstraintSense.EQ) + self.assertEqual(cst[0].rhs, 1) + self.assertEqual(cst[1].name, 'quad_leq') + self.assertDictEqual(cst[1].linear.to_dict(use_name=True), {'x': 1, 'y': 1}) + self.assertDictEqual(cst[1].quadratic.to_dict(use_name=True), + {('x', 'x'): 1, ('y', 'z'): -1, ('z', 'z'): 2}) + self.assertEqual(cst[1].sense, ConstraintSense.LE) + self.assertEqual(cst[1].rhs, 1) + self.assertEqual(cst[2].name, 'quad_geq') + self.assertDictEqual(cst[2].linear.to_dict(use_name=True), {'x': 1, 'y': 1}) + self.assertDictEqual(cst[2].quadratic.to_dict(use_name=True), + {('x', 'x'): 1, ('y', 'z'): -1, ('z', 'z'): 2}) + self.assertEqual(cst[2].sense, ConstraintSense.GE) + self.assertEqual(cst[2].rhs, 1) + + def test_write_to_lp_file(self): """test write problem""" - # TODO - pass + q_p = QuadraticProgram('my problem') + q_p.binary_var('x') + q_p.integer_var(-1, 5, 'y') + q_p.continuous_var(-1, 5, 'z') + q_p.minimize(1, {'x': 1, 'y': -1, 'z': 10}, {('x', 'x'): 0.5, ('y', 'z'): -1}) + q_p.linear_constraint({'x': 1, 'y': 2}, '==', 1, 'lin_eq') + q_p.linear_constraint({'x': 1, 'y': 2}, '<=', 1, 'lin_leq') + q_p.linear_constraint({'x': 1, 'y': 2}, '>=', 1, 'lin_geq') + q_p.quadratic_constraint({'x': 1, 'y': 1}, {('x', 'x'): 1, ('y', 'z'): -1, ('z', 'z'): 2}, + '==', 1, 'quad_eq') + q_p.quadratic_constraint({'x': 1, 'y': 1}, {('x', 'x'): 1, ('y', 'z'): -1, ('z', 'z'): 2}, + '<=', 1, 'quad_leq') + q_p.quadratic_constraint({'x': 1, 'y': 1}, {('x', 'x'): 1, ('y', 'z'): -1, ('z', 'z'): 2}, + '>=', 1, 'quad_geq') + q_p.write_to_lp_file('output.lp') + with open('output.lp') as file1, open('test/optimization/resources/sample.lp') as file2: + lines1 = file1.readlines() + lines2 = file2.readlines() + self.assertListEqual(lines1, lines2) + + q_p.write_to_lp_file('.') + with open('my_problem.lp') as file1, open('test/optimization/resources/sample.lp') as file2: + lines1 = file1.readlines() + lines2 = file2.readlines() + self.assertListEqual(lines1, lines2) + + with self.assertRaises(OSError): + q_p.write_to_lp_file('/cannot/write/this/file.lp') + + with self.assertRaises(DOcplexException): + q_p.write_to_lp_file('') def test_docplex(self): """test from_docplex and to_docplex""" @@ -167,6 +459,16 @@ def test_docplex(self): self.assertEqual(q_p.pprint_as_string(), q_p2.pprint_as_string()) self.assertEqual(q_p.print_as_lp_string(), q_p2.print_as_lp_string()) + mod = Model('test') + x = mod.binary_var('x') + y = mod.integer_var(-2, 4, 'y') + z = mod.continuous_var(-1.5, 3.2, 'z') + mod.minimize(1 + x + 2 * y - x * y + 2 * z * z) + mod.add(2 * x - z == 1, 'c0') + mod.add(2 * x - z + 3 * y * z == 1, 'q0') + self.assertEqual(q_p.pprint_as_string(), mod.pprint_as_string()) + self.assertEqual(q_p.print_as_lp_string(), mod.export_as_lp_string()) + def test_substitute_variables(self): """test substitute variables""" q_p = QuadraticProgram('test') @@ -179,11 +481,14 @@ def test_substitute_variables(self): q_p.quadratic_constraint({'x': 2, 'z': -1}, {('y', 'z'): 3}, '<=', -1) q_p2, status = q_p.substitute_variables(constants={'x': -1}) - self.assertEqual(status.name, 'infeasible') + self.assertEqual(status, SubstitutionStatus.INFEASIBLE) q_p2, status = q_p.substitute_variables(constants={'y': -3}) - self.assertEqual(status.name, 'infeasible') + self.assertEqual(status, SubstitutionStatus.INFEASIBLE) + q_p2, status = q_p.substitute_variables(constants={'x': 1, 'z': 2}) + self.assertEqual(status, SubstitutionStatus.INFEASIBLE) q_p2, status = q_p.substitute_variables(constants={'x': 0}) + self.assertEqual(status, SubstitutionStatus.SUCCESS) self.assertDictEqual(q_p2.objective.linear.to_dict(use_name=True), {'y': 2}) self.assertDictEqual(q_p2.objective.quadratic.to_dict(use_name=True), {('z', 'z'): 2}) self.assertEqual(q_p2.objective.constant, 1) @@ -202,6 +507,7 @@ def test_substitute_variables(self): self.assertEqual(cst.rhs, -1) q_p2, status = q_p.substitute_variables(constants={'z': -1}) + self.assertEqual(status, SubstitutionStatus.SUCCESS) self.assertDictEqual(q_p2.objective.linear.to_dict(use_name=True), {'x': 1, 'y': 2}) self.assertDictEqual(q_p2.objective.quadratic.to_dict(use_name=True), {('x', 'y'): -1}) self.assertEqual(q_p2.objective.constant, 3) @@ -219,6 +525,7 @@ def test_substitute_variables(self): self.assertEqual(cst.rhs, -2) q_p2, status = q_p.substitute_variables(variables={'y': ('x', -0.5)}) + self.assertEqual(status, SubstitutionStatus.SUCCESS) self.assertDictEqual(q_p2.objective.linear.to_dict(use_name=True), {}) self.assertDictEqual(q_p2.objective.quadratic.to_dict(use_name=True), {('x', 'x'): 0.5, ('z', 'z'): 2}) From 484e3419cf8e017b2c1081371a1b940234e26e73 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 22 Apr 2020 11:30:07 +0200 Subject: [PATCH 264/323] Update test_quadratic_program_to_negative_value_oracle.py --- ...dratic_program_to_negative_value_oracle.py | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/test/optimization/test_quadratic_program_to_negative_value_oracle.py b/test/optimization/test_quadratic_program_to_negative_value_oracle.py index 00ed28419d..8270be4234 100644 --- a/test/optimization/test_quadratic_program_to_negative_value_oracle.py +++ b/test/optimization/test_quadratic_program_to_negative_value_oracle.py @@ -27,15 +27,15 @@ class TestQuadraticProgramToNegativeValueOracle(QiskitOptimizationTestCase): """OPtNVO Tests""" def _validate_function(self, func_dict, problem): - linear = problem.objective.get_linear_dict() - quadratic = problem.objective.get_quadratic_dict() + linear = problem.objective.linear.to_dict() + quadratic = problem.objective.quadratic.to_dict() for key in func_dict: if isinstance(key, int) and key >= 0: - self.assertEqual(linear[key], func_dict[key]) + self.assertEqual(linear.get(key, 0.0), func_dict[key]) elif isinstance(key, tuple): - self.assertEqual(quadratic[key[0], key[1]], func_dict[key]) + self.assertEqual(quadratic.get((key[0], key[1]), 0.0), func_dict[key]) else: - self.assertEqual(problem.objective.get_offset(), func_dict[key]) + self.assertEqual(problem.objective.constant, func_dict[key]) def _validate_operator(self, func_dict, n_key, n_value, operator): # Get expected results. @@ -86,11 +86,11 @@ def test_optnvo_3_linear_2_quadratic_no_constant(self): # Input. problem = QuadraticProgram() - problem.variables.add(names=['x0', 'x1', 'x2'], types='BBB') - linear = [('x0', -1), ('x1', 2), ('x2', -3)] - problem.objective.set_linear(linear) - problem.objective.set_quadratic_coefficients('x0', 'x2', -2) - problem.objective.set_quadratic_coefficients('x1', 'x2', -1) + for name in ['x0', 'x1', 'x2']: + problem.binary_var(name) + linear = [-1, 2, -3] + quadratic = {('x0', 'x2'): -2, ('x1', 'x2'): -1} + problem.minimize(linear=linear, quadratic=quadratic) # Convert to dictionary format with operator/oracle. converter = QuadraticProgramToNegativeValueOracle(num_value) @@ -109,13 +109,11 @@ def test_optnvo_4_key_all_negative(self): # Input. problem = QuadraticProgram() - problem.variables.add(names=['x0', 'x1', 'x2'], types='BBB') - linear = [('x0', -1), ('x1', -2), ('x2', -1)] - problem.objective.set_linear(linear) - problem.objective.set_quadratic_coefficients('x0', 'x1', -1) - problem.objective.set_quadratic_coefficients('x0', 'x2', -2) - problem.objective.set_quadratic_coefficients('x1', 'x2', -1) - problem.objective.set_offset(-1) + for name in ['x0', 'x1', 'x2']: + problem.binary_var(name) + linear = [-1, -2, -1] + quadratic = {('x0', 'x1'): -1, ('x0', 'x2'): -2, ('x1', 'x2'): -1} + problem.minimize(constant=-1, linear=linear, quadratic=quadratic) # Convert to dictionary format with operator/oracle. converter = QuadraticProgramToNegativeValueOracle(num_value) @@ -134,11 +132,14 @@ def test_optnvo_6_key(self): # Input. problem = QuadraticProgram() - problem.variables.add(names=['x0', 'x1', 'x2', 'x3', 'x4', 'x5'], types='BBBBBB') - linear = [('x0', -1), ('x1', -2), ('x2', -1), ('x3', 0), ('x4', 1), ('x5', 2)] - problem.objective.set_linear(linear) - problem.objective.set_quadratic_coefficients('x0', 'x3', -1) - problem.objective.set_quadratic_coefficients('x1', 'x5', -2) + + # Input. + problem = QuadraticProgram() + for name in ['x0', 'x1', 'x2', 'x3', 'x4', 'x5']: + problem.binary_var(name) + linear = [-1, -2, -1, 0, 1, 2] + quadratic = {('x0', 'x3'): -1, ('x1', 'x5'): -2} + problem.minimize(linear=linear, quadratic=quadratic) # Convert to dictionary format with operator/oracle. converter = QuadraticProgramToNegativeValueOracle(num_value) From 945d21cec7597364728fb04e3fdf2b12d2e56993 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Wed, 22 Apr 2020 11:38:32 +0100 Subject: [PATCH 265/323] formatting, renaming, minor changes --- .../optimization/algorithms/admm_optimizer.py | 18 +++++++++--------- test/optimization/test_admm.py | 7 ++++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 35c8e90678..9de2bbb26e 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -310,7 +310,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: self._log.debug("cost_iterate=%s, cr=%s, merit=%s", cost_iterate, constraint_residual, merit) - # costs and merits are saved with their original sign. + # costs are saved with their original sign. self._state.cost_iterates.append(self._state.sense * cost_iterate) self._state.residuals.append(residual) self._state.dual_residuals.append(dual_residual) @@ -356,7 +356,7 @@ def _get_variable_indices(op: QuadraticProgram, var_type: VarType) -> List[int]: return indices - def _get_solution(self) -> np.ndarray: + def _get_current_solution(self) -> np.ndarray: return self._revert_solution_indexes(self._state.x0, self._state.u) def _revert_solution_indexes(self, binary_vars: np.ndarray, continuous_vars: np.ndarray) \ @@ -786,19 +786,19 @@ def _update_y(self, op3: QuadraticProgram) -> np.ndarray: """ return np.asarray(self._continuous_optimizer.solve(op3).x) - def _get_best_merit_solution(self) -> (List[np.ndarray], float): + def _get_best_merit_solution(self) -> (np.ndarray, np.ndarray, float): """The ADMM solution is that for which the merit value is the min * sol: Iterate with the min merit value * sol_val: Value of sol, according to the original objective Returns: - A tuple of (sol, sol_val), where - * sol: Solution with the min merit value + A tuple of (binary_vars, continuous_vars, sol_val), where + * binary_vars: binary variable values with the min merit value + * continuous_vars: continuous varible values with the min merit value * sol_val: Value of the objective function """ - it_min_merits = self._state.merits.index( - min(self._state.merits)) + it_min_merits = self._state.merits.index(min(self._state.merits)) binary_vars = self._state.x0_saved[it_min_merits] continuous_vars = self._state.u_saved[it_min_merits] sol_val = self._state.cost_iterates[it_min_merits] @@ -842,7 +842,7 @@ def _get_constraint_residual(self) -> float: Returns: Violation of the constraints as a float value """ - solution = self._get_solution() + solution = self._get_current_solution() # equality constraints cr0 = 0 for constraint in self._state.equality_constraints: @@ -874,7 +874,7 @@ def _get_objective_value(self) -> float: Returns: Value of the objective function as a float """ - return self._state.op.objective.evaluate(self._get_solution()) * self._state.sense + return self._state.op.objective.evaluate(self._get_current_solution()) * self._state.sense def _get_solution_residuals(self, iteration: int) -> (float, float): """Compute primal and dual residual. diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index 7f3226e3b5..8c63b191ac 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -13,11 +13,12 @@ # that they have been altered from the originals. """Tests of the ADMM algorithm.""" +from qiskit.aqua.algorithms import NumPyMinimumEigensolver from test.optimization import QiskitOptimizationTestCase import numpy as np from docplex.mp.model import Model -from qiskit.optimization.algorithms import CplexOptimizer +from qiskit.optimization.algorithms import CplexOptimizer, MinimumEigenOptimizer from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters, \ ADMMOptimizerResult, ADMMState from qiskit.optimization.problems import QuadraticProgram @@ -38,8 +39,8 @@ def test_admm_maximization(self): admm_params = ADMMParameters() - # qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) - qubo_optimizer = CplexOptimizer() + qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) + # qubo_optimizer = CplexOptimizer() continuous_optimizer = CplexOptimizer() solver = ADMMOptimizer(qubo_optimizer=qubo_optimizer, From df5202b5d6b8d0f8cbed174c4b581d7b9d1aa622 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 22 Apr 2020 14:56:41 +0200 Subject: [PATCH 266/323] Update grover_optimizer.py --- .../algorithms/grover_optimizer.py | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/qiskit/optimization/algorithms/grover_optimizer.py b/qiskit/optimization/algorithms/grover_optimizer.py index d5dd31f28d..96b2d8bb74 100644 --- a/qiskit/optimization/algorithms/grover_optimizer.py +++ b/qiskit/optimization/algorithms/grover_optimizer.py @@ -104,21 +104,26 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: operation_count = {} iteration = 0 + # Variables for stopping if we've hit the rotation max. + rotations = 0 + max_rotations = int(np.ceil(100*np.pi/4)) + # Initialize oracle helper object. orig_constant = problem_.objective.constant measurement = not self._quantum_instance.is_statevector opt_prob_converter = QuadraticProgramToNegativeValueOracle(n_value, measurement) - loops_with_no_improvement = 0 while not optimum_found: + m = 1 + improvement_found = False + # Get oracle O and the state preparation operator A for the current threshold. problem_.objective.constant = orig_constant - threshold a_operator, oracle, func_dict = opt_prob_converter.encode(problem_) # Iterate until we measure a negative. loops_with_no_improvement = 0 - improvement_found = False while not improvement_found: # Determine the number of rotations. loops_with_no_improvement += 1 @@ -167,19 +172,11 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: improvement_found = True optimum_found = True - # Check if we've already seen this value. - if k not in keys_measured: - keys_measured.append(k) - - # Stop if we've seen all the keys. - if len(keys_measured) == num_solutions: - optimum_found = True - - # Track the operation count. - operations = circuit.count_ops() - operation_count[iteration] = operations - iteration += 1 - logger.info('Operation Count: %s\n', operations) + # Track the operation count. + operations = circuit.count_ops() + operation_count[iteration] = operations + iteration += 1 + logger.info('Operation Count: %s\n', operations) # Get original key and value pairs. func_dict[-1] = orig_constant From 301b66a5d8f5385e560e7acb090d369e169ec9d7 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Wed, 22 Apr 2020 22:45:11 +0900 Subject: [PATCH 267/323] Add a missing sample LP file for test_quadratic_program --- .../resources/test_quadratic_program.lp | 25 +++++++++++++++++++ test/optimization/test_quadratic_program.py | 8 +++--- 2 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 test/optimization/resources/test_quadratic_program.lp diff --git a/test/optimization/resources/test_quadratic_program.lp b/test/optimization/resources/test_quadratic_program.lp new file mode 100644 index 0000000000..6ffd732144 --- /dev/null +++ b/test/optimization/resources/test_quadratic_program.lp @@ -0,0 +1,25 @@ +\ This file has been generated by DOcplex +\ ENCODING=ISO-8859-1 +\Problem name: my problem + +Minimize + obj: x - y + 10 z + [ x^2 - 2 y*z ]/2 + 1 +Subject To + lin_eq: x + 2 y = 1 + lin_leq: x + 2 y <= 1 + lin_geq: x + 2 y >= 1 + quad_eq: [ x^2 - y*z + 2 z^2 ] + x + y = 1 + quad_leq: [ x^2 - y*z + 2 z^2 ] + x + y <= 1 + quad_geq: [ x^2 - y*z + 2 z^2 ] + x + y >= 1 + +Bounds + 0 <= x <= 1 + -1 <= y <= 5 + -1 <= z <= 5 + +Binaries + x + +Generals + y +End diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index 29cbf26c04..6468811b3a 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -347,7 +347,7 @@ def test_read_from_lp_file(self): q_p.read_from_lp_file('') with self.assertRaises(FileNotFoundError): q_p.read_from_lp_file('no_file.txt') - q_p.read_from_lp_file('test/optimization/resources/sample.lp') + q_p.read_from_lp_file('test/optimization/resources/test_quadratic_program.lp') self.assertEqual(q_p.name, 'my problem') self.assertEqual(q_p.get_num_vars(), 3) self.assertEqual(q_p.get_num_binary_vars(), 1) @@ -427,13 +427,15 @@ def test_write_to_lp_file(self): q_p.quadratic_constraint({'x': 1, 'y': 1}, {('x', 'x'): 1, ('y', 'z'): -1, ('z', 'z'): 2}, '>=', 1, 'quad_geq') q_p.write_to_lp_file('output.lp') - with open('output.lp') as file1, open('test/optimization/resources/sample.lp') as file2: + with open('output.lp') as file1, open( + 'test/optimization/resources/test_quadratic_program.lp') as file2: lines1 = file1.readlines() lines2 = file2.readlines() self.assertListEqual(lines1, lines2) q_p.write_to_lp_file('.') - with open('my_problem.lp') as file1, open('test/optimization/resources/sample.lp') as file2: + with open('my_problem.lp') as file1, open( + 'test/optimization/resources/test_quadratic_program.lp') as file2: lines1 = file1.readlines() lines2 = file2.readlines() self.assertListEqual(lines1, lines2) From 63b59e7f6a03afc10c76fb07a10948ee350cbd24 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Wed, 22 Apr 2020 22:48:44 +0900 Subject: [PATCH 268/323] small fix of parsing problem name of LP file --- qiskit/optimization/problems/quadratic_program.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 4eea60f84f..f8ce3e7f98 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -734,7 +734,7 @@ def _parse_problem_name(filename: str) -> str: # Because docplex model reader uses the base name as model name, # we parse the model name in the LP file manually. # https://ibmdecisionoptimization.github.io/docplex-doc/mp/docplex.mp.model_reader.html - prefix = '\\Problem name: ' + prefix = '\\Problem name:' model_name = '' with open(filename) as file: for line in file: From 0dbfb8a636065e675f4a85ed09677072bca9604e Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Wed, 22 Apr 2020 15:55:47 +0200 Subject: [PATCH 269/323] lint fix --- qiskit/optimization/algorithms/admm_optimizer.py | 6 +++--- qiskit/optimization/infinity.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index e99ad923c8..6d528016e8 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -610,8 +610,8 @@ def _create_step1_problem(self) -> QuadraticProgram: # prepare and set quadratic objective. quadratic_objective = self._state.q0 +\ - self._params.factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) +\ - self._state.rho / 2 * np.eye(binary_size) + self._params.factor_c / 2 * np.dot(self._state.a0.transpose(), self._state.a0) +\ + self._state.rho / 2 * np.eye(binary_size) op1.objective.quadratic = quadratic_objective # prepare and set linear objective. @@ -721,7 +721,7 @@ def _create_step3_problem(self) -> QuadraticProgram: # set quadratic objective y quadratic_y = self._params.beta / 2 * np.eye(binary_size) + \ - self._state.rho / 2 * np.eye(binary_size) + self._state.rho / 2 * np.eye(binary_size) op3.objective.quadratic = quadratic_y # set linear objective for y diff --git a/qiskit/optimization/infinity.py b/qiskit/optimization/infinity.py index 4dc2d09868..db49dbc6da 100644 --- a/qiskit/optimization/infinity.py +++ b/qiskit/optimization/infinity.py @@ -12,7 +12,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Infinity constant from CPLEX optimization.""" +"""Infinity constant.""" -CPX_INFBOUND = 1.0E+20 -infinity = CPX_INFBOUND # pylint: disable=invalid-name + +infinity = 1.0E+20 # pylint: disable=invalid-name From ac6b16a67f99cc6a66effe9ac5e3bd111a675d98 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Wed, 22 Apr 2020 11:27:04 -0400 Subject: [PATCH 270/323] fix spell, lint --- .pylintdict | 6 +++++ .../optimization/algorithms/admm_optimizer.py | 2 +- .../algorithms/cobyla_optimizer.py | 1 + .../algorithms/optimization_algorithm.py | 2 +- .../converters/inequality_to_equality.py | 4 ++-- ...dratic_program_to_negative_value_oracle.py | 6 ++--- .../problems/quadratic_objective.py | 2 +- test/optimization/test_grover_optimizer.py | 22 +++++++++---------- 8 files changed, 26 insertions(+), 19 deletions(-) diff --git a/.pylintdict b/.pylintdict index d6f5eb7ad3..b3cc17fd88 100644 --- a/.pylintdict +++ b/.pylintdict @@ -311,9 +311,11 @@ loglikelihood logn lognormal lor +lowerbound lp lpex lr +lsit lst majorana mapsto @@ -597,6 +599,7 @@ timestamp tnc toctree Todo +todok toffoli tol tomo @@ -629,6 +632,7 @@ unroller unsetting unshifting untapered +upperbound username usr utils @@ -636,6 +640,7 @@ validator vals VariablesInterface variational +vartype vazirani vdag vertices @@ -657,6 +662,7 @@ xc xixj xopt xor +Xs xtol xuxv xyz diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 6d528016e8..26a97cf6be 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -704,7 +704,7 @@ def _create_step2_problem(self) -> QuadraticProgram: return op2 def _binary_indices_to_continuous(self, binary_indices: List[int]) -> List[int]: - # todo: implement + # TODO: implement return binary_indices def _create_step3_problem(self) -> QuadraticProgram: diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index 7f1b7315aa..1fff1765b2 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -118,6 +118,7 @@ def objective(x): if upperbound < infinity: constraints += [lambda x, ub=upperbound: ub - x] + # pylint: disable=no-member # add linear and quadratic constraints for constraint in problem.linear_constraints + problem.quadratic_constraints: rhs = constraint.rhs diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index 4321f8defb..b14b4a5d1b 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -126,7 +126,7 @@ def results(self) -> Any: return self._results @property - def status(self) -> Status: + def status(self) -> 'Status': """Return the termination status of the algorithm. Returns: diff --git a/qiskit/optimization/converters/inequality_to_equality.py b/qiskit/optimization/converters/inequality_to_equality.py index f7b757b127..909811622d 100644 --- a/qiskit/optimization/converters/inequality_to_equality.py +++ b/qiskit/optimization/converters/inequality_to_equality.py @@ -310,12 +310,12 @@ def _calc_linear_bounds(self, linear): def _calc_quadratic_bounds(self, linear, quadratic): lhs_lb, lhs_ub = 0, 0 - # Calcurate the lowerbound and the upperbound of the linear part + # Calculate the lowerbound and the upperbound of the linear part linear_lb, linear_ub = self._calc_linear_bounds(linear) lhs_lb += linear_lb lhs_ub += linear_ub - # Calcurate the lowerbound and the upperbound of the quadratic part + # Calculate the lowerbound and the upperbound of the quadratic part for (name_i, name_j), v in quadratic.items(): x = self._src.get_variable(name_i) y = self._src.get_variable(name_j) diff --git a/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py b/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py index 8c5329c98f..efac7d57ca 100644 --- a/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py @@ -162,10 +162,10 @@ def _build_operator(self, func_dict: Dict[Union[int, Tuple[int, int]], int]) -> for i in range(self._num_value): for k, v in func_dict.items(): if isinstance(k, tuple): - a = [key_val[int(k[0])], key_val[int(k[1])]] - b = key_val[self._num_key + i] + a_v = [key_val[int(k[0])], key_val[int(k[1])]] + b_v = key_val[self._num_key + i] circuit.mcu1(1 / 2 ** self._num_value * 2 * np.pi * 2 ** i * v, - a, b) + a_v, b_v) # Add IQFT. iqft = IQFT(self._num_value) diff --git a/qiskit/optimization/problems/quadratic_objective.py b/qiskit/optimization/problems/quadratic_objective.py index c926bc24aa..909d73af3f 100644 --- a/qiskit/optimization/problems/quadratic_objective.py +++ b/qiskit/optimization/problems/quadratic_objective.py @@ -50,7 +50,7 @@ def __init__(self, quadratic_program: "QuadraticProgram", constant: The constant offset of the objective. linear: The coefficients of the linear part of the objective. quadratic: The coefficients of the quadratic part of the objective. - sense: The optimizaiton sense of the objective. + sense: The optimization sense of the objective. """ super().__init__(quadratic_program) self._constant = constant diff --git a/test/optimization/test_grover_optimizer.py b/test/optimization/test_grover_optimizer.py index 336115f880..e0fb77daab 100644 --- a/test/optimization/test_grover_optimizer.py +++ b/test/optimization/test_grover_optimizer.py @@ -16,10 +16,10 @@ import unittest from test.optimization import QiskitOptimizationTestCase +from docplex.mp.model import Model from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.optimization.algorithms import GroverOptimizer, MinimumEigenOptimizer from qiskit.optimization.problems import QuadraticProgram -from docplex.mp.model import Model class TestGroverOptimizer(QiskitOptimizationTestCase): @@ -40,9 +40,9 @@ def test_qubo_gas_int_zero(self): # Input. model = Model() - x0 = model.binary_var(name='x0') - x1 = model.binary_var(name='x1') - model.minimize(0*x0+0*x1) + x_0 = model.binary_var(name='x0') + x_1 = model.binary_var(name='x1') + model.minimize(0*x_0+0*x_1) op = QuadraticProgram() op.from_docplex(model) @@ -57,9 +57,9 @@ def test_qubo_gas_int_simple(self): # Input. model = Model() - x0 = model.binary_var(name='x0') - x1 = model.binary_var(name='x1') - model.minimize(-x0+2*x1) + x_0 = model.binary_var(name='x0') + x_1 = model.binary_var(name='x1') + model.minimize(-x_0+2*x_1) op = QuadraticProgram() op.from_docplex(model) @@ -74,10 +74,10 @@ def test_qubo_gas_int_paper_example(self): # Input. model = Model() - x0 = model.binary_var(name='x0') - x1 = model.binary_var(name='x1') - x2 = model.binary_var(name='x2') - model.minimize(-x0+2*x1-3*x2-2*x0*x2-1*x1*x2) + x_0 = model.binary_var(name='x0') + x_1 = model.binary_var(name='x1') + x_2 = model.binary_var(name='x2') + model.minimize(-x_0+2*x_1-3*x_2-2*x_0*x_2-1*x_1*x_2) op = QuadraticProgram() op.from_docplex(model) From d16a233ce25f9e28e6a2254ab3361ae637c9f6c0 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 23 Apr 2020 11:02:14 +0900 Subject: [PATCH 271/323] fix a typo --- .pylintdict | 1 - qiskit/optimization/problems/linear_constraint.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.pylintdict b/.pylintdict index b3cc17fd88..ff376f0078 100644 --- a/.pylintdict +++ b/.pylintdict @@ -315,7 +315,6 @@ lowerbound lp lpex lr -lsit lst majorana mapsto diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index 77ad4e7c56..38ac7608f9 100644 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -57,7 +57,7 @@ def linear(self) -> LinearExpression: def linear(self, linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]]) \ -> None: """Sets the linear expression corresponding to the left-hand-side of the constraint. - The coefficients can either be given by an array, a (sparse) 1d matrix, a lsit or a + The coefficients can either be given by an array, a (sparse) 1d matrix, a list or a dictionary. Args: From 8518268ef975bc5526aa88fcb94d41e2aa4586f3 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Thu, 23 Apr 2020 10:08:23 +0200 Subject: [PATCH 272/323] only test based on exact solver (other redundant) --- .../test_recursive_optimization.py | 49 +++---------------- 1 file changed, 6 insertions(+), 43 deletions(-) diff --git a/test/optimization/test_recursive_optimization.py b/test/optimization/test_recursive_optimization.py index 6c7b2b9d82..a2e0ffa972 100755 --- a/test/optimization/test_recursive_optimization.py +++ b/test/optimization/test_recursive_optimization.py @@ -12,68 +12,31 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" Test Recursive Min Eigen Optimizer """ +"""Test Recursive Min Eigen Optimizer.""" import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase -import numpy as np -from ddt import ddt, data, unpack -from qiskit import BasicAer -from qiskit.aqua import QuantumInstance, aqua_globals -from qiskit.aqua.algorithms import NumPyMinimumEigensolver, QAOA -from qiskit.aqua.components.optimizers import COBYLA +from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.optimization.algorithms import (MinimumEigenOptimizer, CplexOptimizer, RecursiveMinimumEigenOptimizer) from qiskit.optimization.problems import QuadraticProgram -@ddt class TestRecursiveMinEigenOptimizer(QiskitOptimizationTestCase): """Recursive Min Eigen Optimizer Tests.""" def setUp(self): super().setUp() - - # fix random seed for reproducible results - np.random.seed = 1 - aqua_globals.random_seed = 2 - self.resource_path = './test/optimization/resources/' - # setup simulators - self.qinstances = {} - self.qinstances['qasm'] = QuantumInstance( - BasicAer.get_backend('qasm_simulator'), - shots=10000, - seed_simulator=51, - seed_transpiler=80 - ) - self.qinstances['statevector'] = QuantumInstance( - BasicAer.get_backend('statevector_simulator'), - seed_simulator=51, - seed_transpiler=80 - ) - - # setup minimum eigen solvers - self.min_eigen_solvers = {} - self.min_eigen_solvers['exact'] = NumPyMinimumEigensolver() - self.min_eigen_solvers['qaoa'] = QAOA(optimizer=COBYLA()) - - @data( - ('exact', None, 'op_ip1.lp'), - ('qaoa', 'statevector', 'op_ip1.lp'), - ('qaoa', 'qasm', 'op_ip1.lp') - ) - @unpack - def test_recursive_min_eigen_optimizer(self, solver, simulator, filename): - """ Min Eigen Optimizer Test """ + def test_recursive_min_eigen_optimizer(self): + """Test the recursive minimum eigen optimizer.""" try: + filename = 'op_ip1.lp' # get minimum eigen solver - min_eigen_solver = self.min_eigen_solvers[solver] - if simulator: - min_eigen_solver.quantum_instance = self.qinstances[simulator] + min_eigen_solver = NumPyMinimumEigensolver() # construct minimum eigen optimizer min_eigen_optimizer = MinimumEigenOptimizer(min_eigen_solver) From 1ae839afc78c92cb8ef0ac55ca32013ff9b927a8 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Thu, 23 Apr 2020 11:35:03 +0200 Subject: [PATCH 273/323] fix random seeds --- test/optimization/test_grover_optimizer.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/optimization/test_grover_optimizer.py b/test/optimization/test_grover_optimizer.py index e0fb77daab..63fee76ea5 100644 --- a/test/optimization/test_grover_optimizer.py +++ b/test/optimization/test_grover_optimizer.py @@ -16,7 +16,10 @@ import unittest from test.optimization import QiskitOptimizationTestCase +import random +import numpy from docplex.mp.model import Model +from qiskit.aqua import aqua_globals from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.optimization.algorithms import GroverOptimizer, MinimumEigenOptimizer from qiskit.optimization.problems import QuadraticProgram @@ -25,6 +28,12 @@ class TestGroverOptimizer(QiskitOptimizationTestCase): """GroverOptimizer tests.""" + def setUp(self): + super().setUp() + random.seed = 2 + numpy.random.seed = 42 + aqua_globals.seed = 42 + def validate_results(self, problem, results): """Validate the results object returned by GroverOptimizer.""" # Get expected value. From bd681614a453ba56ab7aed3645b8bef68566ba7b Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 23 Apr 2020 19:37:40 +0900 Subject: [PATCH 274/323] add remove linear/quadratic constraints --- .../problems/quadratic_program.py | 48 +++++++++++++++---- test/optimization/test_quadratic_program.py | 45 +++++++++++++++++ 2 files changed, 85 insertions(+), 8 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index f8ce3e7f98..0e9b9e3f40 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -223,9 +223,9 @@ def get_num_vars(self, vartype: Optional[VarType] = None) -> int: The total number of variables. """ if vartype: - return sum(variable.vartype == vartype for variable in self.variables) + return sum(variable.vartype == vartype for variable in self._variables) else: - return len(self.variables) + return len(self._variables) def get_num_continuous_vars(self) -> int: """Returns the total number of continuous variables. @@ -323,9 +323,9 @@ def get_linear_constraint(self, i: Union[int, str]) -> LinearConstraint: KeyError: if the name does not exist """ if isinstance(i, int): - return self.linear_constraints[i] + return self._linear_constraints[i] else: - return self.linear_constraints[self._linear_constraints_index[i]] + return self._linear_constraints[self._linear_constraints_index[i]] def get_num_linear_constraints(self) -> int: """Returns the number of linear constraints. @@ -333,7 +333,7 @@ def get_num_linear_constraints(self) -> int: Returns: The number of linear constraints. """ - return len(self.linear_constraints) + return len(self._linear_constraints) @property def quadratic_constraints(self) -> List[QuadraticConstraint]: @@ -416,9 +416,9 @@ def get_quadratic_constraint(self, i: Union[int, str]) -> QuadraticConstraint: KeyError: if the name does not exist """ if isinstance(i, int): - return self.quadratic_constraints[i] + return self._quadratic_constraints[i] else: - return self.quadratic_constraints[self._quadratic_constraints_index[i]] + return self._quadratic_constraints[self._quadratic_constraints_index[i]] def get_num_quadratic_constraints(self) -> int: """Returns the number of quadratic constraints. @@ -426,7 +426,39 @@ def get_num_quadratic_constraints(self) -> int: Returns: The number of quadratic constraints. """ - return len(self.quadratic_constraints) + return len(self._quadratic_constraints) + + def remove_linear_constraint(self, i: Union[str, int]) -> None: + """Remove a linear constraint + + Args: + i: an index or a name of a linear constraint + + Raises: + KeyError: if name does not exist + IndexError: if index is out of range + """ + if isinstance(i, str): + i = self._linear_constraints_index[i] + del self._linear_constraints[i] + self._linear_constraints_index = {cst.name: j for j, cst in + enumerate(self._linear_constraints)} + + def remove_quadratic_constraint(self, i: Union[str, int]) -> None: + """Remove a quadratic constraint + + Args: + i: an index or a name of a quadratic constraint + + Raises: + KeyError: if name does not exist + IndexError: if index is out of range + """ + if isinstance(i, str): + i = self._quadratic_constraints_index[i] + del self._quadratic_constraints[i] + self._quadratic_constraints_index = {cst.name: j for j, cst in + enumerate(self._quadratic_constraints)} @property def objective(self) -> QuadraticObjective: diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index 6468811b3a..b850af1906 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -169,6 +169,7 @@ def test_linear_constraints_handling(self): self.assertEqual(q_p.get_num_linear_constraints(), 3) lin = q_p.linear_constraints self.assertEqual(len(lin), 3) + self.assertDictEqual(lin[0].linear.to_dict(), {0: 1}) self.assertDictEqual(lin[0].linear.to_dict(use_name=True), {'x': 1}) self.assertListEqual(lin[0].linear.to_array().tolist(), [1, 0, 0]) @@ -207,6 +208,23 @@ def test_linear_constraints_handling(self): with self.assertRaises(KeyError): q_p.get_linear_constraint('c3') + q_p.remove_linear_constraint('c1') + lin = q_p.linear_constraints + self.assertEqual(len(lin), 2) + self.assertDictEqual(lin[1].linear.to_dict(), {2: 1}) + self.assertDictEqual(lin[1].linear.to_dict(use_name=True), {'z': 1}) + self.assertListEqual(lin[1].linear.to_array().tolist(), [0, 0, 1]) + self.assertEqual(lin[1].sense, ConstraintSense.GE) + self.assertEqual(lin[1].rhs, 1) + self.assertEqual(lin[1].name, 'c2') + self.assertEqual(q_p.get_linear_constraint(1).name, 'c2') + self.assertEqual(q_p.get_linear_constraint('c2').name, 'c2') + + with self.assertRaises(KeyError): + q_p.remove_linear_constraint('c1') + with self.assertRaises(IndexError): + q_p.remove_linear_constraint(9) + q_p.linear_constraint(sense='E') self.assertEqual(q_p.linear_constraints[-1].sense, ConstraintSense.EQ) q_p.linear_constraint(sense='G') @@ -309,6 +327,33 @@ def test_quadratic_constraints_handling(self): with self.assertRaises(KeyError): q_p.get_quadratic_constraint('q3') + q_p.remove_quadratic_constraint('q1') + quad = q_p.quadratic_constraints + self.assertEqual(len(quad), 2) + self.assertDictEqual(quad[1].linear.to_dict(), {2: 1}) + self.assertDictEqual(quad[1].linear.to_dict(use_name=True), {'z': 1}) + self.assertListEqual(quad[1].linear.to_array().tolist(), [0, 0, 1]) + self.assertDictEqual(quad[1].quadratic.to_dict(), {(0, 2): 1}) + self.assertDictEqual(quad[1].quadratic.to_dict(symmetric=True), + {(0, 2): 0.5, (2, 0): 0.5}) + self.assertDictEqual(quad[1].quadratic.to_dict(use_name=True), {('x', 'z'): 1}) + self.assertDictEqual(quad[1].quadratic.to_dict(use_name=True, symmetric=True), + {('x', 'z'): 0.5, ('z', 'x'): 0.5}) + self.assertListEqual(quad[1].quadratic.to_array().tolist(), + [[0, 0, 1], [0, 0, 0], [0, 0, 0]]) + self.assertListEqual(quad[1].quadratic.to_array(symmetric=True).tolist(), + [[0, 0, 0.5], [0, 0, 0], [0.5, 0, 0]]) + self.assertEqual(quad[1].sense, ConstraintSense.GE) + self.assertEqual(quad[1].rhs, 1) + self.assertEqual(quad[1].name, 'q2') + self.assertEqual(q_p.get_quadratic_constraint(1).name, 'q2') + self.assertEqual(q_p.get_quadratic_constraint('q2').name, 'q2') + + with self.assertRaises(KeyError): + q_p.remove_quadratic_constraint('q1') + with self.assertRaises(IndexError): + q_p.remove_quadratic_constraint(9) + def test_objective_handling(self): """test objective handling""" q_p = QuadraticProgram() From f17a06f58c9b591462fcd23b772b037dcb3f027c Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Thu, 23 Apr 2020 12:54:09 +0200 Subject: [PATCH 275/323] remove util.py --- qiskit/optimization/__init__.py | 4 +- .../algorithms/grover_optimizer.py | 60 ++++++++++++++- qiskit/optimization/util.py | 74 ------------------- ...dratic_program_to_negative_value_oracle.py | 4 +- 4 files changed, 61 insertions(+), 81 deletions(-) delete mode 100644 qiskit/optimization/util.py diff --git a/qiskit/optimization/__init__.py b/qiskit/optimization/__init__.py index eaf91685c6..73fbe10c5a 100644 --- a/qiskit/optimization/__init__.py +++ b/qiskit/optimization/__init__.py @@ -32,12 +32,10 @@ from .problems import QuadraticProgram from ._logging import (get_qiskit_optimization_logging, set_qiskit_optimization_logging) -from .util import get_qubo_solutions __all__ = ['QuadraticProgram', 'QiskitOptimizationError', 'get_qiskit_optimization_logging', 'set_qiskit_optimization_logging', - 'infinity', - 'get_qubo_solutions' + 'infinity' ] diff --git a/qiskit/optimization/algorithms/grover_optimizer.py b/qiskit/optimization/algorithms/grover_optimizer.py index 96b2d8bb74..9659cf0350 100644 --- a/qiskit/optimization/algorithms/grover_optimizer.py +++ b/qiskit/optimization/algorithms/grover_optimizer.py @@ -25,7 +25,6 @@ from qiskit.optimization.converters import (QuadraticProgramToQubo, QuadraticProgramToNegativeValueOracle) from qiskit.optimization.algorithms.optimization_algorithm import OptimizationResult -from qiskit.optimization.util import get_qubo_solutions from qiskit.aqua.algorithms.amplitude_amplifiers.grover import Grover from qiskit import Aer, QuantumCircuit from qiskit.providers import BaseBackend @@ -180,7 +179,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: # Get original key and value pairs. func_dict[-1] = orig_constant - solutions = get_qubo_solutions(func_dict, n_key) + solutions = self._get_qubo_solutions(func_dict, n_key) # If the constant is 0 and we didn't find a negative, the answer is likely 0. if optimum_value >= 0 and orig_constant == 0: @@ -259,6 +258,63 @@ def _bin_to_int(v: str, num_value_bits: int) -> int: return int_v + @staticmethod + def _get_qubo_solutions(function_dict: Dict[Union[int, Tuple[int, int]], int], n_key: int, + print_solutions: Optional[bool] = False): + """ Calculates all of the outputs of a QUBO function representable by n key qubits. + + Args: + function_dict: A dictionary representation of the function, where the keys correspond + to a variable, and the values are the corresponding coefficients. + n_key: The number of key qubits. + print_solutions: If true, the solutions will be formatted and printed. + + Returns: + dict: A dictionary of the inputs (keys) and outputs (values) of the QUBO function. + """ + # Determine constant. + constant = 0 + if -1 in function_dict: + constant = function_dict[-1] + format_string = '{0:0'+str(n_key)+'b}' + + # Iterate through every key combination. + if print_solutions: + print("QUBO Solutions:") + print("==========================") + solutions = {} + for i in range(2**n_key): + solution = constant + + # Convert int to a list of binary variables. + bin_key = format_string.format(i) + bin_list = [int(bin_key[j]) for j in range(len(bin_key))] + + # Handle the linear terms. + for k in range(len(bin_key)): + if bin_list[k] == 1 and k in function_dict: + solution += function_dict[k] + + # Handle the quadratic terms. + for j in range(len(bin_key)): + for q in range(len(bin_key)): + if (j, q) in function_dict and j != q and bin_list[j] == 1 and bin_list[q] == 1: + solution += function_dict[(j, q)] + + # Print row. + if print_solutions: + spacer = "" if i >= 10 else " " + value_spacer = " " if solution < 0 else " " + print(spacer + str(i), "=", bin_key, "->" + value_spacer + str(round(solution, 4))) + + # Record solution. + solutions[i] = solution + + if print_solutions: + print() + + return solutions + class GroverOptimizationResults: """A results object for Grover Optimization methods.""" diff --git a/qiskit/optimization/util.py b/qiskit/optimization/util.py deleted file mode 100644 index 0ed984b42e..0000000000 --- a/qiskit/optimization/util.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -""" Optimization Utilities module """ - -from typing import Dict, Union, Tuple, Optional - - -def get_qubo_solutions(function_dict: Dict[Union[int, Tuple[int, int]], int], n_key: int, - print_solutions: Optional[bool] = False): - """ Calculates all of the outputs of a QUBO function representable by n key qubits. - - Args: - function_dict: A dictionary representation of the function, where the keys correspond - to a variable, and the values are the corresponding coefficients. - n_key: The number of key qubits. - print_solutions: If true, the solutions will be formatted and printed. - - Returns: - dict: A dictionary of the inputs (keys) and outputs (values) of the QUBO function. - """ - # Determine constant. - constant = 0 - if -1 in function_dict: - constant = function_dict[-1] - format_string = '{0:0'+str(n_key)+'b}' - - # Iterate through every key combination. - if print_solutions: - print("QUBO Solutions:") - print("==========================") - solutions = {} - for i in range(2**n_key): - solution = constant - - # Convert int to a list of binary variables. - bin_key = format_string.format(i) - bin_list = [int(bin_key[j]) for j in range(len(bin_key))] - - # Handle the linear terms. - for k in range(len(bin_key)): - if bin_list[k] == 1 and k in function_dict: - solution += function_dict[k] - - # Handle the quadratic terms. - for j in range(len(bin_key)): - for q in range(len(bin_key)): - if (j, q) in function_dict and j != q and bin_list[j] == 1 and bin_list[q] == 1: - solution += function_dict[(j, q)] - - # Print row. - if print_solutions: - spacer = "" if i >= 10 else " " - value_spacer = " " if solution < 0 else " " - print(spacer + str(i), "=", bin_key, "->" + value_spacer + str(round(solution, 4))) - - # Record solution. - solutions[i] = solution - - if print_solutions: - print() - - return solutions diff --git a/test/optimization/test_quadratic_program_to_negative_value_oracle.py b/test/optimization/test_quadratic_program_to_negative_value_oracle.py index 8270be4234..ee26ba9dfd 100644 --- a/test/optimization/test_quadratic_program_to_negative_value_oracle.py +++ b/test/optimization/test_quadratic_program_to_negative_value_oracle.py @@ -17,8 +17,8 @@ import unittest from test.optimization import QiskitOptimizationTestCase import numpy as np +from qiskit.optimization.algorithms import GroverOptimizer from qiskit.optimization.converters import QuadraticProgramToNegativeValueOracle -from qiskit.optimization.util import get_qubo_solutions from qiskit import QuantumCircuit, Aer, execute from qiskit.optimization.problems import QuadraticProgram @@ -39,7 +39,7 @@ def _validate_function(self, func_dict, problem): def _validate_operator(self, func_dict, n_key, n_value, operator): # Get expected results. - solutions = get_qubo_solutions(func_dict, n_key, print_solutions=False) + solutions = GroverOptimizer._get_qubo_solutions(func_dict, n_key, print_solutions=False) # Run the state preparation operator A and observe results. circuit = operator._circuit From 44637aca856a8d4f37cbdd06a7b37ae2769ff6c3 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Thu, 23 Apr 2020 12:58:01 +0200 Subject: [PATCH 276/323] Update quadratic_program_to_qubo.py --- .../converters/quadratic_program_to_qubo.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/qiskit/optimization/converters/quadratic_program_to_qubo.py b/qiskit/optimization/converters/quadratic_program_to_qubo.py index 4b274c4e8d..72f4879c28 100644 --- a/qiskit/optimization/converters/quadratic_program_to_qubo.py +++ b/qiskit/optimization/converters/quadratic_program_to_qubo.py @@ -86,22 +86,17 @@ def decode(self, result: 'OptimizationResult') -> 'OptimizationResult': return self._int_to_bin.decode(result) @staticmethod - def get_compatibility_msg(problem: QuadraticProgram) -> bool: - """Checks whether a given problem can be cast to a QUBO. + def get_compatibility_msg(problem: QuadraticProgram) -> str: + """Checks whether a given problem can be solved with this optimizer. - A quadratic program can be converted to a QUBO (Quadratic Unconstrained Binary - Optimization) problem, if the problem contains only binary and integer variables as well - as linear equality constraints. + Checks whether the given problem is compatible, i.e., whether the problem can be converted + to a QUBO, and otherwise, returns a message explaining the incompatibility. Args: problem: The optimization problem to check compatibility. Returns: - True, if the problem can be converted to a QUBO, otherwise a QiskitOptimizationError - is raised. - - Raises: - QiskitOptimizationError: If the conversion to QUBO is not possible. + A message describing the incompatibility. """ # initialize message From fb19c619608b839986955b29fa117045039e7417 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 23 Apr 2020 21:45:56 +0900 Subject: [PATCH 277/323] fix a factor --- .../algorithms/recursive_minimum_eigen_optimizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 84feaeef8c..6dae54cbb2 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -145,7 +145,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: # 1a. get additional offset constant = problem_.objective.constant - constant += problem_.objective.quadratic[i, i] / 2 + constant += problem_.objective.quadratic[i, i] constant += problem_.objective.linear[i] problem_.objective.constant = constant From e22120f9f6ff71fafafab0ba0503aaf1aed615f1 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Thu, 23 Apr 2020 10:22:33 -0400 Subject: [PATCH 278/323] Bump Terra version requirements to 0.14.0 --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index d09f559708..f071c9440f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -qiskit-terra>=0.13.0 +qiskit-terra>=0.14.0 qiskit-ignis>=0.2.0 scipy>=1.0 sympy>=1.3 diff --git a/setup.py b/setup.py index 0ca22a2bce..95f7c2d27d 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ Qiskit Finance, Qiskit Machine Learning and Qiskit Optimization to experiment with real-world applications to quantum computing.""" requirements = [ - "qiskit-terra>=0.13.0", + "qiskit-terra>=0.14.0", "qiskit-ignis>=0.2.0", "scipy>=1.0", "sympy>=1.3", From 310cc070e2d570861b7af65c2106e720bfc6ac1a Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Thu, 23 Apr 2020 11:44:36 -0400 Subject: [PATCH 279/323] fix var_form initialization --- qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py | 12 +----------- qiskit/aqua/algorithms/vq_algorithm.py | 8 ++++++-- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py index 9027ee7ff2..9e708cd9d1 100755 --- a/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/aqua/algorithms/minimum_eigen_solvers/vqe.py @@ -25,7 +25,7 @@ import numpy as np from qiskit import ClassicalRegister, QuantumCircuit -from qiskit.circuit import Parameter, ParameterVector +from qiskit.circuit import Parameter from qiskit.providers import BaseBackend from qiskit.aqua import QuantumInstance, AquaError from qiskit.aqua.operators import (TPBGroupedWeightedPauliOperator, WeightedPauliOperator, @@ -194,16 +194,6 @@ def aux_operators(self, aux_operators: List[BaseOperator]) -> None: """ Set aux operators """ self._in_aux_operators = aux_operators - @VQAlgorithm.var_form.setter - def var_form(self, var_form: VariationalForm): - """ Sets variational form """ - VQAlgorithm.var_form.fset(self, var_form) - if var_form: - self._var_form_params = ParameterVector('θ', var_form.num_parameters) - if self.initial_point is None: - self.initial_point = var_form.preferred_init_points - self._check_operator_varform() - def _check_operator_varform(self): """Check that the number of qubits of operator and variational form match.""" if self.operator is not None and self.var_form is not None: diff --git a/qiskit/aqua/algorithms/vq_algorithm.py b/qiskit/aqua/algorithms/vq_algorithm.py index bd64c26be4..b394f31d79 100644 --- a/qiskit/aqua/algorithms/vq_algorithm.py +++ b/qiskit/aqua/algorithms/vq_algorithm.py @@ -72,7 +72,9 @@ def __init__(self, self._optimizer = optimizer self._cost_fn = cost_fn self._initial_point = initial_point - self.var_form = var_form + self._var_form = var_form + if var_form is not None: + self.var_form = var_form self._parameterized_circuits = None @@ -92,7 +94,9 @@ def var_form(self, var_form: Union[QuantumCircuit, VariationalForm]): self._var_form_params = ParameterVector('θ', length=var_form.num_parameters) self._var_form = var_form else: - raise ValueError('Unsupported type {} of var_form'.format(type(var_form))) + raise ValueError( + "Unsupported type '{}' of var_form".format( + type(var_form) if var_form else var_form)) if var_form is not None and len(self._var_form_params) == 0: raise AquaError('Passing a variational form with no parameters is not supported.') From e4828363fe55d78b3b4e00a1690d6329fcd90eda Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Thu, 23 Apr 2020 12:47:05 -0400 Subject: [PATCH 280/323] check cplex installed on unit tests --- test/optimization/test_converters.py | 41 ++--- test/optimization/test_min_eigen_optimizer.py | 8 +- test/optimization/test_quadratic_program.py | 141 +++++++++--------- .../test_recursive_optimization.py | 8 +- 4 files changed, 108 insertions(+), 90 deletions(-) diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index 993a7e51cd..250d164254 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -434,25 +434,28 @@ def test_optimizationproblem_to_operator(self): def test_continuous_variable_decode(self): """ Test decode func of IntegerToBinaryConverter for continuous variables""" - mdl = Model('test_continuous_varable_decode') - c = mdl.continuous_var(lb=0, ub=10.9, name='c') - x = mdl.binary_var(name='x') - mdl.maximize(c + x * x) - op = QuadraticProgram() - op.from_docplex(mdl) - converter = IntegerToBinary() - op = converter.encode(op) - admm_params = ADMMParameters() - qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) - continuous_optimizer = CplexOptimizer() - solver = ADMMOptimizer( - qubo_optimizer=qubo_optimizer, - continuous_optimizer=continuous_optimizer, - params=admm_params, - ) - solution = solver.solve(op) - solution = converter.decode(solution) - self.assertEqual(solution.x[0], 10.9) + try: + mdl = Model('test_continuous_varable_decode') + c = mdl.continuous_var(lb=0, ub=10.9, name='c') + x = mdl.binary_var(name='x') + mdl.maximize(c + x * x) + op = QuadraticProgram() + op.from_docplex(mdl) + converter = IntegerToBinary() + op = converter.encode(op) + admm_params = ADMMParameters() + qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) + continuous_optimizer = CplexOptimizer() + solver = ADMMOptimizer( + qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer, + params=admm_params, + ) + solution = solver.solve(op) + solution = converter.decode(solution) + self.assertEqual(solution.x[0], 10.9) + except NameError as ex: + self.skipTest(str(ex)) if __name__ == '__main__': diff --git a/test/optimization/test_min_eigen_optimizer.py b/test/optimization/test_min_eigen_optimizer.py index 52767f117e..01e0b36851 100644 --- a/test/optimization/test_min_eigen_optimizer.py +++ b/test/optimization/test_min_eigen_optimizer.py @@ -79,8 +79,12 @@ def test_min_eigen_optimizer(self, config): # analyze results self.assertAlmostEqual(cplex_result.fval, result.fval) - except NameError as ex: - self.skipTest(str(ex)) + except RuntimeError as ex: + msg = str(ex) + if 'CPLEX' in msg: + self.skipTest(msg) + else: + self.fail(msg) if __name__ == '__main__': diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index b850af1906..d71bee0a65 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -387,73 +387,80 @@ def test_objective_handling(self): def test_read_from_lp_file(self): """test read lp file""" - q_p = QuadraticProgram() - with self.assertRaises(FileNotFoundError): - q_p.read_from_lp_file('') - with self.assertRaises(FileNotFoundError): - q_p.read_from_lp_file('no_file.txt') - q_p.read_from_lp_file('test/optimization/resources/test_quadratic_program.lp') - self.assertEqual(q_p.name, 'my problem') - self.assertEqual(q_p.get_num_vars(), 3) - self.assertEqual(q_p.get_num_binary_vars(), 1) - self.assertEqual(q_p.get_num_integer_vars(), 1) - self.assertEqual(q_p.get_num_continuous_vars(), 1) - self.assertEqual(q_p.get_num_linear_constraints(), 3) - self.assertEqual(q_p.get_num_quadratic_constraints(), 3) - - self.assertEqual(q_p.variables[0].name, 'x') - self.assertEqual(q_p.variables[0].vartype, VarType.BINARY) - self.assertEqual(q_p.variables[0].lowerbound, 0) - self.assertEqual(q_p.variables[0].upperbound, 1) - self.assertEqual(q_p.variables[1].name, 'y') - self.assertEqual(q_p.variables[1].vartype, VarType.INTEGER) - self.assertEqual(q_p.variables[1].lowerbound, -1) - self.assertEqual(q_p.variables[1].upperbound, 5) - self.assertEqual(q_p.variables[2].name, 'z') - self.assertEqual(q_p.variables[2].vartype, VarType.CONTINUOUS) - self.assertEqual(q_p.variables[2].lowerbound, -1) - self.assertEqual(q_p.variables[2].upperbound, 5) - - self.assertEqual(q_p.objective.sense, ObjSense.MINIMIZE) - self.assertEqual(q_p.objective.constant, 1) - self.assertDictEqual(q_p.objective.linear.to_dict(use_name=True), - {'x': 1, 'y': -1, 'z': 10}) - self.assertDictEqual(q_p.objective.quadratic.to_dict(use_name=True), - {('x', 'x'): 0.5, ('y', 'z'): -1}) - - cst = q_p.linear_constraints - self.assertEqual(cst[0].name, 'lin_eq') - self.assertDictEqual(cst[0].linear.to_dict(use_name=True), {'x': 1, 'y': 2}) - self.assertEqual(cst[0].sense, ConstraintSense.EQ) - self.assertEqual(cst[0].rhs, 1) - self.assertEqual(cst[1].name, 'lin_leq') - self.assertDictEqual(cst[1].linear.to_dict(use_name=True), {'x': 1, 'y': 2}) - self.assertEqual(cst[1].sense, ConstraintSense.LE) - self.assertEqual(cst[1].rhs, 1) - self.assertEqual(cst[2].name, 'lin_geq') - self.assertDictEqual(cst[2].linear.to_dict(use_name=True), {'x': 1, 'y': 2}) - self.assertEqual(cst[2].sense, ConstraintSense.GE) - self.assertEqual(cst[2].rhs, 1) - - cst = q_p.quadratic_constraints - self.assertEqual(cst[0].name, 'quad_eq') - self.assertDictEqual(cst[0].linear.to_dict(use_name=True), {'x': 1, 'y': 1}) - self.assertDictEqual(cst[0].quadratic.to_dict(use_name=True), - {('x', 'x'): 1, ('y', 'z'): -1, ('z', 'z'): 2}) - self.assertEqual(cst[0].sense, ConstraintSense.EQ) - self.assertEqual(cst[0].rhs, 1) - self.assertEqual(cst[1].name, 'quad_leq') - self.assertDictEqual(cst[1].linear.to_dict(use_name=True), {'x': 1, 'y': 1}) - self.assertDictEqual(cst[1].quadratic.to_dict(use_name=True), - {('x', 'x'): 1, ('y', 'z'): -1, ('z', 'z'): 2}) - self.assertEqual(cst[1].sense, ConstraintSense.LE) - self.assertEqual(cst[1].rhs, 1) - self.assertEqual(cst[2].name, 'quad_geq') - self.assertDictEqual(cst[2].linear.to_dict(use_name=True), {'x': 1, 'y': 1}) - self.assertDictEqual(cst[2].quadratic.to_dict(use_name=True), - {('x', 'x'): 1, ('y', 'z'): -1, ('z', 'z'): 2}) - self.assertEqual(cst[2].sense, ConstraintSense.GE) - self.assertEqual(cst[2].rhs, 1) + try: + q_p = QuadraticProgram() + with self.assertRaises(FileNotFoundError): + q_p.read_from_lp_file('') + with self.assertRaises(FileNotFoundError): + q_p.read_from_lp_file('no_file.txt') + q_p.read_from_lp_file('test/optimization/resources/test_quadratic_program.lp') + self.assertEqual(q_p.name, 'my problem') + self.assertEqual(q_p.get_num_vars(), 3) + self.assertEqual(q_p.get_num_binary_vars(), 1) + self.assertEqual(q_p.get_num_integer_vars(), 1) + self.assertEqual(q_p.get_num_continuous_vars(), 1) + self.assertEqual(q_p.get_num_linear_constraints(), 3) + self.assertEqual(q_p.get_num_quadratic_constraints(), 3) + + self.assertEqual(q_p.variables[0].name, 'x') + self.assertEqual(q_p.variables[0].vartype, VarType.BINARY) + self.assertEqual(q_p.variables[0].lowerbound, 0) + self.assertEqual(q_p.variables[0].upperbound, 1) + self.assertEqual(q_p.variables[1].name, 'y') + self.assertEqual(q_p.variables[1].vartype, VarType.INTEGER) + self.assertEqual(q_p.variables[1].lowerbound, -1) + self.assertEqual(q_p.variables[1].upperbound, 5) + self.assertEqual(q_p.variables[2].name, 'z') + self.assertEqual(q_p.variables[2].vartype, VarType.CONTINUOUS) + self.assertEqual(q_p.variables[2].lowerbound, -1) + self.assertEqual(q_p.variables[2].upperbound, 5) + + self.assertEqual(q_p.objective.sense, ObjSense.MINIMIZE) + self.assertEqual(q_p.objective.constant, 1) + self.assertDictEqual(q_p.objective.linear.to_dict(use_name=True), + {'x': 1, 'y': -1, 'z': 10}) + self.assertDictEqual(q_p.objective.quadratic.to_dict(use_name=True), + {('x', 'x'): 0.5, ('y', 'z'): -1}) + + cst = q_p.linear_constraints + self.assertEqual(cst[0].name, 'lin_eq') + self.assertDictEqual(cst[0].linear.to_dict(use_name=True), {'x': 1, 'y': 2}) + self.assertEqual(cst[0].sense, ConstraintSense.EQ) + self.assertEqual(cst[0].rhs, 1) + self.assertEqual(cst[1].name, 'lin_leq') + self.assertDictEqual(cst[1].linear.to_dict(use_name=True), {'x': 1, 'y': 2}) + self.assertEqual(cst[1].sense, ConstraintSense.LE) + self.assertEqual(cst[1].rhs, 1) + self.assertEqual(cst[2].name, 'lin_geq') + self.assertDictEqual(cst[2].linear.to_dict(use_name=True), {'x': 1, 'y': 2}) + self.assertEqual(cst[2].sense, ConstraintSense.GE) + self.assertEqual(cst[2].rhs, 1) + + cst = q_p.quadratic_constraints + self.assertEqual(cst[0].name, 'quad_eq') + self.assertDictEqual(cst[0].linear.to_dict(use_name=True), {'x': 1, 'y': 1}) + self.assertDictEqual(cst[0].quadratic.to_dict(use_name=True), + {('x', 'x'): 1, ('y', 'z'): -1, ('z', 'z'): 2}) + self.assertEqual(cst[0].sense, ConstraintSense.EQ) + self.assertEqual(cst[0].rhs, 1) + self.assertEqual(cst[1].name, 'quad_leq') + self.assertDictEqual(cst[1].linear.to_dict(use_name=True), {'x': 1, 'y': 1}) + self.assertDictEqual(cst[1].quadratic.to_dict(use_name=True), + {('x', 'x'): 1, ('y', 'z'): -1, ('z', 'z'): 2}) + self.assertEqual(cst[1].sense, ConstraintSense.LE) + self.assertEqual(cst[1].rhs, 1) + self.assertEqual(cst[2].name, 'quad_geq') + self.assertDictEqual(cst[2].linear.to_dict(use_name=True), {'x': 1, 'y': 1}) + self.assertDictEqual(cst[2].quadratic.to_dict(use_name=True), + {('x', 'x'): 1, ('y', 'z'): -1, ('z', 'z'): 2}) + self.assertEqual(cst[2].sense, ConstraintSense.GE) + self.assertEqual(cst[2].rhs, 1) + except RuntimeError as ex: + msg = str(ex) + if 'CPLEX' in msg: + self.skipTest(msg) + else: + self.fail(msg) def test_write_to_lp_file(self): """test write problem""" diff --git a/test/optimization/test_recursive_optimization.py b/test/optimization/test_recursive_optimization.py index a2e0ffa972..9276607063 100755 --- a/test/optimization/test_recursive_optimization.py +++ b/test/optimization/test_recursive_optimization.py @@ -56,8 +56,12 @@ def test_recursive_min_eigen_optimizer(self): # analyze results self.assertAlmostEqual(cplex_result.fval, result.fval) - except NameError as ex: - self.skipTest(str(ex)) + except RuntimeError as ex: + msg = str(ex) + if 'CPLEX' in msg: + self.skipTest(msg) + else: + self.fail(msg) if __name__ == '__main__': From d858788a7cb9eed88ed53cc919c88c268a50c4ae Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Thu, 23 Apr 2020 18:11:53 +0100 Subject: [PATCH 281/323] step2 generation based on a copy --- .../optimization/algorithms/admm_optimizer.py | 208 +++++++++++------- 1 file changed, 127 insertions(+), 81 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 9de2bbb26e..26980b0676 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -13,6 +13,7 @@ # that they have been altered from the originals. """An implementation of the ADMM algorithm.""" +import copy import logging import time from typing import List, Optional, Any @@ -150,7 +151,7 @@ def __init__(self, self.rho = rho_initial # new features - self.equality_constraints = [] + self.binary_equality_constraints = [] self.inequality_constraints = [] @@ -407,7 +408,7 @@ def _convert_problem_representation(self) -> None: for constraint in self._state.op.linear_constraints: if constraint.sense == ConstraintSense.EQ: # todo: verify that this constraint contains only binary variables - self._state.equality_constraints.append(constraint) + self._state.binary_equality_constraints.append(constraint) elif constraint.sense in (ConstraintSense.LE, ConstraintSense.GE): self._state.inequality_constraints.append(constraint) @@ -639,90 +640,116 @@ def _create_step1_problem(self) -> QuadraticProgram: op1.objective.linear = linear_objective return op1 + # def _create_step2_problem(self) -> QuadraticProgram: + # """Creates a step 2 sub-problem. + # + # Returns: + # A newly created optimization problem. + # """ + # op2 = QuadraticProgram() + # + # continuous_size = len(self._state.continuous_indices) + # binary_size = len(self._state.binary_indices) + # continuous_index = 0 + # for variable in self._state.op.variables: + # if variable.vartype == VarType.CONTINUOUS: + # op2.continuous_var(name="u0_" + str(continuous_index + 1), + # lowerbound=variable.lowerbound, upperbound=variable.upperbound) + # continuous_index += 1 + # + # for i in range(binary_size): + # op2.continuous_var(name="z0_" + str(i + 1), lowerbound=0, upperbound=1.) + # + # q_z = self._state.rho / 2 * np.eye(binary_size) + # op2.objective.quadratic = block_diag(self._state.q1, q_z) + # + # linear_z = -1 * self._state.lambda_mult - self._state.rho * (self._state.x0 - self._state.y) + # op2.objective.linear = np.concatenate((self._state.c1, linear_z)) + # + # # constraints for z. + # # A1 z <= b1. + # constraint_count = self._state.a1.shape[0] + # for i in range(constraint_count): + # linear = np.concatenate((np.zeros(continuous_size), self._state.a1[i, :])) + # op2.linear_constraint(linear=linear, sense=ConstraintSense.LE, rhs=self._state.b1[i]) + # + # if continuous_size: + # # A2 z + A3 u <= b2 + # constraint_count = self._state.a2.shape[0] + # for i in range(constraint_count): + # linear = np.concatenate((self._state.a3[i, :], self._state.a2[i, :])) + # op2.linear_constraint(linear=linear, + # sense=ConstraintSense.LE, + # rhs=self._state.b2[i]) + # + # # A4 u <= b3 + # constraint_count = self._state.a4.shape[0] + # for i in range(constraint_count): + # linear = np.concatenate((self._state.a4[i, :], np.zeros(binary_size))) + # op2.linear_constraint(linear=linear, + # sense=ConstraintSense.LE, + # rhs=self._state.b3[i]) + # + # # add quadratic constraints for + # # In the step 2, we basically need to copy all quadratic constraints of the original + # # problem, and substitute binary variables with variables 0<=z<=1. + # # The quadratic constraint can have any equality/inequality sign. + # # + # # For example: + # # Original problem: + # # Quadratic_constraint: x**2 + u**2 <= 1 + # # Vars: x binary, L <= u <= U + # # + # # Step 2: + # # Quadratic_constraint: z**2 + u**2 <= 1 + # # Vars: 0<=z<=1, L <= u <= U + # # for linear, quad, sense, rhs \ + # # in zip(self._state.op.quadratic_constraints.get_linear_components(), + # # self._state.op.quadratic_constraints.get_quadratic_components(), + # # self._state.op.quadratic_constraints.get_senses(), + # # self._state.op.quadratic_constraints.get_rhs()): + # # # types in the loop: SparsePair, SparseTriple, character, float + # # print(linear, quad, sense, rhs) + # # new_linear = SparsePair(ind=self._binary_indices_to_continuous(linear.ins), + # # val=linear.val) + # # new_quadratic = SparseTriple(ind1=self._binary_indices_to_continuous(quad.ind1), + # # ind2=self._binary_indices_to_continuous(quad.ind2), + # # val=quad.val) + # # op2.quadratic_constraints.add(lin_expr=new_linear, quad_expr=new_quadratic, + # # sense=sense, rhs=rhs) + # + # return op2 + + # def _binary_indices_to_continuous(self, binary_indices: List[int]) -> List[int]: + # # todo: implement + # return binary_indices + def _create_step2_problem(self) -> QuadraticProgram: """Creates a step 2 sub-problem. Returns: A newly created optimization problem. """ - op2 = QuadraticProgram() - - continuous_size = len(self._state.continuous_indices) - binary_size = len(self._state.binary_indices) - continuous_index = 0 - for variable in self._state.op.variables: - if variable.vartype == VarType.CONTINUOUS: - op2.continuous_var(name="u0_" + str(continuous_index + 1), - lowerbound=variable.lowerbound, upperbound=variable.upperbound) - continuous_index += 1 - - for i in range(binary_size): - op2.continuous_var(name="z0_" + str(i + 1), lowerbound=0, upperbound=1.) - - q_z = self._state.rho / 2 * np.eye(binary_size) - op2.objective.quadratic = block_diag(self._state.q1, q_z) - - linear_z = -1 * self._state.lambda_mult - self._state.rho * (self._state.x0 - self._state.y) - op2.objective.linear = np.concatenate((self._state.c1, linear_z)) - - # constraints for z. - # A1 z <= b1. - constraint_count = self._state.a1.shape[0] - for i in range(constraint_count): - linear = np.concatenate((np.zeros(continuous_size), self._state.a1[i, :])) - op2.linear_constraint(linear=linear, sense=ConstraintSense.LE, rhs=self._state.b1[i]) - - if continuous_size: - # A2 z + A3 u <= b2 - constraint_count = self._state.a2.shape[0] - for i in range(constraint_count): - linear = np.concatenate((self._state.a3[i, :], self._state.a2[i, :])) - op2.linear_constraint(linear=linear, - sense=ConstraintSense.LE, - rhs=self._state.b2[i]) - - # A4 u <= b3 - constraint_count = self._state.a4.shape[0] - for i in range(constraint_count): - linear = np.concatenate((self._state.a4[i, :], np.zeros(binary_size))) - op2.linear_constraint(linear=linear, - sense=ConstraintSense.LE, - rhs=self._state.b3[i]) - - # add quadratic constraints for - # In the step 2, we basically need to copy all quadratic constraints of the original - # problem, and substitute binary variables with variables 0<=z<=1. - # The quadratic constraint can have any equality/inequality sign. - # - # For example: - # Original problem: - # Quadratic_constraint: x**2 + u**2 <= 1 - # Vars: x binary, L <= u <= U - # - # Step 2: - # Quadratic_constraint: z**2 + u**2 <= 1 - # Vars: 0<=z<=1, L <= u <= U - # for linear, quad, sense, rhs \ - # in zip(self._state.op.quadratic_constraints.get_linear_components(), - # self._state.op.quadratic_constraints.get_quadratic_components(), - # self._state.op.quadratic_constraints.get_senses(), - # self._state.op.quadratic_constraints.get_rhs()): - # # types in the loop: SparsePair, SparseTriple, character, float - # print(linear, quad, sense, rhs) - # new_linear = SparsePair(ind=self._binary_indices_to_continuous(linear.ins), - # val=linear.val) - # new_quadratic = SparseTriple(ind1=self._binary_indices_to_continuous(quad.ind1), - # ind2=self._binary_indices_to_continuous(quad.ind2), - # val=quad.val) - # op2.quadratic_constraints.add(lin_expr=new_linear, quad_expr=new_quadratic, - # sense=sense, rhs=rhs) + op2 = copy.deepcopy(self._state.op) + # replace binary variables with the continuous ones that look like binary + # x0(bin) -> z(cts) + # u (cts) are still there unchanged + for i, var_index in enumerate(self._state.binary_indices): + variable = op2.variables[var_index] + variable.vartype = VarType.CONTINUOUS + variable.upperbound = 1. + variable.lowerbound = 0. + # replacing Q0 objective and take of min/max sense, initially we consider minimization + op2.objective.quadratic[var_index, var_index] = self._state.sense * self._state.rho / 2 + # replacing linear objective + op2.objective.linear[var_index] = self._state.sense * (-1 * self._state.lambda_mult[i] - self._state.rho * (self._state.x0[i] - self._state.y[i])) + + # remove A0 x0 = b0 constraints + for constraint in self._state.binary_equality_constraints: + op2.remove_linear_constraint(constraint.name) return op2 - def _binary_indices_to_continuous(self, binary_indices: List[int]) -> List[int]: - # todo: implement - return binary_indices - def _create_step3_problem(self) -> QuadraticProgram: """Creates a step 3 sub-problem. @@ -757,6 +784,23 @@ def _update_x0(self, op1: QuadraticProgram) -> np.ndarray: """ return np.asarray(self._qubo_optimizer.solve(op1).x) + # def _update_x1(self, op2: QuadraticProgram) -> (np.ndarray, np.ndarray): + # """Solves the Step2 QuadraticProgram via the continuous optimizer. + # + # Args: + # op2: the Step2 QuadraticProgram + # + # Returns: + # A solution of the Step2, as a pair of numpy arrays. + # First array contains the values of decision variables u, and + # second array contains the values of decision variables z. + # + # """ + # vars_op2 = self._continuous_optimizer.solve(op2).x + # vars_u = np.asarray(vars_op2[:len(self._state.continuous_indices)]) + # vars_z = np.asarray(vars_op2[len(self._state.continuous_indices):]) + # return vars_u, vars_z + def _update_x1(self, op2: QuadraticProgram) -> (np.ndarray, np.ndarray): """Solves the Step2 QuadraticProgram via the continuous optimizer. @@ -769,9 +813,11 @@ def _update_x1(self, op2: QuadraticProgram) -> (np.ndarray, np.ndarray): second array contains the values of decision variables z. """ - vars_op2 = self._continuous_optimizer.solve(op2).x - vars_u = np.asarray(vars_op2[:len(self._state.continuous_indices)]) - vars_z = np.asarray(vars_op2[len(self._state.continuous_indices):]) + vars_op2 = np.asarray(self._continuous_optimizer.solve(op2).x) + # vars_u = np.asarray(vars_op2[:len(self._state.continuous_indices)]) + # vars_z = np.asarray(vars_op2[len(self._state.continuous_indices):]) + vars_u = vars_op2.take(self._state.continuous_indices) + vars_z = vars_op2.take(self._state.binary_indices) return vars_u, vars_z def _update_y(self, op3: QuadraticProgram) -> np.ndarray: @@ -845,7 +891,7 @@ def _get_constraint_residual(self) -> float: solution = self._get_current_solution() # equality constraints cr0 = 0 - for constraint in self._state.equality_constraints: + for constraint in self._state.binary_equality_constraints: cr0 += np.abs(constraint.evaluate(solution) - constraint.rhs) # inequality constraints From e2ec8631ac00db82d09b82bbe07d5f986ce98a64 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Thu, 23 Apr 2020 18:45:20 +0100 Subject: [PATCH 282/323] step2 generation based on a copy --- .../optimization/algorithms/admm_optimizer.py | 200 +++++++++++------- 1 file changed, 121 insertions(+), 79 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 26980b0676..e592862ab7 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -23,7 +23,7 @@ from qiskit.optimization.algorithms.cplex_optimizer import CplexOptimizer from qiskit.optimization.algorithms.optimization_algorithm import (OptimizationAlgorithm, OptimizationResult) -from qiskit.optimization.problems import VarType, ConstraintSense +from qiskit.optimization.problems import VarType, ConstraintSense, LinearConstraint from qiskit.optimization.problems.quadratic_program import QuadraticProgram UPDATE_RHO_BY_TEN_PERCENT = 0 @@ -393,6 +393,17 @@ def _convert_problem_representation(self) -> None: a4 u <= b3 """ + binary_var_indices = set(self._state.binary_indices) + # separate constraints + for constraint in self._state.op.linear_constraints: + if constraint.sense == ConstraintSense.EQ: + constraint_var_indices = set(constraint.linear.to_dict().keys()) + # verify that there are only binary variables in the constraint + if constraint_var_indices.issubset(binary_var_indices): + self._state.binary_equality_constraints.append(constraint) + elif constraint.sense in (ConstraintSense.LE, ConstraintSense.GE): + self._state.inequality_constraints.append(constraint) + # objective self._state.q0 = self._get_q(self._state.binary_indices) self._state.c0 = self._get_c(self._state.binary_indices) @@ -400,17 +411,9 @@ def _convert_problem_representation(self) -> None: self._state.c1 = self._get_c(self._state.continuous_indices) # constraints self._state.a0, self._state.b0 = self._get_a0_b0() - self._state.a1, self._state.b1 = self._get_a1_b1() - self._state.a2, self._state.a3, self._state.b2 = self._get_a2_a3_b2() - self._state.a4, self._state.b3 = self._get_a4_b3() - - # separate constraints - for constraint in self._state.op.linear_constraints: - if constraint.sense == ConstraintSense.EQ: - # todo: verify that this constraint contains only binary variables - self._state.binary_equality_constraints.append(constraint) - elif constraint.sense in (ConstraintSense.LE, ConstraintSense.GE): - self._state.inequality_constraints.append(constraint) + # self._state.a1, self._state.b1 = self._get_a1_b1() + # self._state.a2, self._state.a3, self._state.b2 = self._get_a2_a3_b2() + # self._state.a4, self._state.b3 = self._get_a4_b3() def _get_q(self, variable_indices: List[int]) -> np.ndarray: """Constructs a quadratic matrix for the variables with the specified indices @@ -482,6 +485,29 @@ def _assign_row_values(self, matrix: List[List[float]], vector: List[float], matrix[-1] = [-1 * el for el in matrix[-1]] vector[-1] = -1 * vector[-1] + def _assign_row_values_2(self, matrix: List[List[float]], vector: List[float], + constraint: LinearConstraint, variable_indices: List[int]): + """Appends a row to the specified matrix and vector based on the constraint specified by + the index using specified variables. + + Args: + matrix: a matrix to extend. + vector: a vector to expand. + constraint_index: constraint index to look for. + variable_indices: variables to look for. + + Returns: + None + """ + # assign matrix row, actually pick coefficients at the positions specified in + # the variable_indices list + row = constraint.linear.to_array().take(variable_indices).tolist() + + matrix.append(row) + + # assign vector row. + vector.append(constraint.rhs) + @staticmethod def _create_ndarrays(matrix: List[List[float]], vector: List[float], size: int) \ -> (np.ndarray, np.ndarray): @@ -514,23 +540,39 @@ def _get_a0_b0(self) -> (np.ndarray, np.ndarray): matrix = [] vector = [] - index_set = set(self._state.binary_indices) - for constraint_index, constraint in enumerate(self._state.op.linear_constraints): - # we check only equality constraints here. - if constraint.sense != ConstraintSense.EQ: - continue + binary_var_indices = set(self._state.binary_indices) + # separate constraints + # for constraint in self._state.op.linear_constraints: + # if constraint.sense == ConstraintSense.EQ: + # constraint_var_indices = set(constraint.linear.to_dict().keys()) + # # verify that there are only binary variables in the constraint + # if constraint_var_indices.issubset(binary_var_indices): + # self._state.binary_equality_constraints.append(constraint) + # elif constraint.sense in (ConstraintSense.LE, ConstraintSense.GE): + # self._state.inequality_constraints.append(constraint) + + # index_set = set(self._state.binary_indices) + # for constraint_index, constraint in enumerate(self._state.op.linear_constraints): + # # we check only equality constraints here. + # if constraint.sense != ConstraintSense.EQ: + # continue + # + # constraint_indices = set( + # self._state.op.linear_constraints[constraint_index].linear.to_dict().keys()) + # # verify that there are only binary variables in the constraint + # if constraint_indices.issubset(index_set): + # print("Constraint name: {}".format(constraint.name)) + # self._assign_row_values(matrix, vector, + # constraint_index, self._state.binary_indices) + # else: + # raise ValueError( + # "Linear constraint with the 'E' sense must contain only binary variables, " + # "constraint indices: {}, binary variable indices: {}".format( + # constraint_indices, self._state.binary_indices)) - constraint_indices = set( - self._state.op.linear_constraints[constraint_index].linear.to_dict().keys()) - # verify that there are only binary variables in the constraint - if constraint_indices.issubset(index_set): - self._assign_row_values(matrix, vector, - constraint_index, self._state.binary_indices) - else: - raise ValueError( - "Linear constraint with the 'E' sense must contain only binary variables, " - "constraint indices: {}, binary variable indices: {}".format( - constraint_indices, self._state.binary_indices)) + for constraint in self._state.binary_equality_constraints: + print("Constraint name: {}".format(constraint.name)) + self._assign_row_values_2(matrix, vector, constraint, self._state.binary_indices) return self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) @@ -560,57 +602,57 @@ def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) \ return matrix, vector - def _get_a1_b1(self) -> (np.ndarray, np.ndarray): - """Constructs a matrix and a vector from the constraints in a form of Ax <= b, where - x is a vector of binary variables. - - Returns: - A numpy based representation of the matrix and the vector. - """ - matrix, vector = self._get_inequality_matrix_and_vector(self._state.binary_indices) - return self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) - - def _get_a4_b3(self) -> (np.ndarray, np.ndarray): - """Constructs a matrix and a vector from the constraints in a form of Au <= b, where - u is a vector of continuous variables. - - Returns: - A numpy based representation of the matrix and the vector. - """ - matrix, vector = self._get_inequality_matrix_and_vector(self._state.continuous_indices) - return self._create_ndarrays(matrix, vector, len(self._state.continuous_indices)) - - def _get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): - """Constructs matrices and a vector from the constraints in a form of A_2x + A_3u <= b, - where x is a vector of binary variables and u is a vector of continuous variables. - - Returns: - A numpy representation of two matrices and one vector. - """ - matrix = [] - vector = [] - - binary_index_set = set(self._state.binary_indices) - continuous_index_set = set(self._state.continuous_indices) - all_variables = self._state.binary_indices + self._state.continuous_indices - for constraint_index, constraint in enumerate(self._state.op.linear_constraints): - if constraint.sense in [ConstraintSense.EQ]: - # TODO: Ranged constraints should be supported as well - continue - # sense either G or L. - constraint_indices = set( - self._state.op.linear_constraints[constraint_index].linear.to_dict().keys()) - # we must have a least one binary and one continuous variable, - # otherwise it is another type of constraints. - if len(constraint_indices & binary_index_set) != 0 and len( - constraint_indices & continuous_index_set) != 0: - self._assign_row_values(matrix, vector, constraint_index, all_variables) - - matrix, b_2 = self._create_ndarrays(matrix, vector, len(all_variables)) - # a2 - a_2 = matrix[:, 0:len(self._state.binary_indices)] - a_3 = matrix[:, len(self._state.binary_indices):] - return a_2, a_3, b_2 + # def _get_a1_b1(self) -> (np.ndarray, np.ndarray): + # """Constructs a matrix and a vector from the constraints in a form of Ax <= b, where + # x is a vector of binary variables. + # + # Returns: + # A numpy based representation of the matrix and the vector. + # """ + # matrix, vector = self._get_inequality_matrix_and_vector(self._state.binary_indices) + # return self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) + # + # def _get_a4_b3(self) -> (np.ndarray, np.ndarray): + # """Constructs a matrix and a vector from the constraints in a form of Au <= b, where + # u is a vector of continuous variables. + # + # Returns: + # A numpy based representation of the matrix and the vector. + # """ + # matrix, vector = self._get_inequality_matrix_and_vector(self._state.continuous_indices) + # return self._create_ndarrays(matrix, vector, len(self._state.continuous_indices)) + # + # def _get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): + # """Constructs matrices and a vector from the constraints in a form of A_2x + A_3u <= b, + # where x is a vector of binary variables and u is a vector of continuous variables. + # + # Returns: + # A numpy representation of two matrices and one vector. + # """ + # matrix = [] + # vector = [] + # + # binary_index_set = set(self._state.binary_indices) + # continuous_index_set = set(self._state.continuous_indices) + # all_variables = self._state.binary_indices + self._state.continuous_indices + # for constraint_index, constraint in enumerate(self._state.op.linear_constraints): + # if constraint.sense in [ConstraintSense.EQ]: + # # TODO: Ranged constraints should be supported as well + # continue + # # sense either G or L. + # constraint_indices = set( + # self._state.op.linear_constraints[constraint_index].linear.to_dict().keys()) + # # we must have a least one binary and one continuous variable, + # # otherwise it is another type of constraints. + # if len(constraint_indices & binary_index_set) != 0 and len( + # constraint_indices & continuous_index_set) != 0: + # self._assign_row_values(matrix, vector, constraint_index, all_variables) + # + # matrix, b_2 = self._create_ndarrays(matrix, vector, len(all_variables)) + # # a2 + # a_2 = matrix[:, 0:len(self._state.binary_indices)] + # a_3 = matrix[:, len(self._state.binary_indices):] + # return a_2, a_3, b_2 def _create_step1_problem(self) -> QuadraticProgram: """Creates a step 1 sub-problem. From 29a1f5e2e262756a5ac385f7f8c784d59cb99658 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Thu, 23 Apr 2020 18:46:36 +0100 Subject: [PATCH 283/323] step2 generation based on a copy --- .../optimization/algorithms/admm_optimizer.py | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index e592862ab7..19fbb81bab 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -479,11 +479,11 @@ def _assign_row_values(self, matrix: List[List[float]], vector: List[float], vector.append(self._state.op.linear_constraints[constraint_index].rhs) # flip the sign if constraint is G, we want L constraints. - if self._state.op.linear_constraints[constraint_index].sense == ConstraintSense.GE: - # invert the sign to make constraint "L". - # we invert only last row/last element - matrix[-1] = [-1 * el for el in matrix[-1]] - vector[-1] = -1 * vector[-1] + # if self._state.op.linear_constraints[constraint_index].sense == ConstraintSense.GE: + # # invert the sign to make constraint "L". + # # we invert only last row/last element + # matrix[-1] = [-1 * el for el in matrix[-1]] + # vector[-1] = -1 * vector[-1] def _assign_row_values_2(self, matrix: List[List[float]], vector: List[float], constraint: LinearConstraint, variable_indices: List[int]): @@ -576,31 +576,31 @@ def _get_a0_b0(self) -> (np.ndarray, np.ndarray): return self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) - def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) \ - -> (List[List[float]], List[float]): - """Constructs a matrix and a vector from the constraints in a form of Ax <= b, where - x is a vector of variables specified by the indices. - - Args: - variable_indices: variable indices to look for. - - Returns: - A list based representation of the matrix and the vector. - """ - matrix = [] - vector = [] - - index_set = set(variable_indices) - for constraint_index, constraint in enumerate(self._state.op.linear_constraints): - if constraint.sense in [ConstraintSense.EQ]: - # TODO: Ranged constraints should be supported - continue - constraint_indices = set( - self._state.op.linear_constraints[constraint_index].linear.to_dict().keys()) - if constraint_indices.issubset(index_set): - self._assign_row_values(matrix, vector, constraint_index, variable_indices) - - return matrix, vector + # def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) \ + # -> (List[List[float]], List[float]): + # """Constructs a matrix and a vector from the constraints in a form of Ax <= b, where + # x is a vector of variables specified by the indices. + # + # Args: + # variable_indices: variable indices to look for. + # + # Returns: + # A list based representation of the matrix and the vector. + # """ + # matrix = [] + # vector = [] + # + # index_set = set(variable_indices) + # for constraint_index, constraint in enumerate(self._state.op.linear_constraints): + # if constraint.sense in [ConstraintSense.EQ]: + # # TODO: Ranged constraints should be supported + # continue + # constraint_indices = set( + # self._state.op.linear_constraints[constraint_index].linear.to_dict().keys()) + # if constraint_indices.issubset(index_set): + # self._assign_row_values(matrix, vector, constraint_index, variable_indices) + # + # return matrix, vector # def _get_a1_b1(self) -> (np.ndarray, np.ndarray): # """Constructs a matrix and a vector from the constraints in a form of Ax <= b, where From 745fbc395e231ad77c2639968e42deb0f6bbbbf6 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Thu, 23 Apr 2020 19:01:10 +0100 Subject: [PATCH 284/323] formatting, renaming, minor changes --- .../optimization/algorithms/admm_optimizer.py | 247 +----------------- 1 file changed, 1 insertion(+), 246 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 19fbb81bab..f88615af4d 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -411,9 +411,6 @@ def _convert_problem_representation(self) -> None: self._state.c1 = self._get_c(self._state.continuous_indices) # constraints self._state.a0, self._state.b0 = self._get_a0_b0() - # self._state.a1, self._state.b1 = self._get_a1_b1() - # self._state.a2, self._state.a3, self._state.b2 = self._get_a2_a3_b2() - # self._state.a4, self._state.b3 = self._get_a4_b3() def _get_q(self, variable_indices: List[int]) -> np.ndarray: """Constructs a quadratic matrix for the variables with the specified indices @@ -455,37 +452,6 @@ def _get_c(self, variable_indices: List[int]) -> np.ndarray: return c def _assign_row_values(self, matrix: List[List[float]], vector: List[float], - constraint_index: int, variable_indices: List[int]): - """Appends a row to the specified matrix and vector based on the constraint specified by - the index using specified variables. - - Args: - matrix: a matrix to extend. - vector: a vector to expand. - constraint_index: constraint index to look for. - variable_indices: variables to look for. - - Returns: - None - """ - # assign matrix row, actually pick coefficients at the positions specified in - # the variable_indices list - row = self._state.op.linear_constraints[constraint_index].linear.to_array().take( - variable_indices).tolist() - - matrix.append(row) - - # assign vector row. - vector.append(self._state.op.linear_constraints[constraint_index].rhs) - - # flip the sign if constraint is G, we want L constraints. - # if self._state.op.linear_constraints[constraint_index].sense == ConstraintSense.GE: - # # invert the sign to make constraint "L". - # # we invert only last row/last element - # matrix[-1] = [-1 * el for el in matrix[-1]] - # vector[-1] = -1 * vector[-1] - - def _assign_row_values_2(self, matrix: List[List[float]], vector: List[float], constraint: LinearConstraint, variable_indices: List[int]): """Appends a row to the specified matrix and vector based on the constraint specified by the index using specified variables. @@ -540,120 +506,12 @@ def _get_a0_b0(self) -> (np.ndarray, np.ndarray): matrix = [] vector = [] - binary_var_indices = set(self._state.binary_indices) - # separate constraints - # for constraint in self._state.op.linear_constraints: - # if constraint.sense == ConstraintSense.EQ: - # constraint_var_indices = set(constraint.linear.to_dict().keys()) - # # verify that there are only binary variables in the constraint - # if constraint_var_indices.issubset(binary_var_indices): - # self._state.binary_equality_constraints.append(constraint) - # elif constraint.sense in (ConstraintSense.LE, ConstraintSense.GE): - # self._state.inequality_constraints.append(constraint) - - # index_set = set(self._state.binary_indices) - # for constraint_index, constraint in enumerate(self._state.op.linear_constraints): - # # we check only equality constraints here. - # if constraint.sense != ConstraintSense.EQ: - # continue - # - # constraint_indices = set( - # self._state.op.linear_constraints[constraint_index].linear.to_dict().keys()) - # # verify that there are only binary variables in the constraint - # if constraint_indices.issubset(index_set): - # print("Constraint name: {}".format(constraint.name)) - # self._assign_row_values(matrix, vector, - # constraint_index, self._state.binary_indices) - # else: - # raise ValueError( - # "Linear constraint with the 'E' sense must contain only binary variables, " - # "constraint indices: {}, binary variable indices: {}".format( - # constraint_indices, self._state.binary_indices)) - for constraint in self._state.binary_equality_constraints: print("Constraint name: {}".format(constraint.name)) - self._assign_row_values_2(matrix, vector, constraint, self._state.binary_indices) + self._assign_row_values(matrix, vector, constraint, self._state.binary_indices) return self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) - # def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) \ - # -> (List[List[float]], List[float]): - # """Constructs a matrix and a vector from the constraints in a form of Ax <= b, where - # x is a vector of variables specified by the indices. - # - # Args: - # variable_indices: variable indices to look for. - # - # Returns: - # A list based representation of the matrix and the vector. - # """ - # matrix = [] - # vector = [] - # - # index_set = set(variable_indices) - # for constraint_index, constraint in enumerate(self._state.op.linear_constraints): - # if constraint.sense in [ConstraintSense.EQ]: - # # TODO: Ranged constraints should be supported - # continue - # constraint_indices = set( - # self._state.op.linear_constraints[constraint_index].linear.to_dict().keys()) - # if constraint_indices.issubset(index_set): - # self._assign_row_values(matrix, vector, constraint_index, variable_indices) - # - # return matrix, vector - - # def _get_a1_b1(self) -> (np.ndarray, np.ndarray): - # """Constructs a matrix and a vector from the constraints in a form of Ax <= b, where - # x is a vector of binary variables. - # - # Returns: - # A numpy based representation of the matrix and the vector. - # """ - # matrix, vector = self._get_inequality_matrix_and_vector(self._state.binary_indices) - # return self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) - # - # def _get_a4_b3(self) -> (np.ndarray, np.ndarray): - # """Constructs a matrix and a vector from the constraints in a form of Au <= b, where - # u is a vector of continuous variables. - # - # Returns: - # A numpy based representation of the matrix and the vector. - # """ - # matrix, vector = self._get_inequality_matrix_and_vector(self._state.continuous_indices) - # return self._create_ndarrays(matrix, vector, len(self._state.continuous_indices)) - # - # def _get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): - # """Constructs matrices and a vector from the constraints in a form of A_2x + A_3u <= b, - # where x is a vector of binary variables and u is a vector of continuous variables. - # - # Returns: - # A numpy representation of two matrices and one vector. - # """ - # matrix = [] - # vector = [] - # - # binary_index_set = set(self._state.binary_indices) - # continuous_index_set = set(self._state.continuous_indices) - # all_variables = self._state.binary_indices + self._state.continuous_indices - # for constraint_index, constraint in enumerate(self._state.op.linear_constraints): - # if constraint.sense in [ConstraintSense.EQ]: - # # TODO: Ranged constraints should be supported as well - # continue - # # sense either G or L. - # constraint_indices = set( - # self._state.op.linear_constraints[constraint_index].linear.to_dict().keys()) - # # we must have a least one binary and one continuous variable, - # # otherwise it is another type of constraints. - # if len(constraint_indices & binary_index_set) != 0 and len( - # constraint_indices & continuous_index_set) != 0: - # self._assign_row_values(matrix, vector, constraint_index, all_variables) - # - # matrix, b_2 = self._create_ndarrays(matrix, vector, len(all_variables)) - # # a2 - # a_2 = matrix[:, 0:len(self._state.binary_indices)] - # a_3 = matrix[:, len(self._state.binary_indices):] - # return a_2, a_3, b_2 - def _create_step1_problem(self) -> QuadraticProgram: """Creates a step 1 sub-problem. @@ -682,90 +540,6 @@ def _create_step1_problem(self) -> QuadraticProgram: op1.objective.linear = linear_objective return op1 - # def _create_step2_problem(self) -> QuadraticProgram: - # """Creates a step 2 sub-problem. - # - # Returns: - # A newly created optimization problem. - # """ - # op2 = QuadraticProgram() - # - # continuous_size = len(self._state.continuous_indices) - # binary_size = len(self._state.binary_indices) - # continuous_index = 0 - # for variable in self._state.op.variables: - # if variable.vartype == VarType.CONTINUOUS: - # op2.continuous_var(name="u0_" + str(continuous_index + 1), - # lowerbound=variable.lowerbound, upperbound=variable.upperbound) - # continuous_index += 1 - # - # for i in range(binary_size): - # op2.continuous_var(name="z0_" + str(i + 1), lowerbound=0, upperbound=1.) - # - # q_z = self._state.rho / 2 * np.eye(binary_size) - # op2.objective.quadratic = block_diag(self._state.q1, q_z) - # - # linear_z = -1 * self._state.lambda_mult - self._state.rho * (self._state.x0 - self._state.y) - # op2.objective.linear = np.concatenate((self._state.c1, linear_z)) - # - # # constraints for z. - # # A1 z <= b1. - # constraint_count = self._state.a1.shape[0] - # for i in range(constraint_count): - # linear = np.concatenate((np.zeros(continuous_size), self._state.a1[i, :])) - # op2.linear_constraint(linear=linear, sense=ConstraintSense.LE, rhs=self._state.b1[i]) - # - # if continuous_size: - # # A2 z + A3 u <= b2 - # constraint_count = self._state.a2.shape[0] - # for i in range(constraint_count): - # linear = np.concatenate((self._state.a3[i, :], self._state.a2[i, :])) - # op2.linear_constraint(linear=linear, - # sense=ConstraintSense.LE, - # rhs=self._state.b2[i]) - # - # # A4 u <= b3 - # constraint_count = self._state.a4.shape[0] - # for i in range(constraint_count): - # linear = np.concatenate((self._state.a4[i, :], np.zeros(binary_size))) - # op2.linear_constraint(linear=linear, - # sense=ConstraintSense.LE, - # rhs=self._state.b3[i]) - # - # # add quadratic constraints for - # # In the step 2, we basically need to copy all quadratic constraints of the original - # # problem, and substitute binary variables with variables 0<=z<=1. - # # The quadratic constraint can have any equality/inequality sign. - # # - # # For example: - # # Original problem: - # # Quadratic_constraint: x**2 + u**2 <= 1 - # # Vars: x binary, L <= u <= U - # # - # # Step 2: - # # Quadratic_constraint: z**2 + u**2 <= 1 - # # Vars: 0<=z<=1, L <= u <= U - # # for linear, quad, sense, rhs \ - # # in zip(self._state.op.quadratic_constraints.get_linear_components(), - # # self._state.op.quadratic_constraints.get_quadratic_components(), - # # self._state.op.quadratic_constraints.get_senses(), - # # self._state.op.quadratic_constraints.get_rhs()): - # # # types in the loop: SparsePair, SparseTriple, character, float - # # print(linear, quad, sense, rhs) - # # new_linear = SparsePair(ind=self._binary_indices_to_continuous(linear.ins), - # # val=linear.val) - # # new_quadratic = SparseTriple(ind1=self._binary_indices_to_continuous(quad.ind1), - # # ind2=self._binary_indices_to_continuous(quad.ind2), - # # val=quad.val) - # # op2.quadratic_constraints.add(lin_expr=new_linear, quad_expr=new_quadratic, - # # sense=sense, rhs=rhs) - # - # return op2 - - # def _binary_indices_to_continuous(self, binary_indices: List[int]) -> List[int]: - # # todo: implement - # return binary_indices - def _create_step2_problem(self) -> QuadraticProgram: """Creates a step 2 sub-problem. @@ -826,23 +600,6 @@ def _update_x0(self, op1: QuadraticProgram) -> np.ndarray: """ return np.asarray(self._qubo_optimizer.solve(op1).x) - # def _update_x1(self, op2: QuadraticProgram) -> (np.ndarray, np.ndarray): - # """Solves the Step2 QuadraticProgram via the continuous optimizer. - # - # Args: - # op2: the Step2 QuadraticProgram - # - # Returns: - # A solution of the Step2, as a pair of numpy arrays. - # First array contains the values of decision variables u, and - # second array contains the values of decision variables z. - # - # """ - # vars_op2 = self._continuous_optimizer.solve(op2).x - # vars_u = np.asarray(vars_op2[:len(self._state.continuous_indices)]) - # vars_z = np.asarray(vars_op2[len(self._state.continuous_indices):]) - # return vars_u, vars_z - def _update_x1(self, op2: QuadraticProgram) -> (np.ndarray, np.ndarray): """Solves the Step2 QuadraticProgram via the continuous optimizer. @@ -856,8 +613,6 @@ def _update_x1(self, op2: QuadraticProgram) -> (np.ndarray, np.ndarray): """ vars_op2 = np.asarray(self._continuous_optimizer.solve(op2).x) - # vars_u = np.asarray(vars_op2[:len(self._state.continuous_indices)]) - # vars_z = np.asarray(vars_op2[len(self._state.continuous_indices):]) vars_u = vars_op2.take(self._state.continuous_indices) vars_z = vars_op2.take(self._state.binary_indices) return vars_u, vars_z From c0d81a8ff0b32f83d2fa3f75773644995e591ba7 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Fri, 24 Apr 2020 10:00:39 +0200 Subject: [PATCH 285/323] fixing converters / compatibility checks of algorithms --- .../optimization/algorithms/admm_optimizer.py | 33 ++++++++++++------- .../algorithms/grover_optimizer.py | 11 +++++-- .../algorithms/minimum_eigen_optimizer.py | 9 +++++ .../recursive_minimum_eigen_optimizer.py | 6 ++++ .../converters/inequality_to_equality.py | 9 ++--- ...dratic_program_to_negative_value_oracle.py | 14 ++++++-- ...dratic_program_to_negative_value_oracle.py | 10 ++++-- 7 files changed, 65 insertions(+), 27 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 26a97cf6be..c7303ebe28 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -19,9 +19,12 @@ import numpy as np from scipy.linalg import block_diag + +from qiskit.optimization import QiskitOptimizationError from qiskit.optimization.algorithms.cplex_optimizer import CplexOptimizer from qiskit.optimization.algorithms.optimization_algorithm import (OptimizationAlgorithm, OptimizationResult) +from qiskit.optimization.converters import IntegerToBinary from qiskit.optimization.problems import VarType, ConstraintSense from qiskit.optimization.problems.quadratic_program import QuadraticProgram @@ -217,19 +220,14 @@ def get_compatibility_msg(self, problem: QuadraticProgram) -> Optional[str]: msg = '' - # 1. only binary and continuous variables are supported - for variable in problem.variables: - if variable.vartype not in (VarType.BINARY, VarType.CONTINUOUS): - # variable is not binary and not continuous. - msg += 'Only binary and continuous variables are supported. ' - - binary_indices = self._get_variable_indices(problem, VarType.BINARY) + # 1. get bin/int and continuous variable indices + bin_int_indices = self._get_variable_indices(problem, VarType.BINARY) continuous_indices = self._get_variable_indices(problem, VarType.CONTINUOUS) # 2. binary and continuous variables are separable in objective - for binary_index in binary_indices: + for bin_int_index in bin_int_indices: for continuous_index in continuous_indices: - coeff = problem.objective.quadratic[binary_index, continuous_index] + coeff = problem.objective.quadratic[bin_int_index, continuous_index] if coeff != 0: # binary and continuous vars are mixed. msg += 'Binary and continuous variables are not separable in the objective. ' @@ -255,12 +253,21 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: Raises: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ + # check compatibility and raise exception if incompatible + msg = self.get_compatibility_msg(problem) + if len(msg) > 0: + raise QiskitOptimizationError('Incompatible problem: {}'.format(msg)) + + # map integer variables to binary variables + int2bin = IntegerToBinary() + problem_ = int2bin.encode(problem) + # parse problem and convert to an ADMM specific representation. - binary_indices = self._get_variable_indices(problem, VarType.BINARY) - continuous_indices = self._get_variable_indices(problem, VarType.CONTINUOUS) + binary_indices = self._get_variable_indices(problem_, VarType.BINARY) + continuous_indices = self._get_variable_indices(problem_, VarType.CONTINUOUS) # create our computation state. - self._state = ADMMState(problem, binary_indices, + self._state = ADMMState(problem_, binary_indices, continuous_indices, self._params.rho_initial) # convert optimization problem to a set of matrices and vector that are used @@ -329,6 +336,8 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: # third parameter is our internal state of computations. result = ADMMOptimizerResult(solution, objective_value, self._state) + result = int2bin.decode(result) + # debug self._log.debug("solution=%s, objective=%s at iteration=%s", solution, objective_value, iteration) diff --git a/qiskit/optimization/algorithms/grover_optimizer.py b/qiskit/optimization/algorithms/grover_optimizer.py index 9659cf0350..f7faef6779 100644 --- a/qiskit/optimization/algorithms/grover_optimizer.py +++ b/qiskit/optimization/algorithms/grover_optimizer.py @@ -20,6 +20,7 @@ import random import numpy as np from qiskit.aqua import QuantumInstance +from qiskit.optimization import QiskitOptimizationError from qiskit.optimization.algorithms import OptimizationAlgorithm from qiskit.optimization.problems import QuadraticProgram from qiskit.optimization.converters import (QuadraticProgramToQubo, @@ -67,10 +68,10 @@ def get_compatibility_msg(self, problem: QuadraticProgram) -> str: return QuadraticProgramToQubo.get_compatibility_msg(problem) def solve(self, problem: QuadraticProgram) -> OptimizationResult: - """Tries to solves the given problem using the optimizer. + """Tries to solves the given problem using the grover optimizer. - Runs the optimizer to try to solve the optimization problem. If problem is not convex, - this optimizer may raise an exception due to incompatibility, depending on the settings. + Runs the optimizer to try to solve the optimization problem. If the problem cannot be, + converted to a QUBO, this optimizer raises an exception due to incompatibility. Args: problem: The problem to be solved. @@ -81,6 +82,10 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: Raises: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ + # check compatibility and raise exception if incompatible + msg = self.get_compatibility_msg(problem) + if len(msg) > 0: + raise QiskitOptimizationError('Incompatible problem: {}'.format(msg)) # convert problem to QUBO qubo_converter = QuadraticProgramToQubo() diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index 1e4fa8c1a7..b54797e534 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -26,6 +26,7 @@ from ..problems.quadratic_program import QuadraticProgram from ..converters.quadratic_program_to_operator import QuadraticProgramToOperator from ..converters.quadratic_program_to_qubo import QuadraticProgramToQubo +from .. import QiskitOptimizationError class MinimumEigenOptimizerResult(OptimizationResult): @@ -127,7 +128,15 @@ def solve(self, problem: QuadraticProgram) -> MinimumEigenOptimizerResult: Returns: The result of the optimizer applied to the problem. + + Raises: + QiskitOptimizationError: If problem not compatible. """ + # check compatibility and raise exception if incompatible + msg = self.get_compatibility_msg(problem) + if len(msg) > 0: + raise QiskitOptimizationError('Incompatible problem: {}'.format(msg)) + # convert problem to QUBO qubo_converter = QuadraticProgramToQubo() problem_ = qubo_converter.encode(problem) diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 84feaeef8c..4240d06dfa 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -111,8 +111,14 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: The result of the optimizer applied to the problem. Raises: + QiskitOptimizationError: Incompatible problem. QiskitOptimizationError: Infeasible due to variable substitution """ + # check compatibility and raise exception if incompatible + msg = self.get_compatibility_msg(problem) + if len(msg) > 0: + raise QiskitOptimizationError('Incompatible problem: {}'.format(msg)) + # convert problem to QUBO, this implicitly checks if the problem is compatible qubo_converter = QuadraticProgramToQubo() problem_ = qubo_converter.encode(problem) diff --git a/qiskit/optimization/converters/inequality_to_equality.py b/qiskit/optimization/converters/inequality_to_equality.py index 909811622d..0a4cb3db5d 100644 --- a/qiskit/optimization/converters/inequality_to_equality.py +++ b/qiskit/optimization/converters/inequality_to_equality.py @@ -343,18 +343,13 @@ def decode(self, result: 'OptimizationResult') -> 'OptimizationResult': Returns: The result of the original problem. """ - from ..algorithms.optimization_algorithm import OptimizationResult - new_result = OptimizationResult() # convert the optimization result into that of the original problem names = [x.name for x in self._dst.variables] vals = result.x new_vals = self._decode_var(names, vals) - new_result.x = new_vals - new_result.fval = result.fval - new_result.status = result.status - new_result.results = result.results - return new_result + result.x = new_vals + return result def _decode_var(self, names, vals) -> List[int]: # decode slack variables diff --git a/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py b/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py index efac7d57ca..ad8dcf890e 100644 --- a/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py @@ -20,8 +20,10 @@ import numpy as np from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister +from qiskit.circuit.library import QFT + from qiskit.aqua.components.initial_states import Custom -from qiskit.aqua.components.iqfts import Standard as IQFT +# from qiskit.aqua.components.iqfts import Standard as IQFT from qiskit.aqua.components.oracles import CustomCircuitOracle from qiskit.optimization.problems import QuadraticProgram @@ -168,8 +170,14 @@ def _build_operator(self, func_dict: Dict[Union[int, Tuple[int, int]], int]) -> a_v, b_v) # Add IQFT. - iqft = IQFT(self._num_value) + # iqft = IQFT(self._num_value) value = [key_val[v] for v in range(self._num_key, self._num_key + self._num_value)] - iqft.construct_circuit(qubits=value, circuit=circuit) + # iqft.construct_circuit(qubits=value, circuit=circuit) + + print(self._num_value) + print(value) + + qft = QFT(self._num_value, do_swaps=False) + circuit.append(qft.inverse(), value) return circuit diff --git a/test/optimization/test_quadratic_program_to_negative_value_oracle.py b/test/optimization/test_quadratic_program_to_negative_value_oracle.py index ee26ba9dfd..9f9aa33f34 100644 --- a/test/optimization/test_quadratic_program_to_negative_value_oracle.py +++ b/test/optimization/test_quadratic_program_to_negative_value_oracle.py @@ -38,18 +38,24 @@ def _validate_function(self, func_dict, problem): self.assertEqual(problem.objective.constant, func_dict[key]) def _validate_operator(self, func_dict, n_key, n_value, operator): + # Get expected results. solutions = GroverOptimizer._get_qubo_solutions(func_dict, n_key, print_solutions=False) + print(solutions) # Run the state preparation operator A and observe results. circuit = operator._circuit qc = QuantumCircuit() + circuit hist = self._measure(qc, n_key, n_value) + print(hist) # Validate operator A. for label in hist: key = int(label[:n_key], 2) + print(key, label[:n_key]) value = self._bin_to_int(label[n_key:n_key + n_value], n_value) + print(key, label[:n_key], self._bin_to_int(label[n_key:n_key + n_value], n_value), value) + print() self.assertEqual(int(solutions[key]), value) @staticmethod @@ -101,7 +107,7 @@ def test_optnvo_3_linear_2_quadratic_no_constant(self): except NameError as ex: self.skipTest(str(ex)) - def test_optnvo_4_key_all_negative(self): + def _test_optnvo_4_key_all_negative(self): """Test with all negative values.""" # Circuit parameters. try: @@ -124,7 +130,7 @@ def test_optnvo_4_key_all_negative(self): except NameError as ex: self.skipTest(str(ex)) - def test_optnvo_6_key(self): + def _test_optnvo_6_key(self): """Test with 6 linear coefficients, negative quadratics, no constant.""" # Circuit parameters. try: From 72caac1bf64e404f412d92e13b148fe6f0d3ecfa Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Fri, 24 Apr 2020 10:05:47 +0200 Subject: [PATCH 286/323] clean grover code (remove debugging lines) --- .../converters/quadratic_program_to_negative_value_oracle.py | 4 ---- .../test_quadratic_program_to_negative_value_oracle.py | 5 ----- 2 files changed, 9 deletions(-) diff --git a/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py b/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py index ad8dcf890e..b5ccf41a7e 100644 --- a/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py @@ -173,10 +173,6 @@ def _build_operator(self, func_dict: Dict[Union[int, Tuple[int, int]], int]) -> # iqft = IQFT(self._num_value) value = [key_val[v] for v in range(self._num_key, self._num_key + self._num_value)] # iqft.construct_circuit(qubits=value, circuit=circuit) - - print(self._num_value) - print(value) - qft = QFT(self._num_value, do_swaps=False) circuit.append(qft.inverse(), value) diff --git a/test/optimization/test_quadratic_program_to_negative_value_oracle.py b/test/optimization/test_quadratic_program_to_negative_value_oracle.py index 9f9aa33f34..855091d909 100644 --- a/test/optimization/test_quadratic_program_to_negative_value_oracle.py +++ b/test/optimization/test_quadratic_program_to_negative_value_oracle.py @@ -41,21 +41,16 @@ def _validate_operator(self, func_dict, n_key, n_value, operator): # Get expected results. solutions = GroverOptimizer._get_qubo_solutions(func_dict, n_key, print_solutions=False) - print(solutions) # Run the state preparation operator A and observe results. circuit = operator._circuit qc = QuantumCircuit() + circuit hist = self._measure(qc, n_key, n_value) - print(hist) # Validate operator A. for label in hist: key = int(label[:n_key], 2) - print(key, label[:n_key]) value = self._bin_to_int(label[n_key:n_key + n_value], n_value) - print(key, label[:n_key], self._bin_to_int(label[n_key:n_key + n_value], n_value), value) - print() self.assertEqual(int(solutions[key]), value) @staticmethod From a43cf7ec97656b71ab56c8e54bab395bbbdee3f3 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 24 Apr 2020 10:39:37 +0200 Subject: [PATCH 287/323] fix IQFT and random seeds --- .../quadratic_program_to_negative_value_oracle.py | 11 +++++++---- test/optimization/test_grover_optimizer.py | 15 ++++++++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py b/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py index efac7d57ca..0d4ade34f4 100644 --- a/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py +++ b/qiskit/optimization/converters/quadratic_program_to_negative_value_oracle.py @@ -20,8 +20,8 @@ import numpy as np from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister +from qiskit.circuit.library import QFT from qiskit.aqua.components.initial_states import Custom -from qiskit.aqua.components.iqfts import Standard as IQFT from qiskit.aqua.components.oracles import CustomCircuitOracle from qiskit.optimization.problems import QuadraticProgram @@ -167,9 +167,12 @@ def _build_operator(self, func_dict: Dict[Union[int, Tuple[int, int]], int]) -> circuit.mcu1(1 / 2 ** self._num_value * 2 * np.pi * 2 ** i * v, a_v, b_v) - # Add IQFT. - iqft = IQFT(self._num_value) + # Add IQFT. Adding swaps at the end of the IQFT, not the beginning. + iqft = QFT(self._num_value, do_swaps=False).inverse() value = [key_val[v] for v in range(self._num_key, self._num_key + self._num_value)] - iqft.construct_circuit(qubits=value, circuit=circuit) + circuit.compose(iqft, qubits=value, inplace=True) + + for i in range(len(value) // 2): + circuit.swap(value[i], value[-(i + 1)]) return circuit diff --git a/test/optimization/test_grover_optimizer.py b/test/optimization/test_grover_optimizer.py index 63fee76ea5..0ba09b9191 100644 --- a/test/optimization/test_grover_optimizer.py +++ b/test/optimization/test_grover_optimizer.py @@ -19,7 +19,8 @@ import random import numpy from docplex.mp.model import Model -from qiskit.aqua import aqua_globals +from qiskit import Aer +from qiskit.aqua import aqua_globals, QuantumInstance from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.optimization.algorithms import GroverOptimizer, MinimumEigenOptimizer from qiskit.optimization.problems import QuadraticProgram @@ -72,9 +73,13 @@ def test_qubo_gas_int_simple(self): op = QuadraticProgram() op.from_docplex(model) + # Quantum Instance. + q_instance = QuantumInstance(Aer.get_backend('statevector_simulator'), + seed_simulator=921, seed_transpiler=200) + # Get the optimum key and value. n_iter = 8 - gmf = GroverOptimizer(4, num_iterations=n_iter) + gmf = GroverOptimizer(4, num_iterations=n_iter, quantum_instance=q_instance) results = gmf.solve(op) self.validate_results(op, results) @@ -90,9 +95,13 @@ def test_qubo_gas_int_paper_example(self): op = QuadraticProgram() op.from_docplex(model) + # Quantum Instance. + q_instance = QuantumInstance(Aer.get_backend('statevector_simulator'), + seed_simulator=921, seed_transpiler=200) + # Get the optimum key and value. n_iter = 10 - gmf = GroverOptimizer(6, num_iterations=n_iter) + gmf = GroverOptimizer(6, num_iterations=n_iter, quantum_instance=q_instance) results = gmf.solve(op) self.validate_results(op, results) From 9668020a9932e63fe0e86dbd81910f92e1b0134e Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Fri, 24 Apr 2020 10:44:31 +0200 Subject: [PATCH 288/323] Update test_quadratic_program_to_negative_value_oracle.py --- .../test_quadratic_program_to_negative_value_oracle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/optimization/test_quadratic_program_to_negative_value_oracle.py b/test/optimization/test_quadratic_program_to_negative_value_oracle.py index 855091d909..678fb54692 100644 --- a/test/optimization/test_quadratic_program_to_negative_value_oracle.py +++ b/test/optimization/test_quadratic_program_to_negative_value_oracle.py @@ -102,7 +102,7 @@ def test_optnvo_3_linear_2_quadratic_no_constant(self): except NameError as ex: self.skipTest(str(ex)) - def _test_optnvo_4_key_all_negative(self): + def test_optnvo_4_key_all_negative(self): """Test with all negative values.""" # Circuit parameters. try: @@ -125,7 +125,7 @@ def _test_optnvo_4_key_all_negative(self): except NameError as ex: self.skipTest(str(ex)) - def _test_optnvo_6_key(self): + def test_optnvo_6_key(self): """Test with 6 linear coefficients, negative quadratics, no constant.""" # Circuit parameters. try: From 281c5c5811e7e9a0071f47ddc9a9c55ce86944de Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Fri, 24 Apr 2020 11:02:42 +0200 Subject: [PATCH 289/323] Update operator_to_quadratic_program.py --- qiskit/optimization/converters/operator_to_quadratic_program.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/optimization/converters/operator_to_quadratic_program.py b/qiskit/optimization/converters/operator_to_quadratic_program.py index f77404ba63..35f2463414 100644 --- a/qiskit/optimization/converters/operator_to_quadratic_program.py +++ b/qiskit/optimization/converters/operator_to_quadratic_program.py @@ -35,7 +35,7 @@ def __init__(self) -> None: self._qubo_matrix = None self._qp = None - def encode(self, qubit_op: WeightedPauliOperator, offset: float) -> QuadraticProgram: + def encode(self, qubit_op: WeightedPauliOperator, offset: float = 0.0) -> QuadraticProgram: """Convert a qubit operator and a shift value into a quadratic program Args: From 3d904838d7f3595b61ec971a2b6d8759d578dfc5 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Fri, 24 Apr 2020 18:20:30 +0900 Subject: [PATCH 290/323] Add QuadraticProgram.Status to replace SubstitutionStatus --- .../recursive_minimum_eigen_optimizer.py | 10 +-- .../problems/quadratic_program.py | 85 ++++++++++--------- test/optimization/test_quadratic_program.py | 25 +++--- 3 files changed, 64 insertions(+), 56 deletions(-) diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 4240d06dfa..00b47a511d 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -25,7 +25,7 @@ from .optimization_algorithm import OptimizationAlgorithm, OptimizationResult from .minimum_eigen_optimizer import MinimumEigenOptimizer from ..exceptions.qiskit_optimization_error import QiskitOptimizationError -from ..problems.quadratic_program import QuadraticProgram, SubstitutionStatus +from ..problems.quadratic_program import QuadraticProgram from ..converters.quadratic_program_to_qubo import QuadraticProgramToQubo logger = logging.getLogger(__name__) @@ -140,8 +140,8 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: if correlations[i, j] > 0: # set x_i = x_j problem_.substitute_variables() - problem_, status = problem_.substitute_variables(variables={i: (j, 1)}) - if status == SubstitutionStatus.INFEASIBLE: + problem_ = problem_.substitute_variables(variables={i: (j, 1)}) + if problem_.status == QuadraticProgram.Status.INFEASIBLE: raise QiskitOptimizationError('Infeasible due to variable substitution') replacements[x_i] = (x_j, 1) else: @@ -163,8 +163,8 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: problem_.objective.linear[k] = coeff # 2. replace x_i by -x_j - problem_, status = problem_.substitute_variables(variables={i: (j, -1)}) - if status == SubstitutionStatus.INFEASIBLE: + problem_ = problem_.substitute_variables(variables={i: (j, -1)}) + if problem_.status == QuadraticProgram.Status.INFEASIBLE: raise QiskitOptimizationError('Infeasible due to variable substitution') replacements[x_i] = (x_j, -1) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 0e9b9e3f40..f88c7e9b09 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -43,6 +43,11 @@ class QuadraticProgram: equality constraints as well as continuous, binary, and integer variables. """ + class Status(Enum): + """Status of QuadraticProgram""" + VALID = 0 + INFEASIBLE = 1 + def __init__(self, name: str = '') -> None: """Constructs a quadratic program. @@ -50,6 +55,7 @@ def __init__(self, name: str = '') -> None: name: The name of the quadratic program. """ self._name = name + self._status = self.Status.VALID self._variables: List[Variable] = [] self._variables_index: Dict[str, int] = {} @@ -97,6 +103,16 @@ def name(self, name: str) -> None: """ self._name = name + @property + def status(self) -> 'Status': + """Status of the quadratic program. + It can be infeasible due to variable substitution. + + Returns: + The status of the quadratic program + """ + return self._status + @property def variables(self) -> List[Variable]: """Returns the list of variables of the quadratic program. @@ -356,12 +372,9 @@ def quadratic_constraints_index(self) -> Dict[str, int]: def quadratic_constraint(self, linear: Union[ndarray, spmatrix, List[float], Dict[Union[int, str], float]] = None, - quadratic: Union[ndarray, spmatrix, - List[List[float]], - Dict[ - Tuple[Union[int, str], - Union[int, str]], - float]] = None, + quadratic: Union[ndarray, spmatrix, List[List[float]], + Dict[Tuple[Union[int, str], + Union[int, str]], float]] = None, sense: Union[str, ConstraintSense] = '<=', rhs: float = 0.0, name: Optional[str] = None) -> QuadraticConstraint: """Adds a quadratic equality constraint to the quadratic program of the form: @@ -797,7 +810,7 @@ def write_to_lp_file(self, filename: str) -> None: def substitute_variables( self, constants: Optional[Dict[Union[str, int], float]] = None, variables: Optional[Dict[Union[str, int], Tuple[Union[str, int], float]]] = None) \ - -> Tuple['QuadraticProgram', 'SubstitutionStatus']: + -> 'QuadraticProgram': """Substitutes variables with constants or other variables. Args: @@ -810,9 +823,10 @@ def substitute_variables( e.g., {'x': ('y', 2)} means 'x' is substituted with 'y' * 2 Returns: - An optimization problem by substituting variables and the status. - If the resulting problem has no issue, the status is `success`. - Otherwise, an empty problem and status `infeasible` are returned. + An optimization problem by substituting variables with constants or other variables. + If the substitution is valid, `QuadraticProgram.status` is still + `QuadraticProgram.Status.VALIAD`. + Otherwise, it gets `QuadraticProgram.Status.INFEASIBLE`. Raises: QiskitOptimizationError: if the substitution is invalid as follows. @@ -822,12 +836,6 @@ def substitute_variables( return SubstituteVariables().substitute_variables(self, constants, variables) -class SubstitutionStatus(Enum): - """Status of `QuadraticProgram.substitute_variables`""" - SUCCESS = 1 - INFEASIBLE = 2 - - class SubstituteVariables: """A class to substitute variables of an optimization problem with constants for other variables""" @@ -843,7 +851,7 @@ def substitute_variables( self, src: QuadraticProgram, constants: Optional[Dict[Union[str, int], float]] = None, variables: Optional[Dict[Union[str, int], Tuple[Union[str, int], float]]] = None) \ - -> Tuple[QuadraticProgram, SubstitutionStatus]: + -> QuadraticProgram: """Substitutes variables with constants or other variables. Args: @@ -858,9 +866,10 @@ def substitute_variables( e.g., {'x': ('y', 2)} means 'x' is substituted with 'y' * 2 Returns: - An optimization problem by substituting variables and the status. - If the resulting problem has no issue, the status is `success`. - Otherwise, an empty problem and status `infeasible` are returned. + An optimization problem by substituting variables with constants or other variables. + If the substitution is valid, `QuadraticProgram.status` is still + `QuadraticProgram.Status.VALIAD`. + Otherwise, it gets `QuadraticProgram.Status.INFEASIBLE`. Raises: QiskitOptimizationError: if the substitution is invalid as follows. @@ -876,11 +885,9 @@ def substitute_variables( self._linear_constraints(), self._quadratic_constraints(), ] - if any(r == SubstitutionStatus.INFEASIBLE for r in results): - ret = SubstitutionStatus.INFEASIBLE - else: - ret = SubstitutionStatus.SUCCESS - return self._dst, ret + if any(not r for r in results): + self._dst._status = QuadraticProgram.Status.INFEASIBLE + return self._dst @staticmethod def _feasible(sense: ConstraintSense, rhs: float) -> bool: @@ -943,8 +950,9 @@ def _subs_dict(self, constants, variables): self._subs = subs - def _variables(self) -> SubstitutionStatus: + def _variables(self) -> bool: # copy variables that are not replaced + feasible = True for var in self._src.variables: name = var.name vartype = var.vartype @@ -960,7 +968,7 @@ def _variables(self) -> SubstitutionStatus: if not lb_i <= v <= ub_i: logger.warning( 'Infeasible substitution for variable: %s', i) - return SubstitutionStatus.INFEASIBLE + feasible = False else: # substitute i <- j * v # lb_i <= i <= ub_i --> lb_i / v <= j <= ub_i / v if v > 0 @@ -992,9 +1000,9 @@ def _variables(self) -> SubstitutionStatus: logger.warning( 'Infeasible lower and upper bound: %s %f %f', var, var.lowerbound, var.upperbound) - return SubstitutionStatus.INFEASIBLE + feasible = False - return SubstitutionStatus.SUCCESS + return feasible def _linear_expression(self, lin_expr: LinearExpression) \ -> Tuple[List[float], LinearExpression]: @@ -1034,7 +1042,7 @@ def _quadratic_expression(self, quad_expr: QuadraticExpression) \ coefficients=quad_dict if quad_dict else {}) return const, new_lin, new_quad - def _objective(self) -> SubstitutionStatus: + def _objective(self) -> bool: obj = self._src.objective const1, lin1 = self._linear_expression(obj.linear) const2, lin2, quadratic = self._quadratic_expression(obj.quadratic) @@ -1045,9 +1053,10 @@ def _objective(self) -> SubstitutionStatus: self._dst.minimize(constant=constant, linear=linear, quadratic=quadratic.coefficients) else: self._dst.maximize(constant=constant, linear=linear, quadratic=quadratic.coefficients) - return SubstitutionStatus.SUCCESS + return True - def _linear_constraints(self) -> SubstitutionStatus: + def _linear_constraints(self) -> bool: + feasible = True for lin_cst in self._src.linear_constraints: constant, linear = self._linear_expression(lin_cst.linear) rhs = -fsum([-lin_cst.rhs] + constant) @@ -1057,11 +1066,11 @@ def _linear_constraints(self) -> SubstitutionStatus: else: if not self._feasible(lin_cst.sense, rhs): logger.warning('constraint %s is infeasible due to substitution', lin_cst.name) - return SubstitutionStatus.INFEASIBLE - - return SubstitutionStatus.SUCCESS + feasible = False + return feasible - def _quadratic_constraints(self) -> SubstitutionStatus: + def _quadratic_constraints(self) -> bool: + feasible = True for quad_cst in self._src.quadratic_constraints: const1, lin1 = self._linear_expression(quad_cst.linear) const2, lin2, quadratic = self._quadratic_expression(quad_cst.quadratic) @@ -1081,6 +1090,6 @@ def _quadratic_constraints(self) -> SubstitutionStatus: else: if not self._feasible(quad_cst.sense, rhs): logger.warning('constraint %s is infeasible due to substitution', quad_cst.name) - return SubstitutionStatus.INFEASIBLE + feasible = False - return SubstitutionStatus.SUCCESS + return feasible diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index d71bee0a65..71a5995741 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -21,7 +21,6 @@ from qiskit.optimization import QuadraticProgram, QiskitOptimizationError, infinity from qiskit.optimization.problems import VarType, ConstraintSense, ObjSense -from qiskit.optimization.problems.quadratic_program import SubstitutionStatus class TestQuadraticProgram(QiskitOptimizationTestCase): @@ -534,15 +533,15 @@ def test_substitute_variables(self): q_p.linear_constraint({'x': 2, 'z': -1}, '==', 1) q_p.quadratic_constraint({'x': 2, 'z': -1}, {('y', 'z'): 3}, '<=', -1) - q_p2, status = q_p.substitute_variables(constants={'x': -1}) - self.assertEqual(status, SubstitutionStatus.INFEASIBLE) - q_p2, status = q_p.substitute_variables(constants={'y': -3}) - self.assertEqual(status, SubstitutionStatus.INFEASIBLE) - q_p2, status = q_p.substitute_variables(constants={'x': 1, 'z': 2}) - self.assertEqual(status, SubstitutionStatus.INFEASIBLE) + q_p2 = q_p.substitute_variables(constants={'x': -1}) + self.assertEqual(q_p2.status, QuadraticProgram.Status.INFEASIBLE) + q_p2 = q_p.substitute_variables(constants={'y': -3}) + self.assertEqual(q_p2.status, QuadraticProgram.Status.INFEASIBLE) + q_p2 = q_p.substitute_variables(constants={'x': 1, 'z': 2}) + self.assertEqual(q_p2.status, QuadraticProgram.Status.INFEASIBLE) - q_p2, status = q_p.substitute_variables(constants={'x': 0}) - self.assertEqual(status, SubstitutionStatus.SUCCESS) + q_p2 = q_p.substitute_variables(constants={'x': 0}) + self.assertEqual(q_p2.status, QuadraticProgram.Status.VALID) self.assertDictEqual(q_p2.objective.linear.to_dict(use_name=True), {'y': 2}) self.assertDictEqual(q_p2.objective.quadratic.to_dict(use_name=True), {('z', 'z'): 2}) self.assertEqual(q_p2.objective.constant, 1) @@ -560,8 +559,8 @@ def test_substitute_variables(self): self.assertEqual(cst.sense.name, 'LE') self.assertEqual(cst.rhs, -1) - q_p2, status = q_p.substitute_variables(constants={'z': -1}) - self.assertEqual(status, SubstitutionStatus.SUCCESS) + q_p2 = q_p.substitute_variables(constants={'z': -1}) + self.assertEqual(q_p2.status, QuadraticProgram.Status.VALID) self.assertDictEqual(q_p2.objective.linear.to_dict(use_name=True), {'x': 1, 'y': 2}) self.assertDictEqual(q_p2.objective.quadratic.to_dict(use_name=True), {('x', 'y'): -1}) self.assertEqual(q_p2.objective.constant, 3) @@ -578,8 +577,8 @@ def test_substitute_variables(self): self.assertEqual(cst.sense.name, 'LE') self.assertEqual(cst.rhs, -2) - q_p2, status = q_p.substitute_variables(variables={'y': ('x', -0.5)}) - self.assertEqual(status, SubstitutionStatus.SUCCESS) + q_p2 = q_p.substitute_variables(variables={'y': ('x', -0.5)}) + self.assertEqual(q_p2.status, QuadraticProgram.Status.VALID) self.assertDictEqual(q_p2.objective.linear.to_dict(use_name=True), {}) self.assertDictEqual(q_p2.objective.quadratic.to_dict(use_name=True), {('x', 'x'): 0.5, ('z', 'z'): 2}) From 80882bb72e614065129cf4b4df643ab24a88dd69 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Fri, 24 Apr 2020 18:27:27 +0900 Subject: [PATCH 291/323] add a test of status for clear --- qiskit/optimization/problems/quadratic_program.py | 13 +++++++------ test/optimization/test_quadratic_program.py | 4 ++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index f88c7e9b09..feeb1b53ab 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -73,15 +73,16 @@ def clear(self) -> None: objective function as well as the name. """ self._name = '' + self._status = self.Status.VALID - self._variables: List[Variable] = [] - self._variables_index: Dict[str, int] = {} + self._variables.clear() + self._variables_index.clear() - self._linear_constraints: List[LinearConstraint] = [] - self._linear_constraints_index: Dict[str, int] = {} + self._linear_constraints.clear() + self._linear_constraints_index.clear() - self._quadratic_constraints: List[QuadraticConstraint] = [] - self._quadratic_constraints_index: Dict[str, int] = {} + self._quadratic_constraints.clear() + self._quadratic_constraints_index.clear() self._objective = QuadraticObjective(self) diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index 71a5995741..29da37a2f6 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -31,6 +31,7 @@ def test_constructor(self): """ test constructor """ quadratic_program = QuadraticProgram() self.assertEqual(quadratic_program.name, '') + self.assertEqual(quadratic_program.status, QuadraticProgram.Status.VALID) self.assertEqual(quadratic_program.get_num_vars(), 0) self.assertEqual(quadratic_program.get_num_linear_constraints(), 0) self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 0) @@ -48,6 +49,7 @@ def test_clear(self): q_p.quadratic_constraint({'x': 1}, {('y', 'y'): 2}, '<=', 1) q_p.clear() self.assertEqual(q_p.name, '') + self.assertEqual(q_p.status, QuadraticProgram.Status.VALID) self.assertEqual(q_p.get_num_vars(), 0) self.assertEqual(q_p.get_num_linear_constraints(), 0) self.assertEqual(q_p.get_num_quadratic_constraints(), 0) @@ -539,6 +541,8 @@ def test_substitute_variables(self): self.assertEqual(q_p2.status, QuadraticProgram.Status.INFEASIBLE) q_p2 = q_p.substitute_variables(constants={'x': 1, 'z': 2}) self.assertEqual(q_p2.status, QuadraticProgram.Status.INFEASIBLE) + q_p2.clear() + self.assertEqual(q_p2.status, QuadraticProgram.Status.VALID) q_p2 = q_p.substitute_variables(constants={'x': 0}) self.assertEqual(q_p2.status, QuadraticProgram.Status.VALID) From 4093ae0de0a6693b6bd3908fb2bb9a2a775630a3 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Fri, 24 Apr 2020 20:10:10 +0900 Subject: [PATCH 292/323] Embed enum data in classes --- .../optimization/algorithms/admm_optimizer.py | 29 ++++--- .../algorithms/cobyla_optimizer.py | 8 +- .../converters/inequality_to_equality.py | 46 +++++------ .../converters/integer_to_binary.py | 12 +-- .../converters/linear_equality_to_penalty.py | 18 ++-- .../converters/quadratic_program_to_qubo.py | 4 +- qiskit/optimization/problems/__init__.py | 11 +-- qiskit/optimization/problems/constraint.py | 77 +++++++++-------- .../problems/linear_constraint.py | 4 +- .../problems/quadratic_constraint.py | 4 +- .../problems/quadratic_objective.py | 17 ++-- .../problems/quadratic_program.py | 64 ++++++++------- qiskit/optimization/problems/variable.py | 21 +++-- test/optimization/test_converters.py | 82 +++++++++---------- test/optimization/test_linear_constraint.py | 19 ++--- .../optimization/test_quadratic_constraint.py | 16 ++-- test/optimization/test_quadratic_objective.py | 21 +++-- test/optimization/test_quadratic_program.py | 70 ++++++++-------- test/optimization/test_variable.py | 8 +- 19 files changed, 258 insertions(+), 273 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index c7303ebe28..1ee6825597 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -25,8 +25,7 @@ from qiskit.optimization.algorithms.optimization_algorithm import (OptimizationAlgorithm, OptimizationResult) from qiskit.optimization.converters import IntegerToBinary -from qiskit.optimization.problems import VarType, ConstraintSense -from qiskit.optimization.problems.quadratic_program import QuadraticProgram +from qiskit.optimization.problems import QuadraticProgram, Variable, Constraint UPDATE_RHO_BY_TEN_PERCENT = 0 UPDATE_RHO_BY_RESIDUALS = 1 @@ -221,8 +220,8 @@ def get_compatibility_msg(self, problem: QuadraticProgram) -> Optional[str]: msg = '' # 1. get bin/int and continuous variable indices - bin_int_indices = self._get_variable_indices(problem, VarType.BINARY) - continuous_indices = self._get_variable_indices(problem, VarType.CONTINUOUS) + bin_int_indices = self._get_variable_indices(problem, Variable.Type.BINARY) + continuous_indices = self._get_variable_indices(problem, Variable.Type.CONTINUOUS) # 2. binary and continuous variables are separable in objective for bin_int_index in bin_int_indices: @@ -263,8 +262,8 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: problem_ = int2bin.encode(problem) # parse problem and convert to an ADMM specific representation. - binary_indices = self._get_variable_indices(problem_, VarType.BINARY) - continuous_indices = self._get_variable_indices(problem_, VarType.CONTINUOUS) + binary_indices = self._get_variable_indices(problem_, Variable.Type.BINARY) + continuous_indices = self._get_variable_indices(problem_, Variable.Type.CONTINUOUS) # create our computation state. self._state = ADMMState(problem_, binary_indices, @@ -344,7 +343,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: return result @staticmethod - def _get_variable_indices(op: QuadraticProgram, var_type: VarType) -> List[int]: + def _get_variable_indices(op: QuadraticProgram, var_type: Variable.Type) -> List[int]: """Returns a list of indices of the variables of the specified type. Args: @@ -468,7 +467,7 @@ def _assign_row_values(self, matrix: List[List[float]], vector: List[float], vector.append(self._state.op.linear_constraints[constraint_index].rhs) # flip the sign if constraint is G, we want L constraints. - if self._state.op.linear_constraints[constraint_index].sense == ConstraintSense.GE: + if self._state.op.linear_constraints[constraint_index].sense == Constraint.Sense.GE: # invert the sign to make constraint "L". # we invert only last row/last element matrix[-1] = [-1 * el for el in matrix[-1]] @@ -509,7 +508,7 @@ def _get_a0_b0(self) -> (np.ndarray, np.ndarray): index_set = set(self._state.binary_indices) for constraint_index, constraint in enumerate(self._state.op.linear_constraints): # we check only equality constraints here. - if constraint.sense != ConstraintSense.EQ: + if constraint.sense != Constraint.Sense.EQ: continue constraint_indices = set( @@ -542,7 +541,7 @@ def _get_inequality_matrix_and_vector(self, variable_indices: List[int]) \ index_set = set(variable_indices) for constraint_index, constraint in enumerate(self._state.op.linear_constraints): - if constraint.sense in [ConstraintSense.EQ]: + if constraint.sense in [Constraint.Sense.EQ]: # TODO: Ranged constraints should be supported continue constraint_indices = set( @@ -586,7 +585,7 @@ def _get_a2_a3_b2(self) -> (np.ndarray, np.ndarray, np.ndarray): continuous_index_set = set(self._state.continuous_indices) all_variables = self._state.binary_indices + self._state.continuous_indices for constraint_index, constraint in enumerate(self._state.op.linear_constraints): - if constraint.sense in [ConstraintSense.EQ]: + if constraint.sense in [Constraint.Sense.EQ]: # TODO: Ranged constraints should be supported as well continue # sense either G or L. @@ -644,7 +643,7 @@ def _create_step2_problem(self) -> QuadraticProgram: binary_size = len(self._state.binary_indices) continuous_index = 0 for variable in self._state.op.variables: - if variable.vartype == VarType.CONTINUOUS: + if variable.vartype == Variable.Type.CONTINUOUS: op2.continuous_var(name="u0_" + str(continuous_index + 1), lowerbound=variable.lowerbound, upperbound=variable.upperbound) continuous_index += 1 @@ -663,7 +662,7 @@ def _create_step2_problem(self) -> QuadraticProgram: constraint_count = self._state.a1.shape[0] for i in range(constraint_count): linear = np.concatenate((np.zeros(continuous_size), self._state.a1[i, :])) - op2.linear_constraint(linear=linear, sense=ConstraintSense.LE, rhs=self._state.b1[i]) + op2.linear_constraint(linear=linear, sense=Constraint.Sense.LE, rhs=self._state.b1[i]) if continuous_size: # A2 z + A3 u <= b2 @@ -671,7 +670,7 @@ def _create_step2_problem(self) -> QuadraticProgram: for i in range(constraint_count): linear = np.concatenate((self._state.a3[i, :], self._state.a2[i, :])) op2.linear_constraint(linear=linear, - sense=ConstraintSense.LE, + sense=Constraint.Sense.LE, rhs=self._state.b2[i]) # A4 u <= b3 @@ -679,7 +678,7 @@ def _create_step2_problem(self) -> QuadraticProgram: for i in range(constraint_count): linear = np.concatenate((self._state.a4[i, :], np.zeros(binary_size))) op2.linear_constraint(linear=linear, - sense=ConstraintSense.LE, + sense=Constraint.Sense.LE, rhs=self._state.b3[i]) # add quadratic constraints for diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index 1fff1765b2..6a0518b6c2 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -21,7 +21,7 @@ from scipy.optimize import fmin_cobyla from qiskit.optimization.algorithms import OptimizationAlgorithm, OptimizationResult -from qiskit.optimization.problems import QuadraticProgram, ConstraintSense +from qiskit.optimization.problems import QuadraticProgram, Constraint from qiskit.optimization import QiskitOptimizationError, infinity @@ -124,14 +124,14 @@ def objective(x): rhs = constraint.rhs sense = constraint.sense - if sense == ConstraintSense.EQ: + if sense == Constraint.Sense.EQ: constraints += [ lambda x, rhs=rhs, c=constraint: rhs - c.evaluate(x), lambda x, rhs=rhs, c=constraint: c.evaluate(x) - rhs ] - elif sense == ConstraintSense.LE: + elif sense == Constraint.Sense.LE: constraints += [lambda x, rhs=rhs, c=constraint: rhs - c.evaluate(x)] - elif sense == ConstraintSense.GE: + elif sense == Constraint.Sense.GE: constraints += [lambda x, rhs=rhs, c=constraint: c.evaluate(x) - rhs] else: raise QiskitOptimizationError('Unsupported constraint type!') diff --git a/qiskit/optimization/converters/inequality_to_equality.py b/qiskit/optimization/converters/inequality_to_equality.py index 0a4cb3db5d..327cb58a45 100644 --- a/qiskit/optimization/converters/inequality_to_equality.py +++ b/qiskit/optimization/converters/inequality_to_equality.py @@ -19,9 +19,9 @@ import logging from ..problems.quadratic_program import QuadraticProgram -from ..problems.quadratic_objective import ObjSense -from ..problems.constraint import ConstraintSense -from ..problems.variable import VarType +from ..problems.quadratic_objective import QuadraticObjective +from ..problems.constraint import Constraint +from ..problems.variable import Variable from ..exceptions import QiskitOptimizationError logger = logging.getLogger(__name__) @@ -78,13 +78,13 @@ def encode( # Copy variables for x in self._src.variables: - if x.vartype == VarType.BINARY: + if x.vartype == Variable.Type.BINARY: self._dst.binary_var(name=x.name) - elif x.vartype == VarType.INTEGER: + elif x.vartype == Variable.Type.INTEGER: self._dst.integer_var( name=x.name, lowerbound=x.lowerbound, upperbound=x.upperbound ) - elif x.vartype == VarType.CONTINUOUS: + elif x.vartype == Variable.Type.CONTINUOUS: self._dst.continuous_var( name=x.name, lowerbound=x.lowerbound, upperbound=x.upperbound ) @@ -95,7 +95,7 @@ def encode( constant = self._src.objective.constant linear = self._src.objective.linear.to_dict(use_name=True) quadratic = self._src.objective.quadratic.to_dict(use_name=True) - if self._src.objective.sense == ObjSense.MINIMIZE: + if self._src.objective.sense == QuadraticObjective.Sense.MINIMIZE: self._dst.minimize(constant, linear, quadratic) else: self._dst.maximize(constant, linear, quadratic) @@ -103,11 +103,11 @@ def encode( # For linear constraints for constraint in self._src.linear_constraints: linear = constraint.linear.to_dict(use_name=True) - if constraint.sense == ConstraintSense.EQ: + if constraint.sense == Constraint.Sense.EQ: self._dst.linear_constraint( linear, constraint.sense, constraint.rhs, constraint.name ) - elif constraint.sense == ConstraintSense.LE or constraint.sense == ConstraintSense.GE: + elif constraint.sense == Constraint.Sense.LE or constraint.sense == Constraint.Sense.GE: if mode == 'integer': self._add_integer_slack_var_linear_constraint( linear, constraint.sense, constraint.rhs, constraint.name @@ -131,11 +131,11 @@ def encode( for constraint in self._src.quadratic_constraints: linear = constraint.linear.to_dict(use_name=True) quadratic = constraint.quadratic.to_dict(use_name=True) - if constraint.sense == ConstraintSense.EQ: + if constraint.sense == Constraint.Sense.EQ: self._dst.quadratic_constraint( linear, quadratic, constraint.sense, constraint.rhs, constraint.name ) - elif constraint.sense == ConstraintSense.LE or constraint.sense == ConstraintSense.GE: + elif constraint.sense == Constraint.Sense.LE or constraint.sense == Constraint.Sense.GE: if mode == 'integer': self._add_integer_slack_var_quadratic_constraint( linear, @@ -176,9 +176,9 @@ def _add_integer_slack_var_linear_constraint(self, linear, sense, rhs, name): # If rhs is float number, round up/down to the nearest integer. new_rhs = rhs - if sense == ConstraintSense.LE: + if sense == Constraint.Sense.LE: new_rhs = math.floor(rhs) - if sense == ConstraintSense.GE: + if sense == Constraint.Sense.GE: new_rhs = math.ceil(rhs) # Add a new integer variable. @@ -187,10 +187,10 @@ def _add_integer_slack_var_linear_constraint(self, linear, sense, rhs, name): lhs_lb, lhs_ub = self._calc_linear_bounds(linear) - if sense == ConstraintSense.LE: + if sense == Constraint.Sense.LE: sign = 1 self._dst.integer_var(name=slack_name, lowerbound=0, upperbound=new_rhs - lhs_lb) - elif sense == ConstraintSense.GE: + elif sense == Constraint.Sense.GE: sign = -1 self._dst.integer_var(name=slack_name, lowerbound=0, upperbound=lhs_ub - new_rhs) else: @@ -207,10 +207,10 @@ def _add_continuous_slack_var_linear_constraint(self, linear, sense, rhs, name): lhs_lb, lhs_ub = self._calc_linear_bounds(linear) - if sense == ConstraintSense.LE: + if sense == Constraint.Sense.LE: sign = 1 self._dst.continuous_var(name=slack_name, lowerbound=0, upperbound=rhs - lhs_lb) - elif sense == ConstraintSense.GE: + elif sense == Constraint.Sense.GE: sign = -1 self._dst.continuous_var(name=slack_name, lowerbound=0, upperbound=lhs_ub - rhs) else: @@ -238,9 +238,9 @@ def _add_integer_slack_var_quadratic_constraint(self, linear, quadratic, sense, # If rhs is float number, round up/down to the nearest integer. new_rhs = rhs - if sense == ConstraintSense.LE: + if sense == Constraint.Sense.LE: new_rhs = math.floor(rhs) - if sense == ConstraintSense.GE: + if sense == Constraint.Sense.GE: new_rhs = math.ceil(rhs) # Add a new integer variable. @@ -249,10 +249,10 @@ def _add_integer_slack_var_quadratic_constraint(self, linear, quadratic, sense, lhs_lb, lhs_ub = self._calc_quadratic_bounds(linear, quadratic) - if sense == ConstraintSense.LE: + if sense == Constraint.Sense.LE: sign = 1 self._dst.integer_var(name=slack_name, lowerbound=0, upperbound=new_rhs - lhs_lb) - elif sense == ConstraintSense.GE: + elif sense == Constraint.Sense.GE: sign = -1 self._dst.integer_var(name=slack_name, lowerbound=0, upperbound=lhs_ub - new_rhs) else: @@ -276,10 +276,10 @@ def _add_continuous_slack_var_quadratic_constraint(self, linear, quadratic, sens lhs_lb, lhs_ub = self._calc_quadratic_bounds(linear, quadratic) - if sense == ConstraintSense.LE: + if sense == Constraint.Sense.LE: sign = 1 self._dst.continuous_var(name=slack_name, lowerbound=0, upperbound=rhs - lhs_lb) - elif sense == ConstraintSense.GE: + elif sense == Constraint.Sense.GE: sign = -1 self._dst.continuous_var(name=slack_name, lowerbound=0, upperbound=lhs_ub - rhs) else: diff --git a/qiskit/optimization/converters/integer_to_binary.py b/qiskit/optimization/converters/integer_to_binary.py index 87cd1106fe..d46168ddc6 100644 --- a/qiskit/optimization/converters/integer_to_binary.py +++ b/qiskit/optimization/converters/integer_to_binary.py @@ -21,9 +21,9 @@ import numpy as np from ..exceptions import QiskitOptimizationError -from ..problems.quadratic_objective import ObjSense +from ..problems.quadratic_objective import QuadraticObjective from ..problems.quadratic_program import QuadraticProgram -from ..problems.variable import Variable, VarType +from ..problems.variable import Variable logger = logging.getLogger(__name__) @@ -78,15 +78,15 @@ def encode(self, op: QuadraticProgram, name: Optional[str] = None) -> QuadraticP # declare variables for x in self._src.variables: - if x.vartype == VarType.INTEGER: + if x.vartype == Variable.Type.INTEGER: new_vars = self._encode_var(x.name, x.lowerbound, x.upperbound) self._conv[x] = new_vars for (var_name, _) in new_vars: self._dst.binary_var(var_name) else: - if x.vartype == VarType.CONTINUOUS: + if x.vartype == Variable.Type.CONTINUOUS: self._dst.continuous_var(x.lowerbound, x.upperbound, x.name) - elif x.vartype == VarType.BINARY: + elif x.vartype == Variable.Type.BINARY: self._dst.binary_var(x.name) else: raise QiskitOptimizationError("Unsupported variable type {}".format(x.vartype)) @@ -167,7 +167,7 @@ def _substitute_int_var(self): for i, v in quadratic_linear.items(): linear[i] = linear.get(i, 0) + v - if self._src.objective.sense == ObjSense.MINIMIZE: + if self._src.objective.sense == QuadraticObjective.Sense.MINIMIZE: self._dst.minimize(constant, linear, quadratic) else: self._dst.maximize(constant, linear, quadratic) diff --git a/qiskit/optimization/converters/linear_equality_to_penalty.py b/qiskit/optimization/converters/linear_equality_to_penalty.py index 7d6df5d7c8..0efeda80dc 100644 --- a/qiskit/optimization/converters/linear_equality_to_penalty.py +++ b/qiskit/optimization/converters/linear_equality_to_penalty.py @@ -14,14 +14,10 @@ """Converter to convert a problem with equality constraints to unconstrained with penalty terms.""" -from typing import Optional - import copy +from typing import Optional -from ..problems.quadratic_program import QuadraticProgram -from ..problems.variable import VarType -from ..problems.quadratic_objective import ObjSense -from ..problems.constraint import ConstraintSense +from ..problems import QuadraticProgram, Variable, Constraint, QuadraticObjective from ..exceptions.qiskit_optimization_error import QiskitOptimizationError @@ -55,11 +51,11 @@ def encode(self, op: QuadraticProgram, penalty_factor: float = 1e5, # set variables for x in self._src.variables: - if x.vartype == VarType.CONTINUOUS: + if x.vartype == Variable.Type.CONTINUOUS: self._dst.continuous_var(x.lowerbound, x.upperbound, x.name) - elif x.vartype == VarType.BINARY: + elif x.vartype == Variable.Type.BINARY: self._dst.binary_var(x.name) - elif x.vartype == VarType.INTEGER: + elif x.vartype == Variable.Type.INTEGER: self._dst.integer_var(x.lowerbound, x.upperbound, x.name) else: raise QiskitOptimizationError('Unsupported vartype: {}'.format(x.vartype)) @@ -79,7 +75,7 @@ def encode(self, op: QuadraticProgram, penalty_factor: float = 1e5, # convert linear constraints into penalty terms for constraint in self._src.linear_constraints: - if constraint.sense != ConstraintSense.EQ: + if constraint.sense != Constraint.Sense.EQ: raise QiskitOptimizationError('An inequality constraint exists. ' 'The method supports only equality constraints.') @@ -107,7 +103,7 @@ def encode(self, op: QuadraticProgram, penalty_factor: float = 1e5, quadratic[(j, k)] = quadratic.get((j, k), 0.0) \ + sense * penalty_factor * coef_1 * coef_2 - if self._src.objective.sense == ObjSense.MINIMIZE: + if self._src.objective.sense == QuadraticObjective.Sense.MINIMIZE: self._dst.minimize(offset, linear, quadratic) else: self._dst.maximize(offset, linear, quadratic) diff --git a/qiskit/optimization/converters/quadratic_program_to_qubo.py b/qiskit/optimization/converters/quadratic_program_to_qubo.py index 72f4879c28..5e9e15b04e 100644 --- a/qiskit/optimization/converters/quadratic_program_to_qubo.py +++ b/qiskit/optimization/converters/quadratic_program_to_qubo.py @@ -17,7 +17,7 @@ from typing import Optional from qiskit.optimization.problems import QuadraticProgram -from qiskit.optimization.problems.constraint import ConstraintSense +from qiskit.optimization.problems.constraint import Constraint from qiskit.optimization.converters.linear_equality_to_penalty import LinearEqualityToPenalty from qiskit.optimization.converters.integer_to_binary import IntegerToBinary from qiskit.optimization.exceptions import QiskitOptimizationError @@ -107,7 +107,7 @@ def get_compatibility_msg(problem: QuadraticProgram) -> str: msg += 'Continuous variables are not supported! ' # check whether there are incompatible constraint types - if not all([constraint.sense == ConstraintSense.EQ + if not all([constraint.sense == Constraint.Sense.EQ for constraint in problem.linear_constraints]): msg += 'Only linear equality constraints are supported.' if len(problem.quadratic_constraints) > 0: diff --git a/qiskit/optimization/problems/__init__.py b/qiskit/optimization/problems/__init__.py index 6c185c3695..5b83861117 100644 --- a/qiskit/optimization/problems/__init__.py +++ b/qiskit/optimization/problems/__init__.py @@ -27,16 +27,13 @@ :nosignatures: Constraint - ConstraintSense LinearExpression LinearConstraint QuadraticExpression QuadraticConstraint QuadraticObjective - ObjSense QuadraticProgram Variable - VarType N.B. Additional classes Constraint, LinearConstraint, QuadraticConstraint, QuadraticObjective, and Variable @@ -46,24 +43,20 @@ """ from .constraint import Constraint -from .constraint import ConstraintSense from .linear_constraint import LinearConstraint from .linear_expression import LinearExpression from .quadratic_constraint import QuadraticConstraint from .quadratic_expression import QuadraticExpression -from .quadratic_objective import QuadraticObjective, ObjSense +from .quadratic_objective import QuadraticObjective from .quadratic_program import QuadraticProgram -from .variable import Variable, VarType +from .variable import Variable __all__ = ['Constraint', - 'ConstraintSense', 'LinearExpression', 'LinearConstraint', 'QuadraticExpression', 'QuadraticConstraint', 'QuadraticObjective', - 'ObjSense', 'QuadraticProgram', 'Variable', - 'VarType' ] diff --git a/qiskit/optimization/problems/constraint.py b/qiskit/optimization/problems/constraint.py index 3d6bdb8d91..12ef9298c5 100644 --- a/qiskit/optimization/problems/constraint.py +++ b/qiskit/optimization/problems/constraint.py @@ -20,48 +20,47 @@ from numpy import ndarray -from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram from qiskit.optimization import QiskitOptimizationError - - -class ConstraintSense(Enum): - """Constants Sense Type.""" - - # pylint: disable=invalid-name - LE = 0 - GE = 1 - EQ = 2 - - @staticmethod - def convert(sense: Union[str, "ConstraintSense"]) -> "ConstraintSense": - """Convert a string into a corresponding sense of constraints - - Args: - sense: A string or sense of constraints - - Returns: - The sense of constraints - - Raises: - QiskitOptimizationError: if the input string is invalid. - """ - if isinstance(sense, ConstraintSense): - return sense - sense = sense.upper() - if sense not in ['E', 'L', 'G', 'EQ', 'LE', 'GE', '=', '==', '<=', '<', '>=', '>']: - raise QiskitOptimizationError('Invalid sense: {}'.format(sense)) - if sense in ['E', 'EQ', '=', '==']: - return ConstraintSense.EQ - elif sense in ['L', 'LE', '<=', '<']: - return ConstraintSense.LE - else: - return ConstraintSense.GE +from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram class Constraint(HasQuadraticProgram): """Abstract Constraint Class.""" - def __init__(self, quadratic_program: "QuadraticProgram", name: str, sense: ConstraintSense, + class Sense(Enum): + """Constants Sense Type.""" + + # pylint: disable=invalid-name + LE = 0 + GE = 1 + EQ = 2 + + @staticmethod + def convert(sense: Union[str, 'Sense']) -> 'Sense': + """Convert a string into a corresponding sense of constraints + + Args: + sense: A string or sense of constraints + + Returns: + The sense of constraints + + Raises: + QiskitOptimizationError: if the input string is invalid. + """ + if isinstance(sense, Constraint.Sense): + return sense + sense = sense.upper() + if sense not in ['E', 'L', 'G', 'EQ', 'LE', 'GE', '=', '==', '<=', '<', '>=', '>']: + raise QiskitOptimizationError('Invalid sense: {}'.format(sense)) + if sense in ['E', 'EQ', '=', '==']: + return Constraint.Sense.EQ + elif sense in ['L', 'LE', '<=', '<']: + return Constraint.Sense.LE + else: + return Constraint.Sense.GE + + def __init__(self, quadratic_program: 'QuadraticProgram', name: str, sense: 'Sense', rhs: float) -> None: """ Initializes the constraint. @@ -86,7 +85,7 @@ def name(self) -> str: return self._name @property - def sense(self) -> ConstraintSense: + def sense(self) -> 'Sense': """Returns the sense of the constraint. Returns: @@ -95,7 +94,7 @@ def sense(self) -> ConstraintSense: return self._sense @sense.setter - def sense(self, sense: ConstraintSense) -> None: + def sense(self, sense: 'Sense') -> None: """Sets the sense of the constraint. Args: @@ -113,7 +112,7 @@ def rhs(self) -> float: return self._rhs @rhs.setter - def rhs(self, rhs: ConstraintSense) -> None: + def rhs(self, rhs: float) -> None: """Sets the right-hand-side of the constraint. Args: diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index 38ac7608f9..7412c81a35 100644 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -19,7 +19,7 @@ from numpy import ndarray from scipy.sparse import spmatrix -from qiskit.optimization.problems.constraint import Constraint, ConstraintSense +from qiskit.optimization.problems.constraint import Constraint from qiskit.optimization.problems.linear_expression import LinearExpression @@ -29,7 +29,7 @@ class LinearConstraint(Constraint): def __init__(self, quadratic_program: "QuadraticProgram", name: str, linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]], - sense: ConstraintSense, + sense: Constraint.Sense, rhs: float ) -> None: """Constructs a linear constraint. diff --git a/qiskit/optimization/problems/quadratic_constraint.py b/qiskit/optimization/problems/quadratic_constraint.py index f6e9254002..f87cbab6ba 100644 --- a/qiskit/optimization/problems/quadratic_constraint.py +++ b/qiskit/optimization/problems/quadratic_constraint.py @@ -19,7 +19,7 @@ from numpy import ndarray from scipy.sparse import spmatrix -from qiskit.optimization.problems.constraint import Constraint, ConstraintSense +from qiskit.optimization.problems.constraint import Constraint from qiskit.optimization.problems.linear_expression import LinearExpression from qiskit.optimization.problems.quadratic_expression import QuadraticExpression @@ -32,7 +32,7 @@ def __init__(self, linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]], quadratic: Union[ndarray, spmatrix, List[List[float]], Dict[Tuple[Union[int, str], Union[int, str]], float]], - sense: ConstraintSense, + sense: Constraint.Sense, rhs: float ) -> None: """Constructs a quadratic constraint, consisting of a linear and a quadratic term. diff --git a/qiskit/optimization/problems/quadratic_objective.py b/qiskit/optimization/problems/quadratic_objective.py index 909d73af3f..d0fcacdfd9 100644 --- a/qiskit/optimization/problems/quadratic_objective.py +++ b/qiskit/optimization/problems/quadratic_objective.py @@ -25,23 +25,22 @@ from qiskit.optimization.problems.quadratic_expression import QuadraticExpression -class ObjSense(Enum): - """Objective Sense Type.""" - MINIMIZE = 1 - MAXIMIZE = -1 - - class QuadraticObjective(HasQuadraticProgram): """Representation of quadratic objective function of the form: constant + linear * x + x * quadratic * x. """ + class Sense(Enum): + """Objective Sense Type.""" + MINIMIZE = 1 + MAXIMIZE = -1 + def __init__(self, quadratic_program: "QuadraticProgram", constant: float = 0.0, linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]] = None, quadratic: Union[ndarray, spmatrix, List[List[float]], Dict[Tuple[Union[int, str], Union[int, str]], float]] = None, - sense: ObjSense = ObjSense.MINIMIZE + sense: 'Sense' = Sense.MINIMIZE ) -> None: """Constructs a quadratic objective function. @@ -122,7 +121,7 @@ def quadratic(self, quadratic: Union[ndarray, spmatrix, List[List[float]], self._quadratic = QuadraticExpression(self.quadratic_program, quadratic) @property - def sense(self) -> ObjSense: + def sense(self) -> 'Sense': """Returns the sense of the objective function. Returns: @@ -131,7 +130,7 @@ def sense(self) -> ObjSense: return self._sense @sense.setter - def sense(self, sense: ObjSense) -> None: + def sense(self, sense: 'Sense') -> None: """Sets the sense of the objective function. Args: diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index feeb1b53ab..58e891d74f 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -27,13 +27,13 @@ from scipy.sparse import spmatrix from qiskit.optimization import infinity, QiskitOptimizationError -from qiskit.optimization.problems.constraint import ConstraintSense +from qiskit.optimization.problems.constraint import Constraint from qiskit.optimization.problems.linear_constraint import LinearConstraint from qiskit.optimization.problems.linear_expression import LinearExpression from qiskit.optimization.problems.quadratic_constraint import QuadraticConstraint from qiskit.optimization.problems.quadratic_expression import QuadraticExpression -from qiskit.optimization.problems.quadratic_objective import QuadraticObjective, ObjSense -from qiskit.optimization.problems.variable import Variable, VarType +from qiskit.optimization.problems.quadratic_objective import QuadraticObjective +from qiskit.optimization.problems.variable import Variable logger = logging.getLogger(__name__) @@ -135,7 +135,7 @@ def variables_index(self) -> Dict[str, int]: def _add_variable(self, lowerbound: Union[float, int] = 0, upperbound: Union[float, int] = infinity, - vartype: VarType = VarType.CONTINUOUS, + vartype: Variable.Type = Variable.Type.CONTINUOUS, name: Optional[str] = None) -> Variable: """Checks whether a variable name is already taken and adds the variable to list and index if not. @@ -182,7 +182,7 @@ def continuous_var(self, lowerbound: Union[float, int] = 0, Raises: QiskitOptimizationError: if the variable name is already occupied. """ - return self._add_variable(lowerbound, upperbound, VarType.CONTINUOUS, name) + return self._add_variable(lowerbound, upperbound, Variable.Type.CONTINUOUS, name) def binary_var(self, name: Optional[str] = None) -> Variable: """Adds a binary variable to the quadratic program. @@ -196,7 +196,7 @@ def binary_var(self, name: Optional[str] = None) -> Variable: Raises: QiskitOptimizationError: if the variable name is already occupied. """ - return self._add_variable(0, 1, VarType.BINARY, name) + return self._add_variable(0, 1, Variable.Type.BINARY, name) def integer_var(self, lowerbound: Union[float, int] = 0, upperbound: Union[float, int] = infinity, @@ -214,7 +214,7 @@ def integer_var(self, lowerbound: Union[float, int] = 0, Raises: QiskitOptimizationError: if the variable name is already occupied. """ - return self._add_variable(lowerbound, upperbound, VarType.INTEGER, name) + return self._add_variable(lowerbound, upperbound, Variable.Type.INTEGER, name) def get_variable(self, i: Union[int, str]) -> Variable: """Returns a variable for a given name or index. @@ -230,7 +230,7 @@ def get_variable(self, i: Union[int, str]) -> Variable: else: return self.variables[self._variables_index[i]] - def get_num_vars(self, vartype: Optional[VarType] = None) -> int: + def get_num_vars(self, vartype: Optional[Variable.Type] = None) -> int: """Returns the total number of variables or the number of variables of the specified type. Args: @@ -250,7 +250,7 @@ def get_num_continuous_vars(self) -> int: Returns: The total number of continuous variables. """ - return self.get_num_vars(VarType.CONTINUOUS) + return self.get_num_vars(Variable.Type.CONTINUOUS) def get_num_binary_vars(self) -> int: """Returns the total number of binary variables. @@ -258,7 +258,7 @@ def get_num_binary_vars(self) -> int: Returns: The total number of binary variables. """ - return self.get_num_vars(VarType.BINARY) + return self.get_num_vars(Variable.Type.BINARY) def get_num_integer_vars(self) -> int: """Returns the total number of integer variables. @@ -266,7 +266,7 @@ def get_num_integer_vars(self) -> int: Returns: The total number of integer variables. """ - return self.get_num_vars(VarType.INTEGER) + return self.get_num_vars(Variable.Type.INTEGER) @property def linear_constraints(self) -> List[LinearConstraint]: @@ -289,7 +289,7 @@ def linear_constraints_index(self) -> Dict[str, int]: def linear_constraint(self, linear: Union[ndarray, spmatrix, List[float], Dict[Union[int, str], float]] = None, - sense: Union[str, ConstraintSense] = '<=', + sense: Union[str, Constraint.Sense] = '<=', rhs: float = 0.0, name: Optional[str] = None) -> LinearConstraint: """Adds a linear equality constraint to the quadratic program of the form: linear * x sense rhs. @@ -322,7 +322,7 @@ def linear_constraint(self, self.linear_constraints_index[name] = len(self.linear_constraints) if linear is None: linear = {} - constraint = LinearConstraint(self, name, linear, ConstraintSense.convert(sense), rhs) + constraint = LinearConstraint(self, name, linear, Constraint.Sense.convert(sense), rhs) self.linear_constraints.append(constraint) return constraint @@ -376,7 +376,7 @@ def quadratic_constraint(self, quadratic: Union[ndarray, spmatrix, List[List[float]], Dict[Tuple[Union[int, str], Union[int, str]], float]] = None, - sense: Union[str, ConstraintSense] = '<=', + sense: Union[str, Constraint.Sense] = '<=', rhs: float = 0.0, name: Optional[str] = None) -> QuadraticConstraint: """Adds a quadratic equality constraint to the quadratic program of the form: x * Q * x <= rhs. @@ -412,7 +412,7 @@ def quadratic_constraint(self, if quadratic is None: quadratic = {} constraint = QuadraticConstraint(self, name, linear, quadratic, - ConstraintSense.convert(sense), rhs) + Constraint.Sense.convert(sense), rhs) self.quadratic_constraints.append(constraint) return constraint @@ -499,7 +499,8 @@ def minimize(self, Returns: The created quadratic objective. """ - self._objective = QuadraticObjective(self, constant, linear, quadratic, ObjSense.MINIMIZE) + self._objective = QuadraticObjective(self, constant, linear, quadratic, + QuadraticObjective.Sense.MINIMIZE) def maximize(self, constant: float = 0.0, @@ -517,7 +518,8 @@ def maximize(self, Returns: The created quadratic objective. """ - self._objective = QuadraticObjective(self, constant, linear, quadratic, ObjSense.MAXIMIZE) + self._objective = QuadraticObjective(self, constant, linear, quadratic, + QuadraticObjective.Sense.MAXIMIZE) def from_docplex(self, model: Model) -> None: """Loads this quadratic program from a docplex model. @@ -674,11 +676,11 @@ def to_docplex(self) -> Model: # add variables var = {} for i, x in enumerate(self.variables): - if x.vartype == VarType.CONTINUOUS: + if x.vartype == Variable.Type.CONTINUOUS: var[i] = mdl.continuous_var(lb=x.lowerbound, ub=x.upperbound, name=x.name) - elif x.vartype == VarType.BINARY: + elif x.vartype == Variable.Type.BINARY: var[i] = mdl.binary_var(name=x.name) - elif x.vartype == VarType.INTEGER: + elif x.vartype == Variable.Type.INTEGER: var[i] = mdl.integer_var(lb=x.lowerbound, ub=x.upperbound, name=x.name) else: # should never happen @@ -690,7 +692,7 @@ def to_docplex(self) -> Model: objective += v * var[i] for (i, j), v in self.objective.quadratic.to_dict().items(): objective += v * var[i] * var[j] - if self.objective.sense == ObjSense.MINIMIZE: + if self.objective.sense == QuadraticObjective.Sense.MINIMIZE: mdl.minimize(objective) else: mdl.maximize(objective) @@ -705,11 +707,11 @@ def to_docplex(self) -> Model: for j, v in constraint.linear.to_dict().items(): linear_expr += v * var[j] sense = constraint.sense - if sense == ConstraintSense.EQ: + if sense == Constraint.Sense.EQ: mdl.add_constraint(linear_expr == rhs, ctname=name) - elif sense == ConstraintSense.GE: + elif sense == Constraint.Sense.GE: mdl.add_constraint(linear_expr >= rhs, ctname=name) - elif sense == ConstraintSense.LE: + elif sense == Constraint.Sense.LE: mdl.add_constraint(linear_expr <= rhs, ctname=name) else: # should never happen @@ -729,11 +731,11 @@ def to_docplex(self) -> Model: for (j, k), v in constraint.quadratic.to_dict().items(): quadratic_expr += v * var[j] * var[k] sense = constraint.sense - if sense == ConstraintSense.EQ: + if sense == Constraint.Sense.EQ: mdl.add_constraint(quadratic_expr == rhs, ctname=name) - elif sense == ConstraintSense.GE: + elif sense == Constraint.Sense.GE: mdl.add_constraint(quadratic_expr >= rhs, ctname=name) - elif sense == ConstraintSense.LE: + elif sense == Constraint.Sense.LE: mdl.add_constraint(quadratic_expr <= rhs, ctname=name) else: # should never happen @@ -891,19 +893,19 @@ def substitute_variables( return self._dst @staticmethod - def _feasible(sense: ConstraintSense, rhs: float) -> bool: + def _feasible(sense: Constraint.Sense, rhs: float) -> bool: """Checks feasibility of the following condition 0 `sense` rhs """ # I use the following pylint option because `rhs` should come to right # pylint: disable=misplaced-comparison-constant - if sense == ConstraintSense.EQ: + if sense == Constraint.Sense.EQ: if 0 == rhs: return True - elif sense == ConstraintSense.LE: + elif sense == Constraint.Sense.LE: if 0 <= rhs: return True - elif sense == ConstraintSense.GE: + elif sense == Constraint.Sense.GE: if 0 >= rhs: return True return False diff --git a/qiskit/optimization/problems/variable.py b/qiskit/optimization/problems/variable.py index 506697a7f4..dad0c306c6 100644 --- a/qiskit/optimization/problems/variable.py +++ b/qiskit/optimization/problems/variable.py @@ -21,20 +21,19 @@ from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram -class VarType(Enum): - """Constants defining variable type.""" - CONTINUOUS = 0 - BINARY = 1 - INTEGER = 2 - - class Variable(HasQuadraticProgram): """Representation of a variable.""" + class Type(Enum): + """Constants defining variable type.""" + CONTINUOUS = 0 + BINARY = 1 + INTEGER = 2 + def __init__(self, quadratic_program: 'QuadraticProgram', name: str, lowerbound: Union[float, int] = 0, upperbound: Union[float, int] = infinity, - vartype: VarType = VarType.CONTINUOUS) -> None: + vartype: 'Type' = Type.CONTINUOUS) -> None: """Creates a new Variable. The variables is exposed by the top-level `QuadraticProgram` class @@ -116,7 +115,7 @@ def upperbound(self, upperbound: Union[float, int]) -> None: self._upperbound = upperbound @property - def vartype(self) -> VarType: + def vartype(self) -> 'Type': """Returns the type of the variable. Returns: @@ -126,7 +125,7 @@ def vartype(self) -> VarType: return self._vartype @vartype.setter - def vartype(self, vartype: VarType) -> None: + def vartype(self, vartype: 'Type') -> None: """Sets the type of the variable. Args: @@ -134,7 +133,7 @@ def vartype(self, vartype: VarType) -> None: """ self._vartype = vartype - def as_tuple(self) -> Tuple[str, Union[float, int], Union[float, int], VarType]: + def as_tuple(self) -> Tuple[str, Union[float, int], Union[float, int], 'Type']: """ Returns a tuple corresponding to this variable. Returns: diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index 250d164254..ea8ee263ed 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -22,7 +22,7 @@ from qiskit.aqua.operators import WeightedPauliOperator from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.optimization import QuadraticProgram, QiskitOptimizationError -from qiskit.optimization.problems import ConstraintSense, VarType +from qiskit.optimization.problems import Constraint, Variable from qiskit.optimization.algorithms import OptimizationResult from qiskit.optimization.converters import ( InequalityToEquality, @@ -94,24 +94,24 @@ def test_inequality_binary(self): linear_constraint = {} linear_constraint['x0'] = 1 linear_constraint['x1'] = 1 - op.linear_constraint(linear_constraint, ConstraintSense.EQ, 1, 'x0x1') + op.linear_constraint(linear_constraint, Constraint.Sense.EQ, 1, 'x0x1') linear_constraint = {} linear_constraint['x1'] = 1 linear_constraint['x2'] = -1 - op.linear_constraint(linear_constraint, ConstraintSense.LE, 2, 'x1x2') + op.linear_constraint(linear_constraint, Constraint.Sense.LE, 2, 'x1x2') linear_constraint = {} linear_constraint['x0'] = 1 linear_constraint['x2'] = 3 - op.linear_constraint(linear_constraint, ConstraintSense.GE, 2, 'x0x2') + op.linear_constraint(linear_constraint, Constraint.Sense.GE, 2, 'x0x2') # Quadratic constraints quadratic = {} quadratic[('x0', 'x1')] = 1 quadratic[('x1', 'x2')] = 2 - op.quadratic_constraint({}, quadratic, ConstraintSense.LE, 3, 'x0x1_x1x2LE') + op.quadratic_constraint({}, quadratic, Constraint.Sense.LE, 3, 'x0x1_x1x2LE') quadratic = {} quadratic[('x0', 'x1')] = 3 quadratic[('x1', 'x2')] = 4 - op.quadratic_constraint({}, quadratic, ConstraintSense.GE, 3, 'x0x1_x1x2GE') + op.quadratic_constraint({}, quadratic, Constraint.Sense.GE, 3, 'x0x1_x1x2GE') # Convert inequality constraints into equality constraints conv = InequalityToEquality() op2 = conv.encode(op) @@ -124,7 +124,7 @@ def test_inequality_binary(self): op2.linear_constraints[0].linear.to_dict()[1], ] self.assertListEqual(lst, [1, 1]) - self.assertEqual(op2.linear_constraints[0].sense, ConstraintSense.EQ) + self.assertEqual(op2.linear_constraints[0].sense, Constraint.Sense.EQ) lst = [ op2.linear_constraints[1].linear.to_dict()[1], op2.linear_constraints[1].linear.to_dict()[2], @@ -133,7 +133,7 @@ def test_inequality_binary(self): self.assertListEqual(lst, [1, -1, 1]) lst = [op2.variables[3].lowerbound, op2.variables[3].upperbound] self.assertListEqual(lst, [0, 3]) - self.assertEqual(op2.linear_constraints[1].sense, ConstraintSense.EQ) + self.assertEqual(op2.linear_constraints[1].sense, Constraint.Sense.EQ) lst = [ op2.linear_constraints[2].linear.to_dict()[0], op2.linear_constraints[2].linear.to_dict()[2], @@ -142,7 +142,7 @@ def test_inequality_binary(self): self.assertListEqual(lst, [1, 3, -1]) lst = [op2.variables[4].lowerbound, op2.variables[4].upperbound] self.assertListEqual(lst, [0, 2]) - self.assertEqual(op2.linear_constraints[2].sense, ConstraintSense.EQ) + self.assertEqual(op2.linear_constraints[2].sense, Constraint.Sense.EQ) # For quadratic constraints lst = [ op2.quadratic_constraints[0].quadratic.to_dict()[(0, 1)], @@ -170,24 +170,24 @@ def test_inequality_integer(self): linear_constraint = {} linear_constraint['x0'] = 1 linear_constraint['x1'] = 1 - op.linear_constraint(linear_constraint, ConstraintSense.EQ, 1, 'x0x1') + op.linear_constraint(linear_constraint, Constraint.Sense.EQ, 1, 'x0x1') linear_constraint = {} linear_constraint['x1'] = 1 linear_constraint['x2'] = -1 - op.linear_constraint(linear_constraint, ConstraintSense.LE, 2, 'x1x2') + op.linear_constraint(linear_constraint, Constraint.Sense.LE, 2, 'x1x2') linear_constraint = {} linear_constraint['x0'] = 1 linear_constraint['x2'] = 3 - op.linear_constraint(linear_constraint, ConstraintSense.GE, 2, 'x0x2') + op.linear_constraint(linear_constraint, Constraint.Sense.GE, 2, 'x0x2') # Quadratic constraints quadratic = {} quadratic[('x0', 'x1')] = 1 quadratic[('x1', 'x2')] = 2 - op.quadratic_constraint({}, quadratic, ConstraintSense.LE, 3, 'x0x1_x1x2LE') + op.quadratic_constraint({}, quadratic, Constraint.Sense.LE, 3, 'x0x1_x1x2LE') quadratic = {} quadratic[('x0', 'x1')] = 3 quadratic[('x1', 'x2')] = 4 - op.quadratic_constraint({}, quadratic, ConstraintSense.GE, 3, 'x0x1_x1x2GE') + op.quadratic_constraint({}, quadratic, Constraint.Sense.GE, 3, 'x0x1_x1x2GE') conv = InequalityToEquality() op2 = conv.encode(op) # For linear constraints @@ -196,7 +196,7 @@ def test_inequality_integer(self): op2.linear_constraints[0].linear.to_dict()[1], ] self.assertListEqual(lst, [1, 1]) - self.assertEqual(op2.linear_constraints[0].sense, ConstraintSense.EQ) + self.assertEqual(op2.linear_constraints[0].sense, Constraint.Sense.EQ) lst = [ op2.linear_constraints[1].linear.to_dict()[1], op2.linear_constraints[1].linear.to_dict()[2], @@ -205,7 +205,7 @@ def test_inequality_integer(self): self.assertListEqual(lst, [1, -1, 1]) lst = [op2.variables[3].lowerbound, op2.variables[3].upperbound] self.assertListEqual(lst, [0, 8]) - self.assertEqual(op2.linear_constraints[1].sense, ConstraintSense.EQ) + self.assertEqual(op2.linear_constraints[1].sense, Constraint.Sense.EQ) lst = [ op2.linear_constraints[2].linear.to_dict()[0], op2.linear_constraints[2].linear.to_dict()[2], @@ -214,7 +214,7 @@ def test_inequality_integer(self): self.assertListEqual(lst, [1, 3, -1]) lst = [op2.variables[4].lowerbound, op2.variables[4].upperbound] self.assertListEqual(lst, [0, 10]) - self.assertEqual(op2.linear_constraints[2].sense, ConstraintSense.EQ) + self.assertEqual(op2.linear_constraints[2].sense, Constraint.Sense.EQ) # For quadratic constraints lst = [ op2.quadratic_constraints[0].quadratic.to_dict()[(0, 1)], @@ -242,19 +242,19 @@ def test_inequality_mode_integer(self): linear_constraint = {} linear_constraint['x0'] = 1 linear_constraint['x1'] = 1 - op.linear_constraint(linear_constraint, ConstraintSense.EQ, 1, 'x0x1') + op.linear_constraint(linear_constraint, Constraint.Sense.EQ, 1, 'x0x1') linear_constraint = {} linear_constraint['x1'] = 1 linear_constraint['x2'] = -1 - op.linear_constraint(linear_constraint, ConstraintSense.LE, 2, 'x1x2') + op.linear_constraint(linear_constraint, Constraint.Sense.LE, 2, 'x1x2') linear_constraint = {} linear_constraint['x0'] = 1 linear_constraint['x2'] = 3 - op.linear_constraint(linear_constraint, ConstraintSense.GE, 2, 'x0x2') + op.linear_constraint(linear_constraint, Constraint.Sense.GE, 2, 'x0x2') conv = InequalityToEquality() op2 = conv.encode(op, mode='integer') lst = [op2.variables[3].vartype, op2.variables[4].vartype] - self.assertListEqual(lst, [VarType.INTEGER, VarType.INTEGER]) + self.assertListEqual(lst, [Variable.Type.INTEGER, Variable.Type.INTEGER]) def test_inequality_mode_continuous(self): """ Test continuous mode of InequalityToEqualityConverter() """ @@ -265,19 +265,19 @@ def test_inequality_mode_continuous(self): linear_constraint = {} linear_constraint['x0'] = 1 linear_constraint['x1'] = 1 - op.linear_constraint(linear_constraint, ConstraintSense.EQ, 1, 'x0x1') + op.linear_constraint(linear_constraint, Constraint.Sense.EQ, 1, 'x0x1') linear_constraint = {} linear_constraint['x1'] = 1 linear_constraint['x2'] = -1 - op.linear_constraint(linear_constraint, ConstraintSense.LE, 2, 'x1x2') + op.linear_constraint(linear_constraint, Constraint.Sense.LE, 2, 'x1x2') linear_constraint = {} linear_constraint['x0'] = 1 linear_constraint['x2'] = 3 - op.linear_constraint(linear_constraint, ConstraintSense.GE, 2, 'x0x2') + op.linear_constraint(linear_constraint, Constraint.Sense.GE, 2, 'x0x2') conv = InequalityToEquality() op2 = conv.encode(op, mode='continuous') lst = [op2.variables[3].vartype, op2.variables[4].vartype] - self.assertListEqual(lst, [VarType.CONTINUOUS, VarType.CONTINUOUS]) + self.assertListEqual(lst, [Variable.Type.CONTINUOUS, Variable.Type.CONTINUOUS]) def test_inequality_mode_auto(self): """ Test auto mode of InequalityToEqualityConverter() """ @@ -288,19 +288,19 @@ def test_inequality_mode_auto(self): linear_constraint = {} linear_constraint['x0'] = 1 linear_constraint['x1'] = 1 - op.linear_constraint(linear_constraint, ConstraintSense.EQ, 1, 'x0x1') + op.linear_constraint(linear_constraint, Constraint.Sense.EQ, 1, 'x0x1') linear_constraint = {} linear_constraint['x1'] = 1 linear_constraint['x2'] = -1 - op.linear_constraint(linear_constraint, ConstraintSense.LE, 2, 'x1x2') + op.linear_constraint(linear_constraint, Constraint.Sense.LE, 2, 'x1x2') linear_constraint = {} linear_constraint['x0'] = 1.1 linear_constraint['x2'] = 2.2 - op.linear_constraint(linear_constraint, ConstraintSense.GE, 3.3, 'x0x2') + op.linear_constraint(linear_constraint, Constraint.Sense.GE, 3.3, 'x0x2') conv = InequalityToEquality() op2 = conv.encode(op, mode='auto') lst = [op2.variables[3].vartype, op2.variables[4].vartype] - self.assertListEqual(lst, [VarType.INTEGER, VarType.CONTINUOUS]) + self.assertListEqual(lst, [Variable.Type.INTEGER, Variable.Type.CONTINUOUS]) def test_penalize_sense(self): """ Test PenalizeLinearEqualityConstraints with senses """ @@ -311,15 +311,15 @@ def test_penalize_sense(self): linear_constraint = {} linear_constraint['x0'] = 1 linear_constraint['x1'] = 1 - op.linear_constraint(linear_constraint, ConstraintSense.EQ, 1, 'x0x1') + op.linear_constraint(linear_constraint, Constraint.Sense.EQ, 1, 'x0x1') linear_constraint = {} linear_constraint['x1'] = 1 linear_constraint['x2'] = -1 - op.linear_constraint(linear_constraint, ConstraintSense.LE, 2, 'x1x2') + op.linear_constraint(linear_constraint, Constraint.Sense.LE, 2, 'x1x2') linear_constraint = {} linear_constraint['x0'] = 1 linear_constraint['x2'] = 3 - op.linear_constraint(linear_constraint, ConstraintSense.GE, 2, 'x0x2') + op.linear_constraint(linear_constraint, Constraint.Sense.GE, 2, 'x0x2') self.assertEqual(len(op.linear_constraints), 3) conv = LinearEqualityToPenalty() with self.assertRaises(QiskitOptimizationError): @@ -334,15 +334,15 @@ def test_penalize_binary(self): linear_constraint = {} linear_constraint['x0'] = 1 linear_constraint['x1'] = 1 - op.linear_constraint(linear_constraint, ConstraintSense.EQ, 1, 'x0x1') + op.linear_constraint(linear_constraint, Constraint.Sense.EQ, 1, 'x0x1') linear_constraint = {} linear_constraint['x1'] = 1 linear_constraint['x2'] = -1 - op.linear_constraint(linear_constraint, ConstraintSense.EQ, 2, 'x1x2') + op.linear_constraint(linear_constraint, Constraint.Sense.EQ, 2, 'x1x2') linear_constraint = {} linear_constraint['x0'] = 1 linear_constraint['x2'] = 3 - op.linear_constraint(linear_constraint, ConstraintSense.EQ, 2, 'x0x2') + op.linear_constraint(linear_constraint, Constraint.Sense.EQ, 2, 'x0x2') self.assertEqual(len(op.linear_constraints), 3) conv = LinearEqualityToPenalty() op2 = conv.encode(op) @@ -357,15 +357,15 @@ def test_penalize_integer(self): linear_constraint = {} linear_constraint['x0'] = 1 linear_constraint['x1'] = 1 - op.linear_constraint(linear_constraint, ConstraintSense.EQ, 1, 'x0x1') + op.linear_constraint(linear_constraint, Constraint.Sense.EQ, 1, 'x0x1') linear_constraint = {} linear_constraint['x1'] = 1 linear_constraint['x2'] = -1 - op.linear_constraint(linear_constraint, ConstraintSense.EQ, 2, 'x1x2') + op.linear_constraint(linear_constraint, Constraint.Sense.EQ, 2, 'x1x2') linear_constraint = {} linear_constraint['x0'] = 1 linear_constraint['x2'] = 3 - op.linear_constraint(linear_constraint, ConstraintSense.EQ, 2, 'x0x2') + op.linear_constraint(linear_constraint, Constraint.Sense.EQ, 2, 'x0x2') self.assertEqual(len(op.linear_constraints), 3) conv = LinearEqualityToPenalty() op2 = conv.encode(op) @@ -384,7 +384,7 @@ def test_integer_to_binary(self): conv = IntegerToBinary() op2 = conv.encode(op) for x in op2.variables: - self.assertEqual(x.vartype, VarType.BINARY) + self.assertEqual(x.vartype, Variable.Type.BINARY) dct = op2.objective.linear.to_dict() self.assertEqual(dct[2], 3) self.assertEqual(dct[3], 6) @@ -404,7 +404,7 @@ def test_binary_to_integer(self): linear = {} for x in op.variables: linear[x.name] = 1 - op.linear_constraint(linear, ConstraintSense.EQ, 6, 'x0x1x2') + op.linear_constraint(linear, Constraint.Sense.EQ, 6, 'x0x1x2') conv = IntegerToBinary() _ = conv.encode(op) result = OptimizationResult(x=[0, 1, 1, 1, 1], fval=17) @@ -424,7 +424,7 @@ def test_optimizationproblem_to_operator(self): linear = {} for i, x in enumerate(op.variables): linear[x.name] = i + 1 - op.linear_constraint(linear, ConstraintSense.EQ, 3, 'sum1') + op.linear_constraint(linear, Constraint.Sense.EQ, 3, 'sum1') penalize = LinearEqualityToPenalty() op2ope = QuadraticProgramToOperator() op2 = penalize.encode(op) diff --git a/test/optimization/test_linear_constraint.py b/test/optimization/test_linear_constraint.py index fdc9006786..2afdda0ce7 100644 --- a/test/optimization/test_linear_constraint.py +++ b/test/optimization/test_linear_constraint.py @@ -15,12 +15,12 @@ """ Test LinearConstraint """ import unittest - from test.optimization.optimization_test_case import QiskitOptimizationTestCase + import numpy as np from qiskit.optimization import QuadraticProgram, QiskitOptimizationError -from qiskit.optimization.problems import ConstraintSense +from qiskit.optimization.problems import Constraint class TestLinearConstraint(QiskitOptimizationTestCase): @@ -41,7 +41,7 @@ def test_init(self): self.assertEqual(quadratic_program.get_num_linear_constraints(), 1) self.assertEqual(quadratic_program.linear_constraints[0].name, 'c0') self.assertEqual(len(quadratic_program.linear_constraints[0].linear.to_dict()), 0) - self.assertEqual(quadratic_program.linear_constraints[0].sense, ConstraintSense.EQ) + self.assertEqual(quadratic_program.linear_constraints[0].sense, Constraint.Sense.EQ) self.assertEqual(quadratic_program.linear_constraints[0].rhs, 0.0) self.assertEqual(quadratic_program.linear_constraints[0], quadratic_program.get_linear_constraint('c0')) @@ -56,7 +56,7 @@ def test_init(self): self.assertEqual(quadratic_program.linear_constraints[1].name, 'c1') self.assertTrue( (quadratic_program.linear_constraints[1].linear.to_array() == coefficients).all()) - self.assertEqual(quadratic_program.linear_constraints[1].sense, ConstraintSense.EQ) + self.assertEqual(quadratic_program.linear_constraints[1].sense, Constraint.Sense.EQ) self.assertEqual(quadratic_program.linear_constraints[1].rhs, 1.0) self.assertEqual(quadratic_program.linear_constraints[1], quadratic_program.get_linear_constraint('c1')) @@ -69,7 +69,7 @@ def test_init(self): self.assertEqual(quadratic_program.linear_constraints[2].name, 'c2') self.assertEqual(len(quadratic_program.linear_constraints[2].linear.to_dict()), 0) - self.assertEqual(quadratic_program.linear_constraints[2].sense, ConstraintSense.GE) + self.assertEqual(quadratic_program.linear_constraints[2].sense, Constraint.Sense.GE) self.assertEqual(quadratic_program.linear_constraints[2].rhs, 0.0) self.assertEqual(quadratic_program.linear_constraints[2], quadratic_program.get_linear_constraint('c2')) @@ -84,7 +84,7 @@ def test_init(self): self.assertEqual(quadratic_program.linear_constraints[3].name, 'c3') self.assertTrue( (quadratic_program.linear_constraints[3].linear.to_array() == coefficients).all()) - self.assertEqual(quadratic_program.linear_constraints[3].sense, ConstraintSense.GE) + self.assertEqual(quadratic_program.linear_constraints[3].sense, Constraint.Sense.GE) self.assertEqual(quadratic_program.linear_constraints[3].rhs, 1.0) self.assertEqual(quadratic_program.linear_constraints[3], quadratic_program.get_linear_constraint('c3')) @@ -95,9 +95,8 @@ def test_init(self): quadratic_program.linear_constraint(sense='<=') self.assertEqual(quadratic_program.get_num_linear_constraints(), 5) self.assertEqual(quadratic_program.linear_constraints[4].name, 'c4') - self.assertEqual(len(quadratic_program.linear_constraints[4].linear.to_dict()), - 0) - self.assertEqual(quadratic_program.linear_constraints[4].sense, ConstraintSense.LE) + self.assertEqual(len(quadratic_program.linear_constraints[4].linear.to_dict()), 0) + self.assertEqual(quadratic_program.linear_constraints[4].sense, Constraint.Sense.LE) self.assertEqual(quadratic_program.linear_constraints[4].rhs, 0.0) self.assertEqual(quadratic_program.linear_constraints[4], quadratic_program.get_linear_constraint('c4')) @@ -112,7 +111,7 @@ def test_init(self): self.assertEqual(quadratic_program.linear_constraints[5].name, 'c5') self.assertTrue( (quadratic_program.linear_constraints[5].linear.to_array() == coefficients).all()) - self.assertEqual(quadratic_program.linear_constraints[5].sense, ConstraintSense.LE) + self.assertEqual(quadratic_program.linear_constraints[5].sense, Constraint.Sense.LE) self.assertEqual(quadratic_program.linear_constraints[5].rhs, 1.0) self.assertEqual(quadratic_program.linear_constraints[5], quadratic_program.get_linear_constraint('c5')) diff --git a/test/optimization/test_quadratic_constraint.py b/test/optimization/test_quadratic_constraint.py index 69af4d67ef..e98286798c 100644 --- a/test/optimization/test_quadratic_constraint.py +++ b/test/optimization/test_quadratic_constraint.py @@ -15,12 +15,12 @@ """ Test QuadraticConstraint """ import unittest - from test.optimization.optimization_test_case import QiskitOptimizationTestCase + import numpy as np from qiskit.optimization import QuadraticProgram, QiskitOptimizationError -from qiskit.optimization.problems import ConstraintSense +from qiskit.optimization.problems import Constraint class TestQuadraticConstraint(QiskitOptimizationTestCase): @@ -47,7 +47,7 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[0].name, 'q0') self.assertEqual(len(quadratic_program.quadratic_constraints[0].linear.to_dict()), 0) self.assertEqual(len(quadratic_program.quadratic_constraints[0].quadratic.to_dict()), 0) - self.assertEqual(quadratic_program.quadratic_constraints[0].sense, ConstraintSense.EQ) + self.assertEqual(quadratic_program.quadratic_constraints[0].sense, Constraint.Sense.EQ) self.assertEqual(quadratic_program.quadratic_constraints[0].rhs, 0.0) self.assertEqual(quadratic_program.quadratic_constraints[0], quadratic_program.get_quadratic_constraint('q0')) @@ -67,7 +67,7 @@ def test_init(self): self.assertTrue( (quadratic_program.quadratic_constraints[1].quadratic.to_array() == quadratic_coeffs).all()) - self.assertEqual(quadratic_program.quadratic_constraints[1].sense, ConstraintSense.EQ) + self.assertEqual(quadratic_program.quadratic_constraints[1].sense, Constraint.Sense.EQ) self.assertEqual(quadratic_program.quadratic_constraints[1].rhs, 1.0) self.assertEqual(quadratic_program.quadratic_constraints[1], quadratic_program.get_quadratic_constraint('q1')) @@ -82,7 +82,7 @@ def test_init(self): self.assertEqual(quadratic_program.quadratic_constraints[2].name, 'q2') self.assertEqual(len(quadratic_program.quadratic_constraints[2].linear.to_dict()), 0) self.assertEqual(len(quadratic_program.quadratic_constraints[2].quadratic.to_dict()), 0) - self.assertEqual(quadratic_program.quadratic_constraints[2].sense, ConstraintSense.GE) + self.assertEqual(quadratic_program.quadratic_constraints[2].sense, Constraint.Sense.GE) self.assertEqual(quadratic_program.quadratic_constraints[2].rhs, 0.0) self.assertEqual(quadratic_program.quadratic_constraints[2], quadratic_program.get_quadratic_constraint('q2')) @@ -101,7 +101,7 @@ def test_init(self): (quadratic_program.quadratic_constraints[3].linear.to_array() == linear_coeffs).all()) self.assertTrue((quadratic_program.quadratic_constraints[3].quadratic.to_array() == quadratic_coeffs).all()) - self.assertEqual(quadratic_program.quadratic_constraints[3].sense, ConstraintSense.GE) + self.assertEqual(quadratic_program.quadratic_constraints[3].sense, Constraint.Sense.GE) self.assertEqual(quadratic_program.quadratic_constraints[3].rhs, 1.0) self.assertEqual(quadratic_program.quadratic_constraints[3], quadratic_program.get_quadratic_constraint('q3')) @@ -115,7 +115,7 @@ def test_init(self): self.assertEqual(quadratic_program.get_num_quadratic_constraints(), 5) self.assertEqual(quadratic_program.quadratic_constraints[4].name, 'q4') self.assertEqual(len(quadratic_program.quadratic_constraints[4].linear.to_dict()), 0) - self.assertEqual(quadratic_program.quadratic_constraints[4].sense, ConstraintSense.LE) + self.assertEqual(quadratic_program.quadratic_constraints[4].sense, Constraint.Sense.LE) self.assertEqual(quadratic_program.quadratic_constraints[4].rhs, 0.0) self.assertEqual(quadratic_program.quadratic_constraints[4], quadratic_program.get_quadratic_constraint('q4')) @@ -134,7 +134,7 @@ def test_init(self): (quadratic_program.quadratic_constraints[5].linear.to_array() == linear_coeffs).all()) self.assertTrue((quadratic_program.quadratic_constraints[5].quadratic.to_array() == quadratic_coeffs).all()) - self.assertEqual(quadratic_program.quadratic_constraints[5].sense, ConstraintSense.LE) + self.assertEqual(quadratic_program.quadratic_constraints[5].sense, Constraint.Sense.LE) self.assertEqual(quadratic_program.quadratic_constraints[5].rhs, 1.0) self.assertEqual(quadratic_program.quadratic_constraints[5], quadratic_program.get_quadratic_constraint('q5')) diff --git a/test/optimization/test_quadratic_objective.py b/test/optimization/test_quadratic_objective.py index bf46483940..3babf0f93d 100644 --- a/test/optimization/test_quadratic_objective.py +++ b/test/optimization/test_quadratic_objective.py @@ -15,12 +15,11 @@ """ Test QuadraticObjective """ import unittest - from test.optimization.optimization_test_case import QiskitOptimizationTestCase + import numpy as np -from qiskit.optimization import QuadraticProgram -from qiskit.optimization.problems.quadratic_objective import ObjSense +from qiskit.optimization.problems import QuadraticProgram, QuadraticObjective class TestQuadraticObjective(QiskitOptimizationTestCase): @@ -36,7 +35,7 @@ def test_init(self): self.assertEqual(quadratic_program.objective.constant, 0.0) self.assertEqual(len(quadratic_program.objective.linear.to_dict()), 0) self.assertEqual(len(quadratic_program.objective.quadratic.to_dict()), 0) - self.assertEqual(quadratic_program.objective.sense, ObjSense.MINIMIZE) + self.assertEqual(quadratic_program.objective.sense, QuadraticObjective.Sense.MINIMIZE) constant = 1.0 linear_coeffs = np.array(range(5)) @@ -52,7 +51,7 @@ def test_init(self): self.assertTrue((quadratic_program.objective.linear.to_array() == linear_coeffs).all()) self.assertTrue( (quadratic_program.objective.quadratic.to_array() == quadratic_coeffs).all()) - self.assertEqual(quadratic_program.objective.sense, ObjSense.MINIMIZE) + self.assertEqual(quadratic_program.objective.sense, QuadraticObjective.Sense.MINIMIZE) quadratic_program.maximize(constant, linear_coeffs, quadratic_coeffs) @@ -60,7 +59,7 @@ def test_init(self): self.assertTrue((quadratic_program.objective.linear.to_array() == linear_coeffs).all()) self.assertTrue( (quadratic_program.objective.quadratic.to_array() == quadratic_coeffs).all()) - self.assertEqual(quadratic_program.objective.sense, ObjSense.MAXIMIZE) + self.assertEqual(quadratic_program.objective.sense, QuadraticObjective.Sense.MAXIMIZE) self.assertEqual(quadratic_program.objective.evaluate(linear_coeffs), 931.0) @@ -88,13 +87,13 @@ def test_setters(self): self.assertTrue( (quadratic_program.objective.quadratic.to_array() == quadratic_coeffs).all()) - self.assertEqual(quadratic_program.objective.sense, ObjSense.MINIMIZE) + self.assertEqual(quadratic_program.objective.sense, QuadraticObjective.Sense.MINIMIZE) - quadratic_program.objective.sense = ObjSense.MAXIMIZE - self.assertEqual(quadratic_program.objective.sense, ObjSense.MAXIMIZE) + quadratic_program.objective.sense = quadratic_program.objective.Sense.MAXIMIZE + self.assertEqual(quadratic_program.objective.sense, QuadraticObjective.Sense.MAXIMIZE) - quadratic_program.objective.sense = ObjSense.MINIMIZE - self.assertEqual(quadratic_program.objective.sense, ObjSense.MINIMIZE) + quadratic_program.objective.sense = quadratic_program.objective.Sense.MINIMIZE + self.assertEqual(quadratic_program.objective.sense, QuadraticObjective.Sense.MINIMIZE) if __name__ == '__main__': diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index 29da37a2f6..2643d6a9db 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -20,7 +20,7 @@ from docplex.mp.model import Model, DOcplexException from qiskit.optimization import QuadraticProgram, QiskitOptimizationError, infinity -from qiskit.optimization.problems import VarType, ConstraintSense, ObjSense +from qiskit.optimization.problems import Variable, Constraint, QuadraticObjective class TestQuadraticProgram(QiskitOptimizationTestCase): @@ -78,7 +78,7 @@ def test_variables_handling(self): self.assertEqual(x_0.name, 'x0') self.assertEqual(x_0.lowerbound, 0) self.assertEqual(x_0.upperbound, infinity) - self.assertEqual(x_0.vartype, VarType.CONTINUOUS) + self.assertEqual(x_0.vartype, Variable.Type.CONTINUOUS) self.assertEqual(quadratic_program.get_num_vars(), 1) self.assertEqual(quadratic_program.get_num_continuous_vars(), 1) @@ -89,7 +89,7 @@ def test_variables_handling(self): self.assertEqual(x_1.name, 'x1') self.assertEqual(x_1.lowerbound, 5) self.assertEqual(x_1.upperbound, 10) - self.assertEqual(x_1.vartype, VarType.CONTINUOUS) + self.assertEqual(x_1.vartype, Variable.Type.CONTINUOUS) self.assertEqual(quadratic_program.get_num_vars(), 2) self.assertEqual(quadratic_program.get_num_continuous_vars(), 2) @@ -100,7 +100,7 @@ def test_variables_handling(self): self.assertEqual(x_2.name, 'x2') self.assertEqual(x_2.lowerbound, 0) self.assertEqual(x_2.upperbound, 1) - self.assertEqual(x_2.vartype, VarType.BINARY) + self.assertEqual(x_2.vartype, Variable.Type.BINARY) self.assertEqual(quadratic_program.get_num_vars(), 3) self.assertEqual(quadratic_program.get_num_continuous_vars(), 2) @@ -111,7 +111,7 @@ def test_variables_handling(self): self.assertEqual(x_3.name, 'x3') self.assertEqual(x_3.lowerbound, 0) self.assertEqual(x_3.upperbound, 1) - self.assertEqual(x_3.vartype, VarType.BINARY) + self.assertEqual(x_3.vartype, Variable.Type.BINARY) self.assertEqual(quadratic_program.get_num_vars(), 4) self.assertEqual(quadratic_program.get_num_continuous_vars(), 2) @@ -122,7 +122,7 @@ def test_variables_handling(self): self.assertEqual(x_4.name, 'x4') self.assertEqual(x_4.lowerbound, 0) self.assertEqual(x_4.upperbound, infinity) - self.assertEqual(x_4.vartype, VarType.INTEGER) + self.assertEqual(x_4.vartype, Variable.Type.INTEGER) self.assertEqual(quadratic_program.get_num_vars(), 5) self.assertEqual(quadratic_program.get_num_continuous_vars(), 2) @@ -133,7 +133,7 @@ def test_variables_handling(self): self.assertEqual(x_5.name, 'x5') self.assertEqual(x_5.lowerbound, 5) self.assertEqual(x_5.upperbound, 10) - self.assertEqual(x_5.vartype, VarType.INTEGER) + self.assertEqual(x_5.vartype, Variable.Type.INTEGER) self.assertEqual(quadratic_program.get_num_vars(), 6) self.assertEqual(quadratic_program.get_num_continuous_vars(), 2) @@ -174,7 +174,7 @@ def test_linear_constraints_handling(self): self.assertDictEqual(lin[0].linear.to_dict(), {0: 1}) self.assertDictEqual(lin[0].linear.to_dict(use_name=True), {'x': 1}) self.assertListEqual(lin[0].linear.to_array().tolist(), [1, 0, 0]) - self.assertEqual(lin[0].sense, ConstraintSense.EQ) + self.assertEqual(lin[0].sense, Constraint.Sense.EQ) self.assertEqual(lin[0].rhs, 1) self.assertEqual(lin[0].name, 'c0') self.assertEqual(q_p.get_linear_constraint(0).name, 'c0') @@ -183,7 +183,7 @@ def test_linear_constraints_handling(self): self.assertDictEqual(lin[1].linear.to_dict(), {1: 1}) self.assertDictEqual(lin[1].linear.to_dict(use_name=True), {'y': 1}) self.assertListEqual(lin[1].linear.to_array().tolist(), [0, 1, 0]) - self.assertEqual(lin[1].sense, ConstraintSense.LE) + self.assertEqual(lin[1].sense, Constraint.Sense.LE) self.assertEqual(lin[1].rhs, 1) self.assertEqual(lin[1].name, 'c1') self.assertEqual(q_p.get_linear_constraint(1).name, 'c1') @@ -192,7 +192,7 @@ def test_linear_constraints_handling(self): self.assertDictEqual(lin[2].linear.to_dict(), {2: 1}) self.assertDictEqual(lin[2].linear.to_dict(use_name=True), {'z': 1}) self.assertListEqual(lin[2].linear.to_array().tolist(), [0, 0, 1]) - self.assertEqual(lin[2].sense, ConstraintSense.GE) + self.assertEqual(lin[2].sense, Constraint.Sense.GE) self.assertEqual(lin[2].rhs, 1) self.assertEqual(lin[2].name, 'c2') self.assertEqual(q_p.get_linear_constraint(2).name, 'c2') @@ -215,7 +215,7 @@ def test_linear_constraints_handling(self): self.assertDictEqual(lin[1].linear.to_dict(), {2: 1}) self.assertDictEqual(lin[1].linear.to_dict(use_name=True), {'z': 1}) self.assertListEqual(lin[1].linear.to_array().tolist(), [0, 0, 1]) - self.assertEqual(lin[1].sense, ConstraintSense.GE) + self.assertEqual(lin[1].sense, Constraint.Sense.GE) self.assertEqual(lin[1].rhs, 1) self.assertEqual(lin[1].name, 'c2') self.assertEqual(q_p.get_linear_constraint(1).name, 'c2') @@ -227,21 +227,21 @@ def test_linear_constraints_handling(self): q_p.remove_linear_constraint(9) q_p.linear_constraint(sense='E') - self.assertEqual(q_p.linear_constraints[-1].sense, ConstraintSense.EQ) + self.assertEqual(q_p.linear_constraints[-1].sense, Constraint.Sense.EQ) q_p.linear_constraint(sense='G') - self.assertEqual(q_p.linear_constraints[-1].sense, ConstraintSense.GE) + self.assertEqual(q_p.linear_constraints[-1].sense, Constraint.Sense.GE) q_p.linear_constraint(sense='L') - self.assertEqual(q_p.linear_constraints[-1].sense, ConstraintSense.LE) + self.assertEqual(q_p.linear_constraints[-1].sense, Constraint.Sense.LE) q_p.linear_constraint(sense='EQ') - self.assertEqual(q_p.linear_constraints[-1].sense, ConstraintSense.EQ) + self.assertEqual(q_p.linear_constraints[-1].sense, Constraint.Sense.EQ) q_p.linear_constraint(sense='GE') - self.assertEqual(q_p.linear_constraints[-1].sense, ConstraintSense.GE) + self.assertEqual(q_p.linear_constraints[-1].sense, Constraint.Sense.GE) q_p.linear_constraint(sense='LE') - self.assertEqual(q_p.linear_constraints[-1].sense, ConstraintSense.LE) + self.assertEqual(q_p.linear_constraints[-1].sense, Constraint.Sense.LE) q_p.linear_constraint(sense='=') - self.assertEqual(q_p.linear_constraints[-1].sense, ConstraintSense.EQ) + self.assertEqual(q_p.linear_constraints[-1].sense, Constraint.Sense.EQ) q_p.linear_constraint(sense='>') - self.assertEqual(q_p.linear_constraints[-1].sense, ConstraintSense.GE) + self.assertEqual(q_p.linear_constraints[-1].sense, Constraint.Sense.GE) q_p.linear_constraint(sense='<') with self.assertRaises(QiskitOptimizationError): @@ -273,7 +273,7 @@ def test_quadratic_constraints_handling(self): [[0, 1, 0], [0, 0, 0], [0, 0, 0]]) self.assertListEqual(quad[0].quadratic.to_array(symmetric=True).tolist(), [[0, 0.5, 0], [0.5, 0, 0], [0, 0, 0]]) - self.assertEqual(quad[0].sense, ConstraintSense.EQ) + self.assertEqual(quad[0].sense, Constraint.Sense.EQ) self.assertEqual(quad[0].rhs, 1) self.assertEqual(quad[0].name, 'q0') self.assertEqual(q_p.get_quadratic_constraint(0).name, 'q0') @@ -292,7 +292,7 @@ def test_quadratic_constraints_handling(self): [[0, 0, 0], [0, 0, 1], [0, 0, 0]]) self.assertListEqual(quad[1].quadratic.to_array(symmetric=True).tolist(), [[0, 0, 0], [0, 0, 0.5], [0, 0.5, 0]]) - self.assertEqual(quad[1].sense, ConstraintSense.LE) + self.assertEqual(quad[1].sense, Constraint.Sense.LE) self.assertEqual(quad[1].rhs, 1) self.assertEqual(quad[1].name, 'q1') self.assertEqual(q_p.get_quadratic_constraint(1).name, 'q1') @@ -311,7 +311,7 @@ def test_quadratic_constraints_handling(self): [[0, 0, 1], [0, 0, 0], [0, 0, 0]]) self.assertListEqual(quad[2].quadratic.to_array(symmetric=True).tolist(), [[0, 0, 0.5], [0, 0, 0], [0.5, 0, 0]]) - self.assertEqual(quad[2].sense, ConstraintSense.GE) + self.assertEqual(quad[2].sense, Constraint.Sense.GE) self.assertEqual(quad[2].rhs, 1) self.assertEqual(quad[2].name, 'q2') self.assertEqual(q_p.get_quadratic_constraint(2).name, 'q2') @@ -344,7 +344,7 @@ def test_quadratic_constraints_handling(self): [[0, 0, 1], [0, 0, 0], [0, 0, 0]]) self.assertListEqual(quad[1].quadratic.to_array(symmetric=True).tolist(), [[0, 0, 0.5], [0, 0, 0], [0.5, 0, 0]]) - self.assertEqual(quad[1].sense, ConstraintSense.GE) + self.assertEqual(quad[1].sense, Constraint.Sense.GE) self.assertEqual(quad[1].rhs, 1) self.assertEqual(quad[1].name, 'q2') self.assertEqual(q_p.get_quadratic_constraint(1).name, 'q2') @@ -363,13 +363,13 @@ def test_objective_handling(self): q_p.binary_var('z') q_p.minimize() obj = q_p.objective - self.assertEqual(obj.sense, ObjSense.MINIMIZE) + self.assertEqual(obj.sense, QuadraticObjective.Sense.MINIMIZE) self.assertEqual(obj.constant, 0) self.assertDictEqual(obj.linear.to_dict(), {}) self.assertDictEqual(obj.quadratic.to_dict(), {}) q_p.maximize(1, {'y': 1}, {('z', 'x'): 1, ('y', 'y'): 1}) obj = q_p.objective - self.assertEqual(obj.sense, ObjSense.MAXIMIZE) + self.assertEqual(obj.sense, QuadraticObjective.Sense.MAXIMIZE) self.assertEqual(obj.constant, 1) self.assertDictEqual(obj.linear.to_dict(), {1: 1}) self.assertDictEqual(obj.linear.to_dict(use_name=True), {'y': 1}) @@ -404,19 +404,19 @@ def test_read_from_lp_file(self): self.assertEqual(q_p.get_num_quadratic_constraints(), 3) self.assertEqual(q_p.variables[0].name, 'x') - self.assertEqual(q_p.variables[0].vartype, VarType.BINARY) + self.assertEqual(q_p.variables[0].vartype, Variable.Type.BINARY) self.assertEqual(q_p.variables[0].lowerbound, 0) self.assertEqual(q_p.variables[0].upperbound, 1) self.assertEqual(q_p.variables[1].name, 'y') - self.assertEqual(q_p.variables[1].vartype, VarType.INTEGER) + self.assertEqual(q_p.variables[1].vartype, Variable.Type.INTEGER) self.assertEqual(q_p.variables[1].lowerbound, -1) self.assertEqual(q_p.variables[1].upperbound, 5) self.assertEqual(q_p.variables[2].name, 'z') - self.assertEqual(q_p.variables[2].vartype, VarType.CONTINUOUS) + self.assertEqual(q_p.variables[2].vartype, Variable.Type.CONTINUOUS) self.assertEqual(q_p.variables[2].lowerbound, -1) self.assertEqual(q_p.variables[2].upperbound, 5) - self.assertEqual(q_p.objective.sense, ObjSense.MINIMIZE) + self.assertEqual(q_p.objective.sense, QuadraticObjective.Sense.MINIMIZE) self.assertEqual(q_p.objective.constant, 1) self.assertDictEqual(q_p.objective.linear.to_dict(use_name=True), {'x': 1, 'y': -1, 'z': 10}) @@ -426,15 +426,15 @@ def test_read_from_lp_file(self): cst = q_p.linear_constraints self.assertEqual(cst[0].name, 'lin_eq') self.assertDictEqual(cst[0].linear.to_dict(use_name=True), {'x': 1, 'y': 2}) - self.assertEqual(cst[0].sense, ConstraintSense.EQ) + self.assertEqual(cst[0].sense, Constraint.Sense.EQ) self.assertEqual(cst[0].rhs, 1) self.assertEqual(cst[1].name, 'lin_leq') self.assertDictEqual(cst[1].linear.to_dict(use_name=True), {'x': 1, 'y': 2}) - self.assertEqual(cst[1].sense, ConstraintSense.LE) + self.assertEqual(cst[1].sense, Constraint.Sense.LE) self.assertEqual(cst[1].rhs, 1) self.assertEqual(cst[2].name, 'lin_geq') self.assertDictEqual(cst[2].linear.to_dict(use_name=True), {'x': 1, 'y': 2}) - self.assertEqual(cst[2].sense, ConstraintSense.GE) + self.assertEqual(cst[2].sense, Constraint.Sense.GE) self.assertEqual(cst[2].rhs, 1) cst = q_p.quadratic_constraints @@ -442,19 +442,19 @@ def test_read_from_lp_file(self): self.assertDictEqual(cst[0].linear.to_dict(use_name=True), {'x': 1, 'y': 1}) self.assertDictEqual(cst[0].quadratic.to_dict(use_name=True), {('x', 'x'): 1, ('y', 'z'): -1, ('z', 'z'): 2}) - self.assertEqual(cst[0].sense, ConstraintSense.EQ) + self.assertEqual(cst[0].sense, Constraint.Sense.EQ) self.assertEqual(cst[0].rhs, 1) self.assertEqual(cst[1].name, 'quad_leq') self.assertDictEqual(cst[1].linear.to_dict(use_name=True), {'x': 1, 'y': 1}) self.assertDictEqual(cst[1].quadratic.to_dict(use_name=True), {('x', 'x'): 1, ('y', 'z'): -1, ('z', 'z'): 2}) - self.assertEqual(cst[1].sense, ConstraintSense.LE) + self.assertEqual(cst[1].sense, Constraint.Sense.LE) self.assertEqual(cst[1].rhs, 1) self.assertEqual(cst[2].name, 'quad_geq') self.assertDictEqual(cst[2].linear.to_dict(use_name=True), {'x': 1, 'y': 1}) self.assertDictEqual(cst[2].quadratic.to_dict(use_name=True), {('x', 'x'): 1, ('y', 'z'): -1, ('z', 'z'): 2}) - self.assertEqual(cst[2].sense, ConstraintSense.GE) + self.assertEqual(cst[2].sense, Constraint.Sense.GE) self.assertEqual(cst[2].rhs, 1) except RuntimeError as ex: msg = str(ex) diff --git a/test/optimization/test_variable.py b/test/optimization/test_variable.py index 971bae34b4..32b7ba18a7 100644 --- a/test/optimization/test_variable.py +++ b/test/optimization/test_variable.py @@ -18,7 +18,7 @@ from test.optimization.optimization_test_case import QiskitOptimizationTestCase from qiskit.optimization import infinity -from qiskit.optimization.problems import QuadraticProgram, Variable, VarType +from qiskit.optimization.problems import QuadraticProgram, Variable class TestVariable(QiskitOptimizationTestCase): @@ -31,14 +31,14 @@ def test_init(self): name = 'variable' lowerbound = 0 upperbound = 10 - vartype = VarType.INTEGER + vartype = Variable.Type.INTEGER variable = Variable(quadratic_program, name, lowerbound, upperbound, vartype) self.assertEqual(variable.name, name) self.assertEqual(variable.lowerbound, lowerbound) self.assertEqual(variable.upperbound, upperbound) - self.assertEqual(variable.vartype, VarType.INTEGER) + self.assertEqual(variable.vartype, Variable.Type.INTEGER) def test_init_default(self): """ test init with default values.""" @@ -51,7 +51,7 @@ def test_init_default(self): self.assertEqual(variable.name, name) self.assertEqual(variable.lowerbound, 0) self.assertEqual(variable.upperbound, infinity) - self.assertEqual(variable.vartype, VarType.CONTINUOUS) + self.assertEqual(variable.vartype, Variable.Type.CONTINUOUS) if __name__ == '__main__': From b44ce3240a34a16ea7344d8fa33aab8ab2e91384 Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Fri, 24 Apr 2020 12:31:32 +0100 Subject: [PATCH 293/323] minor --- qiskit/optimization/algorithms/admm_optimizer.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 54abc0b268..cb6e42e61a 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -31,6 +31,9 @@ logger = logging.getLogger(__name__) +# import sys +# logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) + class ADMMParameters: """Defines a set of parameters for ADMM optimizer.""" @@ -278,6 +281,9 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: iteration = 0 residual = 1.e+2 + # debug + self._log.debug(problem.print_as_lp_string()) + while (iteration < self._params.max_iter and residual > self._params.tol) \ and (elapsed_time < self._params.max_time): if binary_indices: @@ -286,11 +292,13 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: # else, no binary variables exist, # and no update to be done in this case. # debug + self._log.debug(op1.print_as_lp_string()) self._log.debug("x0=%s", self._state.x0) op2 = self._create_step2_problem() self._state.u, self._state.z = self._update_x1(op2) # debug + self._log.debug(op2.print_as_lp_string()) self._log.debug("u=%s", self._state.u) self._log.debug("z=%s", self._state.z) @@ -299,9 +307,12 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: op3 = self._create_step3_problem() self._state.y = self._update_y(op3) # debug + self._log.debug(op3.print_as_lp_string()) self._log.debug("y=%s", self._state.y) self._state.lambda_mult = self._update_lambda_mult() + # debug + self._log.debug("lambda={}".format(self._state.lambda_mult)) cost_iterate = self._get_objective_value() constraint_residual = self._get_constraint_residual() @@ -309,7 +320,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: merit = self._get_merit(cost_iterate, constraint_residual) # debug self._log.debug("cost_iterate=%s, cr=%s, merit=%s", - cost_iterate, constraint_residual, merit) + self._state.sense * cost_iterate, constraint_residual, merit) # costs are saved with their original sign. self._state.cost_iterates.append(self._state.sense * cost_iterate) @@ -547,7 +558,7 @@ def _create_step2_problem(self) -> QuadraticProgram: A newly created optimization problem. """ op2 = copy.deepcopy(self._state.op) - # replace binary variables with the continuous ones that look like binary + # replace binary variables with the continuous ones bound in [0,1] # x0(bin) -> z(cts) # u (cts) are still there unchanged for i, var_index in enumerate(self._state.binary_indices): From 745b412b4d2425b82e2b273b45bfdb19efe0b54c Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Fri, 24 Apr 2020 21:44:27 +0900 Subject: [PATCH 294/323] tune --- qiskit/optimization/problems/quadratic_program.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 58e891d74f..5b210c082a 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -55,7 +55,7 @@ def __init__(self, name: str = '') -> None: name: The name of the quadratic program. """ self._name = name - self._status = self.Status.VALID + self._status = QuadraticProgram.Status.VALID self._variables: List[Variable] = [] self._variables_index: Dict[str, int] = {} @@ -73,7 +73,7 @@ def clear(self) -> None: objective function as well as the name. """ self._name = '' - self._status = self.Status.VALID + self._status = QuadraticProgram.Status.VALID self._variables.clear() self._variables_index.clear() From 7d6e066c339a4a5ba60c3bfe4a98488d82a648fb Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Fri, 24 Apr 2020 21:57:17 +0900 Subject: [PATCH 295/323] enable type hint of enums --- .../algorithms/optimization_algorithm.py | 22 +++--- qiskit/optimization/problems/constraint.py | 75 ++++++++++--------- .../problems/quadratic_objective.py | 17 +++-- .../problems/quadratic_program.py | 13 ++-- qiskit/optimization/problems/variable.py | 21 +++--- 5 files changed, 82 insertions(+), 66 deletions(-) diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index b14b4a5d1b..9dc0ae3b7d 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -64,6 +64,14 @@ def solve(self, problem: QuadraticProgram) -> 'OptimizationResult': raise NotImplementedError +class OptimizationResultStatus(Enum): + """Feasible values for the termination status of an optimization algorithm. + """ + SUCCESS = 0 + FAILURE = 1 + INFEASIBLE = 2 + + class OptimizationResult: """The optimization result class. @@ -78,15 +86,11 @@ class OptimizationResult: status: The termination status of the algorithm. """ - class Status(Enum): - """Feasible values for the termination status of an optimization algorithm. - """ - SUCCESS = 0 - FAILURE = 1 - INFEASIBLE = 2 + Status = OptimizationResultStatus def __init__(self, x: Optional[Any] = None, fval: Optional[Any] = None, - results: Optional[Any] = None, status: Status = Status.SUCCESS) -> None: + results: Optional[Any] = None, + status: OptimizationResultStatus = OptimizationResultStatus.SUCCESS) -> None: """Initialize the optimization result.""" self._val = x self._fval = fval @@ -126,7 +130,7 @@ def results(self) -> Any: return self._results @property - def status(self) -> 'Status': + def status(self) -> OptimizationResultStatus: """Return the termination status of the algorithm. Returns: @@ -162,7 +166,7 @@ def results(self, results: Any) -> None: self._results = results @status.setter - def status(self, status: Status) -> None: + def status(self, status: OptimizationResultStatus) -> None: """Set a new termination status. Args: diff --git a/qiskit/optimization/problems/constraint.py b/qiskit/optimization/problems/constraint.py index 12ef9298c5..57229c7ba1 100644 --- a/qiskit/optimization/problems/constraint.py +++ b/qiskit/optimization/problems/constraint.py @@ -24,43 +24,46 @@ from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram +class ConstraintSense(Enum): + """Constants Sense Type.""" + + # pylint: disable=invalid-name + LE = 0 + GE = 1 + EQ = 2 + + @staticmethod + def convert(sense: Union[str, 'ConstraintSense']) -> 'ConstraintSense': + """Convert a string into a corresponding sense of constraints + + Args: + sense: A string or sense of constraints + + Returns: + The sense of constraints + + Raises: + QiskitOptimizationError: if the input string is invalid. + """ + if isinstance(sense, ConstraintSense): + return sense + sense = sense.upper() + if sense not in ['E', 'L', 'G', 'EQ', 'LE', 'GE', '=', '==', '<=', '<', '>=', '>']: + raise QiskitOptimizationError('Invalid sense: {}'.format(sense)) + if sense in ['E', 'EQ', '=', '==']: + return ConstraintSense.EQ + elif sense in ['L', 'LE', '<=', '<']: + return ConstraintSense.LE + else: + return ConstraintSense.GE + + class Constraint(HasQuadraticProgram): """Abstract Constraint Class.""" - class Sense(Enum): - """Constants Sense Type.""" - - # pylint: disable=invalid-name - LE = 0 - GE = 1 - EQ = 2 - - @staticmethod - def convert(sense: Union[str, 'Sense']) -> 'Sense': - """Convert a string into a corresponding sense of constraints - - Args: - sense: A string or sense of constraints - - Returns: - The sense of constraints - - Raises: - QiskitOptimizationError: if the input string is invalid. - """ - if isinstance(sense, Constraint.Sense): - return sense - sense = sense.upper() - if sense not in ['E', 'L', 'G', 'EQ', 'LE', 'GE', '=', '==', '<=', '<', '>=', '>']: - raise QiskitOptimizationError('Invalid sense: {}'.format(sense)) - if sense in ['E', 'EQ', '=', '==']: - return Constraint.Sense.EQ - elif sense in ['L', 'LE', '<=', '<']: - return Constraint.Sense.LE - else: - return Constraint.Sense.GE - - def __init__(self, quadratic_program: 'QuadraticProgram', name: str, sense: 'Sense', + Sense = ConstraintSense + + def __init__(self, quadratic_program: 'QuadraticProgram', name: str, sense: ConstraintSense, rhs: float) -> None: """ Initializes the constraint. @@ -85,7 +88,7 @@ def name(self) -> str: return self._name @property - def sense(self) -> 'Sense': + def sense(self) -> ConstraintSense: """Returns the sense of the constraint. Returns: @@ -94,7 +97,7 @@ def sense(self) -> 'Sense': return self._sense @sense.setter - def sense(self, sense: 'Sense') -> None: + def sense(self, sense: ConstraintSense) -> None: """Sets the sense of the constraint. Args: diff --git a/qiskit/optimization/problems/quadratic_objective.py b/qiskit/optimization/problems/quadratic_objective.py index d0fcacdfd9..42ff06c82d 100644 --- a/qiskit/optimization/problems/quadratic_objective.py +++ b/qiskit/optimization/problems/quadratic_objective.py @@ -25,22 +25,25 @@ from qiskit.optimization.problems.quadratic_expression import QuadraticExpression +class ObjSense(Enum): + """Objective Sense Type.""" + MINIMIZE = 1 + MAXIMIZE = -1 + + class QuadraticObjective(HasQuadraticProgram): """Representation of quadratic objective function of the form: constant + linear * x + x * quadratic * x. """ - class Sense(Enum): - """Objective Sense Type.""" - MINIMIZE = 1 - MAXIMIZE = -1 + Sense = ObjSense def __init__(self, quadratic_program: "QuadraticProgram", constant: float = 0.0, linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]] = None, quadratic: Union[ndarray, spmatrix, List[List[float]], Dict[Tuple[Union[int, str], Union[int, str]], float]] = None, - sense: 'Sense' = Sense.MINIMIZE + sense: ObjSense = ObjSense.MINIMIZE ) -> None: """Constructs a quadratic objective function. @@ -121,7 +124,7 @@ def quadratic(self, quadratic: Union[ndarray, spmatrix, List[List[float]], self._quadratic = QuadraticExpression(self.quadratic_program, quadratic) @property - def sense(self) -> 'Sense': + def sense(self) -> ObjSense: """Returns the sense of the objective function. Returns: @@ -130,7 +133,7 @@ def sense(self) -> 'Sense': return self._sense @sense.setter - def sense(self, sense: 'Sense') -> None: + def sense(self, sense: ObjSense) -> None: """Sets the sense of the objective function. Args: diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 5b210c082a..a9759d793c 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -38,15 +38,18 @@ logger = logging.getLogger(__name__) +class QuadraticProgramStatus(Enum): + """Status of QuadraticProgram""" + VALID = 0 + INFEASIBLE = 1 + + class QuadraticProgram: """Representation of a Quadratically Constrained Quadratic Program supporting inequality and equality constraints as well as continuous, binary, and integer variables. """ - class Status(Enum): - """Status of QuadraticProgram""" - VALID = 0 - INFEASIBLE = 1 + Status = QuadraticProgramStatus def __init__(self, name: str = '') -> None: """Constructs a quadratic program. @@ -105,7 +108,7 @@ def name(self, name: str) -> None: self._name = name @property - def status(self) -> 'Status': + def status(self) -> QuadraticProgramStatus: """Status of the quadratic program. It can be infeasible due to variable substitution. diff --git a/qiskit/optimization/problems/variable.py b/qiskit/optimization/problems/variable.py index dad0c306c6..b116581ad8 100644 --- a/qiskit/optimization/problems/variable.py +++ b/qiskit/optimization/problems/variable.py @@ -21,19 +21,22 @@ from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram +class VarType(Enum): + """Constants defining variable type.""" + CONTINUOUS = 0 + BINARY = 1 + INTEGER = 2 + + class Variable(HasQuadraticProgram): """Representation of a variable.""" - class Type(Enum): - """Constants defining variable type.""" - CONTINUOUS = 0 - BINARY = 1 - INTEGER = 2 + Type = VarType def __init__(self, quadratic_program: 'QuadraticProgram', name: str, lowerbound: Union[float, int] = 0, upperbound: Union[float, int] = infinity, - vartype: 'Type' = Type.CONTINUOUS) -> None: + vartype: VarType = VarType.CONTINUOUS) -> None: """Creates a new Variable. The variables is exposed by the top-level `QuadraticProgram` class @@ -115,7 +118,7 @@ def upperbound(self, upperbound: Union[float, int]) -> None: self._upperbound = upperbound @property - def vartype(self) -> 'Type': + def vartype(self) -> VarType: """Returns the type of the variable. Returns: @@ -125,7 +128,7 @@ def vartype(self) -> 'Type': return self._vartype @vartype.setter - def vartype(self, vartype: 'Type') -> None: + def vartype(self, vartype: VarType) -> None: """Sets the type of the variable. Args: @@ -133,7 +136,7 @@ def vartype(self, vartype: 'Type') -> None: """ self._vartype = vartype - def as_tuple(self) -> Tuple[str, Union[float, int], Union[float, int], 'Type']: + def as_tuple(self) -> Tuple[str, Union[float, int], Union[float, int], VarType]: """ Returns a tuple corresponding to this variable. Returns: From 8b0eb9d279aedc5db351f283d505147081a12548 Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Fri, 24 Apr 2020 15:18:21 +0100 Subject: [PATCH 296/323] supporting equality and quadratic constraints --- .../optimization/algorithms/admm_optimizer.py | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index cb6e42e61a..33f813bf9e 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -154,8 +154,9 @@ def __init__(self, self.rho = rho_initial # new features - self.binary_equality_constraints = [] - self.inequality_constraints = [] + self.binary_equality_constraints = [] # lin. eq. constraints with bin. vars. only + self.equality_constraints = [] # all equality constraints + self.inequality_constraints = [] # all inequality constraints class ADMMOptimizerResult(OptimizationResult): @@ -408,19 +409,29 @@ def _convert_problem_representation(self) -> None: # separate constraints for constraint in self._state.op.linear_constraints: if constraint.sense == ConstraintSense.EQ: - constraint_var_indices = set(constraint.linear.to_dict().keys()) + self._state.equality_constraints.append(constraint) + # verify that there are only binary variables in the constraint + # this is to build A0, b0 in step 1 + constraint_var_indices = set(constraint.linear.to_dict().keys()) if constraint_var_indices.issubset(binary_var_indices): self._state.binary_equality_constraints.append(constraint) + elif constraint.sense in (ConstraintSense.LE, ConstraintSense.GE): self._state.inequality_constraints.append(constraint) - + + for constraint in self._state.op.quadratic_constraints: + if constraint.sense == ConstraintSense.EQ: + self._state.equality_constraints.append(constraint) + elif constraint.sense in (ConstraintSense.LE, ConstraintSense.GE): + self._state.inequality_constraints.append(constraint) + # objective self._state.q0 = self._get_q(self._state.binary_indices) self._state.c0 = self._get_c(self._state.binary_indices) self._state.q1 = self._get_q(self._state.continuous_indices) self._state.c1 = self._get_c(self._state.continuous_indices) - # constraints + # equality constraints with binary vars only self._state.a0, self._state.b0 = self._get_a0_b0() def _get_q(self, variable_indices: List[int]) -> np.ndarray: @@ -689,7 +700,7 @@ def _update_rho(self, primal_residual: float, dual_residual: float) -> None: def _get_constraint_residual(self) -> float: """Compute violation of the constraints of the original problem, as: - * norm 1 of the body-rhs of the constraints A0 x0 - b0 + * norm 1 of the body-rhs of eq. constraints * -1 * min(body - rhs, 0) for geq constraints * max(body - rhs, 0) for leq constraints @@ -698,17 +709,17 @@ def _get_constraint_residual(self) -> float: """ solution = self._get_current_solution() # equality constraints - cr0 = 0 - for constraint in self._state.binary_equality_constraints: - cr0 += np.abs(constraint.evaluate(solution) - constraint.rhs) + cr_eq = 0 + for constraint in self._state.equality_constraints: + cr_eq += np.abs(constraint.evaluate(solution) - constraint.rhs) # inequality constraints - cr12 = 0 + cr_ineq = 0 for constraint in self._state.inequality_constraints: sense = -1 if constraint.sense == ConstraintSense.GE else 1 - cr12 += max(sense * (constraint.evaluate(solution) - constraint.rhs), 0) + cr_ineq += max(sense * (constraint.evaluate(solution) - constraint.rhs), 0) - return cr0 + cr12 + return cr_eq + cr_ineq def _get_merit(self, cost_iterate: float, constraint_residual: float) -> float: """Compute merit value associated with the current iterate From 329d614e888590735b8471695623eed912cf8cf7 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Fri, 24 Apr 2020 15:54:16 +0100 Subject: [PATCH 297/323] more simplifications --- .../optimization/algorithms/admm_optimizer.py | 102 ++++++++++-------- test/optimization/test_admm.py | 12 +-- 2 files changed, 62 insertions(+), 52 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 33f813bf9e..b1717f0f88 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -19,11 +19,10 @@ from typing import List, Optional, Any import numpy as np -from scipy.linalg import block_diag from qiskit.optimization.algorithms.cplex_optimizer import CplexOptimizer from qiskit.optimization.algorithms.optimization_algorithm import (OptimizationAlgorithm, OptimizationResult) -from qiskit.optimization.problems import VarType, ConstraintSense, LinearConstraint +from qiskit.optimization.problems import VarType, ConstraintSense, LinearConstraint, ObjSense from qiskit.optimization.problems.quadratic_program import QuadraticProgram UPDATE_RHO_BY_TEN_PERCENT = 0 @@ -110,7 +109,6 @@ def __init__(self, # Indices of the variables self.binary_indices = binary_indices self.continuous_indices = continuous_indices - self.sense = op.objective.sense.value # define heavily used matrix, they are used at each iteration, so let's cache them, # they are np.ndarrays @@ -123,13 +121,6 @@ def __init__(self, # constraints self.a0 = None self.b0 = None - self.a1 = None - self.b1 = None - self.a2 = None - self.a3 = None - self.b2 = None - self.a4 = None - self.b3 = None # These are the parameters that are updated in the ADMM iterations. self.u: np.ndarray = np.zeros(len(continuous_indices)) @@ -159,8 +150,8 @@ def __init__(self, self.inequality_constraints = [] # all inequality constraints -class ADMMOptimizerResult(OptimizationResult): - """ ADMMOptimizer Result.""" +class ADMMOptimizationResult(OptimizationResult): + """ ADMMOptimization Result.""" def __init__(self, x: Optional[Any] = None, fval: Optional[Any] = None, state: Optional[ADMMState] = None, results: Optional[Any] = None) -> None: @@ -243,16 +234,10 @@ def get_compatibility_msg(self, problem: QuadraticProgram) -> Optional[str]: # binary and continuous vars are mixed. msg += 'Binary and continuous variables are not separable in the objective. ' - # 3. no quadratic constraints are supported. - quad_constraints = len(problem.quadratic_constraints) - if quad_constraints is not None and quad_constraints > 0: - # quadratic constraints are not supported. - msg += 'Quadratic constraints are not supported. ' - # if an error occurred, return error message, otherwise, return None return msg - def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: + def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: """Tries to solves the given problem using ADMM algorithm. Args: @@ -264,6 +249,12 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: Raises: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ + # debug + self._log.debug("Initial problem: ", problem.print_as_lp_string()) + + # we deal with minimization in the optimizer, so turn the problem to minimization + problem, sense = self._turn_to_minimization(problem) + # parse problem and convert to an ADMM specific representation. binary_indices = self._get_variable_indices(problem, VarType.BINARY) continuous_indices = self._get_variable_indices(problem, VarType.CONTINUOUS) @@ -282,24 +273,21 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: iteration = 0 residual = 1.e+2 - # debug - self._log.debug(problem.print_as_lp_string()) - while (iteration < self._params.max_iter and residual > self._params.tol) \ and (elapsed_time < self._params.max_time): if binary_indices: op1 = self._create_step1_problem() self._state.x0 = self._update_x0(op1) - # else, no binary variables exist, - # and no update to be done in this case. + # debug + self._log.debug("Step 1 sub-problem: ", op1.print_as_lp_string()) + # else, no binary variables exist, and no update to be done in this case. # debug - self._log.debug(op1.print_as_lp_string()) self._log.debug("x0=%s", self._state.x0) op2 = self._create_step2_problem() self._state.u, self._state.z = self._update_x1(op2) # debug - self._log.debug(op2.print_as_lp_string()) + self._log.debug("Step 2 sub-problem:", op2.print_as_lp_string()) self._log.debug("u=%s", self._state.u) self._log.debug("z=%s", self._state.z) @@ -307,13 +295,14 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: if binary_indices: op3 = self._create_step3_problem() self._state.y = self._update_y(op3) + # debug + self._log.debug("Step 3 sub-problem: ", op3.print_as_lp_string()) # debug - self._log.debug(op3.print_as_lp_string()) self._log.debug("y=%s", self._state.y) self._state.lambda_mult = self._update_lambda_mult() # debug - self._log.debug("lambda={}".format(self._state.lambda_mult)) + self._log.debug("lambda: ", self._state.lambda_mult) cost_iterate = self._get_objective_value() constraint_residual = self._get_constraint_residual() @@ -321,10 +310,10 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: merit = self._get_merit(cost_iterate, constraint_residual) # debug self._log.debug("cost_iterate=%s, cr=%s, merit=%s", - self._state.sense * cost_iterate, constraint_residual, merit) + cost_iterate, constraint_residual, merit) # costs are saved with their original sign. - self._state.cost_iterates.append(self._state.sense * cost_iterate) + self._state.cost_iterates.append(cost_iterate) self._state.residuals.append(residual) self._state.dual_residuals.append(dual_residual) self._state.cons_r.append(constraint_residual) @@ -344,13 +333,37 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizerResult: 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 + # third parameter is our internal state of computations. - result = ADMMOptimizerResult(solution, objective_value, self._state) + result = ADMMOptimizationResult(solution, objective_value, self._state) # debug self._log.debug("solution=%s, objective=%s at iteration=%s", solution, objective_value, iteration) return result + @staticmethod + def _turn_to_minimization(qp: QuadraticProgram): + """ + 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: + qp: 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 = qp.objective.sense.value + if qp.objective.sense == ObjSense.MAXIMIZE: + qp = copy.deepcopy(qp) + qp.objective.sense = ObjSense.MINIMIZE + qp.objective.linear = (-1) * qp.objective.linear.coefficients + qp.objective.quadratic = (-1) * qp.objective.quadratic.coefficients + return qp, 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. @@ -370,6 +383,12 @@ def _get_variable_indices(op: QuadraticProgram, var_type: VarType) -> List[int]: return indices def _get_current_solution(self) -> np.ndarray: + """ + Returns current solution of the problem. + + Returns: + An array of the current solution. + """ return self._revert_solution_indexes(self._state.x0, self._state.u) def _revert_solution_indexes(self, binary_vars: np.ndarray, continuous_vars: np.ndarray) \ @@ -387,10 +406,6 @@ def _revert_solution_indexes(self, binary_vars: np.ndarray, continuous_vars: np. # restore solution at the original index location solution.put(self._state.binary_indices, binary_vars) solution.put(self._state.continuous_indices, continuous_vars) - # for i, binary_index in enumerate(self._state.binary_indices): - # solution[binary_index] = binary_vars[i] - # for i, continuous_index in enumerate(self._state.continuous_indices): - # solution[continuous_index] = continuous_vars[i] return solution def _convert_problem_representation(self) -> None: @@ -419,7 +434,8 @@ def _convert_problem_representation(self) -> None: elif constraint.sense in (ConstraintSense.LE, ConstraintSense.GE): self._state.inequality_constraints.append(constraint) - + + # separate quadratic constraints into eq and non-eq for constraint in self._state.op.quadratic_constraints: if constraint.sense == ConstraintSense.EQ: self._state.equality_constraints.append(constraint) @@ -453,9 +469,7 @@ def _get_q(self, variable_indices: List[int]) -> np.ndarray: # coefficients_as_array q[i, j] = self._state.op.objective.quadratic[var_index_i, var_index_j] - # flip the sign, according to the optimization sense, e.g. sense == 1 if minimize, - # sense == -1 if maximize. - return q * self._state.sense + return q def _get_c(self, variable_indices: List[int]) -> np.ndarray: """Constructs a vector for the variables with the specified indices from the linear terms @@ -468,9 +482,6 @@ def _get_c(self, variable_indices: List[int]) -> np.ndarray: A numpy array of the shape(len(variable_indices)). """ c = self._state.op.objective.linear.to_array().take(variable_indices) - # flip the sign, according to the optimization sense, e.g. sense == 1 if minimize, - # sense == -1 if maximize. - c *= self._state.sense return c def _assign_row_values(self, matrix: List[List[float]], vector: List[float], @@ -529,7 +540,6 @@ def _get_a0_b0(self) -> (np.ndarray, np.ndarray): vector = [] for constraint in self._state.binary_equality_constraints: - print("Constraint name: {}".format(constraint.name)) self._assign_row_values(matrix, vector, constraint, self._state.binary_indices) return self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) @@ -578,9 +588,9 @@ def _create_step2_problem(self) -> QuadraticProgram: variable.upperbound = 1. variable.lowerbound = 0. # replacing Q0 objective and take of min/max sense, initially we consider minimization - op2.objective.quadratic[var_index, var_index] = self._state.sense * self._state.rho / 2 + op2.objective.quadratic[var_index, var_index] = self._state.rho / 2 # replacing linear objective - op2.objective.linear[var_index] = self._state.sense * (-1 * self._state.lambda_mult[i] - self._state.rho * (self._state.x0[i] - self._state.y[i])) + op2.objective.linear[var_index] = -1 * self._state.lambda_mult[i] - self._state.rho * (self._state.x0[i] - self._state.y[i]) # remove A0 x0 = b0 constraints for constraint in self._state.binary_equality_constraints: @@ -739,7 +749,7 @@ def _get_objective_value(self) -> float: Returns: Value of the objective function as a float """ - return self._state.op.objective.evaluate(self._get_current_solution()) * self._state.sense + return self._state.op.objective.evaluate(self._get_current_solution()) def _get_solution_residuals(self, iteration: int) -> (float, float): """Compute primal and dual residual. diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index 8c63b191ac..c6af5845ac 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -20,7 +20,7 @@ from docplex.mp.model import Model from qiskit.optimization.algorithms import CplexOptimizer, MinimumEigenOptimizer from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters, \ - ADMMOptimizerResult, ADMMState + ADMMOptimizationResult, ADMMState from qiskit.optimization.problems import QuadraticProgram @@ -46,9 +46,9 @@ def test_admm_maximization(self): solver = ADMMOptimizer(qubo_optimizer=qubo_optimizer, continuous_optimizer=continuous_optimizer, params=admm_params) - solution: ADMMOptimizerResult = solver.solve(op) + solution: ADMMOptimizationResult = solver.solve(op) self.assertIsNotNone(solution) - self.assertIsInstance(solution, ADMMOptimizerResult) + self.assertIsInstance(solution, ADMMOptimizationResult) self.assertIsNotNone(solution.x) np.testing.assert_almost_equal([10, 0], solution.x, 3) @@ -96,7 +96,7 @@ def test_admm_ex4(self): continuous_optimizer=continuous_optimizer, ) solution = solver.solve(op) self.assertIsNotNone(solution) - self.assertIsInstance(solution, ADMMOptimizerResult) + self.assertIsInstance(solution, ADMMOptimizationResult) self.assertIsNotNone(solution.x) np.testing.assert_almost_equal([1., 0., 1.], solution.x, 3) self.assertIsNotNone(solution.fval) @@ -143,7 +143,7 @@ def test_admm_ex5(self): solution = solver.solve(op) self.assertIsNotNone(solution) - self.assertIsInstance(solution, ADMMOptimizerResult) + self.assertIsInstance(solution, ADMMOptimizationResult) self.assertIsNotNone(solution.x) np.testing.assert_almost_equal([1., 0., 1.], solution.x, 3) self.assertIsNotNone(solution.fval) @@ -189,7 +189,7 @@ def test_admm_ex6(self): solution = solver.solve(op) self.assertIsNotNone(solution) - self.assertIsInstance(solution, ADMMOptimizerResult) + self.assertIsInstance(solution, ADMMOptimizationResult) self.assertIsNotNone(solution.x) np.testing.assert_almost_equal([1., 0., 0., 2.], solution.x, 3) self.assertIsNotNone(solution.fval) From f3a172e95d01680965235adf2148efa1b056b289 Mon Sep 17 00:00:00 2001 From: Claudio Gambella Date: Fri, 24 Apr 2020 16:07:08 +0100 Subject: [PATCH 298/323] admm tests on equality and quadratic constraints --- test/optimization/test_admm.py | 89 ++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index c6af5845ac..acbbc9d2b0 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -198,3 +198,92 @@ def test_admm_ex6(self): self.assertIsInstance(solution.state, ADMMState) except NameError as ex: self.skipTest(str(ex)) + + + def test_eq_ctn_constr(self): + """ + Simple example to test equality constraints with continuous variables. + """ + try: + mdl = Model() + + # pylint:disable=invalid-name + v = mdl.binary_var(name='v') + w = mdl.continuous_var(name='w', lb=0.) + t = mdl.continuous_var(name='t', lb=0.) + + mdl.minimize(v + w + t) + mdl.add_constraint(2 * v + w >= 2, "cons1") + mdl.add_constraint(w + t == 1, "cons2") + + op = QuadraticProgram() + op.from_docplex(mdl) + + # qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) + qubo_optimizer = CplexOptimizer() + + continuous_optimizer = CplexOptimizer() + + admm_params = ADMMParameters( + rho_initial=1001, beta=1000, factor_c=900, + max_iter=100, three_block=True, + ) + + solver = ADMMOptimizer(params=admm_params, qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer, ) + solution = solver.solve(op) + + self.assertIsNotNone(solution) + self.assertIsInstance(solution, ADMMOptimizationResult) + self.assertIsNotNone(solution.x) + np.testing.assert_almost_equal([0., 1., 0.], solution.x, 3) + self.assertIsNotNone(solution.fval) + np.testing.assert_almost_equal(1., solution.fval, 3) + self.assertIsNotNone(solution.state) + self.assertIsInstance(solution.state, ADMMState) + except NameError as ex: + self.skipTest(str(ex)) + + def test_quad_constraints(self): + """ + Simple example to test quadratic constraints. + """ + + try: + mdl = Model('') + + v = mdl.binary_var(name='v') + w = mdl.continuous_var(name='w', lb=0.) + + mdl.minimize(v + w) + mdl.add_constraint(v + w >= 1, "cons2") + mdl.add_constraint(v ** 2 + w ** 2 <= 1, "cons2") + + op = QuadraticProgram() + op.from_docplex(mdl) + + # qubo_optimizer = MinimumEigenOptimizer(NumPyMinimumEigensolver()) + qubo_optimizer = CplexOptimizer() + + continuous_optimizer = CplexOptimizer() + + admm_params = ADMMParameters( + rho_initial=1001, beta=1000, factor_c=900, + max_iter=100, three_block=True, + ) + + solver = ADMMOptimizer(params=admm_params, qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer, ) + solution = solver.solve(op) + + self.assertIsNotNone(solution) + self.assertIsInstance(solution, ADMMOptimizationResult) + self.assertIsNotNone(solution.x) + np.testing.assert_almost_equal([0., 0.999], solution.x, 3) + self.assertIsNotNone(solution.fval) + np.testing.assert_almost_equal(0.999, solution.fval, 3) + self.assertIsNotNone(solution.state) + self.assertIsInstance(solution.state, ADMMState) + + except NameError as ex: + self.skipTest(str(ex)) From eab98bd6c9f2ab6478378462eb5c78d3c45ce897 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Fri, 24 Apr 2020 17:22:50 +0100 Subject: [PATCH 299/323] fixes and more testing --- .../optimization/algorithms/admm_optimizer.py | 11 ++-- test/optimization/test_admm.py | 62 +++++++++++++++---- 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index b1717f0f88..1422b46a4b 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -250,7 +250,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ # debug - self._log.debug("Initial problem: ", problem.print_as_lp_string()) + self._log.debug("Initial problem: %s", problem.print_as_lp_string()) # we deal with minimization in the optimizer, so turn the problem to minimization problem, sense = self._turn_to_minimization(problem) @@ -279,7 +279,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: op1 = self._create_step1_problem() self._state.x0 = self._update_x0(op1) # debug - self._log.debug("Step 1 sub-problem: ", op1.print_as_lp_string()) + self._log.debug("Step 1 sub-problem: %s", op1.print_as_lp_string()) # else, no binary variables exist, and no update to be done in this case. # debug self._log.debug("x0=%s", self._state.x0) @@ -287,7 +287,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: op2 = self._create_step2_problem() self._state.u, self._state.z = self._update_x1(op2) # debug - self._log.debug("Step 2 sub-problem:", op2.print_as_lp_string()) + self._log.debug("Step 2 sub-problem: %s", op2.print_as_lp_string()) self._log.debug("u=%s", self._state.u) self._log.debug("z=%s", self._state.z) @@ -296,13 +296,13 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: op3 = self._create_step3_problem() self._state.y = self._update_y(op3) # debug - self._log.debug("Step 3 sub-problem: ", op3.print_as_lp_string()) + self._log.debug("Step 3 sub-problem: %s", op3.print_as_lp_string()) # debug self._log.debug("y=%s", self._state.y) self._state.lambda_mult = self._update_lambda_mult() # debug - self._log.debug("lambda: ", self._state.lambda_mult) + self._log.debug("lambda: %s", self._state.lambda_mult) cost_iterate = self._get_objective_value() constraint_residual = self._get_constraint_residual() @@ -360,6 +360,7 @@ def _turn_to_minimization(qp: QuadraticProgram): if qp.objective.sense == ObjSense.MAXIMIZE: qp = copy.deepcopy(qp) qp.objective.sense = ObjSense.MINIMIZE + qp.objective.constant = (-1) * qp.objective.constant qp.objective.linear = (-1) * qp.objective.linear.coefficients qp.objective.quadratic = (-1) * qp.objective.quadratic.coefficients return qp, sense diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index acbbc9d2b0..a344db8e89 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -30,7 +30,7 @@ class TestADMMOptimizer(QiskitOptimizationTestCase): def test_admm_maximization(self): """Tests a simple maximization problem using ADMM optimizer""" try: - mdl = Model('test') + mdl = Model('simple-max') c = mdl.continuous_var(lb=0, ub=10, name='c') x = mdl.binary_var(name='x') mdl.maximize(c + x * x) @@ -199,13 +199,53 @@ def test_admm_ex6(self): except NameError as ex: self.skipTest(str(ex)) + def test_admm_ex6_max(self): + """Example 6 as maximization""" + try: + mdl = Model('ex6-max') + + # pylint:disable=invalid-name + v = mdl.binary_var(name='v') + w = mdl.binary_var(name='w') + t = mdl.binary_var(name='t') + u = mdl.continuous_var(name='u') - def test_eq_ctn_constr(self): - """ - Simple example to test equality constraints with continuous variables. - """ + # mdl.minimize(v + w + t + 5 * (u - 2) ** 2) + mdl.maximize(- v - w - t - 5 * (u - 2) ** 2) + mdl.add_constraint(v + 2 * w + t + u <= 3, "cons1") + mdl.add_constraint(v + w + t >= 1, "cons2") + mdl.add_constraint(v + w == 1, "cons3") + + op = QuadraticProgram() + op.from_docplex(mdl) + + qubo_optimizer = CplexOptimizer() + continuous_optimizer = CplexOptimizer() + + admm_params = ADMMParameters( + rho_initial=1001, beta=1000, factor_c=900, + max_iter=100, three_block=True, tol=1.e-6 + ) + + solver = ADMMOptimizer(params=admm_params, qubo_optimizer=qubo_optimizer, + continuous_optimizer=continuous_optimizer) + solution = solver.solve(op) + + self.assertIsNotNone(solution) + self.assertIsInstance(solution, ADMMOptimizationResult) + self.assertIsNotNone(solution.x) + np.testing.assert_almost_equal([1., 0., 0., 2.], solution.x, 3) + self.assertIsNotNone(solution.fval) + np.testing.assert_almost_equal(-1., solution.fval, 3) + self.assertIsNotNone(solution.state) + self.assertIsInstance(solution.state, ADMMState) + except NameError as ex: + self.skipTest(str(ex)) + + def test_equality_constraints_with_continuous_variables(self): + """Simple example to test equality constraints with continuous variables.""" try: - mdl = Model() + mdl = Model("eq-constraints-cts-vars") # pylint:disable=invalid-name v = mdl.binary_var(name='v') @@ -245,12 +285,10 @@ def test_eq_ctn_constr(self): self.skipTest(str(ex)) def test_quad_constraints(self): - """ - Simple example to test quadratic constraints. - """ + """Simple example to test quadratic constraints.""" try: - mdl = Model('') + mdl = Model('quad-constraints') v = mdl.binary_var(name='v') w = mdl.continuous_var(name='w', lb=0.) @@ -279,9 +317,9 @@ def test_quad_constraints(self): self.assertIsNotNone(solution) self.assertIsInstance(solution, ADMMOptimizationResult) self.assertIsNotNone(solution.x) - np.testing.assert_almost_equal([0., 0.999], solution.x, 3) + np.testing.assert_almost_equal([0., 1.], solution.x, 3) self.assertIsNotNone(solution.fval) - np.testing.assert_almost_equal(0.999, solution.fval, 3) + np.testing.assert_almost_equal(1., solution.fval, 3) self.assertIsNotNone(solution.state) self.assertIsInstance(solution.state, ADMMState) From 3ae0579f7a8fb2de6541ad1ca0e9c4676e084501 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Sat, 25 Apr 2020 01:39:27 +0900 Subject: [PATCH 300/323] deal with from_docplex corner cases --- .../problems/quadratic_program.py | 25 +++++++++++---- test/optimization/test_quadratic_program.py | 32 +++++++++++++++++++ 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index a9759d793c..7eedd69f5c 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -19,6 +19,9 @@ from math import fsum from typing import List, Union, Dict, Optional, Tuple +from docplex.mp.constr import (LinearConstraint as DocplexLinearConstraint, + QuadraticConstraint as DocplexQuadraticConstraint, + NotEqualConstraint) from docplex.mp.linear import Var from docplex.mp.model import Model from docplex.mp.model_reader import ModelReader @@ -558,7 +561,8 @@ def from_docplex(self, model: Model) -> None: x_new = self.integer_var(x.lb, x.ub, x.name) var_names[x] = x_new.name else: - raise QiskitOptimizationError("Unsupported variable type!") + raise QiskitOptimizationError( + "Unsupported variable type: {} {}".format(x.name, x.vartype)) # objective sense minimize = model.objective_sense.is_minimize() @@ -592,8 +596,14 @@ def from_docplex(self, model: Model) -> None: self.maximize(constant, linear, quadratic) # get linear constraints - for i in range(model.number_of_linear_constraints): - constraint = model.get_constraint_by_index(i) + for constraint in model.iter_constraints(): + if isinstance(constraint, DocplexQuadraticConstraint): + # ignore quadratic constraints + continue + if not isinstance(constraint, DocplexLinearConstraint) or \ + isinstance(constraint, NotEqualConstraint): + raise QiskitOptimizationError( + 'Unsupported constraint: {}'.format(constraint)) name = constraint.name sense = constraint.sense @@ -614,11 +624,11 @@ def from_docplex(self, model: Model) -> None: elif sense == sense.LE: self.linear_constraint(lhs, '<=', rhs, name) else: - raise QiskitOptimizationError("Unsupported constraint sense: {}".format(sense)) + raise QiskitOptimizationError( + "Unsupported constraint sense: {}".format(constraint)) # get quadratic constraints - for i in range(model.number_of_quadratic_constraints): - constraint = model.get_quadratic_by_index(i) + for constraint in model.iter_quadratic_constraints(): name = constraint.name sense = constraint.sense @@ -661,7 +671,8 @@ def from_docplex(self, model: Model) -> None: elif sense == sense.LE: self.quadratic_constraint(linear, quadratic, '<=', rhs, name) else: - raise QiskitOptimizationError("Unsupported constraint sense: {}".format(sense)) + raise QiskitOptimizationError( + "Unsupported constraint sense: {}".format(constraint)) def to_docplex(self) -> Model: """Returns a docplex model corresponding to this quadratic program. diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index 2643d6a9db..03587f8a5b 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -524,6 +524,38 @@ def test_docplex(self): self.assertEqual(q_p.pprint_as_string(), mod.pprint_as_string()) self.assertEqual(q_p.print_as_lp_string(), mod.export_as_lp_string()) + with self.assertRaises(QiskitOptimizationError): + mod = Model() + mod.semiinteger_var(lb=1, name='x') + q_p.from_docplex(mod) + + with self.assertRaises(QiskitOptimizationError): + mod = Model() + x = mod.binary_var('x') + mod.add_range(0, 2 * x, 1) + q_p.from_docplex(mod) + + with self.assertRaises(QiskitOptimizationError): + mod = Model() + x = mod.binary_var('x') + y = mod.binary_var('y') + mod.add_indicator(x, x + y <= 1, 1) + q_p.from_docplex(mod) + + with self.assertRaises(QiskitOptimizationError): + mod = Model() + x = mod.binary_var('x') + y = mod.binary_var('y') + mod.add_equivalence(x, x + y <= 1, 1) + q_p.from_docplex(mod) + + with self.assertRaises(QiskitOptimizationError): + mod = Model() + x = mod.binary_var('x') + y = mod.binary_var('y') + mod.add(mod.not_equal_constraint(x, y + 1)) + q_p.from_docplex(mod) + def test_substitute_variables(self): """test substitute variables""" q_p = QuadraticProgram('test') From 58f0bbd56c5228b83518b60893c25fc23f2ff636 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Fri, 24 Apr 2020 17:43:54 +0100 Subject: [PATCH 301/323] more simplifications, linting --- .../optimization/algorithms/admm_optimizer.py | 112 +++++------------- test/optimization/test_admm.py | 6 +- 2 files changed, 32 insertions(+), 86 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 1422b46a4b..d57edb217e 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -344,26 +344,26 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: return result @staticmethod - def _turn_to_minimization(qp: QuadraticProgram): + def _turn_to_minimization(problem: QuadraticProgram) -> (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: - qp: a problem to turn to minimization. + 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 = qp.objective.sense.value - if qp.objective.sense == ObjSense.MAXIMIZE: - qp = copy.deepcopy(qp) - qp.objective.sense = ObjSense.MINIMIZE - qp.objective.constant = (-1) * qp.objective.constant - qp.objective.linear = (-1) * qp.objective.linear.coefficients - qp.objective.quadratic = (-1) * qp.objective.quadratic.coefficients - return qp, sense + sense = problem.objective.sense.value + if problem.objective.sense == ObjSense.MAXIMIZE: + problem = copy.deepcopy(problem) + problem.objective.sense = ObjSense.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]: @@ -410,17 +410,7 @@ def _revert_solution_indexes(self, binary_vars: np.ndarray, continuous_vars: np. return solution def _convert_problem_representation(self) -> None: - """Converts problem representation into set of matrices and vectors. - Specifically, the optimization problem is represented as: - - min_{x0, u} x0^T q0 x0 + c0^T x0 + u^T q1 u + c1^T u - - s.t. a0 x0 = b0 - a1 x0 <= b1 - a2 z + a3 u <= b2 - a4 u <= b3 - - """ + """Converts problem representation into set of matrices and vectors.""" binary_var_indices = set(self._state.binary_indices) # separate constraints for constraint in self._state.op.linear_constraints: @@ -432,7 +422,7 @@ def _convert_problem_representation(self) -> None: constraint_var_indices = set(constraint.linear.to_dict().keys()) if constraint_var_indices.issubset(binary_var_indices): self._state.binary_equality_constraints.append(constraint) - + elif constraint.sense in (ConstraintSense.LE, ConstraintSense.GE): self._state.inequality_constraints.append(constraint) @@ -442,12 +432,12 @@ def _convert_problem_representation(self) -> None: self._state.equality_constraints.append(constraint) elif constraint.sense in (ConstraintSense.LE, ConstraintSense.GE): self._state.inequality_constraints.append(constraint) - + # objective self._state.q0 = self._get_q(self._state.binary_indices) - self._state.c0 = self._get_c(self._state.binary_indices) + self._state.c0 = self._state.op.objective.linear.to_array()[self._state.binary_indices] self._state.q1 = self._get_q(self._state.continuous_indices) - self._state.c1 = self._get_c(self._state.continuous_indices) + self._state.c1 = self._state.op.objective.linear.to_array()[self._state.continuous_indices] # equality constraints with binary vars only self._state.a0, self._state.b0 = self._get_a0_b0() @@ -472,61 +462,6 @@ def _get_q(self, variable_indices: List[int]) -> np.ndarray: return q - def _get_c(self, variable_indices: List[int]) -> np.ndarray: - """Constructs a vector for the variables with the specified indices from the linear terms - in the objective. - - Args: - variable_indices: variable indices to look for. - - Returns: - A numpy array of the shape(len(variable_indices)). - """ - c = self._state.op.objective.linear.to_array().take(variable_indices) - return c - - def _assign_row_values(self, matrix: List[List[float]], vector: List[float], - constraint: LinearConstraint, variable_indices: List[int]): - """Appends a row to the specified matrix and vector based on the constraint specified by - the index using specified variables. - - Args: - matrix: a matrix to extend. - vector: a vector to expand. - constraint_index: constraint index to look for. - variable_indices: variables to look for. - - Returns: - None - """ - # assign matrix row, actually pick coefficients at the positions specified in - # the variable_indices list - row = constraint.linear.to_array().take(variable_indices).tolist() - - matrix.append(row) - - # assign vector row. - vector.append(constraint.rhs) - - @staticmethod - def _create_ndarrays(matrix: List[List[float]], vector: List[float], size: int) \ - -> (np.ndarray, np.ndarray): - """Converts representation of a matrix and a vector in form of lists to numpy array. - - Args: - matrix: matrix to convert. - vector: vector to convert. - size: size to create matrix and vector. - - Returns: - Converted matrix and vector as numpy arrays. - """ - # if we don't have such constraints, return just dummy arrays. - if len(matrix) != 0: - return np.array(matrix), np.array(vector) - else: - return np.array([0] * size).reshape((1, -1)), np.zeros(shape=(1,)) - def _get_a0_b0(self) -> (np.ndarray, np.ndarray): """Constructs a matrix and a vector from the constraints in a form of Ax = b, where x is a vector of binary variables. @@ -541,9 +476,19 @@ def _get_a0_b0(self) -> (np.ndarray, np.ndarray): vector = [] for constraint in self._state.binary_equality_constraints: - self._assign_row_values(matrix, vector, constraint, self._state.binary_indices) + row = constraint.linear.to_array().take(self._state.binary_indices).tolist() + + matrix.append(row) + vector.append(constraint.rhs) + + if len(matrix) != 0: + np_matrix = np.array(matrix) + np_vector = np.array(vector) + else: + np_matrix = np.array([0] * len(self._state.binary_indices)).reshape((1, -1)) + np_vector = np.zeros(shape=(1,)) - return self._create_ndarrays(matrix, vector, len(self._state.binary_indices)) + return np_matrix, np_vector def _create_step1_problem(self) -> QuadraticProgram: """Creates a step 1 sub-problem. @@ -591,7 +536,8 @@ def _create_step2_problem(self) -> QuadraticProgram: # replacing Q0 objective and take of min/max sense, initially we consider minimization op2.objective.quadratic[var_index, var_index] = self._state.rho / 2 # replacing linear objective - op2.objective.linear[var_index] = -1 * self._state.lambda_mult[i] - self._state.rho * (self._state.x0[i] - self._state.y[i]) + op2.objective.linear[var_index] = -1 * self._state.lambda_mult[i] - self._state.rho * \ + (self._state.x0[i] - self._state.y[i]) # remove A0 x0 = b0 constraints for constraint in self._state.binary_equality_constraints: diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index a344db8e89..8e71dcab23 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -13,16 +13,16 @@ # that they have been altered from the originals. """Tests of the ADMM algorithm.""" -from qiskit.aqua.algorithms import NumPyMinimumEigensolver -from test.optimization import QiskitOptimizationTestCase - import numpy as np from docplex.mp.model import Model +from qiskit.aqua.algorithms import NumPyMinimumEigensolver from qiskit.optimization.algorithms import CplexOptimizer, MinimumEigenOptimizer from qiskit.optimization.algorithms.admm_optimizer import ADMMOptimizer, ADMMParameters, \ ADMMOptimizationResult, ADMMState from qiskit.optimization.problems import QuadraticProgram +from test.optimization import QiskitOptimizationTestCase + class TestADMMOptimizer(QiskitOptimizationTestCase): """ADMM Optimizer Tests""" From 17247bdb510bd762606f59794f0f02181bb414d8 Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Fri, 24 Apr 2020 17:46:52 +0100 Subject: [PATCH 302/323] linting --- qiskit/optimization/algorithms/admm_optimizer.py | 2 +- test/optimization/test_admm.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index d57edb217e..337f342efe 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -22,7 +22,7 @@ from qiskit.optimization.algorithms.cplex_optimizer import CplexOptimizer from qiskit.optimization.algorithms.optimization_algorithm import (OptimizationAlgorithm, OptimizationResult) -from qiskit.optimization.problems import VarType, ConstraintSense, LinearConstraint, ObjSense +from qiskit.optimization.problems import VarType, ConstraintSense, ObjSense from qiskit.optimization.problems.quadratic_program import QuadraticProgram UPDATE_RHO_BY_TEN_PERCENT = 0 diff --git a/test/optimization/test_admm.py b/test/optimization/test_admm.py index 8e71dcab23..6c6543e1cf 100644 --- a/test/optimization/test_admm.py +++ b/test/optimization/test_admm.py @@ -13,6 +13,8 @@ # that they have been altered from the originals. """Tests of the ADMM algorithm.""" +from test.optimization import QiskitOptimizationTestCase + import numpy as np from docplex.mp.model import Model from qiskit.aqua.algorithms import NumPyMinimumEigensolver @@ -21,8 +23,6 @@ ADMMOptimizationResult, ADMMState from qiskit.optimization.problems import QuadraticProgram -from test.optimization import QiskitOptimizationTestCase - class TestADMMOptimizer(QiskitOptimizationTestCase): """ADMM Optimizer Tests""" From bbdf00ef2280099e80b745e9381b6f017ef2909d Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Fri, 24 Apr 2020 18:33:55 +0100 Subject: [PATCH 303/323] linting --- .../optimization/algorithms/admm_optimizer.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 4c9f21112a..a84d309a2d 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -24,7 +24,7 @@ from qiskit.optimization.algorithms.optimization_algorithm import (OptimizationAlgorithm, OptimizationResult) from qiskit.optimization.converters import IntegerToBinary -from qiskit.optimization.problems import QuadraticProgram, Variable, Constraint +from qiskit.optimization.problems import QuadraticProgram, Variable, Constraint, QuadraticObjective UPDATE_RHO_BY_TEN_PERCENT = 0 UPDATE_RHO_BY_RESIDUALS = 1 @@ -250,8 +250,8 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: self._log.debug("Initial problem: %s", problem.print_as_lp_string()) # map integer variables to binary variables - int2bin = IntegerToBinary() - problem = int2bin.encode(problem) + # int2bin = IntegerToBinary() + # problem = int2bin.encode(problem) # we deal with minimization in the optimizer, so turn the problem to minimization problem, sense = self._turn_to_minimization(problem) @@ -341,7 +341,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: result = ADMMOptimizationResult(solution, objective_value, self._state) # convert back integer to binary - result = int2bin.decode(result) + # result = int2bin.decode(result) # debug self._log.debug("solution=%s, objective=%s at iteration=%s", solution, objective_value, iteration) @@ -361,9 +361,9 @@ def _turn_to_minimization(problem: QuadraticProgram) -> (QuadraticProgram, float the original sense of the problem in the numerical representation. """ sense = problem.objective.sense.value - if problem.objective.sense == ObjSense.MAXIMIZE: + if problem.objective.sense == QuadraticObjective.Sense.MAXIMIZE: problem = copy.deepcopy(problem) - problem.objective.sense = ObjSense.MINIMIZE + 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 @@ -418,7 +418,7 @@ def _convert_problem_representation(self) -> None: binary_var_indices = set(self._state.binary_indices) # separate constraints for constraint in self._state.op.linear_constraints: - if constraint.sense == ConstraintSense.EQ: + if constraint.sense == Constraint.Sense.EQ: self._state.equality_constraints.append(constraint) # verify that there are only binary variables in the constraint @@ -427,14 +427,14 @@ def _convert_problem_representation(self) -> None: if constraint_var_indices.issubset(binary_var_indices): self._state.binary_equality_constraints.append(constraint) - elif constraint.sense in (ConstraintSense.LE, ConstraintSense.GE): + elif constraint.sense in (Constraint.Sense.LE, Constraint.Sense.GE): self._state.inequality_constraints.append(constraint) # separate quadratic constraints into eq and non-eq for constraint in self._state.op.quadratic_constraints: - if constraint.sense == ConstraintSense.EQ: + if constraint.sense == Constraint.Sense.EQ: self._state.equality_constraints.append(constraint) - elif constraint.sense in (ConstraintSense.LE, ConstraintSense.GE): + elif constraint.sense in (Constraint.Sense.LE, Constraint.Sense.GE): self._state.inequality_constraints.append(constraint) # objective @@ -534,7 +534,7 @@ def _create_step2_problem(self) -> QuadraticProgram: # u (cts) are still there unchanged for i, var_index in enumerate(self._state.binary_indices): variable = op2.variables[var_index] - variable.vartype = Variable.VarType.CONTINUOUS + variable.vartype = Variable.Type.CONTINUOUS variable.upperbound = 1. variable.lowerbound = 0. # replacing Q0 objective and take of min/max sense, initially we consider minimization @@ -677,7 +677,7 @@ def _get_constraint_residual(self) -> float: # inequality constraints cr_ineq = 0 for constraint in self._state.inequality_constraints: - sense = -1 if constraint.sense == ConstraintSense.GE else 1 + sense = -1 if constraint.sense == Constraint.Sense.GE else 1 cr_ineq += max(sense * (constraint.evaluate(solution) - constraint.rhs), 0) return cr_eq + cr_ineq From 67d1cdeeb2a38791c9f1ead59b86fa1130a92929 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Sat, 25 Apr 2020 11:30:51 +0200 Subject: [PATCH 304/323] Update quadratic_program.py use `model.iter_{linear, quadratic}_constraints()` instead of `model.get_constraint_by_index(i)`, since the latter calls CPLEX which is not available in Python 3.8. --- qiskit/optimization/problems/quadratic_program.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index a9759d793c..b708f4fd7c 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -592,8 +592,7 @@ def from_docplex(self, model: Model) -> None: self.maximize(constant, linear, quadratic) # get linear constraints - for i in range(model.number_of_linear_constraints): - constraint = model.get_constraint_by_index(i) + for constraint in model.iter_linear_constraints(): name = constraint.name sense = constraint.sense @@ -617,8 +616,7 @@ def from_docplex(self, model: Model) -> None: raise QiskitOptimizationError("Unsupported constraint sense: {}".format(sense)) # get quadratic constraints - for i in range(model.number_of_quadratic_constraints): - constraint = model.get_quadratic_by_index(i) + for constraint in model.iter_quadratic_constraints(): name = constraint.name sense = constraint.sense From b1cde107a71538080d5abf545843e573d4cc2184 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sun, 26 Apr 2020 15:16:07 +0200 Subject: [PATCH 305/323] easy part of steve's comments --- qiskit/aqua/components/optimizers/gsls.py | 6 +- qiskit/finance/__init__.py | 2 +- .../optimization/algorithms/admm_optimizer.py | 23 ++--- .../algorithms/cobyla_optimizer.py | 2 +- .../algorithms/cplex_optimizer.py | 10 +-- .../algorithms/minimum_eigen_optimizer.py | 5 +- .../recursive_minimum_eigen_optimizer.py | 2 +- .../converters/inequality_to_equality.py | 2 - .../converters/integer_to_binary.py | 2 - .../converters/linear_equality_to_penalty.py | 3 +- .../operator_to_quadratic_program.py | 2 +- .../quadratic_program_to_operator.py | 2 +- ...it_optimization_error.py => exceptions.py} | 0 qiskit/optimization/exceptions/__init__.py | 19 ---- qiskit/optimization/problems/constraint.py | 4 +- .../problems/has_quadratic_program.py | 2 +- .../problems/linear_constraint.py | 3 +- test/aqua/test_compute_min_eigenvalue.py | 86 ------------------- 18 files changed, 33 insertions(+), 142 deletions(-) rename qiskit/optimization/{exceptions/qiskit_optimization_error.py => exceptions.py} (100%) delete mode 100644 qiskit/optimization/exceptions/__init__.py delete mode 100755 test/aqua/test_compute_min_eigenvalue.py diff --git a/qiskit/aqua/components/optimizers/gsls.py b/qiskit/aqua/components/optimizers/gsls.py index 269f761b06..d74b88220e 100644 --- a/qiskit/aqua/components/optimizers/gsls.py +++ b/qiskit/aqua/components/optimizers/gsls.py @@ -136,7 +136,7 @@ def ls_optimize(self, n: int, obj_fun: callable, initial_point: np.ndarray, var_ if len(var_lb) != n: raise ValueError('Length of the lower bound mismatches the number of dimensions.') if len(var_ub) != n: - raise ValueError('Length of the lower bound mismatches the number of dimensions.') + raise ValueError('Length of the upper bound mismatches the number of dimensions.') # Initialize counters and data iter_count = 0 @@ -198,7 +198,9 @@ def ls_optimize(self, n: int, obj_fun: callable, initial_point: np.ndarray, var_ consecutive_fail_iter = 0 # Reset sample set - prev_directions, prev_sample_set_x, prev_sample_set_y = None, None, None + prev_directions = None + prev_sample_set_x = None + prev_sample_set_y = None else: # Do not accept point alpha *= self._options['step_size_multiplier'] diff --git a/qiskit/finance/__init__.py b/qiskit/finance/__init__.py index 0dfe66797e..85e353541c 100644 --- a/qiskit/finance/__init__.py +++ b/qiskit/finance/__init__.py @@ -13,7 +13,7 @@ # that they have been altered from the originals. """ ========================================================== -Finance stack for Aqua (:mod:`qiskit.finance`) +Finance application stack for Aqua (:mod:`qiskit.finance`) ========================================================== This is the finance domain logic.... diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 1ee6825597..f7a57727a0 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -13,6 +13,7 @@ # that they have been altered from the originals. """An implementation of the ADMM algorithm.""" + import logging import time from typing import List, Optional, Any @@ -92,8 +93,7 @@ def __init__(self, binary_indices: List[int], continuous_indices: List[int], rho_initial: float) -> None: - """Constructs an internal computation state of the ADMM implementation. - + """ Args: op: The optimization problem being solved. binary_indices: Indices of the binary decision variables of the original problem. @@ -153,7 +153,7 @@ def __init__(self, class ADMMOptimizerResult(OptimizationResult): - """ ADMMOptimizer Result.""" + """ADMMOptimizer Result.""" def __init__(self, x: Optional[Any] = None, fval: Optional[Any] = None, state: Optional[ADMMState] = None, results: Optional[Any] = None) -> None: @@ -167,18 +167,20 @@ def state(self) -> Optional[ADMMState]: class ADMMOptimizer(OptimizationAlgorithm): + """An implementation of the ADMM-based heuristic. + + This algorithm is introduced in [1]. - """An implementation of the ADMM-based heuristic introduced here: - Gambella, C., & Simonetto, A. (2020). - Multi-block ADMM Heuristics for Mixed-Binary Optimization on Classical and Quantum Computers. - arXiv preprint arXiv:2001.02069. + **References:** + + [1] Gambella, C., & Simonetto, A. (2020). Multi-block ADMM Heuristics for Mixed-Binary + Optimization on Classical and Quantum Computers. arXiv preprint arXiv:2001.02069. """ def __init__(self, qubo_optimizer: Optional[OptimizationAlgorithm] = None, continuous_optimizer: Optional[OptimizationAlgorithm] = None, params: Optional[ADMMParameters] = None) -> None: - """Constructs an instance of ADMMOptimizer. - + """ Args: qubo_optimizer: An instance of OptimizationAlgorithm that can effectively solve QUBO problems. @@ -713,7 +715,8 @@ def _create_step2_problem(self) -> QuadraticProgram: def _binary_indices_to_continuous(self, binary_indices: List[int]) -> List[int]: # TODO: implement - return binary_indices + # return binary_indices + raise NotImplementedError def _create_step3_problem(self) -> QuadraticProgram: """Creates a step 3 sub-problem. diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index 6a0518b6c2..a0dab3867a 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -26,7 +26,7 @@ class CobylaOptimizer(OptimizationAlgorithm): - """The COBYLA optimizer wrapped to be used within Qiskit Optimization. + """The SciPy COBYLA optimizer wrapped as an Qiskit ``OptimizationAlgorithm``. This class provides a wrapper for ``scipy.optimize.fmin_cobyla`` (https://docs.scipy.org/doc/scipy-0.14.0/reference/generated/scipy.optimize.fmin_cobyla.html) diff --git a/qiskit/optimization/algorithms/cplex_optimizer.py b/qiskit/optimization/algorithms/cplex_optimizer.py index b006da25dd..6966aa0da3 100644 --- a/qiskit/optimization/algorithms/cplex_optimizer.py +++ b/qiskit/optimization/algorithms/cplex_optimizer.py @@ -33,16 +33,16 @@ class CplexOptimizer(OptimizationAlgorithm): - """The CPLEX optimizer wrapped to be used within the Qiskit Optimization. + """The CPLEX optimizer wrapped as an Qiskit ``OptimizationAlgorithm``. This class provides a wrapper for ``cplex.Cplex`` (https://pypi.org/project/cplex/) to be used within Qiskit Optimization. Examples: - >>> problem = QuadraticProgram() - >>> # specify problem here - >>> optimizer = CplexOptimizer() - >>> result = optimizer.solve(problem) + >>> problem = QuadraticProgram() + >>> # specify problem here + >>> optimizer = CplexOptimizer() + >>> result = optimizer.solve(problem) """ def __init__(self, disp: Optional[bool] = False) -> None: diff --git a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py index b54797e534..81ffa8b660 100644 --- a/qiskit/optimization/algorithms/minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/minimum_eigen_optimizer.py @@ -90,8 +90,7 @@ class MinimumEigenOptimizer(OptimizationAlgorithm): def __init__(self, min_eigen_solver: MinimumEigensolver, penalty: Optional[float] = None ) -> None: - """Initializes the minimum eigen optimizer. - + """ This initializer takes the minimum eigen solver to be used to approximate the ground state of the resulting Hamiltonian as well as a optional penalty factor to scale penalty terms representing linear equality constraints. If no penalty factor is provided, a default @@ -233,11 +232,9 @@ def eval_operator_at_bitstring(operator: Union[WeightedPauliOperator, MatrixOper Returns: The operator evaluated with the quantum state the bitstring describes. """ - # TODO check that operator size and bitstr are compatible circuit = QuantumCircuit(len(bitstr)) for i, bit in enumerate(bitstr): - # TODO in which order to iterate over the bitstring??? if bit == '1': circuit.x(i) diff --git a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py index 5080c90f6e..ec6bdd745e 100644 --- a/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py +++ b/qiskit/optimization/algorithms/recursive_minimum_eigen_optimizer.py @@ -24,7 +24,7 @@ from .optimization_algorithm import OptimizationAlgorithm, OptimizationResult from .minimum_eigen_optimizer import MinimumEigenOptimizer -from ..exceptions.qiskit_optimization_error import QiskitOptimizationError +from ..exceptions import QiskitOptimizationError from ..problems.quadratic_program import QuadraticProgram from ..converters.quadratic_program_to_qubo import QuadraticProgramToQubo diff --git a/qiskit/optimization/converters/inequality_to_equality.py b/qiskit/optimization/converters/inequality_to_equality.py index 327cb58a45..48d0064f58 100644 --- a/qiskit/optimization/converters/inequality_to_equality.py +++ b/qiskit/optimization/converters/inequality_to_equality.py @@ -40,8 +40,6 @@ class InequalityToEquality: _delimiter = '@' # users are supposed not to use this character in variable names def __init__(self) -> None: - """Initialize the inequality to equality variable converter.""" - self._src = None self._dst = None self._conv: Dict[str, List[Tuple[str, int]]] = {} diff --git a/qiskit/optimization/converters/integer_to_binary.py b/qiskit/optimization/converters/integer_to_binary.py index d46168ddc6..239bdc7999 100644 --- a/qiskit/optimization/converters/integer_to_binary.py +++ b/qiskit/optimization/converters/integer_to_binary.py @@ -47,8 +47,6 @@ class IntegerToBinary: _delimiter = '@' # users are supposed not to use this character in variable names def __init__(self) -> None: - """Initializes the internal data structure.""" - self._src = None self._dst = None self._conv: Dict[Variable, List[Tuple[str, int]]] = {} diff --git a/qiskit/optimization/converters/linear_equality_to_penalty.py b/qiskit/optimization/converters/linear_equality_to_penalty.py index 0efeda80dc..e87f84a664 100644 --- a/qiskit/optimization/converters/linear_equality_to_penalty.py +++ b/qiskit/optimization/converters/linear_equality_to_penalty.py @@ -18,14 +18,13 @@ from typing import Optional from ..problems import QuadraticProgram, Variable, Constraint, QuadraticObjective -from ..exceptions.qiskit_optimization_error import QiskitOptimizationError +from ..exceptions import QiskitOptimizationError class LinearEqualityToPenalty: """Convert a problem with only equality constraints to unconstrained with penalty terms.""" def __init__(self): - """Initialize the internal data structure.""" self._src = None self._dst = None diff --git a/qiskit/optimization/converters/operator_to_quadratic_program.py b/qiskit/optimization/converters/operator_to_quadratic_program.py index 35f2463414..e808ff6ccc 100644 --- a/qiskit/optimization/converters/operator_to_quadratic_program.py +++ b/qiskit/optimization/converters/operator_to_quadratic_program.py @@ -21,7 +21,7 @@ from qiskit.aqua.operators import WeightedPauliOperator from ..problems.quadratic_program import QuadraticProgram -from ..exceptions.qiskit_optimization_error import QiskitOptimizationError +from ..exceptions import QiskitOptimizationError class OperatorToQuadraticProgram: diff --git a/qiskit/optimization/converters/quadratic_program_to_operator.py b/qiskit/optimization/converters/quadratic_program_to_operator.py index 5c7190cbce..693d1699bd 100644 --- a/qiskit/optimization/converters/quadratic_program_to_operator.py +++ b/qiskit/optimization/converters/quadratic_program_to_operator.py @@ -23,7 +23,7 @@ from qiskit.aqua.operators import WeightedPauliOperator from ..problems.quadratic_program import QuadraticProgram -from ..exceptions.qiskit_optimization_error import QiskitOptimizationError +from ..exceptions import QiskitOptimizationError class QuadraticProgramToOperator: diff --git a/qiskit/optimization/exceptions/qiskit_optimization_error.py b/qiskit/optimization/exceptions.py similarity index 100% rename from qiskit/optimization/exceptions/qiskit_optimization_error.py rename to qiskit/optimization/exceptions.py diff --git a/qiskit/optimization/exceptions/__init__.py b/qiskit/optimization/exceptions/__init__.py deleted file mode 100644 index 482aab65e0..0000000000 --- a/qiskit/optimization/exceptions/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""The exceptions module for the Qiskit Optimization stack.""" - -from .qiskit_optimization_error import QiskitOptimizationError - -__all__ = ['QiskitOptimizationError'] diff --git a/qiskit/optimization/problems/constraint.py b/qiskit/optimization/problems/constraint.py index 57229c7ba1..dc39e5e898 100644 --- a/qiskit/optimization/problems/constraint.py +++ b/qiskit/optimization/problems/constraint.py @@ -20,8 +20,8 @@ from numpy import ndarray -from qiskit.optimization import QiskitOptimizationError -from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram +from .has_quadratic_program import HasQuadraticProgram +from ..exceptions import QiskitOptimizationError class ConstraintSense(Enum): diff --git a/qiskit/optimization/problems/has_quadratic_program.py b/qiskit/optimization/problems/has_quadratic_program.py index f8ee7d19d8..c09134c94b 100644 --- a/qiskit/optimization/problems/has_quadratic_program.py +++ b/qiskit/optimization/problems/has_quadratic_program.py @@ -16,7 +16,7 @@ class HasQuadraticProgram: - """Abstract interface class for all objects that have a parent QuadraticProgram.""" + """Interface class for all objects that have a parent QuadraticProgram.""" def __init__(self, quadratic_program: "QuadraticProgram") -> None: """ Initialize object with parent QuadraticProgram. diff --git a/qiskit/optimization/problems/linear_constraint.py b/qiskit/optimization/problems/linear_constraint.py index 7412c81a35..467d30f952 100644 --- a/qiskit/optimization/problems/linear_constraint.py +++ b/qiskit/optimization/problems/linear_constraint.py @@ -32,8 +32,7 @@ def __init__(self, sense: Constraint.Sense, rhs: float ) -> None: - """Constructs a linear constraint. - + """ Args: quadratic_program: The parent quadratic program. name: The name of the constraint. diff --git a/test/aqua/test_compute_min_eigenvalue.py b/test/aqua/test_compute_min_eigenvalue.py deleted file mode 100755 index b147d5f234..0000000000 --- a/test/aqua/test_compute_min_eigenvalue.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -""" Test VQE """ - -import unittest -from test.aqua.aqua_test_case import QiskitAquaTestCase -from qiskit import BasicAer - -from qiskit.aqua import QuantumInstance, aqua_globals -from qiskit.aqua.operators import WeightedPauliOperator, MatrixOperator -from qiskit.aqua.components.variational_forms import RY, RYRZ -from qiskit.aqua.components.optimizers import L_BFGS_B, SPSA -from qiskit.aqua.algorithms import VQE, NumPyMinimumEigensolver - - -class TestComputeMinEigenvalue(QiskitAquaTestCase): - """ Test VQE """ - - def setUp(self): - super().setUp() - self.seed = 50 - aqua_globals.random_seed = self.seed - pauli_dict = { - 'paulis': [{"coeff": {"imag": 0.0, "real": -1.052373245772859}, "label": "II"}, - {"coeff": {"imag": 0.0, "real": 0.39793742484318045}, "label": "IZ"}, - {"coeff": {"imag": 0.0, "real": -0.39793742484318045}, "label": "ZI"}, - {"coeff": {"imag": 0.0, "real": -0.01128010425623538}, "label": "ZZ"}, - {"coeff": {"imag": 0.0, "real": 0.18093119978423156}, "label": "XX"} - ] - } - self.qubit_op = WeightedPauliOperator.from_dict(pauli_dict) - - def test_vqe(self): - """ VQE test """ - quantum_instance = QuantumInstance(BasicAer.get_backend('statevector_simulator'), - basis_gates=['u1', 'u2', 'u3', 'cx', 'id'], - coupling_map=[[0, 1]], - seed_simulator=aqua_globals.random_seed, - seed_transpiler=aqua_globals.random_seed) - - vqe = VQE(var_form=RYRZ(self.qubit_op.num_qubits), - optimizer=L_BFGS_B(), - quantum_instance=quantum_instance) - output = vqe.compute_minimum_eigenvalue(self.qubit_op) - self.assertAlmostEqual(output.eigenvalue, -1.85727503) - - def test_vqe_qasm(self): - """ VQE QASM test """ - backend = BasicAer.get_backend('qasm_simulator') - num_qubits = self.qubit_op.num_qubits - var_form = RY(num_qubits, num_qubits) - optimizer = SPSA(max_trials=300, last_avg=5) - quantum_instance = QuantumInstance(backend, shots=10000, - seed_simulator=self.seed, - seed_transpiler=self.seed) - vqe = VQE(var_form=var_form, - optimizer=optimizer, - max_evals_grouped=1, - quantum_instance=quantum_instance) - - output = vqe.compute_minimum_eigenvalue(self.qubit_op) - self.assertAlmostEqual(output.eigenvalue, -1.85727503, places=1) - - def test_ee(self): - """ EE test """ - dummy_operator = MatrixOperator([[1]]) - nee = NumPyMinimumEigensolver() - output = nee.compute_minimum_eigenvalue(self.qubit_op) - - self.assertAlmostEqual(output.eigenvalue, -1.85727503) - - -if __name__ == '__main__': - unittest.main() From e08d354894b01c0f36d3bae07525b282b3111520 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sun, 26 Apr 2020 17:58:19 +0200 Subject: [PATCH 306/323] part 2 of steve's comments --- README.md | 86 +++++++++---------- qiskit/aqua/components/optimizers/gsls.py | 5 +- qiskit/optimization/__init__.py | 4 +- .../algorithms/cobyla_optimizer.py | 6 +- .../algorithms/grover_optimizer.py | 37 ++++++-- .../algorithms/optimization_algorithm.py | 4 + qiskit/optimization/infinity.py | 2 +- .../problems/quadratic_program.py | 18 ++-- qiskit/optimization/problems/variable.py | 4 +- test/aqua/test_optimizers.py | 3 +- test/optimization/test_converters.py | 36 ++++++++ test/optimization/test_quadratic_program.py | 29 +++++-- test/optimization/test_variable.py | 4 +- 13 files changed, 159 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index 0dd8c711c4..7b50d9c0cc 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ Qiskit is made up elements that work together to enable quantum computing. This element is **Aqua** (Algorithms for QUantum computing Applications) providing a library of cross-domain algorithms upon which domain-specific applications can be built. - + * [Aqua](#aqua) - + Aqua includes domain application support for: * [Chemistry](#chemistry) @@ -19,10 +19,10 @@ Aqua includes domain application support for: _**Note**: the Chemistry application stack was the first domain worked on. At the time of writing the other domains have some logic in them but are not as fully realised. Future work is expected to -build out functionality in all application areas._ +build out functionality in all application areas._ Aqua was designed to be extensible, and uses a framework where algorithms and support objects used -by algorithms, such as optimizers, variational forms, and oracles etc,. are derived from a defined +by algorithms, such as optimizers, variational forms, and oracles etc,. are derived from a defined base class for the type. These along with other building blocks provide a means for end-users and developers alike to have flexibility and facilitate building and experimenting with different configurations and capability. @@ -52,34 +52,34 @@ To do this follow the instructions in the Note: there some optional packages that can be installed such as IBM CPLEX for Aqua and ab-initio chemistry libraries/programs. Refer to Optional Install information in the sections below. - + Note: _Optional install links are currently pointing to the source documentation in the code. -At the time of writing the qiskit.org [API Documentation](https://qiskit.org/documentation) +At the time of writing the qiskit.org [API Documentation](https://qiskit.org/documentation) is being reworked and these links will be redone to point there once the documentation is -updated and republished._ +updated and republished._ ---------------------------------------------------------------------------------------------------- ## Aqua The `qiskit.aqua` package contains the core cross-domain algorithms and supporting logic to run -these on a quantum backend, whether a real device or simulator. +these on a quantum backend, whether a real device or simulator. ### Optional Installs -* **IBM CPLEX** may be [installed](qiskit/aqua/algorithms/minimum_eigen_solvers/cplex/__init__.py#L16) +* **IBM CPLEX** may be [installed](qiskit/aqua/algorithms/minimum_eigen_solvers/cplex/__init__.py#L16) to allow use of the `ClassicalCPLEX` classical solver algorithm. * **PyTorch**, may be installed either using command `pip install qiskit-aqua[torch]` to install the package or refer to PyTorch [getting started](https://pytorch.org/get-started/locally/). PyTorch being installed will enable the neural networks `PyTorchDiscriminator` component to be used with - the QGAN algorithm. - + the QGAN algorithm. + ### Creating Your First Quantum Program in Qiskit Aqua Now that Qiskit is installed, it's time to begin working with Aqua. Let's try an experiment using `Grover`'s algorithm to find a solution for a -Satisfiability (SAT) problem. +Satisfiability (SAT) problem. ``` $ python @@ -121,7 +121,7 @@ Form (CNF): (¬x1x2x3) The Python code above prints out one possible solution for this CNF. -For example, output `1, -2, 3` indicates that logical expression +For example, output `1, -2, 3` indicates that logical expression (x1 ∨ ¬x2x3) satisfies the given CNF. @@ -138,26 +138,26 @@ and ## Chemistry The `qiskit.chemistry` package supports problems including ground state energy computations, -excited states and dipole moments of molecule, both open and closed-shell. +excited states and dipole moments of molecule, both open and closed-shell. The code comprises chemistry drivers, which when provided with a molecular -configuration will return one and two-body integrals as well as other data that is efficiently +configuration will return one and two-body integrals as well as other data that is efficiently computed classically. This output data from a driver can then be used as input to the chemistry application stack that contains logic which is able to translate this into a form that is suitable for quantum algorithms. The conversion first creates a FermionicOperator which must then be mapped, -e.g. by a Jordan Wigner mapping, to a qubit operator in readiness for the quantum computation. +e.g. by a Jordan Wigner mapping, to a qubit operator in readiness for the quantum computation. ### Optional Installs To run chemistry experiments using Qiskit Chemistry, it is recommended that you to install a -classical computation chemistry software program/library interfaced by Qiskit Chemistry. +classical computation chemistry software program/library interfaced by Qiskit Chemistry. Several, as listed below, are supported, and while logic to interface these programs is supplied by Qiskit Chemistry via the above pip installation, the dependent programs/libraries themselves need to be installed separately. Note: As `PySCF` can be installed via pip the installation of Qiskit (Aqua) will install PySCF where it's supported (MacOS and Linux x86). For other platforms see the PySCF information as to -whether this might be possible manually. +whether this might be possible manually. 1. [Gaussian 16™](qiskit/chemistry/drivers/gaussiand/__init__.py#L16), a commercial chemistry program 2. [PSI4](qiskit/chemistry/drivers/psi4d/__init__.py#L16), a chemistry program that exposes a Python interface allowing for accessing internal objects @@ -168,14 +168,14 @@ whether this might be possible manually. A useful functionality integrated into Qiskit Chemistry is its ability to serialize a file in Hierarchical Data Format 5 (HDF5) format representing all the output data from a chemistry driver. - + The [HDF5 driver](qiskit/chemistry/drivers/hdf5d/hdf5driver.py#L25) accepts such such HDF5 files as input so molecular experiments can be run, albeit on the fixed data as stored in the file. As such, if you have some pre-created HDF5 files from created from Qiskit Chemistry, you can use these with the HDF5 driver even if you do not install one of the classical -computation packages listed above. +computation packages listed above. -A few sample HDF5 files for different are provided in the +A few sample HDF5 files for different are provided in the [chemistry folder](https://github.com/Qiskit/qiskit-community-tutorials/tree/master/chemistry) of the [Qiskit Community Tutorials](https://github.com/Qiskit/qiskit-community-tutorials) repository. This @@ -186,7 +186,7 @@ contains further information about creating and using such HDF5 files. Now that Qiskit is installed, it's time to begin working with Chemistry. Let's try a chemistry application experiment using VQE (Variational Quantum Eigensolver) algorithm -to compute the ground-state (minimum) energy of a molecule. +to compute the ground-state (minimum) energy of a molecule. ```python from qiskit.chemistry import FermionicOperator @@ -237,12 +237,12 @@ The program above uses a quantum computer to calculate the ground state energy o H2, where the two atoms are configured to be at a distance of 0.735 angstroms. The molecular input specification is processed by PySCF driver and data is output that includes one- and two-body molecular-orbital integrals. From the output a fermionic-operator is created which is then -parity mapped to generate a qubit operator. Parity mappings allow a precision-preserving optimization +parity mapped to generate a qubit operator. Parity mappings allow a precision-preserving optimization that two qubits can be tapered off; a reduction in complexity that is particularly advantageous for NISQ computers. - + The qubit operator is then passed as an input to the Variational Quantum Eigensolver (VQE) algorithm, -instantiated with a classical optimizer and an RyRz variational form (ansatz). A Hartree-Fock +instantiated with a classical optimizer and an RyRz variational form (ansatz). A Hartree-Fock initial state is used as a starting point for the variational form. The VQE algorithm is then run, in this case on the Qiskit Aer statevector simulator backend. @@ -250,7 +250,7 @@ Here we pass a backend but it can be wrapped into a `QuantumInstance`, and that `run` instead. The `QuantumInstance` API allows you to customize run-time properties of the backend, such as the number of shots, the maximum number of credits to use, settings for the simulator, initial layout of qubits in the mapping and the Terra `PassManager` that will handle the compilation -of the circuits. By passing in a backend as is done above it is internally wrapped into a +of the circuits. By passing in a backend as is done above it is internally wrapped into a `QuantumInstance` and is a convenience when default setting suffice. ### Further examples @@ -267,7 +267,7 @@ and The `qiskit.finance` package contains uncertainty components for stock/securities problems, Ising translators for portfolio optimizations and data providers to source real or random data to -finance experiments. +finance experiments. ### Creating Your First Qiskit Finance Programming Experiment @@ -312,20 +312,20 @@ and [qiskit-community-tutorials/finance](https://github.com/Qiskit/qiskit-community-tutorials/tree/master/finance). ---------------------------------------------------------------------------------------------------- - + ## Machine Learning The `qiskit.ml` package simply contains sample datasets at present. `qiskit.aqua` has some classification algorithms such as QSVM and VQC (Variational Quantum Classifier), where this data can be used for experiments, and there is also QGAN (Quantum Generative Adversarial Network) -algorithm. +algorithm. ### Creating Your First Qiskit Machine Learning Programming Experiment Now that Qiskit is installed, it's time to begin working with Machine Learning. Let's try a experiment using VQC (Variational Quantum Classified) algorithm to train and test samples from a data set to see how accurately the test set can -be classified. +be classified. ```python from qiskit import BasicAer @@ -371,17 +371,17 @@ and ## Optimization -The `qiskit.optimization` package contains Ising translators for various optimization problems such +The `qiskit.optimization` package contains Ising translators for various optimization problems such as Max-Cut, Traveling Salesman and Vehicle Routing. It also has a has an automatic Ising generator for a problem model specified by the user as a model in -[docplex](qiskit/optimization/ising/docplex.py#L16). +[docplex](qiskit/optimization/ising/docplex.py#L16). ### Creating Your First Qiskit Optimization Programming Experiment Now that Qiskit is installed, it's time to begin working with Optimization. Let's try a optimization experiment using QAOA (Quantum Approximate Optimization Algorithm) to compute the solution of a [Max-Cut](https://en.wikipedia.org/wiki/Maximum_cut) problem using -a docplex model to create the Ising Hamiltonian operator for QAOA. +a docplex model to create the Ising Hamiltonian operator for QAOA. ```python import networkx as nx @@ -392,8 +392,8 @@ from qiskit import BasicAer from qiskit.aqua import aqua_globals, QuantumInstance from qiskit.aqua.algorithms import QAOA from qiskit.aqua.components.optimizers import SPSA -from qiskit.optimization.ising import docplex, max_cut -from qiskit.optimization.ising.common import sample_most_likely +from qiskit.optimization.applications.ising import docplex, max_cut +from qiskit.optimization.applications.ising.common import sample_most_likely # Generate a graph of 4 nodes n = 4 @@ -425,7 +425,7 @@ spsa = SPSA(max_trials=250) qaoa = QAOA(qubit_op, spsa, p=5) backend = BasicAer.get_backend('qasm_simulator') quantum_instance = QuantumInstance(backend, shots=1024, seed_simulator=seed, - seed_transpiler=seed) + seed_transpiler=seed) result = qaoa.run(quantum_instance) x = sample_most_likely(result.eigenstate) @@ -471,24 +471,24 @@ For questions that are more suited for a forum, we use the **Qiskit** tag in [St ## Next Steps -Now you're set up and ready to check out some of the other examples from the +Now you're set up and ready to check out some of the other examples from the [Qiskit IQX Tutorials](https://github.com/Qiskit/qiskit-iqx-tutorials) -repository, that are used for the IBM Quantum Experience, and from the -[Qiskit Community Tutorials](https://github.com/Qiskit/qiskit-community-tutorials). +repository, that are used for the IBM Quantum Experience, and from the +[Qiskit Community Tutorials](https://github.com/Qiskit/qiskit-community-tutorials). ## Authors and Citation Aqua was inspired, authored and brought about by the collective work of a team of researchers. -Aqua continues to grow with the help and work of +Aqua continues to grow with the help and work of [many people](https://github.com/Qiskit/qiskit-aqua/graphs/contributors), who contribute -to the project at different levels. -If you use Qiskit, please cite as per the provided +to the project at different levels. +If you use Qiskit, please cite as per the provided [BibTeX file](https://github.com/Qiskit/qiskit/blob/master/Qiskit.bib). Please note that if you do not like the way your name is cited in the BibTex file then consult the information found in the [.mailmap](https://github.com/Qiskit/qiskit-aqua/blob/master/.mailmap) -file. +file. ## License diff --git a/qiskit/aqua/components/optimizers/gsls.py b/qiskit/aqua/components/optimizers/gsls.py index d74b88220e..c7a76e7271 100644 --- a/qiskit/aqua/components/optimizers/gsls.py +++ b/qiskit/aqua/components/optimizers/gsls.py @@ -18,6 +18,7 @@ import logging import numpy as np +from qiskit.aqua import aqua_globals from .optimizer import Optimizer logger = logging.getLogger(__name__) @@ -93,7 +94,7 @@ def optimize(self, num_vars: int, variable_bounds, initial_point) if initial_point is None: - initial_point = np.random.normal(size=num_vars) + initial_point = aqua_globals.random.normal(size=num_vars) else: initial_point = np.array(initial_point) @@ -234,7 +235,7 @@ def sample_points(self, n: int, x: np.ndarray, num_points: int Returns: A tuple containing the sampling points and the directions. """ - normal_samples = np.random.normal(size=(num_points, n)) + normal_samples = aqua_globals.random.normal(size=(num_points, n)) row_norms = np.linalg.norm(normal_samples, axis=1, keepdims=True) directions = normal_samples / row_norms points = x + self._options['sampling_radius'] * directions diff --git a/qiskit/optimization/__init__.py b/qiskit/optimization/__init__.py index 73fbe10c5a..f93fca87ca 100644 --- a/qiskit/optimization/__init__.py +++ b/qiskit/optimization/__init__.py @@ -27,7 +27,7 @@ """ -from .infinity import infinity # must be at the top of the file +from .infinity import INFINITY # must be at the top of the file from .exceptions import QiskitOptimizationError from .problems import QuadraticProgram from ._logging import (get_qiskit_optimization_logging, @@ -37,5 +37,5 @@ 'QiskitOptimizationError', 'get_qiskit_optimization_logging', 'set_qiskit_optimization_logging', - 'infinity' + 'INFINITY' ] diff --git a/qiskit/optimization/algorithms/cobyla_optimizer.py b/qiskit/optimization/algorithms/cobyla_optimizer.py index a0dab3867a..36c5141e5c 100644 --- a/qiskit/optimization/algorithms/cobyla_optimizer.py +++ b/qiskit/optimization/algorithms/cobyla_optimizer.py @@ -22,7 +22,7 @@ from qiskit.optimization.algorithms import OptimizationAlgorithm, OptimizationResult from qiskit.optimization.problems import QuadraticProgram, Constraint -from qiskit.optimization import QiskitOptimizationError, infinity +from qiskit.optimization import QiskitOptimizationError, INFINITY class CobylaOptimizer(OptimizationAlgorithm): @@ -113,9 +113,9 @@ def objective(x): for variable in problem.variables: lowerbound = variable.lowerbound upperbound = variable.upperbound - if lowerbound > -infinity: + if lowerbound > -INFINITY: constraints += [lambda x, lb=lowerbound: x - lb] - if upperbound < infinity: + if upperbound < INFINITY: constraints += [lambda x, ub=upperbound: ub - x] # pylint: disable=no-member diff --git a/qiskit/optimization/algorithms/grover_optimizer.py b/qiskit/optimization/algorithms/grover_optimizer.py index f7faef6779..4c95506d28 100644 --- a/qiskit/optimization/algorithms/grover_optimizer.py +++ b/qiskit/optimization/algorithms/grover_optimizer.py @@ -19,6 +19,8 @@ import math import random import numpy as np +from qiskit import QuantumCircuit +from qiskit.providers import BaseBackend from qiskit.aqua import QuantumInstance from qiskit.optimization import QiskitOptimizationError from qiskit.optimization.algorithms import OptimizationAlgorithm @@ -27,8 +29,6 @@ QuadraticProgramToNegativeValueOracle) from qiskit.optimization.algorithms.optimization_algorithm import OptimizationResult from qiskit.aqua.algorithms.amplitude_amplifiers.grover import Grover -from qiskit import Aer, QuantumCircuit -from qiskit.providers import BaseBackend logger = logging.getLogger(__name__) @@ -48,10 +48,31 @@ def __init__(self, num_value_qubits: int, num_iterations: int = 3, """ self._num_value_qubits = num_value_qubits self._n_iterations = num_iterations - if quantum_instance is None or isinstance(quantum_instance, BaseBackend): - backend = quantum_instance or Aer.get_backend('statevector_simulator') - quantum_instance = QuantumInstance(backend) - self._quantum_instance = quantum_instance + self._quantum_instance = None + + if quantum_instance is not None: + self.quantum_instance = quantum_instance + + @property + def quantum_instance(self) -> QuantumInstance: + """The quantum instance to run the circuits. + + Returns: + The quantum instance used in the algorithm. + """ + return self._quantum_instance + + @quantum_instance.setter + def quantum_instance(self, quantum_instance: Union[BaseBackend, QuantumInstance]) -> None: + """Set the quantum instance used to run the circuits. + + Args: + quantum_instance: The quantum instance to be used in the algorithm. + """ + if isinstance(quantum_instance, BaseBackend): + self._quantum_instance = QuantumInstance(quantum_instance) + else: + self._quantum_instance = quantum_instance def get_compatibility_msg(self, problem: QuadraticProgram) -> str: """Checks whether a given problem can be solved with this optimizer. @@ -80,8 +101,12 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: The result of the optimizer applied to the problem. Raises: + AttributeError: If the quantum instance has not been set. QiskitOptimizationError: If the problem is incompatible with the optimizer. """ + if self.quantum_instance is None: + raise AttributeError('The quantum instance or backend has not been set.') + # check compatibility and raise exception if incompatible msg = self.get_compatibility_msg(problem) if len(msg) > 0: diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index 9dc0ae3b7d..ee5d35cb38 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -100,6 +100,10 @@ def __init__(self, x: Optional[Any] = None, fval: Optional[Any] = None, def __repr__(self): return '([%s] / %s)' % (','.join([str(x_) for x_ in self.x]), self.fval) + def __str__(self): + return 'optimal value=[%s], function value=%s)' % (','.join([str(x_) for x_ in self.x]), + self.fval) + @property def x(self) -> Any: """Returns the optimal value found in the optimization. diff --git a/qiskit/optimization/infinity.py b/qiskit/optimization/infinity.py index db49dbc6da..a0fefc26e3 100644 --- a/qiskit/optimization/infinity.py +++ b/qiskit/optimization/infinity.py @@ -15,4 +15,4 @@ """Infinity constant.""" -infinity = 1.0E+20 # pylint: disable=invalid-name +INFINITY = 1.0E+20 # pylint: disable=invalid-name diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index b708f4fd7c..0f84f7ed76 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -26,7 +26,7 @@ from numpy import ndarray from scipy.sparse import spmatrix -from qiskit.optimization import infinity, QiskitOptimizationError +from qiskit.optimization import INFINITY, QiskitOptimizationError from qiskit.optimization.problems.constraint import Constraint from qiskit.optimization.problems.linear_constraint import LinearConstraint from qiskit.optimization.problems.linear_expression import LinearExpression @@ -137,7 +137,7 @@ def variables_index(self) -> Dict[str, int]: def _add_variable(self, lowerbound: Union[float, int] = 0, - upperbound: Union[float, int] = infinity, + upperbound: Union[float, int] = INFINITY, vartype: Variable.Type = Variable.Type.CONTINUOUS, name: Optional[str] = None) -> Variable: """Checks whether a variable name is already taken and adds the variable to list and index @@ -170,7 +170,7 @@ def _add_variable(self, return variable def continuous_var(self, lowerbound: Union[float, int] = 0, - upperbound: Union[float, int] = infinity, + upperbound: Union[float, int] = INFINITY, name: Optional[str] = None) -> Variable: """Adds a continuous variable to the quadratic program. @@ -202,7 +202,7 @@ def binary_var(self, name: Optional[str] = None) -> Variable: return self._add_variable(0, 1, Variable.Type.BINARY, name) def integer_var(self, lowerbound: Union[float, int] = 0, - upperbound: Union[float, int] = infinity, + upperbound: Union[float, int] = INFINITY, name: Optional[str] = None) -> Variable: """Adds an integer variable to the quadratic program. @@ -642,7 +642,7 @@ def from_docplex(self, model: Model) -> None: if right_expr.is_quad_expr(): for x in right_expr.linear_part.iter_variables(): linear[var_names[x]] = linear.get(var_names[x], 0.0) - \ - right_expr.linear_part.get_coef(x) + right_expr.linear_part.get_coef(x) for quad_triplet in right_expr.iter_quad_triplets(): i = var_names[quad_triplet[0]] j = var_names[quad_triplet[1]] @@ -745,7 +745,7 @@ def to_docplex(self) -> Model: return mdl def pprint_as_string(self) -> str: - """Pretty prints the quadratic program as a string. + """Returns the quadratic program as a string in DOcplex's prettyprint format. Returns: A string representing the quadratic program. @@ -762,7 +762,7 @@ def prettyprint(self, out: Optional[str] = None) -> None: self.to_docplex().prettyprint(out) def print_as_lp_string(self) -> str: - """Prints the quadratic program as a string of LP format. + """Returns the quadratic program as a string of LP format. Returns: A string representing the quadratic program. @@ -981,11 +981,11 @@ def _variables(self) -> bool: raise QiskitOptimizationError( 'Coefficient of variable substitution should be nonzero: ' '{} {} {}'.format(i, j, v)) - if abs(lb_i) < infinity: + if abs(lb_i) < INFINITY: new_lb_i = lb_i / v else: new_lb_i = lb_i if v > 0 else -lb_i - if abs(ub_i) < infinity: + if abs(ub_i) < INFINITY: new_ub_i = ub_i / v else: new_ub_i = ub_i if v > 0 else -ub_i diff --git a/qiskit/optimization/problems/variable.py b/qiskit/optimization/problems/variable.py index b116581ad8..e8d604c014 100644 --- a/qiskit/optimization/problems/variable.py +++ b/qiskit/optimization/problems/variable.py @@ -17,7 +17,7 @@ from enum import Enum from typing import Tuple, Union -from qiskit.optimization import infinity, QiskitOptimizationError +from qiskit.optimization import INFINITY, QiskitOptimizationError from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram @@ -35,7 +35,7 @@ class Variable(HasQuadraticProgram): def __init__(self, quadratic_program: 'QuadraticProgram', name: str, lowerbound: Union[float, int] = 0, - upperbound: Union[float, int] = infinity, + upperbound: Union[float, int] = INFINITY, vartype: VarType = VarType.CONTINUOUS) -> None: """Creates a new Variable. diff --git a/test/aqua/test_optimizers.py b/test/aqua/test_optimizers.py index eface89b73..6ada013597 100644 --- a/test/aqua/test_optimizers.py +++ b/test/aqua/test_optimizers.py @@ -30,8 +30,7 @@ class TestOptimizers(QiskitAquaTestCase): def setUp(self): super().setUp() - aqua_globals.random_seed = 50 - np.random.seed(512310912) + aqua_globals.random_seed = 52 def _optimize(self, optimizer): x_0 = [1.3, 0.7, 0.8, 1.9, 1.2] diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index ea8ee263ed..e3658f78e7 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -17,6 +17,8 @@ import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase import logging +import numpy as np +from scipy.sparse import dok_matrix from docplex.mp.model import Model from qiskit.aqua.operators import WeightedPauliOperator @@ -27,6 +29,7 @@ from qiskit.optimization.converters import ( InequalityToEquality, QuadraticProgramToOperator, + OperatorToQuadraticProgram, IntegerToBinary, LinearEqualityToPenalty, ) @@ -432,6 +435,39 @@ def test_optimizationproblem_to_operator(self): self.assertListEqual(qubitop.paulis, QUBIT_OP_MAXIMIZE_SAMPLE.paulis) self.assertEqual(offset, OFFSET_MAXIMIZE_SAMPLE) + def test_operator_to_quadraticprogram(self): + """ Test optimization problem to operators""" + op = QUBIT_OP_MAXIMIZE_SAMPLE + # offset = OFFSET_MAXIMIZE_SAMPLE + offset = 0 + + op2qp = OperatorToQuadraticProgram() + quadratic = op2qp.encode(op, offset) + + self.assertEqual(len(quadratic.variables), 4) + self.assertEqual(len(quadratic.linear_constraints), 0) + self.assertEqual(len(quadratic.quadratic_constraints), 0) + self.assertEqual(quadratic.objective.sense, quadratic.objective.Sense.MINIMIZE) + + linear_matrix = np.zeros((1, 4)) + linear_matrix[0, 0] = -500001 + linear_matrix[0, 1] = -800001 + linear_matrix[0, 2] = -900001 + linear_matrix[0, 3] = -800001 + + quadratic_matrix = np.zeros((4, 4)) + quadratic_matrix[0, 1] = 400000 + quadratic_matrix[0, 2] = 600000 + quadratic_matrix[1, 2] = 1200000 + quadratic_matrix[0, 3] = 800000 + quadratic_matrix[1, 3] = 1600000 + quadratic_matrix[2, 3] = 2400000 + + np.testing.assert_array_almost_equal(quadratic.objective.linear.coefficients.toarray(), + linear_matrix) + np.testing.assert_array_almost_equal(quadratic.objective.quadratic.coefficients.toarray(), + quadratic_matrix) + def test_continuous_variable_decode(self): """ Test decode func of IntegerToBinaryConverter for continuous variables""" try: diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index 2643d6a9db..9efb35805d 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -15,11 +15,13 @@ """ Test QuadraticProgram """ import unittest +import tempfile +import os from test.optimization.optimization_test_case import QiskitOptimizationTestCase from docplex.mp.model import Model, DOcplexException -from qiskit.optimization import QuadraticProgram, QiskitOptimizationError, infinity +from qiskit.optimization import QuadraticProgram, QiskitOptimizationError, INFINITY from qiskit.optimization.problems import Variable, Constraint, QuadraticObjective @@ -27,6 +29,19 @@ class TestQuadraticProgram(QiskitOptimizationTestCase): """Test QuadraticProgram without the members that have separate test classes (VariablesInterface, etc).""" + def setUp(self): + file, self.temp_output_file = tempfile.mkstemp(suffix='.lp') + os.close(file) + file, self.temp_problem_file = tempfile.mkstemp(suffix='.lp') + os.close(file) + + def tearDown(self): + for temp in [self.temp_output_file, self.temp_problem_file]: + try: + os.remove(temp) + except OSError: + pass + def test_constructor(self): """ test constructor """ quadratic_program = QuadraticProgram() @@ -77,7 +92,7 @@ def test_variables_handling(self): x_0 = quadratic_program.continuous_var() self.assertEqual(x_0.name, 'x0') self.assertEqual(x_0.lowerbound, 0) - self.assertEqual(x_0.upperbound, infinity) + self.assertEqual(x_0.upperbound, INFINITY) self.assertEqual(x_0.vartype, Variable.Type.CONTINUOUS) self.assertEqual(quadratic_program.get_num_vars(), 1) @@ -121,7 +136,7 @@ def test_variables_handling(self): x_4 = quadratic_program.integer_var() self.assertEqual(x_4.name, 'x4') self.assertEqual(x_4.lowerbound, 0) - self.assertEqual(x_4.upperbound, infinity) + self.assertEqual(x_4.upperbound, INFINITY) self.assertEqual(x_4.vartype, Variable.Type.INTEGER) self.assertEqual(quadratic_program.get_num_vars(), 5) @@ -479,15 +494,15 @@ def test_write_to_lp_file(self): '<=', 1, 'quad_leq') q_p.quadratic_constraint({'x': 1, 'y': 1}, {('x', 'x'): 1, ('y', 'z'): -1, ('z', 'z'): 2}, '>=', 1, 'quad_geq') - q_p.write_to_lp_file('output.lp') - with open('output.lp') as file1, open( + q_p.write_to_lp_file(self.temp_output_file) + with open(self.temp_output_file) as file1, open( 'test/optimization/resources/test_quadratic_program.lp') as file2: lines1 = file1.readlines() lines2 = file2.readlines() self.assertListEqual(lines1, lines2) - q_p.write_to_lp_file('.') - with open('my_problem.lp') as file1, open( + q_p.write_to_lp_file(self.temp_problem_file) + with open(self.temp_problem_file) as file1, open( 'test/optimization/resources/test_quadratic_program.lp') as file2: lines1 = file1.readlines() lines2 = file2.readlines() diff --git a/test/optimization/test_variable.py b/test/optimization/test_variable.py index 32b7ba18a7..e909a120d2 100644 --- a/test/optimization/test_variable.py +++ b/test/optimization/test_variable.py @@ -17,7 +17,7 @@ import unittest from test.optimization.optimization_test_case import QiskitOptimizationTestCase -from qiskit.optimization import infinity +from qiskit.optimization import INFINITY from qiskit.optimization.problems import QuadraticProgram, Variable @@ -50,7 +50,7 @@ def test_init_default(self): self.assertEqual(variable.name, name) self.assertEqual(variable.lowerbound, 0) - self.assertEqual(variable.upperbound, infinity) + self.assertEqual(variable.upperbound, INFINITY) self.assertEqual(variable.vartype, Variable.Type.CONTINUOUS) From a5790969016faa805b7b8f8b945ddf5d32714108 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Mon, 27 Apr 2020 14:15:04 +0900 Subject: [PATCH 307/323] add comments --- qiskit/optimization/problems/quadratic_program.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 7eedd69f5c..328d4840cc 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -598,10 +598,13 @@ def from_docplex(self, model: Model) -> None: # get linear constraints for constraint in model.iter_constraints(): if isinstance(constraint, DocplexQuadraticConstraint): - # ignore quadratic constraints + # ignore quadratic constraints here and process them later continue if not isinstance(constraint, DocplexLinearConstraint) or \ isinstance(constraint, NotEqualConstraint): + # If any constraint is not linear/quadratic constraints, it raises an error. + # Notice that NotEqualConstraint is a subclass of Docplex's LinearConstraint, + # but it cannot be handled by Aqua optimization. raise QiskitOptimizationError( 'Unsupported constraint: {}'.format(constraint)) name = constraint.name From 3b03d8e5c876f8184ae3e28b37ef394518b13cb0 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Mon, 27 Apr 2020 09:29:31 +0200 Subject: [PATCH 308/323] fix problem with int2bin converter --- .../converters/integer_to_binary.py | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/qiskit/optimization/converters/integer_to_binary.py b/qiskit/optimization/converters/integer_to_binary.py index 239bdc7999..f09051b147 100644 --- a/qiskit/optimization/converters/integer_to_binary.py +++ b/qiskit/optimization/converters/integer_to_binary.py @@ -67,30 +67,42 @@ def encode(self, op: QuadraticProgram, name: Optional[str] = None) -> QuadraticP QiskitOptimizationError: if variable or constraint type is not supported. """ + # copy original QP as reference. self._src = copy.deepcopy(op) - self._dst = QuadraticProgram() + + if self._src.get_num_integer_vars() > 0: + + # initialize new QP + self._dst = QuadraticProgram() + + # declare variables + for x in self._src.variables: + if x.vartype == Variable.Type.INTEGER: + new_vars = self._encode_var(x.name, x.lowerbound, x.upperbound) + self._conv[x] = new_vars + for (var_name, _) in new_vars: + self._dst.binary_var(var_name) + else: + if x.vartype == Variable.Type.CONTINUOUS: + self._dst.continuous_var(x.lowerbound, x.upperbound, x.name) + elif x.vartype == Variable.Type.BINARY: + self._dst.binary_var(x.name) + else: + raise QiskitOptimizationError( + "Unsupported variable type {}".format(x.vartype)) + + self._substitute_int_var() + + else: + # just copy the problem if no integer variables exist + self._dst = copy.deepcopy(op) + + # adjust name of resulting problem if necessary if name: self._dst.name = name else: self._dst.name = self._src.name - # declare variables - for x in self._src.variables: - if x.vartype == Variable.Type.INTEGER: - new_vars = self._encode_var(x.name, x.lowerbound, x.upperbound) - self._conv[x] = new_vars - for (var_name, _) in new_vars: - self._dst.binary_var(var_name) - else: - if x.vartype == Variable.Type.CONTINUOUS: - self._dst.continuous_var(x.lowerbound, x.upperbound, x.name) - elif x.vartype == Variable.Type.BINARY: - self._dst.binary_var(x.name) - else: - raise QiskitOptimizationError("Unsupported variable type {}".format(x.vartype)) - - self._substitute_int_var() - return self._dst def _encode_var(self, name: str, lowerbound: int, upperbound: int) -> List[Tuple[str, int]]: From 60bf662dff1c306802e6ae581a478a1831c3791c Mon Sep 17 00:00:00 2001 From: Anton Dekusar Date: Mon, 27 Apr 2020 09:44:37 +0100 Subject: [PATCH 309/323] enabled int2bin converter --- qiskit/optimization/algorithms/admm_optimizer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 758f3d78b6..8285c7cf5e 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -251,8 +251,8 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: self._log.debug("Initial problem: %s", problem.print_as_lp_string()) # map integer variables to binary variables - # int2bin = IntegerToBinary() - # problem = int2bin.encode(problem) + int2bin = IntegerToBinary() + problem = int2bin.encode(problem) # we deal with minimization in the optimizer, so turn the problem to minimization problem, sense = self._turn_to_minimization(problem) @@ -342,7 +342,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: result = ADMMOptimizationResult(solution, objective_value, self._state) # convert back integer to binary - # result = int2bin.decode(result) + result = int2bin.decode(result) # debug self._log.debug("solution=%s, objective=%s at iteration=%s", solution, objective_value, iteration) From 5f88f19b310b46a4bf3fa237a7cfd45c0f2bbf76 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Mon, 27 Apr 2020 11:47:09 +0200 Subject: [PATCH 310/323] update problems __init__.py docstring --- qiskit/optimization/problems/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qiskit/optimization/problems/__init__.py b/qiskit/optimization/problems/__init__.py index 5b83861117..d0d4310d7f 100644 --- a/qiskit/optimization/problems/__init__.py +++ b/qiskit/optimization/problems/__init__.py @@ -35,10 +35,8 @@ QuadraticProgram Variable -N.B. Additional classes Constraint, LinearConstraint, QuadraticConstraint, -QuadraticObjective, and Variable -are not to be instantiated directly. Objects of those types are available within -an instantiated QuadraticProgram. +N.B. All classes but `QuadraticProgram` are not to be instantiated directly. +Objects of those types are available within an instantiated `QuadraticProgram`. """ From 871f146dfee1226d369ece5725986d3d5e8f069d Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Mon, 27 Apr 2020 12:06:56 +0200 Subject: [PATCH 311/323] style fixes --- .pylintdict | 4 ++++ qiskit/optimization/algorithms/admm_optimizer.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.pylintdict b/.pylintdict index 92d61540be..2dee19876b 100644 --- a/.pylintdict +++ b/.pylintdict @@ -143,6 +143,9 @@ distro dj dnf docplex +docplex's +Docplex +Docplex's dp dtype durr @@ -165,6 +168,7 @@ enum eoh eom eps +eq Eq erdos eri diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 8285c7cf5e..042218ea13 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -542,7 +542,7 @@ def _create_step2_problem(self) -> QuadraticProgram: op2.objective.quadratic[var_index, var_index] = self._state.rho / 2 # replacing linear objective op2.objective.linear[var_index] = -1 * self._state.lambda_mult[i] - self._state.rho * \ - (self._state.x0[i] - self._state.y[i]) + (self._state.x0[i] - self._state.y[i]) # remove A0 x0 = b0 constraints for constraint in self._state.binary_equality_constraints: From 13c57e1496f89995de4efdb35722f9c07ac0e2ed Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Mon, 27 Apr 2020 12:12:31 +0200 Subject: [PATCH 312/323] rename `HasQuadraticProgram` to `QuadraticProgramElement` --- qiskit/optimization/problems/constraint.py | 4 ++-- qiskit/optimization/problems/linear_expression.py | 4 ++-- qiskit/optimization/problems/quadratic_expression.py | 4 ++-- qiskit/optimization/problems/quadratic_objective.py | 4 ++-- ...{has_quadratic_program.py => quadratic_program_element.py} | 2 +- qiskit/optimization/problems/variable.py | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) rename qiskit/optimization/problems/{has_quadratic_program.py => quadratic_program_element.py} (97%) diff --git a/qiskit/optimization/problems/constraint.py b/qiskit/optimization/problems/constraint.py index dc39e5e898..9b8bfb6190 100644 --- a/qiskit/optimization/problems/constraint.py +++ b/qiskit/optimization/problems/constraint.py @@ -20,7 +20,7 @@ from numpy import ndarray -from .has_quadratic_program import HasQuadraticProgram +from .has_quadratic_program import QuadraticProgramElement from ..exceptions import QiskitOptimizationError @@ -58,7 +58,7 @@ def convert(sense: Union[str, 'ConstraintSense']) -> 'ConstraintSense': return ConstraintSense.GE -class Constraint(HasQuadraticProgram): +class Constraint(QuadraticProgramElement): """Abstract Constraint Class.""" Sense = ConstraintSense diff --git a/qiskit/optimization/problems/linear_expression.py b/qiskit/optimization/problems/linear_expression.py index a243e3d852..5c5883bad0 100644 --- a/qiskit/optimization/problems/linear_expression.py +++ b/qiskit/optimization/problems/linear_expression.py @@ -20,10 +20,10 @@ from scipy.sparse import spmatrix, dok_matrix from qiskit.optimization import QiskitOptimizationError -from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram +from qiskit.optimization.problems.has_quadratic_program import QuadraticProgramElement -class LinearExpression(HasQuadraticProgram): +class LinearExpression(QuadraticProgramElement): """ Representation of a linear expression by its coefficients.""" def __init__(self, quadratic_program: "QuadraticProgram", diff --git a/qiskit/optimization/problems/quadratic_expression.py b/qiskit/optimization/problems/quadratic_expression.py index 1428728c1b..a99dca461c 100644 --- a/qiskit/optimization/problems/quadratic_expression.py +++ b/qiskit/optimization/problems/quadratic_expression.py @@ -21,10 +21,10 @@ from scipy.sparse import spmatrix, dok_matrix, tril, triu from qiskit.optimization import QiskitOptimizationError -from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram +from qiskit.optimization.problems.has_quadratic_program import QuadraticProgramElement -class QuadraticExpression(HasQuadraticProgram): +class QuadraticExpression(QuadraticProgramElement): """ Representation of a quadratic expression by its coefficients.""" def __init__(self, quadratic_program: "QuadraticProgram", diff --git a/qiskit/optimization/problems/quadratic_objective.py b/qiskit/optimization/problems/quadratic_objective.py index 42ff06c82d..d898a98f1b 100644 --- a/qiskit/optimization/problems/quadratic_objective.py +++ b/qiskit/optimization/problems/quadratic_objective.py @@ -20,7 +20,7 @@ from numpy import ndarray from scipy.sparse import spmatrix -from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram +from qiskit.optimization.problems.has_quadratic_program import QuadraticProgramElement from qiskit.optimization.problems.linear_constraint import LinearExpression from qiskit.optimization.problems.quadratic_expression import QuadraticExpression @@ -31,7 +31,7 @@ class ObjSense(Enum): MAXIMIZE = -1 -class QuadraticObjective(HasQuadraticProgram): +class QuadraticObjective(QuadraticProgramElement): """Representation of quadratic objective function of the form: constant + linear * x + x * quadratic * x. """ diff --git a/qiskit/optimization/problems/has_quadratic_program.py b/qiskit/optimization/problems/quadratic_program_element.py similarity index 97% rename from qiskit/optimization/problems/has_quadratic_program.py rename to qiskit/optimization/problems/quadratic_program_element.py index c09134c94b..f17bea036a 100644 --- a/qiskit/optimization/problems/has_quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program_element.py @@ -15,7 +15,7 @@ """Interface for all objects that have a parent QuadraticProgram.""" -class HasQuadraticProgram: +class QuadraticProgramElement: """Interface class for all objects that have a parent QuadraticProgram.""" def __init__(self, quadratic_program: "QuadraticProgram") -> None: diff --git a/qiskit/optimization/problems/variable.py b/qiskit/optimization/problems/variable.py index b116581ad8..8ea0f88212 100644 --- a/qiskit/optimization/problems/variable.py +++ b/qiskit/optimization/problems/variable.py @@ -18,7 +18,7 @@ from typing import Tuple, Union from qiskit.optimization import infinity, QiskitOptimizationError -from qiskit.optimization.problems.has_quadratic_program import HasQuadraticProgram +from qiskit.optimization.problems.has_quadratic_program import QuadraticProgramElement class VarType(Enum): @@ -28,7 +28,7 @@ class VarType(Enum): INTEGER = 2 -class Variable(HasQuadraticProgram): +class Variable(QuadraticProgramElement): """Representation of a variable.""" Type = VarType From 7c0b3b04bebc15755275a53814133c4d81faf819 Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Mon, 27 Apr 2020 12:14:24 +0200 Subject: [PATCH 313/323] rename `HasQuadraticProgram` to `QuadraticProgramElement` (2) --- qiskit/optimization/problems/constraint.py | 2 +- qiskit/optimization/problems/linear_expression.py | 2 +- qiskit/optimization/problems/quadratic_expression.py | 2 +- qiskit/optimization/problems/quadratic_objective.py | 2 +- qiskit/optimization/problems/variable.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/qiskit/optimization/problems/constraint.py b/qiskit/optimization/problems/constraint.py index 9b8bfb6190..fa7390beb8 100644 --- a/qiskit/optimization/problems/constraint.py +++ b/qiskit/optimization/problems/constraint.py @@ -20,7 +20,7 @@ from numpy import ndarray -from .has_quadratic_program import QuadraticProgramElement +from .quadratic_program_element import QuadraticProgramElement from ..exceptions import QiskitOptimizationError diff --git a/qiskit/optimization/problems/linear_expression.py b/qiskit/optimization/problems/linear_expression.py index 5c5883bad0..945aacbdb5 100644 --- a/qiskit/optimization/problems/linear_expression.py +++ b/qiskit/optimization/problems/linear_expression.py @@ -20,7 +20,7 @@ from scipy.sparse import spmatrix, dok_matrix from qiskit.optimization import QiskitOptimizationError -from qiskit.optimization.problems.has_quadratic_program import QuadraticProgramElement +from qiskit.optimization.problems.quadratic_program_element import QuadraticProgramElement class LinearExpression(QuadraticProgramElement): diff --git a/qiskit/optimization/problems/quadratic_expression.py b/qiskit/optimization/problems/quadratic_expression.py index a99dca461c..d219f1f6b4 100644 --- a/qiskit/optimization/problems/quadratic_expression.py +++ b/qiskit/optimization/problems/quadratic_expression.py @@ -21,7 +21,7 @@ from scipy.sparse import spmatrix, dok_matrix, tril, triu from qiskit.optimization import QiskitOptimizationError -from qiskit.optimization.problems.has_quadratic_program import QuadraticProgramElement +from qiskit.optimization.problems.quadratic_program_element import QuadraticProgramElement class QuadraticExpression(QuadraticProgramElement): diff --git a/qiskit/optimization/problems/quadratic_objective.py b/qiskit/optimization/problems/quadratic_objective.py index d898a98f1b..7d2f9f32fd 100644 --- a/qiskit/optimization/problems/quadratic_objective.py +++ b/qiskit/optimization/problems/quadratic_objective.py @@ -20,7 +20,7 @@ from numpy import ndarray from scipy.sparse import spmatrix -from qiskit.optimization.problems.has_quadratic_program import QuadraticProgramElement +from qiskit.optimization.problems.quadratic_program_element import QuadraticProgramElement from qiskit.optimization.problems.linear_constraint import LinearExpression from qiskit.optimization.problems.quadratic_expression import QuadraticExpression diff --git a/qiskit/optimization/problems/variable.py b/qiskit/optimization/problems/variable.py index 8ea0f88212..a722cbbe1e 100644 --- a/qiskit/optimization/problems/variable.py +++ b/qiskit/optimization/problems/variable.py @@ -18,7 +18,7 @@ from typing import Tuple, Union from qiskit.optimization import infinity, QiskitOptimizationError -from qiskit.optimization.problems.has_quadratic_program import QuadraticProgramElement +from qiskit.optimization.problems.quadratic_program_element import QuadraticProgramElement class VarType(Enum): From 34f8a7dea36e33b9a716997442477f6fe9f7932e Mon Sep 17 00:00:00 2001 From: Cryoris Date: Mon, 27 Apr 2020 12:46:02 +0200 Subject: [PATCH 314/323] module level docstrings, offset test --- qiskit/optimization/__init__.py | 12 ++++++++---- .../algorithms/optimization_algorithm.py | 10 ++++------ qiskit/optimization/converters/__init__.py | 18 +++++++++++++++--- qiskit/optimization/problems/__init__.py | 2 +- .../optimization/problems/quadratic_program.py | 2 +- test/optimization/test_converters.py | 5 ++--- 6 files changed, 31 insertions(+), 18 deletions(-) diff --git a/qiskit/optimization/__init__.py b/qiskit/optimization/__init__.py index f93fca87ca..e302d95922 100644 --- a/qiskit/optimization/__init__.py +++ b/qiskit/optimization/__init__.py @@ -15,15 +15,19 @@ ==================================================================== Optimization application stack for Aqua (:mod:`qiskit.optimization`) ==================================================================== -This is the finance domain logic.... .. currentmodule:: qiskit.optimization -Submodules -========== +Contents +======== .. autosummary:: - :toctree: + :toctree: ../stubs/ + :nosignatures: + + QuadraticProgram + QiskitOptimizationError + INFINITY """ diff --git a/qiskit/optimization/algorithms/optimization_algorithm.py b/qiskit/optimization/algorithms/optimization_algorithm.py index ee5d35cb38..48cf97ffd0 100644 --- a/qiskit/optimization/algorithms/optimization_algorithm.py +++ b/qiskit/optimization/algorithms/optimization_algorithm.py @@ -65,8 +65,7 @@ def solve(self, problem: QuadraticProgram) -> 'OptimizationResult': class OptimizationResultStatus(Enum): - """Feasible values for the termination status of an optimization algorithm. - """ + """Feasible values for the termination status of an optimization algorithm.""" SUCCESS = 0 FAILURE = 1 INFEASIBLE = 2 @@ -91,18 +90,17 @@ class OptimizationResult: def __init__(self, x: Optional[Any] = None, fval: Optional[Any] = None, results: Optional[Any] = None, status: OptimizationResultStatus = OptimizationResultStatus.SUCCESS) -> None: - """Initialize the optimization result.""" self._val = x self._fval = fval self._results = results self._status = status def __repr__(self): - return '([%s] / %s)' % (','.join([str(x_) for x_ in self.x]), self.fval) + return '([{}] / {} / {})'.format(','.join([str(x_) for x_ in self.x]), self.fval, + self.status) def __str__(self): - return 'optimal value=[%s], function value=%s)' % (','.join([str(x_) for x_ in self.x]), - self.fval) + return 'x=[{}], fval={}'.format(','.join([str(x_) for x_ in self.x]), self.fval) @property def x(self) -> Any: diff --git a/qiskit/optimization/converters/__init__.py b/qiskit/optimization/converters/__init__.py index dbb9a2ca80..9d41d9c344 100644 --- a/qiskit/optimization/converters/__init__.py +++ b/qiskit/optimization/converters/__init__.py @@ -13,14 +13,26 @@ # that they have been altered from the originals. """ -======================================================== +=================================================================== Optimization stack for Aqua (:mod:`qiskit.optimization.converters`) -======================================================== +=================================================================== .. currentmodule:: qiskit.optimization.converters Structures for converting optimization problems -========== +=============================================== + +.. autosummary:: + :toctree: ../stubs/ + :nosignatures: + + InequalityToEquality + IntegerToBinary + QuadraticProgramToNegativeValueOracle + QuadraticProgramToOperator + QuadraticProgramToQubo + LinearEqualityToPenalty + OperatorToQuadraticProgram """ diff --git a/qiskit/optimization/problems/__init__.py b/qiskit/optimization/problems/__init__.py index 5b83861117..543854ca92 100644 --- a/qiskit/optimization/problems/__init__.py +++ b/qiskit/optimization/problems/__init__.py @@ -20,7 +20,7 @@ .. currentmodule:: qiskit.optimization.problems Structures for defining an optimization problem and its solution -========== +================================================================ .. autosummary:: :toctree: ../stubs/ diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 0f84f7ed76..0d29e2f864 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -761,7 +761,7 @@ def prettyprint(self, out: Optional[str] = None) -> None: """ self.to_docplex().prettyprint(out) - def print_as_lp_string(self) -> str: + def export_as_lp_string(self) -> str: """Returns the quadratic program as a string of LP format. Returns: diff --git a/test/optimization/test_converters.py b/test/optimization/test_converters.py index e3658f78e7..45bdf469f1 100644 --- a/test/optimization/test_converters.py +++ b/test/optimization/test_converters.py @@ -18,7 +18,6 @@ from test.optimization.optimization_test_case import QiskitOptimizationTestCase import logging import numpy as np -from scipy.sparse import dok_matrix from docplex.mp.model import Model from qiskit.aqua.operators import WeightedPauliOperator @@ -438,8 +437,7 @@ def test_optimizationproblem_to_operator(self): def test_operator_to_quadraticprogram(self): """ Test optimization problem to operators""" op = QUBIT_OP_MAXIMIZE_SAMPLE - # offset = OFFSET_MAXIMIZE_SAMPLE - offset = 0 + offset = OFFSET_MAXIMIZE_SAMPLE op2qp = OperatorToQuadraticProgram() quadratic = op2qp.encode(op, offset) @@ -448,6 +446,7 @@ def test_operator_to_quadraticprogram(self): self.assertEqual(len(quadratic.linear_constraints), 0) self.assertEqual(len(quadratic.quadratic_constraints), 0) self.assertEqual(quadratic.objective.sense, quadratic.objective.Sense.MINIMIZE) + self.assertAlmostEqual(quadratic.objective.constant, 900000) linear_matrix = np.zeros((1, 4)) linear_matrix[0, 0] = -500001 From 609768adc582f84dfdb13fcb183d010fc9d987da Mon Sep 17 00:00:00 2001 From: Cryoris Date: Mon, 27 Apr 2020 12:52:51 +0200 Subject: [PATCH 315/323] fix spell & test --- qiskit/optimization/problems/quadratic_program.py | 2 +- test/optimization/test_quadratic_program.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 0d29e2f864..15cfd36a21 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -745,7 +745,7 @@ def to_docplex(self) -> Model: return mdl def pprint_as_string(self) -> str: - """Returns the quadratic program as a string in DOcplex's prettyprint format. + """Returns the quadratic program as a string in Docplex's pretty print format. Returns: A string representing the quadratic program. diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index 9efb35805d..ece03abe5e 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -527,7 +527,7 @@ def test_docplex(self): q_p2 = QuadraticProgram() q_p2.from_docplex(q_p.to_docplex()) self.assertEqual(q_p.pprint_as_string(), q_p2.pprint_as_string()) - self.assertEqual(q_p.print_as_lp_string(), q_p2.print_as_lp_string()) + self.assertEqual(q_p.export_as_lp_string(), q_p2.export_as_lp_string()) mod = Model('test') x = mod.binary_var('x') @@ -537,7 +537,7 @@ def test_docplex(self): mod.add(2 * x - z == 1, 'c0') mod.add(2 * x - z + 3 * y * z == 1, 'q0') self.assertEqual(q_p.pprint_as_string(), mod.pprint_as_string()) - self.assertEqual(q_p.print_as_lp_string(), mod.export_as_lp_string()) + self.assertEqual(q_p.export_as_lp_string(), mod.export_as_lp_string()) def test_substitute_variables(self): """test substitute variables""" From 668608781f0a1fdb9d03d827a865dc5756d7ff17 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Mon, 27 Apr 2020 13:40:10 +0200 Subject: [PATCH 316/323] leftover fixes --- qiskit/optimization/algorithms/admm_optimizer.py | 10 +++++----- test/optimization/test_grover_optimizer.py | 16 +++++----------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index 042218ea13..e33672dd24 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -248,7 +248,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: raise QiskitOptimizationError('Incompatible problem: {}'.format(msg)) # debug - self._log.debug("Initial problem: %s", problem.print_as_lp_string()) + self._log.debug("Initial problem: %s", problem.export_as_lp_string()) # map integer variables to binary variables int2bin = IntegerToBinary() @@ -281,7 +281,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: op1 = self._create_step1_problem() self._state.x0 = self._update_x0(op1) # debug - self._log.debug("Step 1 sub-problem: %s", op1.print_as_lp_string()) + self._log.debug("Step 1 sub-problem: %s", op1.export_as_lp_string()) # else, no binary variables exist, and no update to be done in this case. # debug self._log.debug("x0=%s", self._state.x0) @@ -289,7 +289,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: op2 = self._create_step2_problem() self._state.u, self._state.z = self._update_x1(op2) # debug - self._log.debug("Step 2 sub-problem: %s", op2.print_as_lp_string()) + self._log.debug("Step 2 sub-problem: %s", op2.export_as_lp_string()) self._log.debug("u=%s", self._state.u) self._log.debug("z=%s", self._state.z) @@ -298,7 +298,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: op3 = self._create_step3_problem() self._state.y = self._update_y(op3) # debug - self._log.debug("Step 3 sub-problem: %s", op3.print_as_lp_string()) + self._log.debug("Step 3 sub-problem: %s", op3.export_as_lp_string()) # debug self._log.debug("y=%s", self._state.y) @@ -621,7 +621,7 @@ def _get_best_merit_solution(self) -> (np.ndarray, np.ndarray, float): Returns: A tuple of (binary_vars, continuous_vars, sol_val), where * binary_vars: binary variable values with the min merit value - * continuous_vars: continuous varible values with the min merit value + * continuous_vars: continuous variable values with the min merit value * sol_val: Value of the objective function """ diff --git a/test/optimization/test_grover_optimizer.py b/test/optimization/test_grover_optimizer.py index 0ba09b9191..2fc8bb4bf9 100644 --- a/test/optimization/test_grover_optimizer.py +++ b/test/optimization/test_grover_optimizer.py @@ -34,6 +34,8 @@ def setUp(self): random.seed = 2 numpy.random.seed = 42 aqua_globals.seed = 42 + self.q_instance = QuantumInstance(Aer.get_backend('statevector_simulator'), + seed_simulator=921, seed_transpiler=200) def validate_results(self, problem, results): """Validate the results object returned by GroverOptimizer.""" @@ -57,7 +59,7 @@ def test_qubo_gas_int_zero(self): op.from_docplex(model) # Will not find a negative, should return 0. - gmf = GroverOptimizer(1, num_iterations=1) + gmf = GroverOptimizer(1, num_iterations=1, quantum_instance=self.q_instance) results = gmf.solve(op) self.assertEqual(results.x, [0, 0]) self.assertEqual(results.fval, 0.0) @@ -73,13 +75,9 @@ def test_qubo_gas_int_simple(self): op = QuadraticProgram() op.from_docplex(model) - # Quantum Instance. - q_instance = QuantumInstance(Aer.get_backend('statevector_simulator'), - seed_simulator=921, seed_transpiler=200) - # Get the optimum key and value. n_iter = 8 - gmf = GroverOptimizer(4, num_iterations=n_iter, quantum_instance=q_instance) + gmf = GroverOptimizer(4, num_iterations=n_iter, quantum_instance=self.q_instance) results = gmf.solve(op) self.validate_results(op, results) @@ -95,13 +93,9 @@ def test_qubo_gas_int_paper_example(self): op = QuadraticProgram() op.from_docplex(model) - # Quantum Instance. - q_instance = QuantumInstance(Aer.get_backend('statevector_simulator'), - seed_simulator=921, seed_transpiler=200) - # Get the optimum key and value. n_iter = 10 - gmf = GroverOptimizer(6, num_iterations=n_iter, quantum_instance=q_instance) + gmf = GroverOptimizer(6, num_iterations=n_iter, quantum_instance=self.q_instance) results = gmf.solve(op) self.validate_results(op, results) From d53845b14df201a1c2dfb242323926d00730764e Mon Sep 17 00:00:00 2001 From: Stefan Woerner Date: Mon, 27 Apr 2020 14:11:29 +0200 Subject: [PATCH 317/323] update changelog and ADMM docstring --- CHANGELOG.md | 4 ++++ qiskit/optimization/algorithms/admm_optimizer.py | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb87f8211d..511fb5d864 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,10 @@ Added - Chemistry stack automatic Z2 symmetry reduction (#870) - Ising Optimization: The 0-1 Knapsack problem (#878) - VQE, VQC and QSVM accept `QuantumCircuit`s as variational forms/feature maps (#905) +- Qiskit Optimization: complete optimization stack including new `QuadraticProgram`, + `OptimizationAlgorithm` as well as many implementations and converters (#877). this replaces + the current `docplex` to `Operator` converter, which is deprecated now. +- New `GSLS` (Gaussian Smoothing Line Search) optimizer for variational algorithms (#877) Changed ------- diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index e33672dd24..e53af54eaf 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -238,9 +238,6 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: Returns: The result of the optimizer applied to the problem. - - Raises: - QiskitOptimizationError: If the problem is incompatible with the optimizer. """ # check compatibility and raise exception if incompatible msg = self.get_compatibility_msg(problem) From 4253c89d973a3d753e5c180794ce46d6745d3e4d Mon Sep 17 00:00:00 2001 From: Cryoris Date: Mon, 27 Apr 2020 15:46:25 +0200 Subject: [PATCH 318/323] fix write to dir, use new tempfile functionality --- .../problems/quadratic_program.py | 7 ++-- test/optimization/test_quadratic_program.py | 40 +++++++------------ 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/qiskit/optimization/problems/quadratic_program.py b/qiskit/optimization/problems/quadratic_program.py index 14c0f47559..90c0109124 100644 --- a/qiskit/optimization/problems/quadratic_program.py +++ b/qiskit/optimization/problems/quadratic_program.py @@ -529,10 +529,11 @@ def maximize(self, def from_docplex(self, model: Model) -> None: """Loads this quadratic program from a docplex model. + Note that this supports only basic functions of docplex as follows: - - quadratic objective function - - linear / quadratic constraints - - binary / integer / continuous variables + - quadratic objective function + - linear / quadratic constraints + - binary / integer / continuous variables Args: model: The docplex model to be loaded. diff --git a/test/optimization/test_quadratic_program.py b/test/optimization/test_quadratic_program.py index 239621d3d5..85b73d5ebf 100644 --- a/test/optimization/test_quadratic_program.py +++ b/test/optimization/test_quadratic_program.py @@ -16,7 +16,6 @@ import unittest import tempfile -import os from test.optimization.optimization_test_case import QiskitOptimizationTestCase from docplex.mp.model import Model, DOcplexException @@ -29,19 +28,6 @@ class TestQuadraticProgram(QiskitOptimizationTestCase): """Test QuadraticProgram without the members that have separate test classes (VariablesInterface, etc).""" - def setUp(self): - file, self.temp_output_file = tempfile.mkstemp(suffix='.lp') - os.close(file) - file, self.temp_problem_file = tempfile.mkstemp(suffix='.lp') - os.close(file) - - def tearDown(self): - for temp in [self.temp_output_file, self.temp_problem_file]: - try: - os.remove(temp) - except OSError: - pass - def test_constructor(self): """ test constructor """ quadratic_program = QuadraticProgram() @@ -494,20 +480,24 @@ def test_write_to_lp_file(self): '<=', 1, 'quad_leq') q_p.quadratic_constraint({'x': 1, 'y': 1}, {('x', 'x'): 1, ('y', 'z'): -1, ('z', 'z'): 2}, '>=', 1, 'quad_geq') - q_p.write_to_lp_file(self.temp_output_file) - with open(self.temp_output_file) as file1, open( - 'test/optimization/resources/test_quadratic_program.lp') as file2: - lines1 = file1.readlines() - lines2 = file2.readlines() - self.assertListEqual(lines1, lines2) - q_p.write_to_lp_file(self.temp_problem_file) - with open(self.temp_problem_file) as file1, open( - 'test/optimization/resources/test_quadratic_program.lp') as file2: - lines1 = file1.readlines() - lines2 = file2.readlines() + temp_output_file = tempfile.NamedTemporaryFile(mode='w+t', suffix='.lp') + q_p.write_to_lp_file(temp_output_file.name) + with open('test/optimization/resources/test_quadratic_program.lp') as reference: + lines1 = temp_output_file.readlines() + lines2 = reference.readlines() self.assertListEqual(lines1, lines2) + temp_output_file.close() # automatically deleted + + with tempfile.TemporaryDirectory() as temp_problem_dir: + q_p.write_to_lp_file(temp_problem_dir) + with open(temp_problem_dir + '/my_problem.lp') as file1, open( + 'test/optimization/resources/test_quadratic_program.lp') as file2: + lines1 = file1.readlines() + lines2 = file2.readlines() + self.assertListEqual(lines1, lines2) + with self.assertRaises(OSError): q_p.write_to_lp_file('/cannot/write/this/file.lp') From 18a4a522db37e5d485c9bc623795ac5ecddffa0d Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Mon, 27 Apr 2020 10:14:28 -0400 Subject: [PATCH 319/323] fix docstring --- qiskit/optimization/algorithms/admm_optimizer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qiskit/optimization/algorithms/admm_optimizer.py b/qiskit/optimization/algorithms/admm_optimizer.py index e53af54eaf..bbb20897f5 100644 --- a/qiskit/optimization/algorithms/admm_optimizer.py +++ b/qiskit/optimization/algorithms/admm_optimizer.py @@ -238,6 +238,9 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult: Returns: The result of the optimizer applied to the problem. + + Raises: + QiskitOptimizationError: If the problem is not compatible with the ADMM optimizer. """ # check compatibility and raise exception if incompatible msg = self.get_compatibility_msg(problem) From 88eb071ced9e2cd33b7b14a79a7aa15e977bafa5 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Mon, 27 Apr 2020 18:12:35 +0200 Subject: [PATCH 320/323] add changelog --- CHANGELOG.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 511fb5d864..bcd23fe7d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,10 +27,25 @@ Added - Chemistry stack automatic Z2 symmetry reduction (#870) - Ising Optimization: The 0-1 Knapsack problem (#878) - VQE, VQC and QSVM accept `QuantumCircuit`s as variational forms/feature maps (#905) -- Qiskit Optimization: complete optimization stack including new `QuadraticProgram`, - `OptimizationAlgorithm` as well as many implementations and converters (#877). this replaces - the current `docplex` to `Operator` converter, which is deprecated now. - New `GSLS` (Gaussian Smoothing Line Search) optimizer for variational algorithms (#877) +- Qiskit optimization, an application stack for solving quadratic programs (#877) + - QuadraticProblem: A class representing quadratic programs with quadratic and linear objective and constraints + - OptimizationAlgorithm: A base class for optimization algorithm + - OptimizationResult: A base class for optimization results + - Summary of the optimization algorithms: + - MinimumEigenOptimizer: An optimization algorithm using a minimum eigen solver, such as VQE (or a classical alternative). See the MinimumEigenSolver algorithms in Aqua. + - GroverOptimizer: The Grover Adaptive Search algorithm (Gilliam et al.) + - ADMMOptimizer: The ADMM-based heuristic (Gambella et al.) + - RecursiveMinimumEigenOptimizer: A meta-algorithm applying recursive optimization on top of a MinimumEigenOptimizer (Bravyi et al.) + - CobylaOptimizer: Wrapping of SciPy’s COBYLA subroutine as optimization algorithm + - CplexOptimizer: Wrapping the CPLEX API as optimization algorithm + - A set of converters to translate different problem representations + - InequalityToEquality: Converts inequality constraints to equality constraints by adding slack variables + - IntegerToBinary: Converts integer variables to binary variables + - LinearEqualityToPenalty: Converts linear equality constraints to quadratic penalty terms that are added to the objective + - QuadraticProgramToOperator: Converts a QuadraticProgram to an Aqua operator + - QuadraticProgramToNegativeValueOracle: Converts a QuadraticProgram to a negative-value oracle used for Grover Adaptive Search + - QuadraticProgramToQubo: Converts a QuadraticProgram to a QUBO problem, a convenience converter wrapping the functionality of the IntegerToBinary and LinearEqualityToPenalty converters Changed ------- From 2c86e6604a9e6f452063eddbd0e27765cc20d1f0 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Mon, 27 Apr 2020 19:03:20 +0200 Subject: [PATCH 321/323] remove Grover Optimization changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcd23fe7d3..da0e7216c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,7 +64,6 @@ Removed ------- - Declarative api (#758) (#759) (#760) (#762) (#763) -- Add Grover Optimization to the Optimization Stack (#847) - Moved to Terra: - multi-controlled Toffoli, U1 and Pauli rotation gates (including tests) (#833) - arithmetic circuits in qiskit/aqua/circuits (#895) From 040d31ab08240b2958f0e2118f03b92969c42a31 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Mon, 27 Apr 2020 13:42:40 -0400 Subject: [PATCH 322/323] trigger CLA check From ee47bdb38c1f7f4554095bccf146e869996287d6 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Mon, 27 Apr 2020 14:16:59 -0400 Subject: [PATCH 323/323] fix qasm string on unit tests --- test/aqua/test_initial_state_custom.py | 2 +- test/chemistry/test_initial_state_hartree_fock.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/aqua/test_initial_state_custom.py b/test/aqua/test_initial_state_custom.py index 965b80a1cd..84e9e08cde 100644 --- a/test/aqua/test_initial_state_custom.py +++ b/test/aqua/test_initial_state_custom.py @@ -70,7 +70,7 @@ def test_qubits_2_uniform_circuit(self): cct = custom.construct_circuit('circuit') self.assertEqual(cct.qasm(), 'OPENQASM 2.0;\ninclude "qelib1.inc";\nqreg q[2];\n' - 'u2(0.0,3.141592653589793) q[0];\nu2(0.0,3.141592653589793) q[1];\n') + 'u2(0,pi) q[0];\nu2(0,pi) q[1];\n') def test_qubits_2_random_vector(self): """ qubits 2 random vector test """ diff --git a/test/chemistry/test_initial_state_hartree_fock.py b/test/chemistry/test_initial_state_hartree_fock.py index 28daab6d05..7e8c2c1a08 100644 --- a/test/chemistry/test_initial_state_hartree_fock.py +++ b/test/chemistry/test_initial_state_hartree_fock.py @@ -61,15 +61,15 @@ def test_qubits_2_py_h2_cct(self): hrfo = HartreeFock(2, 4, [1, 1], 'parity', True) cct = hrfo.construct_circuit('circuit') self.assertEqual(cct.qasm(), 'OPENQASM 2.0;\ninclude "qelib1.inc";\nqreg q[2];\n' - 'u3(3.141592653589793,0.0,3.141592653589793) q[0];\n') + 'u3(pi,0,pi) q[0];\n') def test_qubits_6_py_lih_cct(self): """ qubits 6 py lih cct test """ hrfo = HartreeFock(6, 10, [1, 1], 'parity', True, [1, 2]) cct = hrfo.construct_circuit('circuit') self.assertEqual(cct.qasm(), 'OPENQASM 2.0;\ninclude "qelib1.inc";\nqreg q[6];\n' - 'u3(3.141592653589793,0.0,3.141592653589793) q[0];\n' - 'u3(3.141592653589793,0.0,3.141592653589793) q[1];\n') + 'u3(pi,0,pi) q[0];\n' + 'u3(pi,0,pi) q[1];\n') def test_qubits_10_bk_lih_bitstr(self): """ qubits 10 bk lih bitstr test """