def objective(self) -> Union[BinaryQuadraticModel, QuadraticModel]: """The objective to be minimized.""" try: return self._objective except AttributeError: pass objective = BinaryQuadraticModel('BINARY') self._objective: Union[BinaryQuadraticModel, QuadraticModel] = objective return objective
def add_discrete(self, variables: Collection[Variable], label: Optional[Hashable] = None) -> Hashable: """Add a iterable of binary variables as a disjoint one-hot constraint. Adds a special kind of one-hot constraint. These one-hot constraints must be disjoint, that is they must not have any overlapping variables. Args: variables: An iterable of variables. Raises: ValueError: If any of the given variables have already been added to the model with any vartype other than `BINARY`. ValueError: If any of the given variables are already used in another discrete variable. """ if label is not None and label in self.constraints: raise ValueError("a constraint with that label already exists") for v in variables: if v in self._discrete: # todo: language around discrete variables? raise ValueError( f"variable {v!r} is already used in a discrete variable") elif v in self.variables and self.vartype(v) != Vartype.BINARY: raise ValueError( f"variable {v!r} has already been added but is not BINARY") # we can! So add them for v in variables: self.add_variable(v, Vartype.BINARY) self._discrete.update(variables) bqm = BinaryQuadraticModel('BINARY', dtype=np.float32) bqm.add_variables_from((v, 1) for v in variables) label = self.add_constraint(bqm == 1, label=label) self.discrete.add(label) return label
def binary_encoding(v: Variable, upper_bound: int) -> BinaryQuadraticModel: """Return a binary quadratic model encoding an integer. Args: v: The integer variable label. upper_bound: The upper bound on the integer value (inclusive). Returns: A binary quadratic model. The variables in the BQM will be labelled with tuples of length two or three. The first value of the tuple will be the variable label ``v`` provided. The second value will be the coefficient in the integer encoding. One of the variables will have a third value in the tuple, ``'msb'``. This is the variable occupying the position of the most significant bit. Though it may actually be a smaller number in order to enforce the ``upper_bound``. Example: >>> bqm = dimod.generators.binary_encoding('i', 6) >>> bqm BinaryQuadraticModel({('i', 1): 1.0, ('i', 2): 2.0, ('i', 3, 'msb'): 3.0}, {}, 0.0, 'BINARY') We can use a sample to restore the original integer value. >>> sample = {('i', 1): 1, ('i', 2): 0, ('i', 3, 'msb'): 1} >>> bqm.energy(sample) 4.0 >>> sum(v[1]*val for v, val in sample.items()) + bqm.offset 4.0 If you wish to encode integers with a lower bound, you can use the binary quadratic model's :attr:`~BinaryQuadraticModel.offset` attribute. >>> i = dimod.generators.binary_encoding('i', 10) + 5 # integer in [5, 15] References: [1]: Sahar Karimi, Pooya Ronagh (2017), Practical Integer-to-Binary Mapping for Quantum Annealers. arxiv.org:1706.01945. """ # note: the paper above also gives a nice way to handle bounded coefficients # if we want to do that in the future. if upper_bound <= 1: raise ValueError("upper_bound must be greater than or equal to 1, " f"received {upper_bound}") upper_bound = math.floor(upper_bound) bqm = BinaryQuadraticModel(Vartype.BINARY) max_pow = math.floor(math.log2(upper_bound)) for exp in range(max_pow): val = 1 << exp bqm.set_linear((v, val), val) else: val = upper_bound - ((1 << max_pow) - 1) bqm.set_linear((v, val, 'msb'), val) return bqm
def independent_set( edges: Iterable[Tuple[Variable, Variable]], nodes: Optional[Iterable[Variable]] = None, ) -> BinaryQuadraticModel: """Return a binary quadratic model encoding an independent set problem. Given a graph `G`, an independent set is a set of nodes such that the subgraph of `G` induced by these nodes contains no edges. Args: edges: The edges of the graph as an iterable of two-tuples. nodes: The nodes of the graph as an iterable. Returns: A binary quadratic model. The binary quadratic model will have variables and interactions corresponding to ``nodes`` and ``edges``. Each interaction will have a quadratic bias of exactly ``1`` and each node will have a linear bias of ``0``. Examples: >>> from dimod.generators import independent_set Get an independent set binary quadratic model from a list of edges. >>> independent_set([(0, 1), (1, 2)]) BinaryQuadraticModel({0: 0.0, 1: 0.0, 2: 0.0}, {(1, 0): 1.0, (2, 1): 1.0}, 0.0, 'BINARY') Get an independent set binary quadratic model from a list of edges and nodes. >>> independent_set([(0, 1)], [0, 1, 2]) BinaryQuadraticModel({0: 0.0, 1: 0.0, 2: 0.0}, {(1, 0): 1.0}, 0.0, 'BINARY') Get an independent set binary quadratic model from a :class:`networkx.Graph`. >>> import networkx as nx >>> G = nx.complete_graph(2) >>> independent_set(G.edges, G.nodes) BinaryQuadraticModel({0: 0.0, 1: 0.0}, {(1, 0): 1.0}, 0.0, 'BINARY') """ bqm = BinaryQuadraticModel(vartype=Vartype.BINARY) bqm.add_quadratic_from((u, v, 1) for u, v in edges) if nodes is not None: bqm.add_linear_from((v, 0) for v in nodes) return bqm
def fulladder_gate(in0: Variable, in1: Variable, in2: Variable, sum_: Variable, carry: Variable, *, strength: float = 1.0) -> BinaryQuadraticModel: """Return a binary quadratic model with ground states corresponding to a full adder gate. Args: in0: The variable label for one of the inputs. in1: The variable label for one of the inputs. in2: The variable label for one of the inputs sum_: The variable label for the sum output. carry: The variable label for the carry output. strength: The energy of the lowest-energy infeasible state. Returns: A binary quadratic model with ground states corresponding to a full adder gate. The model has five variables and ten interactions. """ bqm = BinaryQuadraticModel(Vartype.BINARY) # add the variables (in order) bqm.add_variable(in0, bias=1) bqm.add_variable(in1, bias=1) bqm.add_variable(in2, bias=1) bqm.add_variable(sum_, bias=1) bqm.add_variable(carry, bias=4) # add the quadratic biases bqm.add_quadratic(in0, in1, 2) bqm.add_quadratic(in0, in2, 2) bqm.add_quadratic(in0, sum_, -2) bqm.add_quadratic(in0, carry, -4) bqm.add_quadratic(in1, in2, 2) bqm.add_quadratic(in1, sum_, -2) bqm.add_quadratic(in1, carry, -4) bqm.add_quadratic(in2, sum_, -2) bqm.add_quadratic(in2, carry, -4) bqm.add_quadratic(sum_, carry, 4) # the bqm currently has a strength of 1, so just need to scale if strength <= 0: raise ValueError("strength must be positive") bqm.scale(strength) return bqm
def and_gate(in0: Variable, in1: Variable, out: Variable, *, strength: float = 1.0) -> BinaryQuadraticModel: """Return a binary quadratic model with ground states corresponding to an AND gate. Args: in0: The variable label for one of the inputs. in1: The variable label for one of the inputs. out: The variable label for the output. strength: The energy of the lowest-energy infeasible state. Returns: A binary quadratic model with ground states corresponding to an AND gate. The model has three variables and three interactions. """ bqm = BinaryQuadraticModel(Vartype.BINARY) # add the variables (in order) bqm.add_variable(in0) bqm.add_variable(in1) bqm.add_variable(out, bias=3) # add the quadratic biases bqm.add_quadratic(in0, in1, 1) bqm.add_quadratic(in0, out, -2) bqm.add_quadratic(in1, out, -2) # the bqm currently has a strength of 1, so just need to scale if strength <= 0: raise ValueError("strength must be positive") bqm.scale(strength) return bqm
def multiplication_circuit(nbit: int) -> BinaryQuadraticModel: """Return a binary quadratic model with ground states corresponding to a multiplication circuit. The generated BQM represents the binary multiplication :math:`ab=p`, where the multiplicands are binary variables of length `nbit`; for example, :math:`2^ma_{nbit} + ... + 4a_2 + 2a_1 + a0`. The square below shows a graphic representation of the circuit:: ________________________________________________________________________________ | and20 and10 and00 | | | | | | | and21 add11──and11 add01──and01 | | | |┌───────────┘|┌───────────┘| | | | and22 add12──and12 add02──and02 | | | | |┌───────────┘|┌───────────┘| | | | | add13─────────add03 | | | | | ┌───────────┘| | | | | | | p5 p4 p3 p2 p1 p0 | -------------------------------------------------------------------------------- Args: nbit: Number of bits in the multiplicands. Returns: A binary quadratic model with ground states corresponding to a multiplication circuit. Examples: This example creates a multiplication circuit BQM that multiplies two 2-bit numbers. It fixes the multiplacands as :math:`a=2, b=3` (:math:`10` and :math:`11`) and uses a brute-force solver to find the product, :math:`p=6` (:math:`110`). >>> from dimod.generators import multiplication_circuit >>> from dimod import ExactSolver >>> bqm = multiplication_circuit(2) >>> for fixed_var, fixed_val in {'a0': 0, 'a1': 1, 'b0':1, 'b1': 1}.items(): ... bqm.fix_variable(fixed_var, fixed_val) >>> best = ExactSolver().sample(bqm).first >>> p = {key: best.sample[key] for key in best.sample.keys() if "p" in key} >>> print(p) {'p0': 0, 'p1': 1, 'p2': 1} """ if nbit < 1: raise ValueError("nbit must be a positive integer") num_multiplier_bits = num_multiplicand_bits = nbit bqm = BinaryQuadraticModel(Vartype.BINARY) # throughout, we will use the following convention: # i to refer to the bits of the multiplier # j to refer to the bits of the multiplicand # k to refer to the bits of the product # create the variables corresponding to the input and output wires for the circuit a = {i: 'a%d' % i for i in range(nbit)} b = {j: 'b%d' % j for j in range(nbit)} p = {k: 'p%d' % k for k in range(nbit + nbit)} # we will want to store the internal variables somewhere AND = defaultdict( dict ) # the output of the AND gate associated with ai, bj is stored in AND[i][j] SUM = defaultdict( dict ) # the sum of the ADDER gate associated with ai, bj is stored in SUM[i][j] CARRY = defaultdict( dict ) # the carry of the ADDER gate associated with ai, bj is stored in CARRY[i][j] # we follow a shift adder for i in range(num_multiplier_bits): for j in range(num_multiplicand_bits): ai = a[i] bj = b[j] if i == 0 and j == 0: # in this case there are no inputs from lower bits, so our only input is the AND # gate. And since we only have one bit to add, we don't need an adder, have no # carry out andij = AND[i][j] = p[0] gate = and_gate(ai, bj, andij) bqm.update(gate) continue # we always need an AND gate andij = AND[i][j] = 'and%s,%s' % (i, j) gate = and_gate(ai, bj, andij) bqm.update(gate) # the number of inputs will determine the type of adder inputs = [andij] # determine if there is a carry in if i - 1 in CARRY and j in CARRY[i - 1]: inputs.append(CARRY[i - 1][j]) # determine if there is a sum in if i - 1 in SUM and j + 1 in SUM[i - 1]: inputs.append(SUM[i - 1][j + 1]) # ok, create adders if necessary if len(inputs) == 1: # we don't need an adder and we don't have a carry SUM[i][j] = andij elif len(inputs) == 2: # we need a HALFADDER so we have a sum and a carry if j == 0: sumij = SUM[i][j] = p[i] else: sumij = SUM[i][j] = 'sum%d,%d' % (i, j) carryij = CARRY[i][j] = 'carry%d,%d' % (i, j) gate = halfadder_gate(inputs[0], inputs[1], sumij, carryij) bqm.update(gate) else: assert len(inputs) == 3, 'unexpected number of inputs' # we need a FULLADDER so we have a sum and a carry if j == 0: sumij = SUM[i][j] = p[i] else: sumij = SUM[i][j] = 'sum%d,%d' % (i, j) carryij = CARRY[i][j] = 'carry%d,%d' % (i, j) gate = fulladder_gate(inputs[0], inputs[1], inputs[2], sumij, carryij) bqm.update(gate) # now we have a final row of full adders for col in range(nbit - 1): inputs = [CARRY[nbit - 1][col], SUM[nbit - 1][col + 1]] if col == 0: sumout = p[nbit + col] carryout = CARRY[nbit][col] = 'carry%d,%d' % (nbit, col) gate = halfadder_gate(inputs[0], inputs[1], sumout, carryout) bqm.update(gate) continue inputs.append(CARRY[nbit][col - 1]) sumout = p[nbit + col] if col < nbit - 2: carryout = CARRY[nbit][col] = 'carry%d,%d' % (nbit, col) else: carryout = p[2 * nbit - 1] gate = fulladder_gate(inputs[0], inputs[1], inputs[2], sumout, carryout) bqm.update(gate) return bqm
def anti_crossing_loops(num_variables: int) -> BinaryQuadraticModel: """Generate an anti-crossing problem with two loops. This is the problem studied in [DJA]_. Note that for small values of ``num_variables``, the loops can be as small as a single edge. The ground state of this problem is `+1` for all variables. Args: num_variables: Number of variables used to generate the problem. Must be an even number greater than or equal to 8. Returns: A binary quadratic model. .. [DJA] Dickson, N., Johnson, M., Amin, M. et al. Thermally assisted quantum annealing of a 16-qubit problem. Nat Commun 4, 1903 (2013). https://doi.org/10.1038/ncomms2920 """ if num_variables % 2 or num_variables < 8: raise ValueError('num_variables must be an even number >= 8') bqm = BinaryQuadraticModel(Vartype.SPIN) hf = int(num_variables / 4) for n in range(hf): if n % 2 == 1: bqm.set_quadratic(n, n + hf, -1) bqm.set_quadratic(n, (n + 1) % hf, -1) bqm.set_quadratic(n + hf, (n + 1) % hf + hf, -1) bqm.set_quadratic(n, n + 2 * hf, -1) bqm.set_quadratic(n + hf, n + 3 * hf, -1) bqm.add_linear(n, 1) bqm.add_linear(n + hf, 1) bqm.add_linear(n + 2 * hf, -1) bqm.add_linear(n + 3 * hf, -1) bqm.set_linear(0, 0) bqm.set_linear(hf, 0) return bqm
def anti_crossing_clique(num_variables: int) -> BinaryQuadraticModel: """Generate an anti-crossing problem with a single clique. Let ``N = num_variables // 2``. This function returns a binary quadratic model where half the variables, `[0, N)`, form a ferromagnetic clique, with each variable, `v`, also ferromagnetically interacting with one variable, `v+N`, of the remaining half of the variables, `[N, 2*N)`. All of the variables in the clique except variable `1` have a linear bias of `+1`, and all of the variables attached to the clique have a linear bias of `-1`. The ground state of this problem is therefore `+1` for all variables. Args: num_variables: Number of variables used to generate the problem. Must be an even number greater than or equal to 6. Returns: A binary quadratic model. """ if num_variables % 2 or num_variables < 6: raise ValueError('num_variables must be an even number >= 6') bqm = BinaryQuadraticModel(Vartype.SPIN) hf = int(num_variables / 2) for n in range(hf): for m in range(n + 1, hf): bqm.add_quadratic(n, m, -1) bqm.add_quadratic(n, n + hf, -1) bqm.add_linear(n, 1) bqm.add_linear(n + hf, -1) bqm.set_linear(1, 0) return bqm
def maximum_weight_independent_set( edges: Iterable[Tuple[Variable, Variable]], nodes: Optional[Iterable[Tuple[Variable, float]]] = None, *, strength: Optional[float] = None, strength_multiplier: float = 2, ) -> BinaryQuadraticModel: """Return a binary quadratic model encoding a maximum-weight independent set problem. Given a graph `G`, an independent set is a set of nodes such that the subgraph of `G` induced by these nodes contains no edges. A maximum-weight independent set is the independent set with the highest total node weight. Args: edges: The edges of the graph as an iterable of two-tuples. nodes: The nodes of the graph as an iterable of two-tuples where the first element of the tuple is the node label and the second element is the node weight. Nodes not specified are given a weight of ``1``. strength: The strength of the quadratic biases. Must be strictly greater than ``1`` in order to enforce the independent set constraint. If not given, the strength is determined by the ``strength_multiplier``. strength_multiplier: The strength of the quadratic biases is given by the maximum node weight multiplied by ``strength_multiplier``. Returns: A binary quadratic model. The binary quadratic model will have variables and interactions corresponding to ``nodes`` and ``edges``. Examples: >>> from dimod.generators import maximum_weight_independent_set Get a maximum-weight independent set binary quadratic model from a list of edges and nodes. >>> maximum_weight_independent_set([(0, 1)], [(0, .25), (1, .5), (2, 1)]) BinaryQuadraticModel({0: -0.25, 1: -0.5, 2: -1.0}, {(1, 0): 2.0}, 0.0, 'BINARY') Get a maximum-weight independent set binary quadratic model from a :class:`networkx.Graph`. >>> import networkx as nx >>> G = nx.Graph() >>> G.add_edges_from([(0, 1), (1, 2)]) >>> G.add_nodes_from([0, 2], weight=.25) >>> G.add_node(1, weight=.5) >>> maximum_weight_independent_set(G.edges, G.nodes('weight')) BinaryQuadraticModel({0: -0.25, 1: -0.5, 2: -0.25}, {(1, 0): 1.0, (2, 1): 1.0}, 0.0, 'BINARY') """ bqm = independent_set(edges) objective = BinaryQuadraticModel(vartype=Vartype.BINARY) objective.add_linear_from((v, 1) for v in bqm.variables) if nodes is None: max_weight = 1. else: for v, weight in nodes: objective.set_linear(v, weight) max_weight = objective.linear.max(default=1) if strength is None: bqm *= max_weight * strength_multiplier bqm -= objective else: bqm *= strength bqm -= objective bqm.offset = 0 # otherwise subtracting the objective gives -0 offset return bqm