Exemple #1
0
    def sample_exact_k_dpp(self, size, mode='GS', random_state=None):
        """ Sample exactly from :math:`\\operatorname{k-DPP}`.
        A priori the :class:`FiniteDPP <FiniteDPP>` object was instanciated by its likelihood :math:`\\mathbf{L}` kernel so that

        .. math::

            \\mathbb{P}_{\\operatorname{k-DPP}}(\\mathcal{X} = S)
                \\propto \\det \\mathbf{L}_S ~ 1_{|S|=k}

        :param size:
            size :math:`k` of the :math:`\\operatorname{k-DPP}`
        :type size:
            int

        :param mode:
            - ``projection=True``:
                - ``'GS'`` (default): Gram-Schmidt on the rows of :math:`\\mathbf{K}`.
                - ``'Schur'``: Use Schur complement to compute conditionals.

            - ``projection=False``:
                - ``'GS'`` (default): Gram-Schmidt on the rows of the eigenvectors of :math:`\\mathbf{K}` selected in Phase 1.
                - ``'GS_bis'``: Slight modification of ``'GS'``
                - ``'KuTa12'``: Algorithm 1 in :cite:`KuTa12`
        :type mode:
            string, default ``'GS'``

        :return:
            A sample from the corresponding :math:`\\operatorname{k-DPP}`
        :rtype:
            array_like

        .. note::

            Each time you call this function, the sample is added to the :py:attr:`~FiniteDPP.list_of_samples`.

            The latter can be emptied using :py:meth:`~FiniteDPP.flush_samples`

        .. caution::

            The underlying kernel :math:`\\mathbf{K}`, resp. :math:`\\mathbf{L}` must be real valued for now.

        .. seealso::

            - :py:meth:`~FiniteDPP.sample_exact`
            - :py:meth:`~FiniteDPP.sample_mcmc_k_dpp`
        """

        rng = check_random_state(random_state)

        self.sampling_mode = mode

        # If DPP defined via projection kernel
        if self.projection:
            if self.kernel_type == 'correlation':

                if self.K_eig_vals is not None:
                    rank = np.rint(np.sum(self.K_eig_vals)).astype(int)
                elif self.A_zono is not None:
                    rank = self.A_zono.shape[0]
                else:  # self.K is not None
                    rank = np.rint(np.trace(self.K)).astype(int)

                if size != rank:
                    raise ValueError(
                        'size k={} != rank={} for projection correlation K kernel'
                        .format(k, rank))

                if self.K_eig_vals is not None:
                    # K_eig_vals > 0.5 below to get indices where e_vals = 1
                    sampl = proj_dpp_sampler_eig(
                        eig_vecs=self.eig_vecs[:, self.K_eig_vals > 0.5],
                        mode=self.sampling_mode,
                        size=size,
                        random_state=rng)

                elif self.A_zono is not None:
                    warn(
                        'DPP defined via `A_zono`, apriori you want to use `sampl_mcmc`, but you have called `sample_exact`'
                    )

                    self.K_eig_vals = np.ones(rank)
                    self.eig_vecs, _ = la.qr(self.A_zono.T, mode='economic')

                    sampl = proj_dpp_sampler_eig(eig_vecs=self.eig_vecs,
                                                 mode=self.sampling_mode,
                                                 size=size,
                                                 random_state=rng)

                else:
                    sampl = proj_dpp_sampler_kernel(kernel=self.K,
                                                    mode=self.sampling_mode,
                                                    size=size,
                                                    random_state=rng)

            else:  # self.kernel_type == 'likelihood':
                if self.L_eig_vals is not None:
                    # L_eig_vals > 0.5 below to get indices where e_vals = 1
                    sampl = proj_dpp_sampler_eig(
                        eig_vecs=self.eig_vecs[:, self.L_eig_vals > 0.5],
                        mode=self.sampling_mode,
                        size=size,
                        random_state=rng)
                else:
                    self.compute_L()
                    # size > rank treated internally in proj_dpp_sampler_kernel
                    sampl = proj_dpp_sampler_kernel(self.L,
                                                    mode=self.sampling_mode,
                                                    size=size,
                                                    random_state=rng)

            self.size_k_dpp = size
            self.list_of_samples.append(sampl)

        # If eigen decoposition of K, L or L_dual is available USE IT!
        elif self.L_eig_vals is not None:

            # Phase 1
            # Precompute elementary symmetric polynomials
            if self.E_poly is None or self.size_k_dpp < size:
                self.E_poly = elementary_symmetric_polynomials(
                    self.L_eig_vals, size)
            # Select eigenvectors
            V = k_dpp_eig_vecs_selector(self.L_eig_vals,
                                        self.eig_vecs,
                                        size=size,
                                        E_poly=self.E_poly,
                                        random_state=rng)
            # Phase 2
            self.size_k_dpp = size
            sampl = proj_dpp_sampler_eig(V,
                                         self.sampling_mode,
                                         random_state=rng)
            self.list_of_samples.append(sampl)

        elif self.L_dual_eig_vals is not None:
            # There is
            self.L_eig_vals = self.L_dual_eig_vals
            self.eig_vecs =\
                self.L_gram_factor.T.dot(
                    self.L_dual_eig_vecs / np.sqrt(self.L_dual_eig_vals))
            self.sample_exact_k_dpp(size, self.sampling_mode, random_state=rng)

        elif self.K_eig_vals is not None:
            np.seterr(divide='raise')
            self.L_eig_vals = self.K_eig_vals / (1.0 - self.K_eig_vals)
            self.sample_exact_k_dpp(size, self.sampling_mode, random_state=rng)

        # Otherwise eigendecomposition is necessary
        elif self.L_dual is not None:
            self.L_dual_eig_vals, self.L_dual_eig_vecs =\
                la.eigh(self.L_dual)
            self.sample_exact_k_dpp(size, self.sampling_mode, random_state=rng)

        elif self.K is not None:
            self.K_eig_vals, self.eig_vecs = la.eigh(self.K)
            self.sample_exact_k_dpp(size, self.sampling_mode, random_state=rng)

        elif self.L is not None:
            self.L_eig_vals, self.eig_vecs = la.eigh(self.L)
            self.sample_exact_k_dpp(size, self.sampling_mode, random_state=rng)
