コード例 #1
0
ファイル: test_variables.py プロジェクト: pau557/dimod
    def test_permissive_deprecated_api(self):
        variables = Variables([0, 1])

        with self.assertWarns(DeprecationWarning):
            variables.relabel({0: 'a', 1: 'b', 2: 'c'})

        self.assertEqual(variables, Variables('ab'))
コード例 #2
0
ファイル: test_variables.py プロジェクト: xpin/dimod
    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'))
コード例 #3
0
 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)
コード例 #4
0
ファイル: test_variables.py プロジェクト: xpin/dimod
 def test_count(self):
     variables = Variables([1, 1, 1, 4, 5])
     self.assertEqual(list(variables), [1, 4, 5])
     for v in range(10):
         if v in variables:
             self.assertEqual(variables.count(v), 1)
         else:
             self.assertEqual(variables.count(v), 0)
コード例 #5
0
ファイル: test_variables.py プロジェクト: pau557/dimod
    def test_pprint(self):
        import pprint

        variables = Variables(range(10))
        variables._append('a')  # make not range

        string = pprint.pformat(variables, width=20)
        target = '\n'.join([
            "Variables([0,", "           1,", "           2,", "           3,",
            "           4,", "           5,", "           6,", "           7,",
            "           8,", "           9,", "           'a'])"
        ])
        self.assertEqual(string, target)
コード例 #6
0
ファイル: test_variables.py プロジェクト: xpin/dimod
    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)
コード例 #7
0
    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
コード例 #8
0
    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
コード例 #9
0
    def __init__(self, record, variables, info, vartype):

        # make sure that record is a numpy recarray and that it has the expected fields
        if not isinstance(record, np.recarray):
            raise TypeError("input record must be a numpy recarray")
        elif not set(self._REQUIRED_FIELDS).issubset(record.dtype.fields):
            raise ValueError(
                "input record must have {}, {} and {} as fields".format(
                    *self._REQUIRED_FIELDS))
        self._record = record

        num_samples, num_variables = record.sample.shape

        self._variables = variables = Variables(variables)
        if len(variables) != num_variables:
            msg = (
                "mismatch between number of variables in record.sample ({}) "
                "and labels ({})").format(num_variables, len(variables))
            raise ValueError(msg)

        # cast info to a dict if it's a mapping or similar
        if not isinstance(info, dict):
            info = dict(info)
        self._info = info

        # vartype is checked by vartype_argument decorator
        self._vartype = vartype
コード例 #10
0
ファイル: samples.py プロジェクト: pau557/dimod
    def __init__(self, samples, variables):
        self._samples = samples

        if isinstance(variables, Variables):
            # we will be treating this as immutable so we don't need to
            # recreate it
            self._variables = variables
        else:
            self._variables = Variables(variables)
コード例 #11
0
ファイル: test_variables.py プロジェクト: pau557/dimod
 def test_unlike_types_eq_hash(self):
     zeros = [
         0, 0.0,
         np.int8(0),
         np.float64(0),
         fractions.Fraction(0),
         decimal.Decimal(0)
     ]
     for perm in itertools.permutations(zeros, len(zeros)):
         variables = Variables(perm)
         self.assertEqual(len(variables), len(set(zeros)))
コード例 #12
0
ファイル: constrained.py プロジェクト: wbernoudy/dimod
    def __init__(self):
        self.variables = TypedVariables()
        self.labels = Variables()
        self.constraints = {}

        # discrete variable tracking, we probably can do this with less memory
        # but for now let's keep it simple
        self.discrete: Set[Hashable] = set(
        )  # collection of discrete constraints
        self._discrete: Set[Variable] = set(
        )  # collection of all variables used in discrete
コード例 #13
0
    def test_conflict(self):
        variables = Variables()
        variables._append(1)
        variables._append()  # should take the label 0
        variables._append()

        self.assertEqual(variables, [1, 0, 2])
コード例 #14
0
    def test_permissive(self):
        variables = Variables()

        with self.assertRaises(ValueError):
            variables.index(0)

        self.assertEqual(variables.index(0, permissive=True), 0)
        self.assertEqual(variables.index(0, permissive=True), 0)
        self.assertEqual(variables.index('a', permissive=True), 1)
コード例 #15
0
ファイル: test_variables.py プロジェクト: xpin/dimod
 def test_len(self):
     variables = Variables(self.iterable)
     self.assertEqual(len(variables), len(self.iterable))
コード例 #16
0
ファイル: test_variables.py プロジェクト: xpin/dimod
 def test_equality(self):
     variables = Variables(self.iterable)
     self.assertEqual(variables, self.iterable)
コード例 #17
0
ファイル: test_variables.py プロジェクト: xpin/dimod
 def test_iterable(self):
     variables = Variables(self.iterable)
     self.assertEqual(list(variables), list(self.iterable))
コード例 #18
0
ファイル: test_variables.py プロジェクト: xpin/dimod
 def test_index(self):
     variables = Variables(self.iterable)
     for idx, v in enumerate(self.iterable):
         self.assertEqual(variables.index(v), idx)
コード例 #19
0
ファイル: test_variables.py プロジェクト: xpin/dimod
 def test_count_unhashable(self):
     variables = Variables(self.iterable)
     self.assertEqual(variables.count([]), 0)
コード例 #20
0
ファイル: test_variables.py プロジェクト: xpin/dimod
 def test_contains_unhashable(self):
     variables = Variables(self.iterable)
     self.assertFalse([] in variables)
コード例 #21
0
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)
コード例 #22
0
ファイル: test_variables.py プロジェクト: pau557/dimod
 def test_repr_empty(self):
     variables = Variables()
     self.assertEqual(repr(variables), 'Variables()')
コード例 #23
0
ファイル: test_variables.py プロジェクト: xpin/dimod
 def test_len(self):
     variables = Variables('aaaaa')
     self.assertEqual(len(variables), 1)
コード例 #24
0
ファイル: test_variables.py プロジェクト: pau557/dimod
 def test_repr_mixed(self):
     variables = Variables('abc')
     self.assertEqual(repr(variables), "Variables(['a', 'b', 'c'])")
コード例 #25
0
ファイル: test_variables.py プロジェクト: xpin/dimod
 def test_duplicates(self):
     # should have no duplicates
     variables = Variables(['a', 'b', 'c', 'b'])
     self.assertEqual(list(variables), ['a', 'b', 'c'])
コード例 #26
0
ファイル: test_variables.py プロジェクト: xpin/dimod
 def test_relabel_not_hashable(self):
     variables = Variables(self.iterable)
     mapping = {v: [v] for v in variables}
     with self.assertRaises(ValueError):
         variables.relabel(mapping)
コード例 #27
0
ファイル: test_variables.py プロジェクト: pau557/dimod
 def test_repr_range(self):
     self.assertEqual(repr(Variables(range(10))),
                      'Variables({!r})'.format(list(range(10))))
     self.assertEqual(repr(Variables(range(11))), 'Variables(range(0, 11))')
コード例 #28
0
ファイル: test_variables.py プロジェクト: xpin/dimod
 def test_index_api(self):
     variables = Variables(self.iterable)
     self.assertTrue(hasattr(variables, 'index'))
     self.assertTrue(callable(variables.index))
     self.assertTrue(isinstance(variables.index, abc.Mapping))
コード例 #29
0
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)
コード例 #30
0
 def __init__(self):
     self.variables = Variables()
     self._cydqm = cyDiscreteQuadraticModel()