예제 #1
0
def to_qubo(bqm):
    """Converts the binary quadratic model into the (Q, offset) QUBO format.

    If the binary quadratic model's vartype is not binary, it is converted.

    Args:
        bqm (:class:`.BinaryQuadraticModel`):
            A binary quadratic model.

    Returns:
        tuple: A 2-tuple:

            dict: The qubo biases. A dict where the keys are pairs of variables and the values
            are the associated linear or quadratic bias.

            number: The offset.

    """
    qubo = {}

    for v, bias in iteritems(bqm.binary.linear):
        qubo[(v, v)] = bias

    for edge, bias in iteritems(bqm.binary.quadratic):
        qubo[edge] = bias

    return qubo, bqm.binary.offset
예제 #2
0
    def binary_to_spin(linear, quadratic, offset):
        """convert linear, quadratic and offset from binary to spin.
        Does no checking of vartype. Copies all of the values into new objects.
        """
        h = {}
        J = {}
        linear_offset = 0.0
        quadratic_offset = 0.0

        for u, bias in iteritems(linear):
            h[u] = .5 * bias
            linear_offset += bias

        for (u, v), bias in iteritems(quadratic):

            J[(u, v)] = .25 * bias

            h[u] += .25 * bias
            h[v] += .25 * bias

            quadratic_offset += bias

        offset += .5 * linear_offset + .25 * quadratic_offset

        return h, J, offset
예제 #3
0
def to_networkx_graph(bqm, node_attribute_name='bias', edge_attribute_name='bias'):
    """Return the BinaryQuadraticModel as a NetworkX graph.

    Args:
        node_attribute_name (hashable):
            The attribute name for the linear biases.
        edge_attribute_name (hashable):
            The attribute name for the quadratic biases.

    Returns:
        :class:`networkx.Graph`: A NetworkX with the biases stored as
        node/edge attributes.

    Examples:
        >>> import networkx as nx
        >>> bqm = dimod.BinaryQuadraticModel({0: 1, 1: -1, 2: .5},
        ...                                  {(0, 1): .5, (1, 2): 1.5},
        ...                                  1.4,
        ...                                  dimod.SPIN)
        >>> BQM = dimod.to_networkx_graph(bqm)
        >>> BQM[0][1]['bias']
        0.5
        >>> BQM.node[0]['bias']
        1

        Also, if the preferred notation is 'weights'

        >>> import networkx as nx
        >>> bqm = dimod.BinaryQuadraticModel({0: 1, 1: -1, 2: .5},
        ...                                  {(0, 1): .5, (1, 2): 1.5},
        ...                                  1.4,
        ...                                  dimod.SPIN)
        >>> BQM = dimod.to_networkx_graph(bqm, edge_attribute_name='weight')
        >>> BQM[0][1]['weight']
        0.5


    """
    import networkx as nx

    BQM = nx.Graph()

    vartype = bqm.vartype

    # add the linear biases
    BQM.add_nodes_from(((v, {node_attribute_name: bias, 'vartype': vartype})
                        for v, bias in iteritems(bqm.linear)))

    # add the quadratic biases
    BQM.add_edges_from(((u, v, {edge_attribute_name: bias}) for (u, v), bias in iteritems(bqm.quadratic)))

    # set the offset and vartype properties for the graph
    BQM.offset = bqm.offset
    BQM.vartype = vartype

    return BQM
예제 #4
0
def qubo_to_ising(Q, offset=0.0):
    """Converts a QUBO problem to an Ising problem.

    Map a binary quadratic program x' * Q * x defined over 0/1 variables to
    an Ising model defined over -1/+1 variables. We return the h and J
    defining the Ising model as well as the offset in energy between the
    two problem formulations, i.e. x' * Q * x = offset + s' * J * s + h' * s. The
    linear term of the QUBO is contained along the diagonal of Q.

    See ising_to_qubo(h, J) for the inverse function.

    Args:
        Q: A dict of the QUBO coefficients.
        offset (numeric, optional): The constant offset to be applied
            to the energy. Default 0.

    Returns:
        (dict, dict, float): A 3-tuple containing:

            dict: the linear coefficients of the Ising problem.

            dict: the quadratic coefficients of the Ising problem.

            float: The new energy offset.

    """
    h = {}
    J = {}
    linear_offset = 0.0
    quadratic_offset = 0.0

    for (u, v), bias in iteritems(Q):
        if u == v:
            if u in h:
                h[u] += .5 * bias
            else:
                h[u] = .5 * bias
            linear_offset += bias

        else:
            if bias != 0.0:
                J[(u, v)] = .25 * bias

            if u in h:
                h[u] += .25 * bias
            else:
                h[u] = .25 * bias

            if v in h:
                h[v] += .25 * bias
            else:
                h[v] = .25 * bias

            quadratic_offset += bias

    offset += .5 * linear_offset + .25 * quadratic_offset

    return h, J, offset