Exemple #2
0
    def sample_exact(self, mode='GS', random_state=None):
        """ Sample exactly from the corresponding :class:`FiniteDPP <FiniteDPP>` object. The sampling scheme is based on the chain rule with Gram-Schmidt like updates of the conditionals.

        :param mode:

            - ``projection=True``:
                - ``'GS'`` (default): Gram-Schmidt on the rows of :math:`\\mathbf{K}`.
                - ``'Chol'`` :cite:`Pou19` Algorithm 3
                - ``'Schur'``: when DPP defined from correlation kernel ``K``, use Schur complement to compute conditionals

            - ``projection=False``:
                - ``'GS'`` (default): Gram-Schmidt on the rows of the eigenvectors of :math:`\\mathbf{K}` selected in Phase 1.
                - ``'GS_bis'``: Slight modification of ``'GS'``
                - ``'Chol'`` :cite:`Pou19` Algorithm 1
                - ``'KuTa12'``: Algorithm 1 in :cite:`KuTa12`
        :type mode:
            string, default ``'GS'``

        :return:
            A sample from the corresponding :class:`FiniteDPP <FiniteDPP>` object.
        :rtype:
            array_like

        .. note::

            Each time you call this function, the sample is added to the :py:attr:`~FiniteDPP.list_of_samples`.

            The latter can be emptied using :py:meth:`~FiniteDPP.flush_samples`

        .. caution::

            The underlying kernel :math:`\\mathbf{K}`, resp. :math:`\\mathbf{L}` must be real valued for now.

        .. seealso::

            - :ref:`finite_dpps_exact_sampling`
            - :py:meth:`~FiniteDPP.flush_samples`
            - :py:meth:`~FiniteDPP.sample_mcmc`
        """

        rng = check_random_state(random_state)

        self.sampling_mode = mode

        if self.sampling_mode == 'Schur':
            if self.kernel_type == 'correlation' and self.projection:
                self.compute_K()
                sampl = proj_dpp_sampler_kernel(self.K,
                                                self.sampling_mode,
                                                random_state=rng)
                self.list_of_samples.append(sampl)
            else:
                err_print =\
                    ['`Schur` sampling mode is only available for projection DPPs, i.e., `kernel_type="correlation"` and `projection=True`',
                     'Given: {}'.format((self.kernel_type, self.projection))]
                raise ValueError('\n'.join(err_print))

        elif self.sampling_mode == 'Chol':
            self.compute_K()
            if self.kernel_type == 'correlation' and self.projection:
                sampl = proj_dpp_sampler_kernel(self.K,
                                                self.sampling_mode,
                                                random_state=rng)
            else:
                sampl, _ = dpp_sampler_generic_kernel(self.K, random_state=rng)
            self.list_of_samples.append(sampl)

        # If eigen decoposition of K, L or L_dual is available USE IT!
        elif self.K_eig_vals is not None:
            # Phase 1
            if self.kernel_type == 'correlation' and self.projection:
                V = self.eig_vecs[:, self.K_eig_vals > 0.5]
            else:
                V = dpp_eig_vecs_selector(self.K_eig_vals,
                                          self.eig_vecs,
                                          random_state=rng)
            # Phase 2
            if V.shape[1]:
                sampl = proj_dpp_sampler_eig(V,
                                             self.sampling_mode,
                                             random_state=rng)
            else:
                sampl = np.array([])
            self.list_of_samples.append(sampl)

        elif self.L_eig_vals is not None:
            self.K_eig_vals = self.L_eig_vals / (1.0 + self.L_eig_vals)
            self.sample_exact(mode=self.sampling_mode, random_state=rng)

        elif self.L_dual_eig_vals is not None:
            # Phase 1
            V = dpp_eig_vecs_selector_L_dual(self.L_dual_eig_vals,
                                             self.L_dual_eig_vecs,
                                             self.L_gram_factor,
                                             random_state=rng)
            # Phase 2
            sampl = proj_dpp_sampler_eig(V,
                                         self.sampling_mode,
                                         random_state=rng)
            self.list_of_samples.append(sampl)

        # If DPP defined via projection correlation kernel K
        # no eigendecomposition required
        elif (self.K is not None) and self.projection:
            sampl = proj_dpp_sampler_kernel(self.K,
                                            self.sampling_mode,
                                            random_state=rng)
            self.list_of_samples.append(sampl)

        elif self.L_dual is not None:
            self.L_dual_eig_vals, self.L_dual_eig_vecs =\
                la.eigh(self.L_dual)
            self.sample_exact(mode=self.sampling_mode, random_state=rng)

        elif self.K is not None:
            self.K_eig_vals, self.eig_vecs = la.eigh(self.K)
            self.sample_exact(mode=self.sampling_mode, random_state=rng)

        elif self.L is not None:
            self.L_eig_vals, self.eig_vecs = la.eigh(self.L)
            self.sample_exact(mode=self.sampling_mode, random_state=rng)

        # If DPP defined through correlation kernel with parameter 'A_zono'
        # a priori you wish to use the zonotope approximate sampler
        elif self.A_zono is not None:
            warn(
                'DPP defined via `A_zono`, apriori you want to use `sample_mcmc`, but you have called `sample_exact`'
            )

            self.K_eig_vals = np.ones(self.A_zono.shape[0])
            self.eig_vecs, _ = la.qr(self.A_zono.T, mode='economic')

            self.sample_exact(self.sampling_mode, random_state=rng)
