def to_quadratic_problem(self): mdl = Model() x = { i: mdl.binary_var(name='x_{0}'.format(i)) for i in range(self.g.number_of_nodes()) } for u, v in self.g.edges: self.g.edges[u, v].setdefault('weight', 1) objective = mdl.sum(self.g.edges[i, j]['weight'] * x[i] * (1 - x[j]) for i, j in self.g.edges) mdl.maximize(objective) # print(mdl.export_as_lp_string()) qp = QuadraticProgram() qp.from_docplex(mdl) #print(qp.export_as_lp_string()) self.qp = qp return self.qp
def to_quadratic_program(self): num_assets = len(self._mu) mdl = AdvModel(name='portfolio') x = [mdl.binary_var(name='x_{0}'.format(i)) for i in range(num_assets)] quad = mdl.quad_matrix_sum(self._sigma, x) linear = np.dot(self._mu, x) mdl.minimize(quad + linear) mdl.add_constraint(mdl.sum(x[i] for i in range(num_assets)) == self._budget) qp = QuadraticProgram() qp.from_docplex(mdl) return qp
def to_quadratic_program(self) -> QuadraticProgram: """Convert a vehicle routing problem instance into a :class:`~qiskit_optimization.problems.QuadraticProgram` Returns: The :class:`~qiskit_optimization.problems.QuadraticProgram` created from the vehicle routing problem instance. """ mdl = Model(name='Vehicle routing') n = self._graph.number_of_nodes() x = {} for i in range(n): for j in range(n): if i != j: x[(i, j)] = mdl.binary_var(name='x_{0}_{1}'.format(i, j)) mdl.minimize( mdl.sum(self._graph.edges[i, j]['weight'] * x[(i, j)] for i in range(n) for j in range(n) if i != j)) # Only 1 edge goes out from each node for i in range(n): if i != self.depot: mdl.add_constraint( mdl.sum(x[i, j] for j in range(n) if i != j) == 1) # Only 1 edge comes into each node for j in range(n): if j != self.depot: mdl.add_constraint( mdl.sum(x[i, j] for i in range(n) if i != j) == 1) # For the depot node mdl.add_constraint( mdl.sum(x[i, self.depot] for i in range(n) if i != self.depot) == self.num_vehicles) mdl.add_constraint( mdl.sum(x[self.depot, j] for j in range(n) if j != self.depot) == self.num_vehicles) # To eliminate sub-routes node_list = [i for i in range(n) if i != self.depot] clique_set = [] for i in range(2, len(node_list) + 1): for comb in itertools.combinations(node_list, i): clique_set.append(list(comb)) for clique in clique_set: mdl.add_constraint( mdl.sum(x[(i, j)] for i in clique for j in clique if i != j) <= len(clique) - 1) op = QuadraticProgram() op.from_docplex(mdl) return op
def __init__(self, model: Model): """ Args: model: Docplex model """ self._model: Model = model self._quadratic_program: QuadraticProgram = QuadraticProgram() self._var_names: Dict[Var, str] = {} self._var_bounds: Dict[str, Tuple[float, float]] = {}
def to_quadratic_program(self) -> QuadraticProgram: """Convert a vertex cover instance into a :class:`~qiskit_optimization.problems.QuadraticProgram` Returns: The :class:`~qiskit_optimization.problems.QuadraticProgram` created from the vertex cover instance. """ mdl = Model(name='Vertex cover') n = self._graph.number_of_nodes() x = {i: mdl.binary_var(name='x_{0}'.format(i)) for i in range(n)} objective = mdl.sum(x[i] for i in x) for w, v in self._graph.edges: mdl.add_constraint(x[w] + x[v] >= 1) mdl.minimize(objective) op = QuadraticProgram() op.from_docplex(mdl) return op
def to_quadratic_program(self) -> QuadraticProgram: """Convert a number partitioning problem instance into a :class:`~qiskit_optimization.problems.QuadraticProgram` Returns: The :class:`~qiskit_optimization.problems.QuadraticProgram` created from the number partitioning problem instance. """ mdl = Model(name='Number partitioning') x = { i: mdl.binary_var(name='x_{0}'.format(i)) for i in range(len(self._number_set)) } mdl.add_constraint( mdl.sum(num * (-2 * x[i] + 1) for i, num in enumerate(self._number_set)) == 0) op = QuadraticProgram() op.from_docplex(mdl) return op
def to_quadratic_program(self) -> QuadraticProgram: """Convert a knapsack problem instance into a :class:`~qiskit_optimization.problems.QuadraticProgram` Returns: The :class:`~qiskit_optimization.problems.QuadraticProgram` created from the knapsack problem instance. """ mdl = Model(name="Knapsack") x = { i: mdl.binary_var(name="x_{0}".format(i)) for i in range(len(self._values)) } mdl.maximize(mdl.sum(self._values[i] * x[i] for i in x)) mdl.add_constraint( mdl.sum(self._weights[i] * x[i] for i in x) <= self._max_weight) op = QuadraticProgram() op.from_docplex(mdl) return op
def to_quadratic_program(self) -> QuadraticProgram: """Convert a stable set instance into a :class:`~qiskit_optimization.problems.QuadraticProgram` Returns: The :class:`~qiskit_optimization.problems.QuadraticProgram` created from the stable set instance. """ mdl = Model(name='Stable set') n = self._graph.number_of_nodes() x = {i: mdl.binary_var(name='x_{0}'.format(i)) for i in range(n)} for w, v in self._graph.edges: self._graph.edges[w, v].setdefault('weight', 1) objective = mdl.sum(x[i] for i in x) for w, v in self._graph.edges: mdl.add_constraint(x[w] + x[v] <= 1) mdl.maximize(objective) op = QuadraticProgram() op.from_docplex(mdl) return op
def to_quadratic_program(self) -> QuadraticProgram: """Convert a Max-cut problem instance into a :class:`~qiskit_optimization.problems.QuadraticProgram` Returns: The :class:`~qiskit_optimization.problems.QuadraticProgram` created from the Max-cut problem instance. """ mdl = Model(name='Max-cut') x = {i: mdl.binary_var(name='x_{0}'.format(i)) for i in range(self._graph.number_of_nodes())} for w, v in self._graph.edges: self._graph.edges[w, v].setdefault('weight', 1) objective = mdl.sum(self._graph.edges[i, j]['weight'] * x[i] * (1 - x[j]) + self._graph.edges[i, j]['weight'] * x[j] * (1 - x[i]) for i, j in self._graph.edges) mdl.maximize(objective) op = QuadraticProgram() op.from_docplex(mdl) return op
def to_quadratic_program(self) -> QuadraticProgram: """Convert a graph partition instance into a :class:`~qiskit_optimization.problems.QuadraticProgram` Returns: The :class:`~qiskit_optimization.problems.QuadraticProgram` created from the graph partition instance. """ mdl = Model(name='Graph partition') n = self._graph.number_of_nodes() x = {i: mdl.binary_var(name='x_{0}'.format(i)) for i in range(n)} for w, v in self._graph.edges: self._graph.edges[w, v].setdefault('weight', 1) objective = mdl.sum(self._graph.edges[i, j]['weight'] * (x[i] + x[j] - 2*x[i]*x[j]) for i, j in self._graph.edges) mdl.minimize(objective) mdl.add_constraint(mdl.sum([x[i] for i in x]) == n//2) op = QuadraticProgram() op.from_docplex(mdl) return op
def to_quadratic_program(self) -> QuadraticProgram: """Convert a set packing instance into a :class:`~qiskit_optimization.problems.QuadraticProgram` Returns: The :class:`~qiskit_optimization.problems.QuadraticProgram` created from the set packing instance. """ mdl = Model(name='Set packing') x = { i: mdl.binary_var(name='x_{0}'.format(i)) for i in range(len(self._subsets)) } mdl.maximize(mdl.sum(x[i] for i in x)) for element in self._set: mdl.add_constraint( mdl.sum(x[i] for i, sub in enumerate(self._subsets) if element in sub) <= 1) op = QuadraticProgram() op.from_docplex(mdl) return op
def to_quadratic_program(self) -> QuadraticProgram: """Convert a clique problem instance into a :class:`~qiskit_optimization.problems.QuadraticProgram`. When "size" is None, this makes an optimization model for a maximal clique instead of the specified size of a clique. Returns: The :class:`~qiskit_optimization.problems.QuadraticProgram` created from the clique problem instance. """ complement_g = nx.complement(self._graph) mdl = Model(name='Clique') n = self._graph.number_of_nodes() x = {i: mdl.binary_var(name='x_{0}'.format(i)) for i in range(n)} for w, v in complement_g.edges: mdl.add_constraint(x[w] + x[v] <= 1) if self.size is None: mdl.maximize(mdl.sum(x[i] for i in x)) else: mdl.add_constraint(mdl.sum(x[i] for i in x) == self.size) op = QuadraticProgram() op.from_docplex(mdl) return op
def to_ising(quad_prog: QuadraticProgram) -> Tuple[OperatorBase, float]: """Return the Ising Hamiltonian of this problem. Variables are mapped to qubits in the same order, i.e., i-th variable is mapped to i-th qubit. See https://github.com/Qiskit/qiskit-terra/issues/1148 for details. Returns: qubit_op: The qubit operator for the problem offset: The constant value in the Ising Hamiltonian. Raises: QiskitOptimizationError: If an integer variable or a continuous variable exists in the problem. QiskitOptimizationError: If constraints exist in the problem. """ # if problem has variables that are not binary, raise an error if quad_prog.get_num_vars() > quad_prog.get_num_binary_vars(): raise QiskitOptimizationError( "The type of all variables must be binary. " "You can use `QuadraticProgramToQubo` converter " "to convert integer variables to binary variables. " "If the problem contains continuous variables, `to_ising` cannot handle it. " "You might be able to solve it with `ADMMOptimizer`.") # if constraints exist, raise an error if quad_prog.linear_constraints or quad_prog.quadratic_constraints: raise QiskitOptimizationError( "There must be no constraint in the problem. " "You can use `QuadraticProgramToQubo` converter " "to convert constraints to penalty terms of the objective function." ) # initialize Hamiltonian. num_nodes = quad_prog.get_num_vars() pauli_list = [] offset = 0.0 zero = np.zeros(num_nodes, dtype=bool) # set a sign corresponding to a maximized or minimized problem. # sign == 1 is for minimized problem. sign == -1 is for maximized problem. sense = quad_prog.objective.sense.value # convert a constant part of the object function into Hamiltonian. offset += quad_prog.objective.constant * sense # convert linear parts of the object function into Hamiltonian. for idx, coef in quad_prog.objective.linear.to_dict().items(): z_p = zero.copy() weight = coef * sense / 2 z_p[idx] = True pauli_list.append(PauliOp(Pauli((z_p, zero)), -weight)) offset += weight # create Pauli terms for (i, j), coeff in quad_prog.objective.quadratic.to_dict().items(): weight = coeff * sense / 4 if i == j: offset += weight else: z_p = zero.copy() z_p[i] = True z_p[j] = True pauli_list.append(PauliOp(Pauli((z_p, zero)), weight)) z_p = zero.copy() z_p[i] = True pauli_list.append(PauliOp(Pauli((z_p, zero)), -weight)) z_p = zero.copy() z_p[j] = True pauli_list.append(PauliOp(Pauli((z_p, zero)), -weight)) offset += weight # Remove paulis whose coefficients are zeros. qubit_op = sum(pauli_list) # qubit_op could be the integer 0, in this case return an identity operator of # appropriate size if isinstance(qubit_op, OperatorBase): qubit_op = qubit_op.reduce() else: qubit_op = I ^ num_nodes return qubit_op, offset
class _FromDocplexMp: _sense_dict = { ComparisonType.EQ: "==", ComparisonType.LE: "<=", ComparisonType.GE: ">=" } def __init__(self, model: Model): """ Args: model: Docplex model """ self._model: Model = model self._quadratic_program: QuadraticProgram = QuadraticProgram() self._var_names: Dict[Var, str] = {} self._var_bounds: Dict[str, Tuple[float, float]] = {} def _variables(self): # keep track of names separately, since docplex allows to have None names. for x in self._model.iter_variables(): if isinstance(x.vartype, ContinuousVarType): x_new = self._quadratic_program.continuous_var( x.lb, x.ub, x.name) elif isinstance(x.vartype, BinaryVarType): x_new = self._quadratic_program.binary_var(x.name) elif isinstance(x.vartype, IntegerVarType): x_new = self._quadratic_program.integer_var(x.lb, x.ub, x.name) else: raise QiskitOptimizationError( f"Unsupported variable type: {x.name} {x.vartype}") self._var_names[x] = x_new.name self._var_bounds[x.name] = (x_new.lowerbound, x_new.upperbound) def _linear_expr(self, expr: AbstractLinearExpr) -> Dict[str, float]: # AbstractLinearExpr is a parent of LinearExpr, ConstantExpr, and ZeroExpr linear = {} for x, coeff in expr.iter_terms(): linear[self._var_names[x]] = coeff return linear def _quadratic_expr( self, expr: QuadExpr ) -> Tuple[Dict[str, float], Dict[Tuple[str, str], float]]: linear = self._linear_expr(expr.get_linear_part()) quad = {} for x, y, coeff in expr.iter_quad_triplets(): i = self._var_names[x] j = self._var_names[y] quad[i, j] = coeff return linear, quad def quadratic_program( self, indicator_big_m: Optional[float]) -> QuadraticProgram: """Generate a quadratic program corresponding to the input Docplex model. Args: indicator_big_m: The big-M value used for the big-M formulation to convert indicator constraints into linear constraints. If ``None``, it is automatically derived from the model. Returns: a quadratic program corresponding to the input Docplex model. """ self._quadratic_program = QuadraticProgram(self._model.name) # prepare variables self._variables() # objective sense minimize = self._model.objective_sense.is_minimize() # make sure objective expression is linear or quadratic and not a variable if isinstance(self._model.objective_expr, Var): self._model.objective_expr = self._model.objective_expr + 0 # Var + 0 -> LinearExpr constant = self._model.objective_expr.constant if isinstance(self._model.objective_expr, QuadExpr): linear, quadratic = self._quadratic_expr( self._model.objective_expr) else: linear = self._linear_expr( self._model.objective_expr.get_linear_part()) quadratic = {} # set objective if minimize: self._quadratic_program.minimize(constant, linear, quadratic) else: self._quadratic_program.maximize(constant, linear, quadratic) # set linear constraints for constraint in self._model.iter_linear_constraints(): linear, sense, rhs = self._linear_constraint(constraint) if not linear: # lhs == 0 warn(f"Trivial constraint: {constraint}", stacklevel=3) self._quadratic_program.linear_constraint(linear, sense, rhs, constraint.name) # set quadratic constraints for constraint in self._model.iter_quadratic_constraints(): linear, quadratic, sense, rhs = self._quadratic_constraint( constraint) if not linear and not quadratic: # lhs == 0 warn(f"Trivial constraint: {constraint}", stacklevel=3) self._quadratic_program.quadratic_constraint( linear, quadratic, sense, rhs, constraint.name) # set indicator constraints for index, constraint in enumerate( self._model.iter_indicator_constraints()): linear, _, _ = self._linear_constraint( constraint.linear_constraint) if not linear: # lhs == 0 warn(f"Trivial constraint: {constraint}", stacklevel=3) prefix = constraint.name or f"ind{index}" linear_constraints = self._indicator_constraints( constraint, prefix, indicator_big_m) for linear, sense, rhs, name in linear_constraints: self._quadratic_program.linear_constraint( linear, sense, rhs, name) return self._quadratic_program @staticmethod def _subtract(dict1: Dict[Any, float], dict2: Dict[Any, float]) -> Dict[Any, float]: """Calculate dict1 - dict2""" ret = dict1.copy() for key, val2 in dict2.items(): if key in dict1: val1 = ret[key] if isclose(val1, val2): del ret[key] else: ret[key] -= val2 else: ret[key] = -val2 return ret def _linear_constraint( self, constraint: LinearConstraint ) -> Tuple[Dict[str, float], str, float]: left_expr = constraint.get_left_expr() right_expr = constraint.get_right_expr() # for linear constraints we may get an instance of Var instead of expression, # e.g. x + y = z if not isinstance(left_expr, (Expr, Var)): raise QiskitOptimizationError( f"Unsupported expression: {left_expr} {type(left_expr)}") if not isinstance(right_expr, (Expr, Var)): raise QiskitOptimizationError( f"Unsupported expression: {right_expr} {type(right_expr)}") if constraint.sense not in self._sense_dict: raise QiskitOptimizationError( f"Unsupported constraint sense: {constraint}") if isinstance(left_expr, Var): left_expr = left_expr + 0 # Var + 0 -> LinearExpr left_linear = self._linear_expr(left_expr) if isinstance(right_expr, Var): right_expr = right_expr + 0 right_linear = self._linear_expr(right_expr) linear = self._subtract(left_linear, right_linear) rhs = right_expr.constant - left_expr.constant return linear, self._sense_dict[constraint.sense], rhs def _quadratic_constraint( self, constraint: QuadraticConstraint ) -> Tuple[Dict[str, float], Dict[Tuple[str, str], float], str, float]: left_expr = constraint.get_left_expr() right_expr = constraint.get_right_expr() if not isinstance(left_expr, (Expr, Var)): raise QiskitOptimizationError( f"Unsupported expression: {left_expr} {type(left_expr)}") if not isinstance(right_expr, (Expr, Var)): raise QiskitOptimizationError( f"Unsupported expression: {right_expr} {type(right_expr)}") if constraint.sense not in self._sense_dict: raise QiskitOptimizationError( f"Unsupported constraint sense: {constraint}") if isinstance(left_expr, Var): left_expr = left_expr + 0 # Var + 0 -> LinearExpr if left_expr.is_quad_expr(): left_lin, left_quad = self._quadratic_expr(left_expr) else: left_lin = self._linear_expr(left_expr) left_quad = {} if isinstance(right_expr, Var): right_expr = right_expr + 0 if right_expr.is_quad_expr(): right_lin, right_quad = self._quadratic_expr(right_expr) else: right_lin = self._linear_expr(right_expr) right_quad = {} linear = self._subtract(left_lin, right_lin) quadratic = self._subtract(left_quad, right_quad) rhs = right_expr.constant - left_expr.constant return linear, quadratic, self._sense_dict[constraint.sense], rhs def _linear_bounds(self, linear: Dict[str, float]): linear_lb = 0.0 linear_ub = 0.0 for var_name, val in linear.items(): x_lb, x_ub = self._var_bounds[var_name] x_lb *= val x_ub *= val linear_lb += min(x_lb, x_ub) linear_ub += max(x_lb, x_ub) return linear_lb, linear_ub def _indicator_constraints( self, constraint: IndicatorConstraint, name: str, indicator_big_m: Optional[float] = None, ): binary_var = constraint.binary_var active_value = constraint.active_value linear_constraint = constraint.linear_constraint linear, sense, rhs = self._linear_constraint(linear_constraint) linear_lb, linear_ub = self._linear_bounds(linear) ret = [] if sense in ["<=", "=="]: big_m = max(0.0, linear_ub - rhs) if indicator_big_m is None else indicator_big_m if active_value: # rhs += big_m * (1 - binary_var) linear2 = self._subtract(linear, {binary_var.name: -big_m}) rhs2 = rhs + big_m else: # rhs += big_m * binary_var linear2 = self._subtract(linear, {binary_var.name: big_m}) rhs2 = rhs name2 = name + "_LE" if sense == "==" else name ret.append((linear2, "<=", rhs2, name2)) if sense in [">=", "=="]: big_m = max( 0.0, rhs - linear_lb) if indicator_big_m is None else indicator_big_m if active_value: # rhs += -big_m * (1 - binary_var) linear2 = self._subtract(linear, {binary_var.name: big_m}) rhs2 = rhs - big_m else: # rhs += -big_m * binary_var linear2 = self._subtract(linear, {binary_var.name: -big_m}) rhs2 = rhs name2 = name + "_GE" if sense == "==" else name ret.append((linear2, ">=", rhs2, name2)) if sense not in ["<=", ">=", "=="]: raise QiskitOptimizationError( f"Internal error: invalid sense of indicator constraint: {sense}" ) return ret
def from_gurobipy(model: Model) -> QuadraticProgram: """Translate a gurobipy model into a quadratic program. Note that this supports only basic functions of gurobipy as follows: - quadratic objective function - linear / quadratic constraints - binary / integer / continuous variables Args: model: The gurobipy model to be loaded. Returns: The quadratic program corresponding to the model. Raises: QiskitOptimizationError: if the model contains unsupported elements. MissingOptionalLibraryError: if gurobipy is not installed. """ _check_gurobipy_is_installed("from_gurobipy") if not isinstance(model, Model): raise QiskitOptimizationError(f"The model is not compatible: {model}") quadratic_program = QuadraticProgram() # Update the model to make sure everything works as expected model.update() # get name quadratic_program.name = model.ModelName # get variables # keep track of names separately, since gurobipy allows to have None names. var_names = {} for x in model.getVars(): if x.vtype == gp.GRB.CONTINUOUS: x_new = quadratic_program.continuous_var(x.lb, x.ub, x.VarName) elif x.vtype == gp.GRB.BINARY: x_new = quadratic_program.binary_var(x.VarName) elif x.vtype == gp.GRB.INTEGER: x_new = quadratic_program.integer_var(x.lb, x.ub, x.VarName) else: raise QiskitOptimizationError( f"Unsupported variable type: {x.VarName} {x.vtype}") var_names[x] = x_new.name # objective sense minimize = model.ModelSense == gp.GRB.MINIMIZE # Retrieve the objective objective = model.getObjective() has_quadratic_objective = False # Retrieve the linear part in case it is a quadratic objective if isinstance(objective, gp.QuadExpr): linear_part = objective.getLinExpr() has_quadratic_objective = True else: linear_part = objective # Get the constant constant = linear_part.getConstant() # get linear part of objective linear = {} for i in range(linear_part.size()): linear[var_names[linear_part.getVar(i)]] = linear_part.getCoeff(i) # get quadratic part of objective quadratic = {} if has_quadratic_objective: for i in range(objective.size()): x = var_names[objective.getVar1(i)] y = var_names[objective.getVar2(i)] v = objective.getCoeff(i) quadratic[x, y] = v # set objective if minimize: quadratic_program.minimize(constant, linear, quadratic) else: quadratic_program.maximize(constant, linear, quadratic) # check whether there are any general constraints if model.NumSOS > 0 or model.NumGenConstrs > 0: raise QiskitOptimizationError( "Unsupported constraint: SOS or General Constraint") # get linear constraints for constraint in model.getConstrs(): name = constraint.ConstrName sense = constraint.Sense left_expr = model.getRow(constraint) rhs = constraint.RHS lhs = {} for i in range(left_expr.size()): lhs[var_names[left_expr.getVar(i)]] = left_expr.getCoeff(i) if sense == gp.GRB.EQUAL: quadratic_program.linear_constraint(lhs, "==", rhs, name) elif sense == gp.GRB.GREATER_EQUAL: quadratic_program.linear_constraint(lhs, ">=", rhs, name) elif sense == gp.GRB.LESS_EQUAL: quadratic_program.linear_constraint(lhs, "<=", rhs, name) else: raise QiskitOptimizationError( f"Unsupported constraint sense: {constraint}") # get quadratic constraints for constraint in model.getQConstrs(): name = constraint.QCName sense = constraint.QCSense left_expr = model.getQCRow(constraint) rhs = constraint.QCRHS linear = {} quadratic = {} linear_part = left_expr.getLinExpr() for i in range(linear_part.size()): linear[var_names[linear_part.getVar(i)]] = linear_part.getCoeff(i) for i in range(left_expr.size()): x = var_names[left_expr.getVar1(i)] y = var_names[left_expr.getVar2(i)] v = left_expr.getCoeff(i) quadratic[x, y] = v if sense == gp.GRB.EQUAL: quadratic_program.quadratic_constraint(linear, quadratic, "==", rhs, name) elif sense == gp.GRB.GREATER_EQUAL: quadratic_program.quadratic_constraint(linear, quadratic, ">=", rhs, name) elif sense == gp.GRB.LESS_EQUAL: quadratic_program.quadratic_constraint(linear, quadratic, "<=", rhs, name) else: raise QiskitOptimizationError( f"Unsupported constraint sense: {constraint}") return quadratic_program
def quadratic_program( self, indicator_big_m: Optional[float]) -> QuadraticProgram: """Generate a quadratic program corresponding to the input Docplex model. Args: indicator_big_m: The big-M value used for the big-M formulation to convert indicator constraints into linear constraints. If ``None``, it is automatically derived from the model. Returns: a quadratic program corresponding to the input Docplex model. """ self._quadratic_program = QuadraticProgram(self._model.name) # prepare variables self._variables() # objective sense minimize = self._model.objective_sense.is_minimize() # make sure objective expression is linear or quadratic and not a variable if isinstance(self._model.objective_expr, Var): self._model.objective_expr = self._model.objective_expr + 0 # Var + 0 -> LinearExpr constant = self._model.objective_expr.constant if isinstance(self._model.objective_expr, QuadExpr): linear, quadratic = self._quadratic_expr( self._model.objective_expr) else: linear = self._linear_expr( self._model.objective_expr.get_linear_part()) quadratic = {} # set objective if minimize: self._quadratic_program.minimize(constant, linear, quadratic) else: self._quadratic_program.maximize(constant, linear, quadratic) # set linear constraints for constraint in self._model.iter_linear_constraints(): linear, sense, rhs = self._linear_constraint(constraint) if not linear: # lhs == 0 warn(f"Trivial constraint: {constraint}", stacklevel=3) self._quadratic_program.linear_constraint(linear, sense, rhs, constraint.name) # set quadratic constraints for constraint in self._model.iter_quadratic_constraints(): linear, quadratic, sense, rhs = self._quadratic_constraint( constraint) if not linear and not quadratic: # lhs == 0 warn(f"Trivial constraint: {constraint}", stacklevel=3) self._quadratic_program.quadratic_constraint( linear, quadratic, sense, rhs, constraint.name) # set indicator constraints for index, constraint in enumerate( self._model.iter_indicator_constraints()): linear, _, _ = self._linear_constraint( constraint.linear_constraint) if not linear: # lhs == 0 warn(f"Trivial constraint: {constraint}", stacklevel=3) prefix = constraint.name or f"ind{index}" linear_constraints = self._indicator_constraints( constraint, prefix, indicator_big_m) for linear, sense, rhs, name in linear_constraints: self._quadratic_program.linear_constraint( linear, sense, rhs, name) return self._quadratic_program
def from_docplex_mp( model: Model, indicator_big_m: Optional[float] = None) -> QuadraticProgram: """Translate a docplex.mp model into a quadratic program. Note that this supports only basic functions of docplex as follows: - quadratic objective function - linear / quadratic / indicator constraints - binary / integer / continuous variables Args: model: The docplex.mp model to be loaded. indicator_big_m: The big-M value used for the big-M formulation to convert indicator constraints into linear constraints. If ``None``, it is automatically derived from the model. Returns: The quadratic program corresponding to the model. Raises: QiskitOptimizationError: if the model contains unsupported elements. """ if not isinstance(model, Model): raise QiskitOptimizationError(f"The model is not compatible: {model}") if model.number_of_user_cut_constraints > 0: raise QiskitOptimizationError("User cut constraints are not supported") if model.number_of_lazy_constraints > 0: raise QiskitOptimizationError("Lazy constraints are not supported") if model.number_of_sos > 0: raise QiskitOptimizationError("SOS sets are not supported") # get name quadratic_program = QuadraticProgram(model.name) # get variables # keep track of names separately, since docplex allows to have None names. var_names = {} var_bounds = {} for x in model.iter_variables(): if isinstance(x.vartype, ContinuousVarType): x_new = quadratic_program.continuous_var(x.lb, x.ub, x.name) elif isinstance(x.vartype, BinaryVarType): x_new = quadratic_program.binary_var(x.name) elif isinstance(x.vartype, IntegerVarType): x_new = quadratic_program.integer_var(x.lb, x.ub, x.name) else: raise QiskitOptimizationError( f"Unsupported variable type: {x.name} {x.vartype}") var_names[x] = x_new.name var_bounds[x.name] = (x_new.lowerbound, x_new.upperbound) # 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 = {} linear_part = model.objective_expr.get_linear_part() for x in linear_part.iter_variables(): linear[var_names[x]] = linear_part.get_coef(x) # get quadratic part of objective quadratic = {} if isinstance(model.objective_expr, QuadExpr): for quad_triplet in model.objective_expr.iter_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: quadratic_program.minimize(constant, linear, quadratic) else: quadratic_program.maximize(constant, linear, quadratic) # check constraint type for constraint in model.iter_constraints(): # If any constraint is not linear/quadratic/indicator constraints, it raises an error. if isinstance(constraint, LinearConstraint): if isinstance(constraint, NotEqualConstraint): # Notice that NotEqualConstraint is a subclass of Docplex's LinearConstraint, # but it cannot be handled by optimization. raise QiskitOptimizationError( f"Unsupported constraint: {constraint}") elif not isinstance(constraint, (QuadraticConstraint, IndicatorConstraint)): raise QiskitOptimizationError( f"Unsupported constraint: {constraint}") # get linear constraints for constraint in model.iter_linear_constraints(): lhs, sense, rhs = _FromDocplexMp._linear_constraint( var_names, constraint) quadratic_program.linear_constraint(lhs, sense, rhs, constraint.name) # get quadratic constraints for constraint in model.iter_quadratic_constraints(): linear, quadratic, sense, rhs = _FromDocplexMp._quadratic_constraint( var_names, constraint) quadratic_program.quadratic_constraint(linear, quadratic, sense, rhs, constraint.name) # get indicator constraints for constraint in model.iter_indicator_constraints(): linear_constraints = _FromDocplexMp._indicator_constraints( var_names, var_bounds, constraint, indicator_big_m) for linear, sense, rhs, name in linear_constraints: quadratic_program.linear_constraint(linear, sense, rhs, name) return quadratic_program
def from_ising( qubit_op: OperatorBase, offset: float = 0.0, linear: bool = False, ) -> QuadraticProgram: r"""Create a quadratic program from a qubit operator and a shift value. Variables are mapped to qubits in the same order, i.e., i-th variable is mapped to i-th qubit. See https://github.com/Qiskit/qiskit-terra/issues/1148 for details. Args: qubit_op: The qubit operator of the problem. offset: The constant term in the Ising Hamiltonian. linear: If linear is True, :math:`x^2` is treated as a linear term since :math:`x^2 = x` for :math:`x \in \{0,1\}`. Otherwise, :math:`x^2` is treat as a quadratic term. The default value is False. Returns: The quadratic program corresponding to the qubit operator. Raises: QiskitOptimizationError: if there are Pauli Xs or Ys in any Pauli term QiskitOptimizationError: if there are more than 2 Pauli Zs in any Pauli term QiskitOptimizationError: if any Pauli term has an imaginary coefficient NotImplementedError: If the input operator is a ListOp """ if isinstance(qubit_op, PauliSumOp): qubit_op = qubit_op.to_pauli_op() # No support for ListOp yet, this can be added in future # pylint: disable=unidiomatic-typecheck if type(qubit_op) == ListOp: raise NotImplementedError( "Conversion of a ListOp is not supported, convert each " "operator in the ListOp separately.") quad_prog = QuadraticProgram() quad_prog.binary_var_list(qubit_op.num_qubits) if not isinstance(qubit_op, SummedOp): pauli_list = [qubit_op.to_pauli_op()] else: pauli_list = qubit_op.to_pauli_op() # prepare a matrix of coefficients of Pauli terms # `pauli_coeffs_diag` is the diagonal part # `pauli_coeffs_triu` is the upper triangular part pauli_coeffs_diag = [0.0] * qubit_op.num_qubits pauli_coeffs_triu = {} for pauli_op in pauli_list: pauli_op = pauli_op.to_pauli_op() pauli = pauli_op.primitive coeff = pauli_op.coeff if not math.isclose(coeff.imag, 0.0, abs_tol=1e-10): raise QiskitOptimizationError( f"Imaginary coefficient exists: {pauli_op}") if np.any(pauli.x): raise QiskitOptimizationError( f"Pauli X or Y exists in the Pauli term: {pauli}") # indices of Pauli Zs in the Pauli term z_index = np.where(pauli.z)[0] num_z = len(z_index) if num_z == 1: pauli_coeffs_diag[z_index[0]] = coeff.real elif num_z == 2: pauli_coeffs_triu[z_index[0], z_index[1]] = coeff.real else: raise QiskitOptimizationError( f"There are more than 2 Pauli Zs in the Pauli term: {pauli}") linear_terms = {} quadratic_terms = {} # For quadratic pauli terms of operator # x_i * x_j = (1 - Z_i - Z_j + Z_i * Z_j)/4 for (i, j), weight in pauli_coeffs_triu.items(): # Add a quadratic term to the object function of `QuadraticProgram` # The coefficient of the quadratic term in `QuadraticProgram` is # 4 * weight of the pauli quadratic_terms[i, j] = 4 * weight pauli_coeffs_diag[i] += weight pauli_coeffs_diag[j] += weight offset -= weight # After processing quadratic pauli terms, only linear paulis are left # x_i = (1 - Z_i)/2 for i, weight in enumerate(pauli_coeffs_diag): # Add a linear term to the object function of `QuadraticProgram` # The coefficient of the linear term in `QuadraticProgram` is # 2 * weight of the pauli if linear: linear_terms[i] = -2 * weight else: quadratic_terms[i, i] = -2 * weight offset += weight quad_prog.minimize(constant=offset, linear=linear_terms, quadratic=quadratic_terms) return quad_prog