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
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
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
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
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
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
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)})
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.")
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.")
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)
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)
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)
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)