Example #1
0
    def apply(self, nn_state, samples):
        r"""Computes the swap operator which an estimator for the 2nd Renyi
        entropy.

        The swap operator requires access to two identical copies of a
        wavefunction. In practice, this translates to the requirement of
        having two independent sets of samples from the wavefunction replicas.
        For this purpose, the batch of samples stored in the param samples is
        split into two subsets. Although this procedure is designed to break
        the autocorrelation between the samples, it must be used with caution.
        For a fully unbiased estimate of the entanglement entropy, the batch
        of samples needs to be built from two independent initializations of
        the wavefunction each having a different random number generator.

        :param nn_state: The NeuralState that drew the samples.
        :type nn_state: qucumber.nn_states.NeuralStateBase
        :param samples: A batch of samples to calculate the observable on.
        :type samples: torch.Tensor
        """
        samples = samples.to(device=nn_state.device)

        # assuming each sample is independent, we perform a swap against
        #  the next sample in the batch (looping around to the first if we've
        #  reached the end of the batch).
        samples1 = samples
        samples2 = torch.roll(samples1, 1, 0)
        samples1_, samples2_ = swap(samples1.clone(), samples2.clone(), self.A)

        weight1 = nn_state.importance_sampling_weight(samples1_, samples1)
        weight2 = nn_state.importance_sampling_weight(samples2_, samples2)
        weight = cplx.elementwise_mult(weight1, weight2)

        return cplx.real(weight)
Example #2
0
    def apply(self, nn_state, samples):
        r"""Computes the magnetization along Y of each sample in the given batch of samples.

        :param nn_state: The WaveFunction that drew the samples.
        :type nn_state: qucumber.nn_states.WaveFunction
        :param samples: A batch of samples to calculate the observable on.
                        Must be using the :math:`\sigma_i = 0, 1` convention.
        :type samples: torch.Tensor
        """
        samples = samples.to(device=nn_state.device)

        # vectors of shape: (2, num_samples,)
        psis = nn_state.psi(samples)
        psi_ratio_sum = torch.zeros_like(psis)

        for i in range(samples.shape[-1]):  # sum over spin sites

            coeff = -to_pm1(samples[:, i])
            coeff = cplx.make_complex(torch.zeros_like(coeff), coeff)

            flip_spin(i, samples)  # flip the spin at site i

            # compute ratio of psi_(-i) / psi, multiply it by the appropriate
            # eigenvalue, and add it to the running sum
            psi_ratio = nn_state.psi(samples)
            psi_ratio = cplx.elementwise_division(psi_ratio, psis)
            psi_ratio = cplx.elementwise_mult(psi_ratio, coeff)
            psi_ratio_sum.add_(psi_ratio)

            flip_spin(i, samples)  # flip it back

        # take real part (imaginary part should be approximately zero)
        # and divide by number of spins
        return psi_ratio_sum[0].div_(samples.shape[-1])
Example #3
0
    def test_elementwise_mult(self):
        z1 = torch.tensor([[2, 3, 5], [6, 7, 2]], dtype=torch.double)
        z2 = torch.tensor([[1, 2, 2], [3, 4, 8]], dtype=torch.double)

        expect = torch.tensor([[-16, -22, -6], [12, 26, 44]], dtype=torch.double)

        self.assertTensorsEqual(
            cplx.elementwise_mult(z1, z2),
            expect,
            msg="Elementwise multiplication failed!",
        )
Example #4
0
    def apply(self, nn_state, samples):
        r"""Computes the swap operator which an estimator for the 2nd Renyi
        entropy.

        The swap operator requires access to two identical copies of a
        wavefunction. In practice, this translates to the requirement of
        having two independent sets of samples from the wavefunction replicas.
        For this purpose, the batch of samples stored in the param samples is
        split into two subsets. Although this procedure is designed to break
        the autocorrelation between the samples, it must be used with caution.
        For a fully unbiased estimate of the entanglement entropy, the batch
        of samples needs to be built from two independent initializations of
        the wavefunction each having a different random number generator.

        :param nn_state: The WaveFunction that drew the samples.
        :type nn_state: qucumber.nn_states.WaveFunctionBase
        :param samples: A batch of samples to calculate the observable on.
                        Must be using the :math:`\sigma_i = 0, 1` convention.
        :type samples: torch.Tensor
        """
        samples = samples.to(device=nn_state.device, copy=True)

        # split the batch of samples into two equal batches
        # if their total number is odd, the last sample is ignored
        _ns = samples.shape[0] // 2
        samples1 = samples[:_ns, :]
        samples2 = samples[_ns:_ns * 2, :]

        psi_ket1 = nn_state.psi(samples1)
        psi_ket2 = nn_state.psi(samples2)

        psi_ket = cplx.elementwise_mult(psi_ket1, psi_ket2)
        psi_ket_star = cplx.conjugate(psi_ket)

        samples1_, samples2_ = swap(samples1, samples2, self.A)
        psi_bra1 = nn_state.psi(samples1_)
        psi_bra2 = nn_state.psi(samples2_)

        psi_bra = cplx.elementwise_mult(psi_bra1, psi_bra2)
        psi_bra_star = cplx.conjugate(psi_bra)
        return cplx.real(cplx.elementwise_division(psi_bra_star, psi_ket_star))
Example #5
0
    def apply(self, nn_state, samples):
        r"""Computes the average magnetization along Y of each sample in the given batch of samples.

        Assumes that the computational basis that the NeuralState was trained
        on was the Z basis.

        :param nn_state: The NeuralState that drew the samples.
        :type nn_state: qucumber.nn_states.NeuralStateBase
        :param samples: A batch of samples to calculate the observable on.
                        Must be using the :math:`\sigma_i = 0, 1` convention.
        :type samples: torch.Tensor
        """
        samples = samples.to(device=nn_state.device)

        # vectors of shape: (2, num_samples,)
        denom = nn_state.importance_sampling_denominator(samples)
        numer_sum = torch.zeros_like(denom)

        for i in range(samples.shape[-1]):  # sum over spin sites
            coeff = to_pm1(samples[..., i])
            coeff = cplx.make_complex(torch.zeros_like(coeff), coeff)

            samples_ = flip_spin(i, samples.clone())  # flip the spin at site i

            # compute the numerator of the importance and add it to the running sum
            numer = nn_state.importance_sampling_numerator(samples_, samples)
            numer = cplx.elementwise_mult(numer, coeff)
            numer_sum.add_(numer)

        numer_sum = cplx.elementwise_division(numer_sum, denom)
        # take real part (imaginary part should be approximately zero)
        # and divide by number of spins
        res = cplx.real(numer_sum).div_(samples.shape[-1])
        if self.absolute:
            return res.abs_()
        else:
            return res