Exemple #3
0
    def sample_exact_k_dpp(self, size, mode='GS', **params):
        """ Sample exactly from :math:`\\operatorname{k-DPP}`. A priori the :class:`FiniteDPP <FiniteDPP>` object was instanciated by its likelihood :math:`\\mathbf{L}` kernel so that

        .. math::

            \\mathbb{P}_{\\operatorname{k-DPP}}(\\mathcal{X} = S)
                \\propto \\det \\mathbf{L}_S ~ 1_{|S|=k}

        :param size:
            size :math:`k` of the :math:`\\operatorname{k-DPP}`

        :type size:
            int

        :param mode:
            - ``projection=True``:
                - ``'GS'`` (default): Gram-Schmidt on the rows of :math:`\\mathbf{K}`.
                - ``'Schur'``: Use Schur complement to compute conditionals.

            - ``projection=False``:
                - ``'GS'`` (default): Gram-Schmidt on the rows of the eigenvectors of :math:`\\mathbf{K}` selected in Phase 1.
                - ``'GS_bis'``: Slight modification of ``'GS'``
                - ``'KuTa12'``: Algorithm 1 in :cite:`KuTa12`
                - ``'vfx'``: the dpp-vfx rejection sampler in :cite:`DeCaVa19`

        :type mode:
            string, default ``'GS'``

        :param dict params:
            Dictionary containing the parameters for exact samplers with keys

            ``'random_state'`` (default None)

            - If ``mode='vfx'``

                See :py:meth:`~dppy.exact_sampling.k_dpp_vfx_sampler` for a full list of all parameters accepted by 'vfx' sampling. We report here the most impactful

                + ``'rls_oversample_dppvfx'`` (default 4.0) Oversampling parameter used to construct dppvfx's internal Nystrom approximation. This makes each rejection round slower and more memory intensive, but reduces variance and the number of rounds of rejections.
                + ``'rls_oversample_bless'`` (default 4.0) Oversampling parameter used during bless's internal Nystrom approximation. This makes the one-time pre-processing slower and more memory intensive, but reduces variance and the number of rounds of rejections

                Empirically, a small factor [2,10] seems to work for both parameters. It is suggested to start with
                a small number and increase if the algorithm fails to terminate.

        :return:
            A sample from the corresponding :math:`\\operatorname{k-DPP}`.

            In any case, the sample is appended to the :py:attr:`~FiniteDPP.list_of_samples` attribute as a list.

        :rtype:
            list

        .. note::

            Each time you call this method, the sample is appended to the :py:attr:`~FiniteDPP.list_of_samples` attribute as a list.

            The :py:attr:`~FiniteDPP.list_of_samples` attribute can be emptied using :py:meth:`~FiniteDPP.flush_samples`

        .. caution::

            The underlying kernel :math:`\\mathbf{K}`, resp. :math:`\\mathbf{L}` must be real valued for now.

        .. seealso::

            - :py:meth:`~FiniteDPP.sample_exact`
            - :py:meth:`~FiniteDPP.sample_mcmc_k_dpp`
        """

        rng = check_random_state(params.get('random_state', None))

        self.sampling_mode = mode
        self.size_k_dpp = size

        if self.sampling_mode == 'vfx':
            if self.eval_L is None or self.X_data is None:
                raise ValueError(
                    "The vfx sampler is currently only available for the 'L_eval_X_data' representation."
                )

            params.pop("random_state", None)
            sampl, self.intermediate_sample_info = k_dpp_vfx_sampler(
                size,
                self.intermediate_sample_info,
                self.X_data,
                self.eval_L,
                random_state=rng,
                **params)

        # If DPP defined via projection kernel
        elif self.projection:
            if self.kernel_type == 'correlation':

                if self.K_eig_vals is not None:
                    rank = np.rint(np.sum(self.K_eig_vals)).astype(int)
                elif self.A_zono is not None:
                    rank = self.A_zono.shape[0]
                else:  # self.K is not None
                    rank = np.rint(np.trace(self.K)).astype(int)

                if size != rank:
                    raise ValueError(
                        'size k={} != rank={} for projection correlation K kernel'
                        .format(size, rank))

                if self.K_eig_vals is not None:
                    # K_eig_vals > 0.5 below to get indices where e_vals = 1
                    sampl = proj_dpp_sampler_eig(
                        eig_vecs=self.eig_vecs[:, self.K_eig_vals > 0.5],
                        mode=self.sampling_mode,
                        size=size,
                        random_state=rng)

                elif self.A_zono is not None:
                    warn(
                        'DPP defined via `A_zono`, apriori you want to use `sampl_mcmc`, but you have called `sample_exact`'
                    )

                    self.K_eig_vals = np.ones(rank)
                    self.eig_vecs, _ = la.qr(self.A_zono.T, mode='economic')

                    sampl = proj_dpp_sampler_eig(eig_vecs=self.eig_vecs,
                                                 mode=self.sampling_mode,
                                                 size=size,
                                                 random_state=rng)

                else:
                    sampl = proj_dpp_sampler_kernel(kernel=self.K,
                                                    mode=self.sampling_mode,
                                                    size=size,
                                                    random_state=rng)

            else:  # self.kernel_type == 'likelihood':
                if self.L_eig_vals is not None:
                    # L_eig_vals > 0.5 below to get indices where e_vals = 1
                    sampl = proj_dpp_sampler_eig(
                        eig_vecs=self.eig_vecs[:, self.L_eig_vals > 0.5],
                        mode=self.sampling_mode,
                        size=size,
                        random_state=rng)
                else:
                    self.compute_L()
                    # size > rank treated internally in proj_dpp_sampler_kernel
                    sampl = proj_dpp_sampler_kernel(self.L,
                                                    mode=self.sampling_mode,
                                                    size=size,
                                                    random_state=rng)

        # If eigen decoposition of K, L or L_dual is available USE IT!
        elif self.L_eig_vals is not None:

            # Phase 1
            # Precompute elementary symmetric polynomials
            if self.E_poly is None or self.size_k_dpp < size:
                self.E_poly = elementary_symmetric_polynomials(
                    self.L_eig_vals, size)
            # Select eigenvectors
            V = k_dpp_eig_vecs_selector(self.L_eig_vals,
                                        self.eig_vecs,
                                        size=size,
                                        E_poly=self.E_poly,
                                        random_state=rng)
            # Phase 2
            self.size_k_dpp = size
            sampl = proj_dpp_sampler_eig(V,
                                         self.sampling_mode,
                                         random_state=rng)

        elif self.K_eig_vals is not None:
            np.seterr(divide='raise')
            self.L_eig_vals = self.K_eig_vals / (1.0 - self.K_eig_vals)
            return self.sample_exact_k_dpp(size,
                                           self.sampling_mode,
                                           random_state=rng)

        # Otherwise eigendecomposition is necessary
        elif self.L_dual is not None:
            # L_dual = Phi Phi.T = W Theta W.T
            # L = Phi.T Phi = V Gamma V.T
            # implies Gamma = Theta and V = Phi.T W Theta^{-1/2}
            self.L_eig_vals, L_dual_eig_vecs = la.eigh(self.L_dual)
            self.L_eig_vals = is_geq_0(self.L_eig_vals)
            self.eig_vecs = self.L_gram_factor.T.dot(L_dual_eig_vecs /
                                                     np.sqrt(self.L_eig_vals))
            return self.sample_exact_k_dpp(size,
                                           mode=self.sampling_mode,
                                           random_state=rng)

        elif self.L is not None:
            self.L_eig_vals, self.eig_vecs = la.eigh(self.L)
            self.L_eig_vals = is_geq_0(self.L_eig_vals)
            return self.sample_exact_k_dpp(size,
                                           self.sampling_mode,
                                           random_state=rng)

        elif self.K is not None:
            self.K_eig_vals, self.eig_vecs = la.eigh(self.K)
            self.K_eig_vals = is_in_01(self.K_eig_vals)
            return self.sample_exact_k_dpp(size,
                                           self.sampling_mode,
                                           random_state=rng)

        elif self.eval_L is not None and self.X_data is not None:
            # In case mode!='vfx'
            self.compute_L()
            return self.sample_exact_k_dpp(size,
                                           self.sampling_mode,
                                           random_state=rng)

        else:
            raise ValueError(
                'None of the available samplers could be used based on the current DPP representation. This should never happen, please consider rasing an issue on github at https://github.com/guilgautier/DPPy/issues'
            )

        self.list_of_samples.append(sampl)
        return sampl