예제 #5
0
    def spin_to_binary(linear, quadratic, offset):
        """convert linear, quadratic, and offset from spin to binary.
        Does no checking of vartype. Copies all of the values into new objects.
        """

        # the linear biases are the easiest
        new_linear = {v: 2. * bias for v, bias in iteritems(linear)}

        # next the quadratic biases
        new_quadratic = {}
        for (u, v), bias in iteritems(quadratic):
            new_quadratic[(u, v)] = 4. * bias
            new_linear[u] -= 2. * bias
            new_linear[v] -= 2. * bias

        # finally calculate the offset
        offset += sum(itervalues(quadratic)) - sum(itervalues(linear))

        return new_linear, new_quadratic, offset
예제 #6
0
def ising_to_qubo(h, J, offset=0.0):
    """Converts an Ising problem to a QUBO problem.

    Map an Ising model defined over -1/+1 variables to a binary quadratic
    program x' * Q * x defined over 0/1 variables. We return the Q defining
    the BQP model as well as the offset in energy between the two problem
    formulations, i.e. s' * J * s + h' * s = offset + x' * Q * x. The linear term
    of the BQP is contained along the diagonal of Q.

    See qubo_to_ising(Q) for the inverse function.

    Args:
        h (dict): A dict of the linear coefficients of the Ising problem.
        J (dict): A dict of the quadratic coefficients of the Ising problem.
        offset (numeric, optional): The constant offset to be applied
            to the energy. Default 0.

    Returns:
        (dict, float): A 2-tuple containing:

            dict: the QUBO coefficients.

            float: The new energy offset.

    """
    # the linear biases are the easiest
    q = {(v, v): 2. * bias for v, bias in iteritems(h)}

    # next the quadratic biases
    for (u, v), bias in iteritems(J):
        if bias == 0.0:
            continue
        q[(u, v)] = 4. * bias
        q[(u, u)] -= 2. * bias
        q[(v, v)] -= 2. * bias

    # finally calculate the offset
    offset += sum(itervalues(J)) - sum(itervalues(h))

    return q, offset
예제 #7
0
    def add_sample(self, sample, energy, **kwargs):
        """Add a sample to the response.

        Args:
            sample (dict/:class:`pandas.Series`/list):
                A single sample as a dict or a pandas Series. If a dict, the keys should be the
                variables and the values are their value. If a Series or list, the index should be
                the variables and the data should be their value.

            energy (number):
                The energy of the given sample.

            **kwargs:
                Additional keywords will store additional data about the sample. See examples.

        Examples:
            >>> response = dimod.Response(dimod.SPIN)
            >>> response.add_sample({'a': -1, 'b': +1}, 1.)
            >>> print(response)
               a  b energy
            0 -1  1      1
            >>> response.add_sample({'a': +1, 'b': +1}, -1., num_spin_up=2)
            >>> print(response)
               a  b  energy  num_spin_up
            0 -1  1     1.0          NaN
            1  1  1    -1.0          2.0

            The sample can also be a :class:`pandas.Series` or a list.

            >>> response = dimod.Response(dimod.SPIN)
            >>> response.add_sample(pd.Series([-1, +1]), 1)
            >>> print(response)
               0  1 energy
            0  0  1      1
            >>> response.add_sample([+1, +1], -1)
            >>> print(response)
               0  1 energy
            0 -1  1      1
            1  1  1     -1

        See also:
            add_samples_from

        Notes:
            Very little input checking is performed in the interests of speed. It is up to the
            dimod sampler or composite that is populating the response to ensure correct
            variable labels and correct variable types.

        """
        self.add_samples_from([sample], [energy], **{key: [val] for key, val in iteritems(kwargs)})
예제 #8
0
    def add_interactions_from(self, quadratic, vartype=None):
        """Add quadratic biases.

        Args:
            quadratic (dict[(variable, variable), bias]/iterable[(variable, variable, bias)]):
                Variables that have an interaction and their quadratic bias. If a dict, the keys
                should be 2-tuples of the variables and the values should be their corresponding
                bias. Can also be an iterable of 3-tuples. Each interaction in quadratic should be
                unique - that is if `(u, v)` is a key in quadratic, then `(v, u)` should not be.
                The variables can be any python object that could be used as a key in a dict.
                Many methods and functions expect the biases to be numbers but this is not
                explicitly checked.

            vartype (:class:`.Vartype`, optional, default=None):
                The vartype of the given bias. If None will be the same vartype as the binary
                quadratic model. If given, should be :class:`.Vartype.SPIN` or
                :class:`.Vartype.BINARY`.

        Examples:
            >>> bqm = dimod.BinaryQuadraticModel({}, {}, 0.0, dimod.SPIN)
            >>> bqm.add_interactions_from({('a', 'b'): -.5})
            >>> bqm.quadratic
            {('a', 'b'): -.5}

            Variables that already exist have their bias added.

            >>> bqm = dimod.BinaryQuadraticModel({}, {('b', 'a'): -.5}, 0.0, dimod.SPIN)
            >>> bqm.add_interactions_from({('a', 'b'): -.5})
            >>> bqm.quadratic
            {('b', 'a'): -1.}

        """
        if isinstance(quadratic, dict):
            for (u, v), bias in iteritems(quadratic):
                self.add_interaction(u, v, bias, vartype=vartype)
        else:
            try:
                for u, v, bias in quadratic:
                    self.add_interaction(u, v, bias, vartype=vartype)
            except TypeError:
                raise TypeError("expected 'quadratic' to be a dict or an iterable of 3-tuples.")
