def test_permissive(self): variables = Variables([0, 1]) # relabels a non-existant variable 2 variables._relabel({0: 'a', 1: 'b', 2: 'c'}) self.assertEqual(variables, Variables('ab'))
def test_copy(self): variables = Variables('abc') new = copy.copy(variables) variables._relabel({'a': 0}) # should not change the copy self.assertIsNot(new, variables) self.assertEqual(new, 'abc') self.assertIsInstance(new, Variables)
def test_relabel_conflict(self): variables = Variables(self.iterable) iterable = self.iterable # want a relabelling with identity relabels and that maps to the same # set of labels as the original target = [iterable[-i] for i in range(len(iterable))] mapping = dict(zip(iterable, target)) variables._relabel(mapping) self.assertEqual(variables, target)
class DiscreteQuadraticModel: """Encodes a discrete quadratic model. A discrete quadratic model is a polynomial over discrete variables with terms all of degree two or less. Examples: This example constructs a map coloring with Canadian provinces. To solve the problem we penalize adjacent provinces having the same color. >>> provinces = ["AB", "BC", "ON", "MB", "NB", "NL", "NS", "NT", "NU", ... "PE", "QC", "SK", "YT"] >>> borders = [("BC", "AB"), ("BC", "NT"), ("BC", "YT"), ("AB", "SK"), ... ("AB", "NT"), ("SK", "MB"), ("SK", "NT"), ("MB", "ON"), ... ("MB", "NU"), ("ON", "QC"), ("QC", "NB"), ("QC", "NL"), ... ("NB", "NS"), ("YT", "NT"), ("NT", "NU")] >>> colors = [0, 1, 2, 3] ... >>> dqm = dimod.DiscreteQuadraticModel() >>> for p in provinces: ... _ = dqm.add_variable(4, label=p) >>> for p0, p1 in borders: ... dqm.set_quadratic(p0, p1, {(c, c): 1 for c in colors}) The next examples show how to view and manipulate the model biases. >>> dqm = dimod.DiscreteQuadraticModel() Add the variables to the model >>> u = dqm.add_variable(5) # unlabeled variable with 5 cases >>> v = dqm.add_variable(3, label='v') # labeled variable with 3 cases The linear biases default to 0. They can be read by case or by batch. >>> dqm.get_linear_case(u, 1) 0.0 >>> dqm.get_linear(u) array([0., 0., 0., 0., 0.]) >>> dqm.get_linear(v) array([0., 0., 0.]) The linear biases can be overwritten either by case or in a batch. >>> dqm.set_linear_case(u, 3, 17) >>> dqm.get_linear(u) array([ 0., 0., 0., 17., 0.]) >>> dqm.set_linear(v, [0, -1, 3]) >>> dqm.get_linear(v) array([ 0., -1., 3.]) The quadratic biases can also be manipulated sparsely or densely. >>> dqm.set_quadratic(u, v, {(0, 2): 1.5}) >>> dqm.get_quadratic(u, v) {(0, 2): 1.5} >>> dqm.get_quadratic(u, v, array=True) # as a NumPy array array([[0. , 0. , 1.5], [0. , 0. , 0. ], [0. , 0. , 0. ], [0. , 0. , 0. ], [0. , 0. , 0. ]]) >>> dqm.set_quadratic_case(u, 2, v, 1, -3) >>> dqm.get_quadratic(u, v, array=True) array([[ 0. , 0. , 1.5], [ 0. , 0. , 0. ], [ 0. , -3. , 0. ], [ 0. , 0. , 0. ], [ 0. , 0. , 0. ]]) >>> dqm.get_quadratic(u, v) # doctest:+SKIP {(0, 2): 1.5, (2, 1): -3.0} """ def __init__(self): self.variables = Variables() self._cydqm = cyDiscreteQuadraticModel() variables = None # overwritten by __init__, here for the docstring """:class:`~.variables.Variables` of variable labels.""" @property def adj(self): """dict[hashable, set]: The adjacency structure of the variables.""" try: return self._adj except AttributeError: pass self._adj = adj = VariableAdjacency(self) return adj @property def offset(self): return self._cydqm.offset @offset.setter def offset(self, offset: float): self._cydqm.offset = offset def add_linear_equality_constraint(self, terms: LinearTriplets, lagrange_multiplier: float, constant: float): """Add a linear constraint as a quadratic objective. Adds a linear constraint of the form :math:`\sum_{i,k} a_{i,k} x_{i,k} + C = 0` to the discrete quadratic model as a quadratic objective. Args: terms: A list of tuples of the type (variable, case, bias). Each tuple is evaluated to the term (bias * variable_case). All terms in the list are summed. lagrange_multiplier: The coefficient or the penalty strength constant: The constant value of the constraint. """ index_terms = ((self.variables.index(v), c, x) for v, c, x in terms) self._cydqm.add_linear_equality_constraint( index_terms, lagrange_multiplier, constant) def add_linear_inequality_constraint(self, terms: LinearTriplets, lagrange_multiplier: float, label: str, constant: int = 0, lb: int = np.iinfo(np.int64).min, ub: int = 0, slack_method: str = "log2", cross_zero: bool = False)\ -> LinearTriplets: """Add a linear inequality constraint as a quadratic objective. Adds a linear inequality constraint of the form: math:'lb <= \sum_{i,k} a_{i,k} x_{i,k} + constant <= ub' to the discrete quadratic model as a quadratic objective. Coefficients should be integers. For constraints with fractional coefficients, multiply both sides of the inequality by an appropriate factor of ten to attain or approximate integer coefficients. Args: terms: A list of tuples of the type (variable, case, bias). Each tuple is evaluated to the term (bias * variable_case). All terms in the list are summed. lagrange_multiplier: A weight or the penalty strength. This value is multiplied by the entire constraint objective and added to the discrete quadratic model (it doesn't appear explicitly in the + equation above). label: Prefix used to label the slack variables used to create the new objective. constant: The constant value of the constraint. lb: lower bound for the constraint ub: upper bound for the constraint slack_method: "The method for adding slack variables. Supported methods are: - log2: Adds up to log2(ub - lb) number of dqm variables each with two cases to the constraint. - log10: Adds log10 dqm variables each with up to 10 cases. - linear: Adds one dqm variable for each constraint with linear number of cases. cross_zero: When True, adds zero to the domain of constraint Returns: slack_terms: A list of tuples of the type (variable, case, bias) for the new slack variables. Each tuple is evaluated to the term (bias * variable_case). All terms in the list are summed. """ if slack_method not in ['log2', 'log10', 'linear']: raise ValueError( "expected slack_method to be 'log2', 'log10' or 'linear' " f"but got {slack_method!r}") if isinstance(terms, Iterator): terms = list(terms) if int(constant) != constant or int(lb) != lb or int(ub) != ub or any( int(bias) != bias for _, _, bias in terms): warnings.warn("For constraints with fractional coefficients, " "multiply both sides of the inequality by an " "appropriate factor of ten to attain or " "approximate integer coefficients. ") terms_upper_bound = sum(v for _, _, v in terms if v > 0) terms_lower_bound = sum(v for _, _, v in terms if v < 0) ub_c = min(terms_upper_bound, ub - constant) lb_c = max(terms_lower_bound, lb - constant) if terms_upper_bound <= ub_c and terms_lower_bound >= lb_c: warnings.warn( f'Did not add constraint {label}.' ' This constraint is feasible' ' with any value for state variables.') return [] if ub_c < lb_c: raise ValueError( f'The given constraint ({label}) is infeasible with any value' ' for state variables.') slack_upper_bound = int(ub_c - lb_c) if slack_upper_bound == 0: self.add_linear_equality_constraint(terms, lagrange_multiplier, -ub_c) return [] else: slack_terms = [] zero_constraint = False if cross_zero: if lb_c > 0 or ub_c < 0: zero_constraint = True if slack_method == "log2": num_slack = int(np.floor(np.log2(slack_upper_bound))) slack_coefficients = [2 ** j for j in range(num_slack)] if slack_upper_bound - 2 ** num_slack >= 0: slack_coefficients.append( slack_upper_bound - 2 ** num_slack + 1) for j, s in enumerate(slack_coefficients): sv = self.add_variable(2, f'slack_{label}_{j}') slack_terms.append((sv, 1, s)) if zero_constraint: sv = self.add_variable(2, f'slack_{label}_{num_slack + 1}') slack_terms.append((sv, 1, ub_c)) elif slack_method == "log10": num_dqm_vars = int(np.ceil(np.log10(slack_upper_bound+1))) for j in range(num_dqm_vars): slack_term = list(range(0, min(slack_upper_bound + 1, 10 ** (j + 1)), 10 ** j))[1:] if j < num_dqm_vars - 1 or not zero_constraint: sv = self.add_variable(len(slack_term) + 1, f'slack_{label}_{j}') else: sv = self.add_variable(len(slack_term) + 2, f'slack_{label}_{j}') for i, val in enumerate(slack_term): slack_terms.append((sv, i + 1, val)) if zero_constraint: slack_terms.append((sv, len(slack_term) + 1, ub_c)) elif slack_method == 'linear': slack_term = list(range(1, slack_upper_bound + 1)) if not zero_constraint: sv = self.add_variable(len(slack_term) + 1, f'slack_{label}') else: sv = self.add_variable(len(slack_term) + 2, f'slack_{label}') for i, val in enumerate(slack_term): slack_terms.append((sv, i + 1, val)) if zero_constraint: slack_terms.append((sv, len(slack_term) + 1, ub_c)) self.add_linear_equality_constraint(terms + slack_terms, lagrange_multiplier, -ub_c) return slack_terms def add_variable(self, num_cases, label=None): """Add a discrete variable. Args: num_cases (int): The number of cases in the variable. Must be a positive integer. label (hashable, optional): A label for the variable. Can be any hashable except `None`. Defaults to the length of the discrete quadratic model, if that label is available. Otherwise defaults to the lowest available positive integer label. Returns: The label of the new variable. Raises: ValueError: If `label` already exists as a variable label. TypeError: If `label` is not hashable. """ self.variables._append(label) variable_index = self._cydqm.add_variable(num_cases) assert variable_index + 1 == len(self.variables) return self.variables[-1] # todo: support __copy__ and __deepcopy__ def copy(self): """Return a copy of the discrete quadratic model.""" new = type(self)() new._cydqm = self._cydqm.copy() for v in self.variables: new.variables._append(v) return new def degree(self, v): return self._cydqm.degree(self.variables.index(v)) def energy(self, sample): energy, = self.energies(sample) return energy def energies(self, samples): samples, labels = as_samples(samples, dtype=self._cydqm.case_dtype) # reorder as needed if len(labels) != self.num_variables(): raise ValueError( "Given sample(s) have incorrect number of variables") if self.variables != labels: # need to reorder the samples label_to_idx = dict((v, i) for i, v in enumerate(labels)) try: order = [label_to_idx[v] for v in self.variables] except KeyError: raise ValueError("given samples-like does not match labels") samples = samples[:, order] return np.asarray(self._cydqm.energies(samples)) @classmethod def _from_file_numpy(cls, file_like): magic = file_like.read(len(DATA_MAGIC_PREFIX)) if magic != DATA_MAGIC_PREFIX: raise ValueError("unknown file type, expected magic string {} but " "got {}".format(DATA_MAGIC_PREFIX, magic)) length = np.frombuffer(file_like.read(4), '<u4')[0] start = file_like.tell() data = np.load(file_like) obj = cls.from_numpy_vectors(data['case_starts'], data['linear_biases'], (data['quadratic_row_indices'], data['quadratic_col_indices'], data['quadratic_biases'], ), offset=data.get('offset', 0), ) # move to the end of the data section file_like.seek(start+length, io.SEEK_SET) return obj @classmethod def from_file(cls, file_like): """Construct a DQM from a file-like object. The inverse of :meth:`~DiscreteQuadraticModel.to_file`. """ if isinstance(file_like, (bytes, bytearray, memoryview)): file_like = _BytesIO(file_like) header_info = read_header(file_like, DQM_MAGIC_PREFIX) version = header_info.version header_data = header_info.data if version >= (2, 0): raise ValueError("cannot load a DQM serialized with version " f"{version!r}, try upgrading your dimod version") obj = cls._from_file_numpy(file_like) if header_data['variables']: obj.variables = Variables() for v in VariablesSection.load(file_like): obj.variables._append(v) if len(obj.variables) != obj.num_variables(): raise ValueError("mismatched labels to BQM in given file") return obj @classmethod def from_numpy_vectors(cls, case_starts, linear_biases, quadratic, labels=None, offset=0): """Construct a DQM from five numpy vectors. Args: case_starts (array-like): A length :meth:`~DiscreteQuadraticModel.num_variables` array. The cases associated with variable `v` are in the range `[case_starts[v], cases_starts[v+1])`. linear_biases (array-like): A length :meth:`~DiscreteQuadraticModel.num_cases` array. The linear biases. quadratic (tuple): A three tuple containing: - `irow`: A length :meth:`~DiscreteQuadraticModel.num_case_interactions` array. If the case interactions were defined in a sparse matrix, these would be the row indices. - `icol`: A length :meth:`~DiscreteQuadraticModel.num_case_interactions` array. If the case interactions were defined in a sparse matrix, these would be the column indices. - `quadratic_biases`: A length :meth:`~DiscreteQuadraticModel.num_case_interactions` array. If the case interactions were defined in a sparse matrix, these would be the values. labels (list, optional): The variable labels. Defaults to index-labeled. offset (float): Energy offset of the DQM. Example: >>> dqm = dimod.DiscreteQuadraticModel() >>> u = dqm.add_variable(5) >>> v = dqm.add_variable(3, label='3var') >>> dqm.set_quadratic(u, v, {(0, 2): 1}) >>> vectors = dqm.to_numpy_vectors() >>> new = dimod.DiscreteQuadraticModel.from_numpy_vectors(*vectors) See Also: :meth:`~DiscreteQuadraticModel.to_numpy_vectors` """ obj = cls() obj._cydqm = cyDiscreteQuadraticModel.from_numpy_vectors( case_starts, linear_biases, quadratic, offset) if labels is not None: if len(labels) != obj._cydqm.num_variables(): raise ValueError( "labels does not match the length of the DQM" ) for v in labels: obj.variables._append(v) else: for v in range(obj._cydqm.num_variables()): obj.variables._append() return obj def get_cases(self, v): """The cases of variable `v` as a sequence""" return range(self.num_cases(v)) def get_linear(self, v): """The linear biases associated with variable `v`. Args: v: A variable in the discrete quadratic model. Returns: :class:`~numpy.ndarray`: The linear biases in an array. """ return self._cydqm.get_linear(self.variables.index(v)) def get_linear_case(self, v, case): """The linear bias associated with case `case` of variable `v`. Args: v: A variable in the discrete quadratic model. case (int): The case of `v`. Returns: The linear bias. """ return self._cydqm.get_linear_case(self.variables.index(v), case) def get_quadratic(self, u, v, array=False): """The biases associated with the interaction between `u` and `v`. Args: u: A variable in the discrete quadratic model. v: A variable in the discrete quadratic model. array (bool, optional, default=False): If True, a dense array is returned rather than a dict. Returns: The quadratic biases. If `array=False`, returns a dictionary of the form `{case_u, case_v: bias, ...}` If `array=True`, returns a :meth:`~DiscreteQuadraticModel.num_cases(u)` by :meth:`~DiscreteQuadraticModel.num_cases(v)` numpy array. """ return self._cydqm.get_quadratic( self.variables.index(u), self.variables.index(v), array=array) def get_quadratic_case(self, u, u_case, v, v_case): """The bias associated with the interaction between two cases of `u` and `v`. Args: u: A variable in the discrete quadratic model. u_case (int): The case of `u`. v: A variable in the discrete quadratic model. v_case (int): The case of `v`. Returns: The quadratic bias. """ return self._cydqm.get_quadratic_case( self.variables.index(u), u_case, self.variables.index(v), v_case) def num_cases(self, v=None): """If v is provided, the number of cases associated with v, otherwise the total number of cases in the DQM. """ if v is None: return self._cydqm.num_cases() return self._cydqm.num_cases(self.variables.index(v)) def num_case_interactions(self): """The total number of case interactions.""" return self._cydqm.num_case_interactions() def num_variable_interactions(self): """The total number of variable interactions""" return self._cydqm.num_variable_interactions() def num_variables(self): """The number of variables in the discrete quadratic model.""" return self._cydqm.num_variables() def relabel_variables(self, mapping, inplace=True): if not inplace: return self.copy().relabel_variables(mapping, inplace=True) self.variables._relabel(mapping) return self def relabel_variables_as_integers(self, inplace=True): """Relabel the variables of the DQM to integers. Args: inplace (bool, optional, default=True): If True, the discrete quadratic model is updated in-place; otherwise, a new discrete quadratic model is returned. Returns: tuple: A 2-tuple containing: A discrete quadratic model with the variables relabeled. If `inplace` is set to True, returns itself. dict: The mapping that will restore the original labels. """ if not inplace: return self.copy().relabel_variables_as_integers(inplace=True) return self, self.variables._relabel_as_integers() def set_linear(self, v, biases): """Set the linear biases associated with `v`. Args: v: A variable in the discrete quadratic model. biases (array-like): The linear biases in an array. """ self._cydqm.set_linear(self.variables.index(v), np.asarray(biases)) def set_linear_case(self, v, case, bias): """The linear bias associated with case `case` of variable `v`. Args: v: A variable in the discrete quadratic model. case (int): The case of `v`. bias (float): The linear bias. """ self._cydqm.set_linear_case(self.variables.index(v), case, bias)
class DiscreteQuadraticModel: """Encodes a discrete quadratic model. A discrete quadratic model is a polynomial over discrete variables with terms all of degree two or less. Examples: This example constructs a map coloring with Canadian provinces. To solve the problem we penalize adjacent provinces having the same color. >>> provinces = ["AB", "BC", "ON", "MB", "NB", "NL", "NS", "NT", "NU", ... "PE", "QC", "SK", "YT"] >>> borders = [("BC", "AB"), ("BC", "NT"), ("BC", "YT"), ("AB", "SK"), ... ("AB", "NT"), ("SK", "MB"), ("SK", "NT"), ("MB", "ON"), ... ("MB", "NU"), ("ON", "QC"), ("QC", "NB"), ("QC", "NL"), ... ("NB", "NS"), ("YT", "NT"), ("NT", "NU")] >>> colors = [0, 1, 2, 3] ... >>> dqm = dimod.DiscreteQuadraticModel() >>> for p in provinces: ... _ = dqm.add_variable(4, label=p) >>> for p0, p1 in borders: ... dqm.set_quadratic(p0, p1, {(c, c): 1 for c in colors}) The next examples show how to view and manipulate the model biases. >>> dqm = dimod.DiscreteQuadraticModel() Add the variables to the model >>> u = dqm.add_variable(5) # unlabeled variable with 5 cases >>> v = dqm.add_variable(3, label='v') # labeled variable with 3 cases The linear biases default to 0. They can be read by case or by batch. >>> dqm.get_linear_case(u, 1) 0.0 >>> dqm.get_linear(u) array([0., 0., 0., 0., 0.]) >>> dqm.get_linear(v) array([0., 0., 0.]) The linear biases can be overwritten either by case or in a batch. >>> dqm.set_linear_case(u, 3, 17) >>> dqm.get_linear(u) array([ 0., 0., 0., 17., 0.]) >>> dqm.set_linear(v, [0, -1, 3]) >>> dqm.get_linear(v) array([ 0., -1., 3.]) The quadratic biases can also be manipulated sparsely or densely. >>> dqm.set_quadratic(u, v, {(0, 2): 1.5}) >>> dqm.get_quadratic(u, v) {(0, 2): 1.5} >>> dqm.get_quadratic(u, v, array=True) # as a NumPy array array([[0. , 0. , 1.5], [0. , 0. , 0. ], [0. , 0. , 0. ], [0. , 0. , 0. ], [0. , 0. , 0. ]]) >>> dqm.set_quadratic_case(u, 2, v, 1, -3) >>> dqm.get_quadratic(u, v, array=True) array([[ 0. , 0. , 1.5], [ 0. , 0. , 0. ], [ 0. , -3. , 0. ], [ 0. , 0. , 0. ], [ 0. , 0. , 0. ]]) >>> dqm.get_quadratic(u, v) # doctest:+SKIP {(0, 2): 1.5, (2, 1): -3.0} """ def __init__(self): self.variables = Variables() self._cydqm = cyDiscreteQuadraticModel() @property def adj(self): """dict[hashable, set]: The adjacency structure of the variables.""" return dict( (self.variables[ui], set(self.variables[vi] for vi in neighborhood)) for ui, neighborhood in enumerate(self._cydqm.adj)) def add_linear_equality_constraint(self, terms: LinearTriplets, lagrange_multiplier: float, constant: float): """Add a linear constraint as a quadratic objective. Adds a linear constraint of the form :math:`\sum_{i,k} a_{i,k} x_{i,k} + C = 0` to the discrete quadratic model as a quadratic objective. Args: terms: A list of tuples of the type (variable, case, bias). Each tuple is evaluated to the term (bias * variable_case). All terms in the list are summed. lagrange_multiplier: The coefficient or the penalty strength constant: The constant value of the constraint. """ index_terms = ((self.variables.index(v), c, x) for v, c, x in terms) self._cydqm.add_linear_equality_constraint(index_terms, lagrange_multiplier, constant) def add_variable(self, num_cases, label=None): """Add a discrete variable. Args: num_cases (int): The number of cases in the variable. Must be a positive integer. label (hashable, optional): A label for the variable. Can be any hashable except `None`. Defaults to the length of the discrete quadratic model, if that label is available. Otherwise defaults to the lowest available positive integer label. Returns: The label of the new variable. Raises: ValueError: If `label` already exists as a variable label. TypeError: If `label` is not hashable. """ self.variables._append(label) variable_index = self._cydqm.add_variable(num_cases) assert variable_index + 1 == len(self.variables) return self.variables[-1] # todo: support __copy__ and __deepcopy__ def copy(self): """Return a copy of the discrete quadratic model.""" new = type(self)() new._cydqm = self._cydqm.copy() for v in self.variables: new.variables._append(v) return new def energy(self, sample): energy, = self.energies(sample) return energy def energies(self, samples): samples, labels = as_samples(samples, dtype=self._cydqm.case_dtype) # reorder as needed if len(labels) != self.num_variables(): raise ValueError( "Given sample(s) have incorrect number of variables") if self.variables != labels: # need to reorder the samples label_to_idx = dict((v, i) for i, v in enumerate(labels)) try: order = [label_to_idx[v] for v in self.variables] except KeyError: raise ValueError("given samples-like does not match labels") samples = samples[:, order] return np.asarray(self._cydqm.energies(samples)) @classmethod def _from_file_numpy(cls, file_like): magic = file_like.read(len(DATA_MAGIC_PREFIX)) if magic != DATA_MAGIC_PREFIX: raise ValueError("unknown file type, expected magic string {} but " "got {}".format(DATA_MAGIC_PREFIX, magic)) length = np.frombuffer(file_like.read(4), '<u4')[0] start = file_like.tell() data = np.load(file_like) obj = cls.from_numpy_vectors(data['case_starts'], data['linear_biases'], ( data['quadratic_row_indices'], data['quadratic_col_indices'], data['quadratic_biases'], )) # move to the end of the data section file_like.seek(start + length, io.SEEK_SET) return obj @classmethod def from_file(cls, file_like): """Construct a DQM from a file-like object. The inverse of :meth:`~DiscreteQuadraticModel.to_file`. """ if isinstance(file_like, (bytes, bytearray, memoryview)): file_like = _BytesIO(file_like) magic = file_like.read(len(DQM_MAGIC_PREFIX)) if magic != DQM_MAGIC_PREFIX: raise ValueError("unknown file type, expected magic string {} but " "got {}".format(DQM_MAGIC_PREFIX, magic)) version = tuple(file_like.read(2)) if version[0] != 1: raise ValueError("cannot load a DQM serialized with version {!r}, " "try upgrading your dimod version" "".format(version)) header_len = int(np.frombuffer(file_like.read(4), '<u4')[0]) header_data = json.loads(file_like.read(header_len).decode('ascii')) obj = cls._from_file_numpy(file_like) if header_data['variables']: obj.variables = Variables() for v in VariablesSection.load(file_like): obj.variables._append(v) if len(obj.variables) != obj.num_variables(): raise ValueError("mismatched labels to BQM in given file") return obj @classmethod def from_numpy_vectors(cls, case_starts, linear_biases, quadratic, labels=None): """Construct a DQM from five numpy vectors. Args: case_starts (array-like): A length :meth:`~DiscreteQuadraticModel.num_variables` array. The cases associated with variable `v` are in the range `[case_starts[v], cases_starts[v+1])`. linear_biases (array-like): A length :meth:`~DiscreteQuadraticModel.num_cases` array. The linear biases. quadratic (tuple): A three tuple containing: - `irow`: A length :meth:`~DiscreteQuadraticModel.num_interactions` array. If the case interactions were defined in a sparse matrix, these would be the row indices. - `icol`: A length :meth:`~DiscreteQuadraticModel.num_interactions` array. If the case interactions were defined in a sparse matrix, these would be the column indices. - `quadratic_biases`: A length :meth:`~DiscreteQuadraticModel.num_interactions` array. If the case interactions were defined in a sparse matrix, these would be the values. labels (list, optional): The variable labels. Defaults to index-labeled. Example: >>> dqm = dimod.DiscreteQuadraticModel() >>> u = dqm.add_variable(5) >>> v = dqm.add_variable(3, label='3var') >>> dqm.set_quadratic(u, v, {(0, 2): 1}) >>> vectors = dqm.to_numpy_vectors() >>> new = dimod.DiscreteQuadraticModel.from_numpy_vectors(*vectors) See Also: :meth:`~DiscreteQuadraticModel.to_numpy_vectors` """ obj = cls() obj._cydqm = cyDiscreteQuadraticModel.from_numpy_vectors( case_starts, linear_biases, quadratic) if labels is not None: if len(labels) != obj._cydqm.num_variables(): raise ValueError("labels does not match the length of the DQM") for v in labels: obj.variables._append(v) else: for v in range(obj._cydqm.num_variables()): obj.variables._append() return obj def get_linear(self, v): """The linear biases associated with variable `v`. Args: v: A variable in the discrete quadratic model. Returns: :class:`~numpy.ndarray`: The linear biases in an array. """ return self._cydqm.get_linear(self.variables.index(v)) def get_linear_case(self, v, case): """The linear bias associated with case `case` of variable `v`. Args: v: A variable in the discrete quadratic model. case (int): The case of `v`. Returns: The linear bias. """ return self._cydqm.get_linear_case(self.variables.index(v), case) def get_quadratic(self, u, v, array=False): """The biases associated with the interaction between `u` and `v`. Args: u: A variable in the discrete quadratic model. v: A variable in the discrete quadratic model. array (bool, optional, default=False): If True, a dense array is returned rather than a dict. Returns: The quadratic biases. If `array=False`, returns a dictionary of the form `{case_u, case_v: bias, ...}` If `array=True`, returns a :meth:`~DiscreteQuadraticModel.num_cases(u)` by :meth:`~DiscreteQuadraticModel.num_cases(v)` numpy array. """ return self._cydqm.get_quadratic(self.variables.index(u), self.variables.index(v), array=array) def get_quadratic_case(self, u, u_case, v, v_case): """The bias associated with the interaction between two cases of `u` and `v`. Args: u: A variable in the discrete quadratic model. u_case (int): The case of `u`. v: A variable in the discrete quadratic model. v_case (int): The case of `v`. Returns: The quadratic bias. """ return self._cydqm.get_quadratic_case(self.variables.index(u), u_case, self.variables.index(v), v_case) def num_cases(self, v=None): """If v is provided, the number of cases associated with v, otherwise the total number of cases in the DQM. """ if v is None: return self._cydqm.num_cases() return self._cydqm.num_cases(self.variables.index(v)) def num_case_interactions(self): """The total number of case interactions.""" return self._cydqm.num_case_interactions() def num_variable_interactions(self): """The total number of variable interactions""" return self._cydqm.num_variable_interactions() def num_variables(self): """The number of variables in the discrete quadratic model.""" return self._cydqm.num_variables() def relabel_variables(self, mapping, inplace=True): if not inplace: return self.copy().relabel_variables(mapping, inplace=True) self.variables._relabel(mapping) return self def relabel_variables_as_integers(self, inplace=True): """Relabel the variables of the DQM to integers. Args: inplace (bool, optional, default=True): If True, the discrete quadratic model is updated in-place; otherwise, a new discrete quadratic model is returned. Returns: tuple: A 2-tuple containing: A discrete quadratic model with the variables relabeled. If `inplace` is set to True, returns itself. dict: The mapping that will restore the original labels. """ if not inplace: return self.copy().relabel_variables_as_integers(inplace=True) return self, self.variables._relabel_as_integers() def set_linear(self, v, biases): """Set the linear biases associated with `v`. Args: v: A variable in the discrete quadratic model. biases (array-like): The linear biases in an array. """ self._cydqm.set_linear(self.variables.index(v), np.asarray(biases)) def set_linear_case(self, v, case, bias): """The linear bias associated with case `case` of variable `v`. Args: v: A variable in the discrete quadratic model. case (int): The case of `v`. bias (float): The linear bias. """ self._cydqm.set_linear_case(self.variables.index(v), case, bias)
def test_relabel_not_hashable(self): variables = Variables(self.iterable) mapping = {v: [v] for v in variables} with self.assertRaises(ValueError): variables._relabel(mapping)
def test_swap(self): variables = Variables([1, 0, 3, 4, 5]) variables._relabel({5: 3, 3: 5}) self.assertEqual(variables, [1, 0, 5, 4, 3])