Exemple #4
0
    def sample_exact(self, mode='GS', **params):
        """ Sample exactly from the corresponding :class:`FiniteDPP <FiniteDPP>` object.

        :param mode:

            - ``projection=True``:
                - ``'GS'`` (default): Gram-Schmidt on the rows of :math:`\\mathbf{K}`.
                - ``'Chol'`` :cite:`Pou19` Algorithm 3
                - ``'Schur'``: when DPP defined from correlation kernel ``K``, use Schur complement to compute conditionals

            - ``projection=False``:
                - ``'GS'`` (default): Gram-Schmidt on the rows of the eigenvectors of :math:`\\mathbf{K}` selected in Phase 1.
                - ``'GS_bis'``: Slight modification of ``'GS'``
                - ``'Chol'`` :cite:`Pou19` Algorithm 1
                - ``'KuTa12'``: Algorithm 1 in :cite:`KuTa12`
                - ``'vfx'``: the dpp-vfx rejection sampler in :cite:`DeCaVa19`

        :type mode:
            string, default ``'GS'``

        :param dict params:
            Dictionary containing the parameters for exact samplers with keys

            - ``'random_state'`` (default None)
            - If ``mode='vfx'``

                See :py:meth:`~dppy.exact_sampling.dpp_vfx_sampler` for a full list of all parameters accepted by 'vfx' sampling. We report here the most impactful

                + ``'rls_oversample_dppvfx'`` (default 4.0) Oversampling parameter used to construct dppvfx's internal Nystrom approximation. This makes each rejection round slower and more memory intensive, but reduces variance and the number of rounds of rejections.
                + ``'rls_oversample_bless'`` (default 4.0) Oversampling parameter used during bless's internal Nystrom approximation. This makes the one-time pre-processing slower and more memory intensive, but reduces variance and the number of rounds of rejections

                Empirically, a small factor [2,10] seems to work for both parameters. It is suggested to start with a small number and increase if the algorithm fails to terminate.

        :return:
            Returns a sample from the corresponding :class:`FiniteDPP <FiniteDPP>` object. In any case, the sample is appended to the :py:attr:`~FiniteDPP.list_of_samples` attribute as a list.

        :rtype:
            list

        .. note::

            Each time you call this method, the sample is appended to the :py:attr:`~FiniteDPP.list_of_samples` attribute as a list.

            The :py:attr:`~FiniteDPP.list_of_samples` attribute can be emptied using :py:meth:`~FiniteDPP.flush_samples`

        .. caution::

            The underlying kernel :math:`\\mathbf{K}`, resp. :math:`\\mathbf{L}` must be real valued for now.

        .. seealso::

            - :ref:`finite_dpps_exact_sampling`
            - :py:meth:`~FiniteDPP.flush_samples`
            - :py:meth:`~FiniteDPP.sample_mcmc`
        """

        rng = check_random_state(params.get('random_state', None))

        self.sampling_mode = mode

        if self.sampling_mode == 'Schur':
            if self.kernel_type == 'correlation' and self.projection:
                self.compute_K()
                sampl = proj_dpp_sampler_kernel(self.K,
                                                self.sampling_mode,
                                                random_state=rng)
            else:
                err_print =\
                    ['`Schur` sampling mode is only available for projection DPPs, i.e., `kernel_type="correlation"` and `projection=True`',
                     'Given: {}'.format((self.kernel_type, self.projection))]
                raise ValueError('\n'.join(err_print))

        elif self.sampling_mode == 'Chol':
            self.compute_K()
            if self.kernel_type == 'correlation' and self.projection:
                sampl = proj_dpp_sampler_kernel(self.K,
                                                self.sampling_mode,
                                                random_state=rng)
            else:
                sampl, _ = dpp_sampler_generic_kernel(self.K, random_state=rng)

        elif self.sampling_mode == 'vfx':
            if self.eval_L is None or self.X_data is None:
                raise ValueError(
                    'The vfx sampler is currently only available with '
                    '{"L_eval_X_data": (L_eval, X_data)} representation.')

            params.pop("random_state", None)
            sampl, self.intermediate_sample_info = dpp_vfx_sampler(
                self.intermediate_sample_info,
                self.X_data,
                self.eval_L,
                random_state=rng,
                **params)

        # If eigen decoposition of K, L or L_dual is available USE IT!
        elif self.K_eig_vals is not None:
            # Phase 1
            if self.kernel_type == 'correlation' and self.projection:
                V = self.eig_vecs[:, self.K_eig_vals > 0.5]
            else:
                V = dpp_eig_vecs_selector(self.K_eig_vals,
                                          self.eig_vecs,
                                          random_state=rng)
            # Phase 2
            sampl = proj_dpp_sampler_eig(V,
                                         self.sampling_mode,
                                         random_state=rng)

        # elif self.L_dual_eig_vals is not None:
        #     # Phase 1
        #     V = dpp_eig_vecs_selector_L_dual(self.L_dual_eig_vals,
        #                                      self.L_dual_eig_vecs,
        #                                      self.L_gram_factor,
        #                                      random_state=rng)
        #     # Phase 2
        #     sampl = proj_dpp_sampler_eig(V, self.sampling_mode,
        #                                  random_state=rng)
        #

        elif self.L_eig_vals is not None:
            self.K_eig_vals = self.L_eig_vals / (1.0 + self.L_eig_vals)
            return self.sample_exact(mode=self.sampling_mode, random_state=rng)

        elif self.L_dual is not None:
            # L_dual = Phi Phi.T = W Theta W.T
            # L = Phi.T Phi = V Gamma V
            # implies Gamma = Theta and V = Phi.T W Theta^{-1/2}
            self.L_eig_vals, L_dual_eig_vecs = la.eigh(self.L_dual)
            self.L_eig_vals = is_geq_0(self.L_eig_vals)
            self.eig_vecs = self.L_gram_factor.T.dot(L_dual_eig_vecs /
                                                     np.sqrt(self.L_eig_vals))
            return self.sample_exact(mode=self.sampling_mode, random_state=rng)

        # If DPP defined via projection correlation kernel K
        # no eigendecomposition required
        elif self.K is not None and self.projection:
            sampl = proj_dpp_sampler_kernel(self.K,
                                            self.sampling_mode,
                                            random_state=rng)

        elif self.K is not None:
            self.K_eig_vals, self.eig_vecs = la.eigh(self.K)
            self.K_eig_vals = is_in_01(self.K_eig_vals)
            return self.sample_exact(mode=self.sampling_mode, random_state=rng)

        elif self.L is not None:
            self.L_eig_vals, self.eig_vecs = la.eigh(self.L)
            self.L_eig_vals = is_geq_0(self.L_eig_vals)
            return self.sample_exact(mode=self.sampling_mode, random_state=rng)

        # If DPP defined through correlation kernel with parameter 'A_zono'
        # a priori you wish to use the zonotope approximate sampler
        elif self.A_zono is not None:
            warn(
                'DPP defined via `A_zono`, apriori you want to use `sample_mcmc`, but you have called `sample_exact`'
            )

            self.K_eig_vals = np.ones(self.A_zono.shape[0])
            self.eig_vecs, _ = la.qr(self.A_zono.T, mode='economic')
            return self.sample_exact(mode=self.sampling_mode, random_state=rng)

        elif self.eval_L is not None and self.X_data is not None:
            self.compute_L()
            return self.sample_exact(mode=self.sampling_mode, random_state=rng)

        else:
            raise ValueError(
                'None of the available samplers could be used based on the current DPP representation. This should never happen, please consider rasing an issue on github at https://github.com/guilgautier/DPPy/issues'
            )

        self.list_of_samples.append(sampl)
        return sampl
