def make_quadratic(poly, strength, vartype=None, bqm=None): """Create a binary quadratic model from a higher order polynomial. Args: poly (dict): A polynomial. Should be a dict of the form {term: bias, ...} where term is a tuple of variables and bias is their associated bias. strength (float): The strength of the reduction constraint. Insufficient strength can result in the binary quadratic model not having the same minimizations as the polynomial. vartype (:class:`.Vartype`, optional): The vartype of the polynomial. If a bqm is provided, then vartype is not required. bqm (:class:`.BinaryQuadraticModel`, optional): The terms of the reduced polynomial are added to this bqm. If not provided a new empty binary quadratic model is created. Returns: :class:`.BinaryQuadraticModel` Examples: >>> import dimod ... >>> poly = {(0,): -1, (1,): 1, (2,): 1.5, (0, 1): -1, (0, 1, 2): -2} >>> bqm = make_quadratic(poly, 5.0, dimod.SPIN) """ if bqm is None: if vartype is None: raise ValueError("one of vartype and create_using must be provided") bqm = BinaryQuadraticModel.empty(vartype) else: if not isinstance(bqm, BinaryQuadraticModel): raise TypeError('create_using must be a BinaryQuadraticModel') if vartype is not None and vartype is not bqm.vartype: raise ValueError("one of vartype and create_using must be provided") bqm.info['reduction'] = {} new_poly = {} for term, bias in iteritems(poly): if len(term) == 0: bqm.add_offset(bias) elif len(term) == 1: v, = term bqm.add_variable(v, bias) else: new_poly[term] = bias return _reduce_degree(bqm, new_poly, vartype, strength)
def combinations(n, k, strength=1, vartype=BINARY): r"""Generate a BQM that is minimized when k of n variables are selected. More fully, generates a binary quadratic model (BQM) that is minimized for each of the k-combinations of its variables. The energy for the BQM is given by :math:`(\sum_{i} x_i - k)^2`. Args: n (int/list/set): If n is an integer, variables are labelled [0, n-1]. If n is a list or set, variables are labelled accordingly. k (int): The generated BQM has 0 energy when any k of its variables are 1. strength (number, optional, default=1): The energy of the first excited state of the BQM. vartype (:class:`.Vartype`/str/set): Variable type for the BQM. Accepted input values: * :class:`.Vartype.SPIN`, ``'SPIN'``, ``{-1, 1}`` * :class:`.Vartype.BINARY`, ``'BINARY'``, ``{0, 1}`` Returns: :obj:`.BinaryQuadraticModel` Examples: >>> bqm = dimod.generators.combinations(['a', 'b', 'c'], 2) >>> bqm.energy({'a': 1, 'b': 0, 'c': 1}) 0.0 >>> bqm.energy({'a': 1, 'b': 1, 'c': 1}) 1.0 >>> bqm = dimod.generators.combinations(5, 1) >>> bqm.energy({0: 0, 1: 0, 2: 1, 3: 0, 4: 0}) 0.0 >>> bqm.energy({0: 0, 1: 0, 2: 1, 3: 1, 4: 0}) 1.0 >>> bqm = dimod.generators.combinations(['a', 'b', 'c'], 2, strength=3.0) >>> bqm.energy({'a': 1, 'b': 0, 'c': 1}) 0.0 >>> bqm.energy({'a': 1, 'b': 1, 'c': 1}) 3.0 """ if isinstance(n, abc.Sized) and isinstance(n, abc.Iterable): # what we actually want is abc.Collection but that doesn't exist in # python2 variables = n else: try: variables = range(n) except TypeError: raise TypeError('n should be a collection or an integer') if k > len(variables) or k < 0: raise ValueError("cannot select k={} from {} variables".format(k, len(variables))) # (\sum_i x_i - k)^2 # = \sum_i x_i \sum_j x_j - 2k\sum_i x_i + k^2 # = \sum_{i,j} x_ix_j + (1 - 2k)\sum_i x_i + k^2 lbias = float(strength*(1 - 2*k)) qbias = float(2*strength) bqm = BinaryQuadraticModel.empty(BINARY) bqm.add_variables_from(((v, lbias) for v in variables)) bqm.add_interactions_from(((u, v, qbias) for u, v in itertools.combinations(variables, 2))) bqm.offset += strength*(k**2) return bqm.change_vartype(vartype, inplace=True)
def make_quadratic(poly, strength, vartype=None, bqm=None): """Create a binary quadratic model from a higher order polynomial. Args: poly (dict): Polynomial as a dict of form {term: bias, ...}, where `term` is a tuple of variables and `bias` the associated bias. strength (float): The energy penalty for violating the prodcut constraint. Insufficient strength can result in the binary quadratic model not having the same minimizations as the polynomial. vartype (:class:`.Vartype`/str/set, optional): Variable type for the binary quadratic model. Accepted input values: * :class:`.Vartype.SPIN`, ``'SPIN'``, ``{-1, 1}`` * :class:`.Vartype.BINARY`, ``'BINARY'``, ``{0, 1}`` If `bqm` is provided, `vartype` is not required. bqm (:class:`.BinaryQuadraticModel`, optional): The terms of the reduced polynomial are added to this binary quadratic model. If not provided, a new binary quadratic model is created. Returns: :class:`.BinaryQuadraticModel` Examples: >>> poly = {(0,): -1, (1,): 1, (2,): 1.5, (0, 1): -1, (0, 1, 2): -2} >>> bqm = dimod.make_quadratic(poly, 5.0, dimod.SPIN) """ if vartype is None: if bqm is None: raise ValueError("one of vartype or bqm must be provided") else: vartype = bqm.vartype else: vartype = as_vartype(vartype) # handle other vartype inputs if bqm is None: bqm = BinaryQuadraticModel.empty(vartype) else: bqm = bqm.change_vartype(vartype, inplace=False) bqm.info['reduction'] = {} # we want to be able to mutate the polynomial so copy. We treat this as a # dict but by using BinaryPolynomial we also get automatic handling of # square terms poly = BinaryPolynomial(poly, vartype=bqm.vartype) variables = set().union(*poly) while any(len(term) > 2 for term in poly): # determine which pair of variables appear most often paircounter = Counter() for term in poly: if len(term) <= 2: # we could leave these in but it can lead to cases like # {'ab': -1, 'cdef': 1} where ab keeps being chosen for # elimination. So we just ignore all the pairs continue for u, v in itertools.combinations(term, 2): pair = frozenset((u, v)) # so order invarient paircounter[pair] += 1 pair, __ = paircounter.most_common(1)[0] u, v = pair # make a new product variable p == u*v and replace all (u, v) with p p = _new_product(variables, u, v) terms = [term for term in poly if u in term and v in term] for term in terms: new = tuple(w for w in term if w != u and w != v) + (p, ) poly[new] = poly.pop(term) # add a constraint enforcing the relationship between p == u*v if vartype is Vartype.BINARY: constraint = _binary_product([u, v, p]) bqm.info['reduction'][(u, v)] = {'product': p} elif vartype is Vartype.SPIN: aux = _new_aux(variables, u, v) # need an aux in SPIN-space constraint = _spin_product([u, v, p, aux]) bqm.info['reduction'][(u, v)] = {'product': p, 'auxiliary': aux} else: raise RuntimeError("unknown vartype: {!r}".format(vartype)) # scale constraint and update the polynomial with it constraint.scale(strength) for v, bias in constraint.linear.items(): try: poly[v, ] += bias except KeyError: poly[v, ] = bias for uv, bias in constraint.quadratic.items(): try: poly[uv] += bias except KeyError: poly[uv] = bias try: poly[()] += constraint.offset except KeyError: poly[()] = constraint.offset # convert poly to a bqm (it already is one) for term, bias in poly.items(): if len(term) == 2: u, v = term bqm.add_interaction(u, v, bias) elif len(term) == 1: v, = term bqm.add_variable(v, bias) elif len(term) == 0: bqm.add_offset(bias) else: # still has higher order terms, this shouldn't happen msg = ('Internal error: not all higher-order terms were reduced. ' 'Please file a bug report.') raise RuntimeError(msg) return bqm