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_constraint( cls, var_names: Dict[Var, str], 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, (AbstractLinearExpr, Var)): raise QiskitOptimizationError( f"Unsupported expression: {left_expr} {type(left_expr)}") if not isinstance(right_expr, (AbstractLinearExpr, Var)): raise QiskitOptimizationError( f"Unsupported expression: {right_expr} {type(right_expr)}") if isinstance(left_expr, Var): left_expr = left_expr + 0 if isinstance(right_expr, Var): right_expr = right_expr + 0 linear = {} for x in left_expr.iter_variables(): linear[var_names[x]] = left_expr.get_coef(x) for x in right_expr.iter_variables(): linear[var_names[x]] = linear.get(var_names[x], 0.0) - right_expr.get_coef(x) rhs = right_expr.constant - left_expr.constant if constraint.sense not in cls._sense_dict: raise QiskitOptimizationError( f"Unsupported constraint sense: {constraint}") return linear, cls._sense_dict[constraint.sense], rhs
def parse_tsplib_format(filename: str) -> "Tsp": """Read a graph in TSPLIB format from file and return a Tsp instance. Args: filename: the name of the file. Raises: QiskitOptimizationError: If the type is not "TSP" QiskitOptimizationError: If the edge weight type is not "EUC_2D" Returns: A Tsp instance data. """ name = "" coord = [] # type: ignore 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": raise QiskitOptimizationError( 'This supports only "TSP" type. Actual: {}'.format(typ) ) elif line.startswith("DIMENSION"): dim = int(line.split(":")[1]) coord = np.zeros((dim, 2)) # type: ignore elif line.startswith("EDGE_WEIGHT_TYPE"): typ = line.split(":")[1] typ.strip() if typ != "EUC_2D": raise QiskitOptimizationError( 'This supports only "EUC_2D" edge weight. Actual: {}'.format(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]) x_max = max(coord_[0] for coord_ in coord) x_min = min(coord_[0] for coord_ in coord) y_max = max(coord_[1] for coord_ in coord) y_min = min(coord_[1] for coord_ in coord) graph = nx.random_geometric_graph( len(coord), np.hypot(x_max - x_min, y_max - y_min) + 1, pos=coord ) for w, v in graph.edges: delta = [graph.nodes[w]["pos"][i] - graph.nodes[v]["pos"][i] for i in range(2)] graph.edges[w, v]["weight"] = np.rint(np.hypot(delta[0], delta[1])) return Tsp(graph)
def _quadratic_constraint( cls, var_names: Dict[Var, str], 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, (QuadExpr, AbstractLinearExpr, Var)): raise QiskitOptimizationError( f"Unsupported expression: {left_expr} {type(left_expr)}") if not isinstance(right_expr, (QuadExpr, AbstractLinearExpr, Var)): raise QiskitOptimizationError( f"Unsupported expression: {right_expr} {type(right_expr)}") lin = {} quad = {} if left_expr.is_quad_expr(): for x in left_expr.linear_part.iter_variables(): lin[var_names[x]] = left_expr.linear_part.get_coef(x) for quad_triplet in left_expr.iter_quad_triplets(): i = var_names[quad_triplet[0]] j = var_names[quad_triplet[1]] v = quad_triplet[2] quad[i, j] = v else: for x in left_expr.iter_variables(): lin[var_names[x]] = left_expr.get_coef(x) if right_expr.is_quad_expr(): for x in right_expr.linear_part.iter_variables(): lin[var_names[x]] = lin.get( var_names[x], 0.0) - 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]] v = quad_triplet[2] quad[i, j] = quad.get((i, j), 0.0) - v else: for x in right_expr.iter_variables(): lin[var_names[x]] = lin.get(var_names[x], 0.0) - right_expr.get_coef(x) rhs = right_expr.constant - left_expr.constant if constraint.sense not in cls._sense_dict: raise QiskitOptimizationError( f"Unsupported constraint sense: {constraint}") return lin, quad, cls._sense_dict[constraint.sense], rhs
def optimization_level(self, optimization_level: Optional[int] = None): """Set the optimization level.""" if optimization_level is not None and self.use_swap_strategies: raise QiskitOptimizationError( "optimization_level cannot be set if use_swap_strategies is True." ) self._optimization_level = optimization_level
def _indicator_constraints( cls, var_names: Dict[Var, str], var_bounds: Dict[str, Tuple[float, float]], constraint: IndicatorConstraint, indicator_big_m: Optional[float] = None, ): name = constraint.name binary_var = constraint.binary_var active_value = constraint.active_value linear_constraint = constraint.linear_constraint linear, sense, rhs = cls._linear_constraint(var_names, linear_constraint) linear_lb, linear_ub = cls._linear_bounds(var_bounds, linear) if sense == "<=": big_m = max(0.0, linear_ub - rhs) if indicator_big_m is None else indicator_big_m if active_value: linear[binary_var.name] = big_m rhs += big_m else: linear[binary_var.name] = -big_m return [(linear, sense, rhs, name)] elif sense == ">=": big_m = max( 0.0, rhs - linear_lb) if indicator_big_m is None else indicator_big_m if active_value: linear[binary_var.name] = -big_m rhs -= big_m else: linear[binary_var.name] = big_m return [(linear, sense, rhs, name)] elif sense == "==": # for equality constraints, add both GE and LE constraints. # linear2, rhs2, and big_m2 are for the GE constraint. linear2 = linear.copy() rhs2 = rhs big_m = max(0.0, linear_ub - rhs) if indicator_big_m is None else indicator_big_m big_m2 = max( 0.0, rhs - linear_lb) if indicator_big_m is None else indicator_big_m if active_value: linear[binary_var.name] = big_m rhs += big_m linear2[binary_var.name] = -big_m2 rhs2 -= big_m2 else: linear[binary_var.name] = -big_m linear2[binary_var.name] = big_m2 return [(linear, "<=", rhs, name + "_LE"), (linear2, ">=", rhs2, name + "_GE")] else: raise QiskitOptimizationError( f"Internal error: invalid sense of indicator constraint: {sense}" )
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 _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_docplex_mp( model: Model, indicator_big_m: Optional[float] = None) -> QuadraticProgram: """Translate a docplex.mp model into a quadratic program. Note that this supports the following features of docplex: - linear / quadratic objective function - linear / quadratic / indicator constraints - binary / integer / continuous variables - logical expressions (``logical_not``, ``logical_and``, and ``logical_or``) 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") # 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}") return _FromDocplexMp(model).quadratic_program(indicator_big_m)
def __init__( self, optimizer: Optional[Union[Optimizer, Dict[str, Any]]] = None, reps: int = 1, initial_state: Optional[QuantumCircuit] = None, mixer: Union[QuantumCircuit, OperatorBase] = None, initial_point: Optional[np.ndarray] = None, alpha: float = 1.0, provider: Optional[Provider] = None, backend: Optional[Backend] = None, shots: int = 1024, measurement_error_mitigation: bool = False, callback: Optional[Callable[[int, np.ndarray, float, float], None]] = None, store_intermediate: bool = False, use_swap_strategies: bool = False, use_initial_mapping: bool = False, use_pulse_efficient: bool = False, optimization_level: Optional[int] = None, ) -> None: """ Args: optimizer: An optimizer or dictionary specifying a classical optimizer. If a dictionary, only SPSA and QN-SPSA are supported. The dictionary must contain a key ``name`` for the name of the optimizer and may contain additional keys for the settings. E.g. ``{'name': 'SPSA', 'maxiter': 100}``. Per default, SPSA is used. reps: the integer parameter :math:`p` as specified in https://arxiv.org/abs/1411.4028, Has a minimum valid value of 1. initial_state: An optional initial state to prepend the QAOA circuit with mixer: the mixer Hamiltonian to evolve with or a custom quantum circuit. Allows support of optimizations in constrained subspaces as per https://arxiv.org/abs/1709.03489 as well as warm-starting the optimization as introduced in http://arxiv.org/abs/2009.10095. initial_point: An optional initial point (i.e. initial parameter values) for the optimizer. If ``None`` a random vector is chosen in the :class:`VQE` class in Qiskit terra using a uniform distribution. alpha: The fraction of top measurement samples to be used for the expectation value (CVaR expectation). Defaults to 1, i.e. using all samples to construct the expectation value. This value must be contained in the interval [0, 1]. provider: The provider. backend: The backend to run the circuits on. shots: The number of shots to be used measurement_error_mitigation: Whether or not to use measurement error mitigation. callback: a callback that can access the intermediate data during the optimization. Four parameter values are passed to the callback as follows during each evaluation by the optimizer for its current set of parameters as it works towards the minimum. These are: the evaluation count, the optimizer parameters for the ansatz, the evaluated mean and the evaluated standard deviation. store_intermediate: Whether or not to store intermediate values of the optimization steps. Per default False. use_swap_strategies: A boolean on whether or not to use swap strategies when transpiling. If this is False then the standard transpiler with the given optimization level will run. use_initial_mapping: A boolean flag that, if set to True (the default is False), runs a heuristic algorithm to permute the Paulis in the cost operator to better fit the coupling map and the swap strategy. This is only needed when the optimization problem is sparse and when using swap strategies to transpile. use_pulse_efficient: A boolean on whether or not to use a pulse-efficient transpilation. If this flag is set to False by default. See https://arxiv.org/abs/2105.01063. optimization_level: The transpiler optimization level to run if the swap strategies are not used. This value defaults to 1 in the QAOA runtime. Raises: QiskitOptimizationError: if reps is smaller than 1. QiskitOptimizationError: if alpha is not in the interval [0, 1]. QiskitOptimizationError: if optimization_level is not None and use_swap_strategies is True. """ if reps < 1: raise QiskitOptimizationError(f"reps must be greater than 0, received {reps}.") if alpha < 0 or alpha > 1: raise QiskitOptimizationError(f"alpha must range from 0 to 1. Received {alpha}.") super().__init__( ansatz=None, optimizer=optimizer, initial_point=initial_point, provider=provider, backend=backend, shots=shots, measurement_error_mitigation=measurement_error_mitigation, callback=callback, store_intermediate=store_intermediate, ) self._initial_state = initial_state self._mixer = mixer self._reps = reps self._use_swap_strategies = use_swap_strategies self._use_initial_mapping = use_initial_mapping self._use_pulse_efficient = use_pulse_efficient self._alpha = alpha self._program_id = "qaoa" # Use the setter to check consistency with other settings. self.optimization_level = optimization_level
def ansatz(self, ansatz: QuantumCircuit) -> None: raise QiskitOptimizationError( "Cannot set the ansatz for QAOA, it is directly inferred from " "the problem Hamiltonian." )
def to_gurobipy(quadratic_program: QuadraticProgram) -> Model: """Returns a gurobipy model corresponding to a quadratic program. Args: quadratic_program: The quadratic program to be translated. Returns: The gurobipy model corresponding to a quadratic program. Raises: QiskitOptimizationError: if non-supported elements (should never happen). MissingOptionalLibraryError: if gurobipy is not installed. """ _check_gurobipy_is_installed("to_gurobipy") # initialize model mdl = gp.Model(quadratic_program.name) # add variables var = {} for idx, x in enumerate(quadratic_program.variables): if x.vartype == Variable.Type.CONTINUOUS: var[idx] = mdl.addVar(vtype=gp.GRB.CONTINUOUS, lb=x.lowerbound, ub=x.upperbound, name=x.name) elif x.vartype == Variable.Type.BINARY: var[idx] = mdl.addVar(vtype=gp.GRB.BINARY, name=x.name) elif x.vartype == Variable.Type.INTEGER: var[idx] = mdl.addVar(vtype=gp.GRB.INTEGER, lb=x.lowerbound, ub=x.upperbound, name=x.name) else: # should never happen raise QiskitOptimizationError( f"Unsupported variable type: {x.vartype}") # add objective objective = quadratic_program.objective.constant for i, v in quadratic_program.objective.linear.to_dict().items(): objective += v * var[cast(int, i)] for (i, j), v in quadratic_program.objective.quadratic.to_dict().items(): objective += v * var[cast(int, i)] * var[cast(int, j)] if quadratic_program.objective.sense == QuadraticObjective.Sense.MINIMIZE: mdl.setObjective(objective, sense=gp.GRB.MINIMIZE) else: mdl.setObjective(objective, sense=gp.GRB.MAXIMIZE) # add linear constraints for i, l_constraint in enumerate(quadratic_program.linear_constraints): name = l_constraint.name rhs = l_constraint.rhs if rhs == 0 and l_constraint.linear.coefficients.nnz == 0: continue linear_expr = 0 for j, v in l_constraint.linear.to_dict().items(): linear_expr += v * var[cast(int, j)] sense = l_constraint.sense if sense == Constraint.Sense.EQ: mdl.addConstr(linear_expr == rhs, name=name) elif sense == Constraint.Sense.GE: mdl.addConstr(linear_expr >= rhs, name=name) elif sense == Constraint.Sense.LE: mdl.addConstr(linear_expr <= rhs, name=name) else: # should never happen raise QiskitOptimizationError( f"Unsupported constraint sense: {sense}") # add quadratic constraints for i, q_constraint in enumerate(quadratic_program.quadratic_constraints): name = q_constraint.name rhs = q_constraint.rhs if (rhs == 0 and q_constraint.linear.coefficients.nnz == 0 and q_constraint.quadratic.coefficients.nnz == 0): continue quadratic_expr = 0 for j, v in q_constraint.linear.to_dict().items(): quadratic_expr += v * var[cast(int, j)] for (j, k), v in q_constraint.quadratic.to_dict().items(): quadratic_expr += v * var[cast(int, j)] * var[cast(int, k)] sense = q_constraint.sense if sense == Constraint.Sense.EQ: mdl.addConstr(quadratic_expr == rhs, name=name) elif sense == Constraint.Sense.GE: mdl.addConstr(quadratic_expr >= rhs, name=name) elif sense == Constraint.Sense.LE: mdl.addConstr(quadratic_expr <= rhs, name=name) else: # should never happen raise QiskitOptimizationError( f"Unsupported constraint sense: {sense}") mdl.update() return mdl
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 to_docplex_mp(quadratic_program: QuadraticProgram) -> Model: """Returns a docplex.mp model corresponding to a quadratic program. Args: quadratic_program: The quadratic program to be translated. Returns: The docplex.mp model corresponding to a quadratic program. Raises: QiskitOptimizationError: if the model contains non-supported elements (should never happen). """ # initialize model mdl = Model(quadratic_program.name) # add variables var = {} for idx, x in enumerate(quadratic_program.variables): if x.vartype == Variable.Type.CONTINUOUS: var[idx] = mdl.continuous_var(lb=x.lowerbound, ub=x.upperbound, name=x.name) elif x.vartype == Variable.Type.BINARY: var[idx] = mdl.binary_var(name=x.name) elif x.vartype == Variable.Type.INTEGER: var[idx] = mdl.integer_var(lb=x.lowerbound, ub=x.upperbound, name=x.name) else: # should never happen raise QiskitOptimizationError( f"Internal error: unsupported variable type: {x.vartype}") # add objective objective = quadratic_program.objective.constant for i, v in quadratic_program.objective.linear.to_dict().items(): objective += v * var[cast(int, i)] for (i, j), v in quadratic_program.objective.quadratic.to_dict().items(): objective += v * var[cast(int, i)] * var[cast(int, j)] if quadratic_program.objective.sense == QuadraticObjective.Sense.MINIMIZE: mdl.minimize(objective) else: mdl.maximize(objective) # add linear constraints for i, l_constraint in enumerate(quadratic_program.linear_constraints): name = l_constraint.name rhs = l_constraint.rhs if rhs == 0 and l_constraint.linear.coefficients.nnz == 0: continue linear_expr = 0 for j, v in l_constraint.linear.to_dict().items(): linear_expr += v * var[cast(int, j)] sense = l_constraint.sense if sense == Constraint.Sense.EQ: mdl.add_constraint(linear_expr == rhs, ctname=name) elif sense == Constraint.Sense.GE: mdl.add_constraint(linear_expr >= rhs, ctname=name) elif sense == Constraint.Sense.LE: mdl.add_constraint(linear_expr <= rhs, ctname=name) else: # should never happen raise QiskitOptimizationError( f"Internal error: unsupported constraint sense: {sense}") # add quadratic constraints for i, q_constraint in enumerate(quadratic_program.quadratic_constraints): name = q_constraint.name rhs = q_constraint.rhs if (rhs == 0 and q_constraint.linear.coefficients.nnz == 0 and q_constraint.quadratic.coefficients.nnz == 0): continue quadratic_expr = 0 for j, v in q_constraint.linear.to_dict().items(): quadratic_expr += v * var[cast(int, j)] for (j, k), v in q_constraint.quadratic.to_dict().items(): quadratic_expr += v * var[cast(int, j)] * var[cast(int, k)] sense = q_constraint.sense if sense == Constraint.Sense.EQ: mdl.add_constraint(quadratic_expr == rhs, ctname=name) elif sense == Constraint.Sense.GE: mdl.add_constraint(quadratic_expr >= rhs, ctname=name) elif sense == Constraint.Sense.LE: mdl.add_constraint(quadratic_expr <= rhs, ctname=name) else: # should never happen raise QiskitOptimizationError( f"Internal error: unsupported constraint sense: {sense}") return mdl
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
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