Exemple #5
0
    def sample(self, mode='Wilson', root=None, random_state=None):
        """ Sample a spanning of the underlying graph uniformly at random.
        It generates a networkx graph object.

        :param mode:

            Markov-chain-based samplers:

            - ``'Wilson'``, ``'Aldous-Broder'``

            Chain-rule-based samplers:

            - ``'GS'``, ``'GS_bis'``, ``'KuTa12'`` from eigenvectors
            - ``'Schur'``, ``'Chol'``, from :math:`\\mathbf{K}` correlation kernel

        :type mode:
            string, default ``'Wilson'``

        :param root:
            Starting node of the random walk when using Markov-chain-based samplers
        :type root:
            int

        :param random_state:
        :type random_state:
            None, np.random, int, np.random.RandomState

        .. seealso::

            - Wilson :cite:`PrWi98`
            - Aldous-Broder :cite:`Ald90`
            - :py:meth:`~dppy.FiniteDPP.sample`
        """

        rng = check_random_state(random_state)

        self.sampling_mode = mode

        if self.sampling_mode in self._sampling_modes['markov-chain']:
            if self.sampling_mode == 'Wilson':
                sampl = ust_sampler_wilson(self.neighbors, random_state=rng)

            elif self.sampling_mode == 'Aldous-Broder':
                sampl = ust_sampler_aldous_broder(self.neighbors,
                                                  random_state=rng)

        elif self.sampling_mode in self._sampling_modes['spectral-method']:

            self.compute_kernel_eig_vecs()
            dpp_sample = proj_dpp_sampler_eig(self.kernel_eig_vecs,
                                              mode=self.sampling_mode,
                                              random_state=rng)

            sampl = nx.Graph()
            sampl.add_edges_from([self.edges[e] for e in dpp_sample])

        elif self.sampling_mode in self._sampling_modes['projection-K-kernel']:

            self.compute_kernel()
            dpp_sample = proj_dpp_sampler_kernel(self.kernel,
                                                 mode=self.sampling_mode,
                                                 random_state=rng)

            sampl = nx.Graph()
            sampl.add_edges_from([self.edges[e] for e in dpp_sample])

        else:
            err_print = '\n'.join(
                'Invalid sampling mode',
                'Chose from: {}'.format(self._sampling_modes),
                'Given {}'.format(mode))
            raise ValueError()

        self.list_of_samples.append(sampl)