예제 #9
0
    def add_variables_from(self, linear, vartype=None):
        """Add linear biases.

        Args:
            linear (dict[variable, bias]/iterable[(variable, bias)]):
                A collection of linear biases. If a dict, the keys should be variables in the
                binary quadratic model and the values should be biases. Otherwise should be
                an iterable of (variable, bias) pairs. The variables can be any python object
                that could be used as a key in a dict. Many methods and functions expect the biases
                to be numbers but this is not explicitly checked.
                If any of the variables already exist in the model, their bias is added to the
                existing linear bias.

            vartype (:class:`.Vartype`, optional, default=None):
                The vartype of the given bias. If None will be the same vartype as the binary
                quadratic model. If given, should be :class:`.Vartype.SPIN` or
                :class:`.Vartype.BINARY`.

        Examples:
            >>> bqm = dimod.BinaryQuadraticModel({}, {}, 0.0, dimod.SPIN)
            >>> bqm.add_variables_from({'a': .5, 'b': -1.})
            >>> bqm.linear
            {'a': .5, 'b': -1.}

            Variables that already exist have their bias added.

            >>> bqm = dimod.BinaryQuadraticModel({'b': -1.}, {}, 0.0, dimod.SPIN)
            >>> bqm.add_variables_from({'a': .5, 'b': -1.})
            >>> bqm.linear
            {'a': .5, 'b': -2.}

        """
        if isinstance(linear, dict):
            for v, bias in iteritems(linear):
                self.add_variable(v, bias, vartype=vartype)
        else:
            try:
                for v, bias in linear:
                    self.add_variable(v, bias, vartype=vartype)
            except TypeError:
                raise TypeError("expected 'linear' to be a dict or an iterable of 2-tuples.")
예제 #10
0
    def _index_label(sampler, bqm, **kwargs):
        if not hasattr(bqm, 'linear'):
            raise TypeError('expected input to be a BinaryQuadraticModel')
        linear = bqm.linear

        # if already index-labelled, just continue
        if all(v in linear for v in range(len(bqm))):
            return f(sampler, bqm, **kwargs)

        try:
            inverse_mapping = dict(enumerate(sorted(linear)))
        except TypeError:
            # in python3 unlike types cannot be sorted
            inverse_mapping = dict(enumerate(linear))
        mapping = {v: i for i, v in iteritems(inverse_mapping)}

        response = f(sampler, bqm.relabel_variables(mapping, inplace=False),
                     **kwargs)

        # unapply the relabeling
        return response.relabel_variables(inverse_mapping, inplace=True)
예제 #11
0
def from_qubo(Q, offset=0.0):
    """Build a binary quadratic model from a qubo.

    Args:
        Q (dict):
            The qubo coefficients.

        offset (optional, default=0.0):
            The constant offset applied to the model.

    Returns:
        :class:`.BinaryQuadraticModel`

    """
    linear = {}
    quadratic = {}
    for (u, v), bias in iteritems(Q):
        if u == v:
            linear[u] = bias
        else:
            quadratic[(u, v)] = bias

    return BinaryQuadraticModel(linear, quadratic, offset, Vartype.BINARY)
