예제 #1
0
    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
예제 #2
0
    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
예제 #3
0
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
예제 #4
0
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
예제 #5
0
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
예제 #6
0
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
예제 #7
0
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
예제 #8
0
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
예제 #9
0
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
예제 #10
0
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