def _append(self, vartype: VartypeLike, v: Optional[Variable] = None, permissive: bool = False) -> Variable: if permissive and v is not None and self.count(v): if as_vartype(vartype, extended=True) != self.vartype(v): raise ValueError("inconsistent vartype") return v else: v = super()._append(v) self.vartypes.append(as_vartype(vartype, extended=True)) return v
def _enforce_single_arg(name, args, kwargs): try: vartype = kwargs[name] except KeyError: raise TypeError('vartype argument missing') kwargs[name] = as_vartype(vartype)
def _init_bqm(self, bqm, vartype=None): self.linear.update(bqm.linear) self.quadratic.update(bqm.quadratic) self.offset = bqm.offset self._vartype = bqm.vartype if vartype is not None: self.change_vartype(as_vartype(vartype), inplace=True)
def add_variable(self, v: Variable, vartype: VartypeLike): """Add a variable to the model.""" if self.variables.count(v): if as_vartype(vartype, extended=True) != self.variables.vartype(v): raise ValueError( "given variable has already been added with a different vartype" ) else: return self.variables._append(vartype, v)
def test_sets_correct_vartype(vartype): """BQM loaded from coo should have correct vartype.""" coo = make_coo("""0 1 2.5 1 1 -3 2 0 4 4 3 -1.2 0 3 0.1123456 """) bqm = bqm_from_coo(coo, vartype=vartype) assert bqm.vartype == vartypes.as_vartype(vartype)
def change_vartype(self, vartype, inplace=True): """Return a binary quadratic model with the specified vartype. Args: vartype (:class:`.Vartype`/str/set, optional): Variable type for the changed model. Accepted input values: * :class:`.Vartype.SPIN`, ``'SPIN'``, ``{-1, 1}`` * :class:`.Vartype.BINARY`, ``'BINARY'``, ``{0, 1}`` inplace (bool, optional, default=True): If True, the binary quadratic model is updated in-place; otherwise, a new binary quadratic model is returned. Returns: :obj:`.AdjDictBQM`: A binary quadratic model with the specified vartype. """ if not inplace: return self.copy().change_vartype(vartype, inplace=True) vartype = as_vartype(vartype) # in place and we are already correct, so nothing to do if self.vartype == vartype: return self if vartype is Vartype.BINARY: lin_mp, lin_offset_mp = 2.0, -1.0 quad_mp, lin_quad_mp, quad_offset_mp = 4.0, -2.0, 1.0 elif vartype is Vartype.SPIN: lin_mp, lin_offset_mp = 0.5, .5 quad_mp, lin_quad_mp, quad_offset_mp = 0.25, 0.25, 0.25 else: raise ValueError("unkown vartype") for v, bias in self.linear.items(): self.linear[v] = lin_mp * bias self.offset += lin_offset_mp * bias for (u, v), bias in self.quadratic.items(): self.adj[u][v] = quad_mp * bias self.linear[u] += lin_quad_mp * bias self.linear[v] += lin_quad_mp * bias self.offset += quad_offset_mp * bias self._vartype = vartype return self
def _init_quadratic_model(qm, vartype, qm_factory): if vartype is None: if qm is None: raise ValueError("one of vartype or qm must be provided") else: vartype = qm.vartype else: vartype = as_vartype(vartype) # handle other vartype inputs if qm is None: qm = qm_factory(vartype) else: qm = qm.change_vartype(vartype, inplace=False) # for backwards compatibility, add an info field if not hasattr(qm, 'info'): qm.info = {} qm.info['reduction'] = {} return qm, vartype
def add_variables_from(self, vartype: VartypeLike, variables: Iterable[Variable]): """Add multiple variables of the same type to the quadratic model. Args: vartype: Variable type. One of: * :class:`.Vartype.SPIN`, ``'SPIN'``, ``{-1, 1}`` * :class:`.Vartype.BINARY`, ``'BINARY'``, ``{0, 1}`` * :class:`.Vartype.INTEGER`, ``'INTEGER'`` variables: Iterable of variable labels. Examples: >>> from dimod import QuadraticModel, Binary >>> qm = QuadraticModel() >>> qm.add_variables_from('BINARY', ['x', 'y']) """ vartype = as_vartype(vartype, extended=True) for v in variables: self.add_variable(vartype, v)
def change_vartype(self, vartype: VartypeLike) -> 'pyBQM': vartype = as_vartype(vartype) # in place and we are already correct, so nothing to do if self._vartype == vartype: return self if vartype == Vartype.BINARY: lin_mp = 2. lin_offset_mp = -1. quad_mp = 4. lin_quad_mp = -2. quad_offset_mp = .5 elif vartype == Vartype.SPIN: lin_mp = .5 lin_offset_mp = .5 quad_mp = .25 lin_quad_mp = .25 quad_offset_mp = .125 else: raise RuntimeError("unexpected vartype") adj = self._adj for u, Nu in adj.items(): lbias = Nu[u] self.offset += lin_offset_mp * lbias Nu[u] = lin_mp * lbias for v, qbias in Nu.items(): if v == u: continue Nu[v] = quad_mp * qbias Nu[u] += lin_quad_mp * qbias # linear self.offset += quad_offset_mp * qbias self._vartype = vartype return self
def as_bqm(*args, cls=None, copy=False): """Convert the input to a binary quadratic model. Converts the following input formats to a binary quadratic model (BQM): as_bqm(vartype) Creates an empty binary quadratic model. as_bqm(bqm) Creates a BQM from another BQM. See `copy` and `cls` kwargs below. as_bqm(bqm, vartype) Creates a BQM from another BQM, changing to the appropriate `vartype` if necessary. See `copy` and `cls` kwargs below. as_bqm(n, vartype) Creates a BQM with `n` variables, indexed linearly from zero, setting all biases to zero. as_bqm(quadratic, vartype) Creates a BQM from quadratic biases given as a square array_like_ or a dictionary of the form `{(u, v): b, ...}`. Note that when formed with SPIN-variables, biases on the diagonal are added to the offset. as_bqm(linear, quadratic, vartype) Creates a BQM from linear and quadratic biases, where `linear` is a one-dimensional array_like_ or a dictionary of the form `{v: b, ...}`, and `quadratic` is a square array_like_ or a dictionary of the form `{(u, v): b, ...}`. Note that when formed with SPIN-variables, biases on the diagonal are added to the offset. as_bqm(linear, quadratic, offset, vartype) Creates a BQM from linear and quadratic biases, where `linear` is a one-dimensional array_like_ or a dictionary of the form `{v: b, ...}`, and `quadratic` is a square array_like_ or a dictionary of the form `{(u, v): b, ...}`, and `offset` is a numerical offset. Note that when formed with SPIN-variables, biases on the diagonal are added to the offset. Args: *args: See above. cls (type/list, optional): Class of the returned BQM. If given as a list, the returned BQM is of one of the types in the list. Default is :class:`.AdjVectorBQM`. copy (bool, optional, default=False): If False, a new BQM is only constructed when necessary. Returns: A binary quadratic model. .. _array_like: https://numpy.org/doc/stable/user/basics.creation.html """ if cls is None: if isinstance(args[0], BQM): cls = type(args[0]) else: cls = AdjVectorBQM elif isinstance(cls, (Sequence, Set)): # want Collection, but not in 3.5 classes = tuple(cls) if not classes: raise ValueError("cls kwarg should be a type or a list of types") if type(args[0]) in classes: cls = type(args[0]) else: # otherwise just pick the first one cls = classes[0] if isinstance(args[0], cls) and not copy: # this is the only case (currently) in which copy matters if len(args) == 1: return args[0] elif len(args) == 2: bqm, vartype = args if bqm.vartype is as_vartype(vartype): return bqm # otherwise we're doing a copy # otherwise we don't have a well-formed bqm input so pass off the check # to cls(*args) return cls(*args)
def _init_number(self, n, vartype): self.linear.update((v, 0.0) for v in range(n)) self.offset = 0.0 self._vartype = as_vartype(vartype)
def _init_components(self, linear, quadratic, offset, vartype): self._vartype = vartype = as_vartype(vartype) if isinstance(linear, (abc.Mapping, abc.Iterator)): self.linear.update(linear) else: # assume a sequence self.linear.update(enumerate(linear)) adj = self._adj if isinstance(quadratic, abc.Mapping): for (u, v), bias in quadratic.items(): self.add_variable(u) self.add_variable(v) if u == v and vartype is Vartype.SPIN: offset = offset + bias # not += on off-chance it's mutable elif u in adj[v]: adj[u][v] = adj[v][u] = adj[u][v] + bias else: adj[u][v] = adj[v][u] = bias elif isinstance(quadratic, abc.Iterator): for u, v, bias in quadratic: self.add_variable(u) self.add_variable(v) if u == v and vartype is Vartype.SPIN: offset = offset + bias # not += on off-chance it's mutable elif u in adj[v]: adj[u][v] = adj[v][u] = adj[u][v] + bias else: adj[u][v] = adj[v][u] = bias else: # unlike the other BQM types we let numpy handle the typing if isinstance(quadratic, np.ndarray): dtype = quadratic.dtype else: quadratic = np.asarray(quadratic, dtype=object) D = np.atleast_2d(quadratic) num_variables = D.shape[0] if D.ndim != 2 or num_variables != D.shape[1]: raise ValueError("expected dense to be a 2 dim square array") # make sure all the variables are present for v in range(num_variables): self.add_variable(v) it = np.nditer(D, flags=['multi_index', 'refs_ok'], op_flags=['readonly']) while not it.finished: u, v = it.multi_index bias = it.value[()] if bias: if u == v and vartype is Vartype.SPIN: # not += on off-chance it's mutable offset = offset + bias elif u in adj[v]: adj[u][v] = adj[v][u] = adj[u][v] + bias else: adj[u][v] = adj[v][u] = bias it.iternext() self.offset = offset
def __init__(self, vartype: VartypeLike): self._adj: Dict[Variable, Dict[Variable, Any]] = dict() self._vartype = as_vartype(vartype) self.offset = 0
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
def change_vartype(self, vartype: VartypeLike): self._vartype = as_vartype(vartype)
def add_variables_from(self, vartype: VartypeLike, variables: Iterable[Variable]): vartype = as_vartype(vartype, extended=True) for v in variables: self.add_variable(vartype, v)