Exemple #6
0
    def sample_exact(self, mode='GS'):
        """ Sample exactly from the corresponding :class:`FiniteDPP <FiniteDPP>` object. The sampling scheme is based on the chain rule with Gram-Schmidt like updates of the conditionals.

        :param mode:

            - ``projection=True``:
                - ``'GS'`` (default): Gram-Schmidt on the rows of :math:`\mathbf{K}`.
                - ``'Schur'``: Use Schur complement to compute conditionals.

            - ``projection=False``:
                - ``'GS'`` (default): Gram-Schmidt on the rows of the eigenvectors of :math:`\mathbf{K}` selected in Phase 1.
                - ``'GS_bis'``: Slight modification of ``'GS'``
                - ``'KuTa12'``: Algorithm 1 in :cite:`KuTa12`
        :type mode:
            string, default ``'GS'``

        :return:
            A sample from the corresponding :class:`FiniteDPP <FiniteDPP>` object.
        :rtype:
            array_like

        .. note::

            Each time you call this function, the sample is added to the :py:attr:`~FiniteDPP.list_of_samples`.

            The latter can be emptied using :py:meth:`~FiniteDPP.flush_samples`

        .. caution::

            The underlying kernel :math:`\mathbf{K}`, resp. :math:`\mathbf{L}` must be real valued for now.

        .. seealso::

            - :ref:`finite_dpps_exact_sampling`
            - :py:meth:`~FiniteDPP.flush_samples`
            - :py:meth:`~FiniteDPP.sample_mcmc`
        """

        self.sampling_mode = mode

        if self.sampling_mode == 'Chol':
            self.compute_K()
            if self.projection:
                sampl = proj_dpp_sampler_kernel(self.K, self.sampling_mode)
            else:
                sampl = dpp_sampler_generic_kernel(self.K)[0]
            self.list_of_samples.append(sampl)

        # If eigen decoposition of K, L or L_dual is available USE IT!
        elif self.K_eig_vals is not None:
            # Phase 1
            V = dpp_eig_vecs_selector(self.K_eig_vals, self.eig_vecs)
            # Phase 2
            if V.shape[1]:
                sampl = proj_dpp_sampler_eig(V, self.sampling_mode)
            else:
                sampl = np.array([])
            self.list_of_samples.append(sampl)

        elif self.L_eig_vals is not None:
            self.K_eig_vals = self.L_eig_vals / (1.0 + self.L_eig_vals)
            self.sample_exact(self.sampling_mode)

        elif self.L_dual_eig_vals is not None:
            # Phase 1
            V = dpp_eig_vecs_selector_L_dual(self.L_dual_eig_vals,
                                             self.L_dual_eig_vecs,
                                             self.L_gram_factor)
            # Phase 2
            sampl = proj_dpp_sampler_eig(V, self.sampling_mode)
            self.list_of_samples.append(sampl)

        # If DPP defined via projection correlation kernel K
        # no eigendecomposition required
        elif (self.K is not None) and self.projection:
            sampl = proj_dpp_sampler_kernel(self.K, self.sampling_mode)
            self.list_of_samples.append(sampl)

        elif self.L_dual is not None:
            self.L_dual_eig_vals, self.L_dual_eig_vecs =\
                la.eigh(self.L_dual)
            self.sample_exact(self.sampling_mode)

        elif self.K is not None:
            self.K_eig_vals, self.eig_vecs = la.eigh(self.K)
            self.sample_exact(self.sampling_mode)

        elif self.L is not None:
            self.L_eig_vals, self.eig_vecs = la.eigh(self.L)
            self.sample_exact(self.sampling_mode)

        # If DPP defined through correlation kernel with parameter 'A_zono'
        # a priori you wish to use the zonotope approximate sampler
        elif self.A_zono:
            warn(
                'DPP defined via `A_zono`, apriori you want to use `sampl_mcmc`, but you have called `sample_exact`'
            )

            self.K_eig_vals = np.ones(self.A_zono.shape[0])
            self.eig_vecs, _ = la.qr(self.A_zono.T, mode='economic')

            self.sample_exact(self.sampling_mode)