예제 #12
0
def to_numpy_matrix(bqm, variable_order=None):
    """Return the binary quadratic model as a matrix.

    Args:
        bqm (:class:`.BinaryQuadraticModel`):
            A binary quadratic model. Should either be index-labeled from 0 to N-1 or variable_order
            should be provided.

        variable_order (list, optional):
            If variable_order is provided, the rows/columns of the numpy array are indexed by
            the variables in variable_order. If any variables are included in variable_order that
            are not in `bqm`, they will be included in the matrix.

    Returns:
        :class:`numpy.matrix`: The binary quadratic model as a matrix. The matrix has binary
        vartype.

    Notes:
        The matrix representation of a binary quadratic model only makes sense for binary models.
        For a binary sample x, the energy of the model is given by:

        .. math::

            E(x) = x^T Q x


        The offset is dropped when converting to a numpy matrix.

    """
    import numpy as np

    if variable_order is None:
        # just use the existing variable labels, assuming that they are [0, N)
        num_variables = len(bqm)
        mat = np.zeros((num_variables, num_variables), dtype=float)

        try:
            for v, bias in iteritems(bqm.binary.linear):
                mat[v, v] = bias
        except IndexError:
            raise ValueError(("if 'variable_order' is not provided, binary quadratic model must be "
                              "index labeled [0, ..., N-1]"))

        for (u, v), bias in iteritems(bqm.binary.quadratic):
            if u < v:
                mat[u, v] = bias
            else:
                mat[v, u] = bias

    else:
        num_variables = len(variable_order)
        idx = {v: i for i, v in enumerate(variable_order)}

        mat = np.zeros((num_variables, num_variables), dtype=float)

        try:
            for v, bias in iteritems(bqm.binary.linear):
                mat[idx[v], idx[v]] = bias
        except KeyError as e:
            raise ValueError(("variable {} is missing from variable_order".format(e)))

        for (u, v), bias in iteritems(bqm.binary.quadratic):
            iu, iv = idx[u], idx[v]
            if iu < iv:
                mat[iu, iv] = bias
            else:
                mat[iv, iu] = bias

    return np.asmatrix(mat)
예제 #13
0
    def relabel_variables(self, mapping, inplace=True):
        """Relabel the variables according to the given mapping.

        Args:
            mapping (dict):
                A dict mapping the current variable labels to new ones. If an incomplete mapping is
                provided unmapped variables will keep their labels

            inplace (bool, optional, default=True):
                If True, the binary quadratic model is updated in-place, otherwise a new binary
                quadratic model is returned.

        Returns:
            :class:`.BinaryQuadraticModel`: A BinaryQuadraticModel with the variables relabeled.
            If inplace=True, returns itself.

        Examples:
            >>> model = pm.BinaryQuadraticModel({0: 0., 1: 1.}, {(0, 1): -1}, 0.0, vartype=pm.SPIN)
            >>> model.relabel_variables({0: 'a'})
            >>> model.quadratic
            {('a', 1): -1}

            >>> model = pm.BinaryQuadraticModel({0: 0., 1: 1.}, {(0, 1): -1}, 0.0, vartype=pm.SPIN)
            >>> new_model = model.relabel_variables({0: 'a', 1: 'b'}, inplace=False)
            >>> new_model.quadratic
            {('a', 'b'): -1}

        """
        try:
            old_labels = set(iterkeys(mapping))
            new_labels = set(itervalues(mapping))
        except TypeError:
            raise ValueError("mapping targets must be hashable objects")

        for v in new_labels:
            if v in self.linear and v not in old_labels:
                raise ValueError(('A variable cannot be relabeled "{}" without also relabeling '
                                  "the existing variable of the same name").format(v))

        if inplace:
            shared = old_labels & new_labels
            if shared:
                # in this case relabel to a new intermediate labeling, then map from the intermediate
                # labeling to the desired labeling

                # counter will be used to generate the intermediate labels, as an easy optimization
                # we start the counter with a high number because often variables are labeled by
                # integers starting from 0
                counter = itertools.count(2 * len(self))

                old_to_intermediate = {}
                intermediate_to_new = {}

                for old, new in iteritems(mapping):
                    if old == new:
                        # we can remove self-labels
                        continue

                    if old in new_labels or new in old_labels:

                        # try to get a new unique label
                        lbl = next(counter)
                        while lbl in new_labels or lbl in old_labels:
                            lbl = next(counter)

                        # add it to the mapping
                        old_to_intermediate[old] = lbl
                        intermediate_to_new[lbl] = new

                    else:
                        old_to_intermediate[old] = new
                        # don't need to add it to intermediate_to_new because it is a self-label

                self.relabel_variables(old_to_intermediate, inplace=True)
                self.relabel_variables(intermediate_to_new, inplace=True)
                return self

            linear = self.linear
            quadratic = self.quadratic
            adj = self.adj

            # rebuild linear and adj with the new labels
            for old in list(linear):
                if old not in mapping:
                    continue

                new = mapping[old]

                # get the new interactions that need to be added
                new_interactions = [(new, v, adj[old][v]) for v in adj[old]]

                self.add_variable(new, linear[old])
                self.add_interactions_from(new_interactions)
                self.remove_variable(old)

            return self
        else:
            return BinaryQuadraticModel({mapping.get(v, v): bias for v, bias in iteritems(self.linear)},
                                        {(mapping.get(u, u), mapping.get(v, v)): bias
                                         for (u, v), bias in iteritems(self.quadratic)},
                                        self.offset, self.vartype)