Example #1
0
    def __init__(self, q_a: np.ndarray, num_reps: int) -> None:
        """
        Initialize the variables for semidefinite program.

        :param q_a: The fixed SDP variable.
        :param num_reps: The number of parallel repetitions.
        """
        self._q_a = q_a
        self._num_reps = num_reps

        self._sys = list(range(1, 2 * self._num_reps, 2))
        if len(self._sys) == 1:
            self._sys = self._sys[0]

        self._dim = 2 * np.ones((1, 2 * self._num_reps)).astype(int).flatten()
        self._dim = self._dim.tolist()

        # For the dual problem, the following unitary operator is used to
        # permute the subsystems of Alice and Bob which is defined by the
        # action:
        #   π(y1 ⊗ y2 ⊗ x1 ⊗ x2) = y1 ⊗ x1 ⊗ y2 ⊗ x2
        # for all y1 ∈ Y1, y2 ∈ Y2, x1 ∈ X1, x2 ∈ X2.).
        l_1 = list(range(1, self._num_reps + 1))
        l_2 = list(range(self._num_reps + 1, self._num_reps**2 + 1))
        if self._num_reps == 1:
            self._pperm = np.array([1])
        else:
            perm = [*sum(zip(l_1, l_2), ())]
            self._pperm = permutation_operator(2, perm)
    def test_standard_swap_list_dim(self):
        """Generates the standard swap operator on two qubits."""
        expected_res = np.array(
            [[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]]
        )

        res = permutation_operator([2, 2], [2, 1])

        bool_mat = np.isclose(res, expected_res)
        self.assertEqual(np.all(bool_mat), True)
def antisymmetric_projection(dim: int,
                             p_param: int = 2,
                             partial: bool = False) -> sparse.lil_matrix:
    """
    Produce the projection onto the antisymmetric subspace.

    Produces the orthogonal projection onto the anti-symmetric subspace of `p`
    copies of `dim`-dimensional space. If `partial = True`, then the
    antisymmetric projection (PA) isn't the orthogonal projection itself, but
    rather a matrix whose columns form an orthonormal basis for the symmetric
    subspace (and hence the PA * PA' is the orthogonal projection onto the
    symmetric subspace.)

    References:
        [1] Wikipedia: Anti-symmetric operator
        https://en.wikipedia.org/wiki/Anti-symmetric_operator

    :param dim: The dimension of the local systems.
    :param p_param: Default value of 2.
    :param partial: Default value of 0.
    :return: Projection onto the antisymmetric subspace.
    """
    dimp = dim**p_param

    if p_param == 1:
        return sparse.eye(dim)
    # The antisymmetric subspace is empty if `dim < p`.
    if dim < p_param:
        return sparse.lil_matrix((dimp, dimp * (1 - partial)))

    p_list = np.array(list(permutations(np.arange(1, p_param + 1))))
    p_fac = p_list.shape[0]

    anti_proj = sparse.lil_matrix((dimp, dimp))
    for j in range(p_fac):
        anti_proj += perm_sign(p_list[j, :]) * permutation_operator(
            dim * np.ones(p_param), p_list[j, :], False, True)
    anti_proj = anti_proj / p_fac

    if partial:
        anti_proj = anti_proj.todense()
        anti_proj = sparse.lil_matrix(linalg.orth(anti_proj))
    return anti_proj
    def test_sparse_option(self):
        """Sparse swap operator on two qutrits."""
        res = permutation_operator(3, [2, 1], False, True)

        self.assertEqual(res[0][0], 1)
def antisymmetric_projection(dim: int,
                             p_param: int = 2,
                             partial: bool = False) -> sparse.lil_matrix:
    r"""
    Produce the projection onto the antisymmetric subspace [WIKASYM]_.

    Produces the orthogonal projection onto the anti-symmetric subspace of `p`
    copies of `dim`-dimensional space. If `partial = True`, then the
    antisymmetric projection (PA) isn't the orthogonal projection itself, but
    rather a matrix whose columns form an orthonormal basis for the symmetric
    subspace (and hence the PA * PA' is the orthogonal projection onto the
    symmetric subspace.)

    Examples
    ==========

    The :math:`2`-dimensional antisymmetric projection with :math:`p=1` is given
    as :math:`2`-by-:math:`2` identity matrix

    .. math::
        \begin{pmatrix}
            1 & 0 \\
            0 & 1
        \end{pmatrix}.

    Using `toqito`, we can see this gives the proper result.

    >>> from toqito.perms.antisymmetric_projection import antisymmetric_projection
    >>> antisymmetric_projection(2, 1).todense()
    matrix([[1., 0.],
            [0., 1.]])

    When the :math:`p` value is greater than the dimension of the antisymmetric
    projection, this just gives the matrix consisting of all zero entries. For
    instance, when :math:`d = 2` and :math:`p = 3` we have that

    .. math::
        \begin{pmatrix}
            0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
            0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
            0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
            0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
            0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
            0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
            0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
            0 & 0 & 0 & 0 & 0 & 0 & 0 & 0
        \end{pmatrix}.

    Using `toqito` we can see this gives the proper result.

    >>> from toqito.perms.antisymmetric_projection import antisymmetric_projection
    >>> antisymmetric_projection(2, 3).todense()
    matrix([[0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0., 0., 0.]])

    References
    ==========
    .. [WIKASYM] Wikipedia: Anti-symmetric operator
        https://en.wikipedia.org/wiki/Anti-symmetric_operator

    :param dim: The dimension of the local systems.
    :param p_param: Default value of 2.
    :param partial: Default value of 0.
    :return: Projection onto the antisymmetric subspace.
    """
    dimp = dim**p_param

    if p_param == 1:
        return sparse.eye(dim)
    # The antisymmetric subspace is empty if `dim < p`.
    if dim < p_param:
        return sparse.lil_matrix((dimp, dimp * (1 - partial)))

    p_list = np.array(list(permutations(np.arange(1, p_param + 1))))
    p_fac = p_list.shape[0]

    anti_proj = sparse.lil_matrix((dimp, dimp))
    for j in range(p_fac):
        anti_proj += perm_sign(p_list[j, :]) * permutation_operator(
            dim * np.ones(p_param), p_list[j, :], False, True)
    anti_proj = anti_proj / p_fac

    if partial:
        anti_proj = anti_proj.todense()
        anti_proj = sparse.lil_matrix(linalg.orth(anti_proj))
    return anti_proj
Example #6
0
def counterfeit_attack(q_a: np.ndarray, num_reps: int = 1) -> float:
    r"""
    Compute probability of counterfeiting quantum money [MVW12]_.

    The primal problem for the :math:`n`-fold parallel repetition is given as
    follows:

    .. math::
        \begin{equation}
            \begin{aligned}
                \text{maximize:} \quad & \langle W_{\pi} \left(Q^{\otimes n}
                                         \right) W_{\pi}^*, X \rangle \\
                \text{subject to:} \quad & \text{Tr}_{\mathcal{Y}^{\otimes n}
                                           \otimes \mathcal{Z}^{\otimes n}}(X)
                                           = \mathbb{I}_{\mathcal{X}^{\otimes
                                           n}},\\
                                           & X \in \text{Pos}(
                                           \mathcal{Y}^{\otimes n}
                                           \otimes \mathcal{Z}^{\otimes n}
                                           \otimes \mathcal{X}^{\otimes n})
            \end{aligned}
        \end{equation}

    The dual problem for the :math:`n`-fold parallel repetition is given as
    follows:

    .. math::
            \begin{equation}
                \begin{aligned}
                    \text{minimize:} \quad & \text{Tr}(Y) \\
                    \text{subject to:} \quad & \mathbb{I}_{\mathcal{Y}^{\otimes n}
                    \otimes \mathcal{Z}^{\otimes n}} \otimes Y \geq W_{\pi}
                    \left( Q^{\otimes n} \right) W_{\pi}^*, \\
                    & Y \in \text{Herm} \left(\mathcal{X}^{\otimes n} \right)
                \end{aligned}
            \end{equation}

    Examples
    ==========

    Wiesner's original quantum money scheme [Wies83]_ was shown in [MVW12]_ to
    have an optimal probability of 3/4 for succeeding a counterfeiting attack.

    Specifically, in the single-qubit case, Wiesner's quantum money scheme
    corresponds to the following ensemble:

    .. math::
        \left{
            \left( \frac{1}{4}, |0\rangle \right),
            \left( \frac{1}{4}, |1\rangle \right),
            \left( \frac{1}{4}, |+\rangle \right),
            \left( \frac{1}{4}, |-\rangle \right)
        \right},

    which yields the operator

    .. math::
        Q = \frac{1}{4} \left(
            |000\rangle + \langle 000| + |111\rangle \langle 111| +
            |+++\rangle + \langle +++| + |---\rangle \langle ---|
        \right)

    We can see that the optimal value we obtain in solving the SDP is 3/4.

    >>> from toqito.states.operations.tensor import tensor
    >>> from toqito.core.ket import ket
    >>> import numpy as np
    >>> e_0, e_1 = ket(2, 0), ket(2, 1)
    >>> e_p = (e_0 + e_1) / np.sqrt(2)
    >>> e_m = (e_0 - e_1) / np.sqrt(2)
    >>>
    >>> e_000 = tensor(e_0, e_0, e_0)
    >>> e_111 = tensor(e_1, e_1, e_1)
    >>> e_ppp = tensor(e_p, e_p, e_p)
    >>> e_mmm = tensor(e_m, e_m, e_m)
    >>>
    >>> q_a = 1 / 4 * (e_000 * e_000.conj().T + e_111 * e_111.conj().T + \
    >>> e_ppp * e_ppp.conj().T + e_mmm * e_mmm.conj().T)
    >>> print(counterfeit_attack(q_a))
    0.749999999967631

    References
    ==========
    .. [MVW12] Abel Molina, Thomas Vidick, and John Watrous.
        "Optimal counterfeiting attacks and generalizations for Wiesner’s
        quantum money."
        Conference on Quantum Computation, Communication, and Cryptography.
        Springer, Berlin, Heidelberg, 2012.
        https://arxiv.org/abs/1202.4010

    .. [Wies83] Stephen Wiesner
        "Conjugate coding."
        ACM Sigact News 15.1 (1983): 78-88.
        https://dl.acm.org/doi/pdf/10.1145/1008908.1008920

    :param q_a: The fixed SDP variable.
    :param num_reps: The number of parallel repetitions.
    :return: The optimal probability with of counterfeiting quantum money.
    """
    # The system is over:
    # Y_1 ⊗ Z_1 ⊗ X_1, ... , Y_n ⊗ Z_n ⊗ X_n.
    num_spaces = 3

    # In the event of more than a single repetition, one needs to apply a
    # permutation operator to the variables in the SDP to properly align
    # the spaces.
    if num_reps == 1:
        pperm = np.array([1])
    else:
        # The permutation vector `perm` contains elements of the
        # sequence from: https://oeis.org/A023123
        q_a = tensor(q_a, num_reps)
        perm = []
        for i in range(1, num_spaces + 1):
            perm.append(i)
            var = i
            for j in range(1, num_reps):
                perm.append(var + num_spaces * j)
        pperm = permutation_operator(2, perm)

    return dual_problem(q_a, pperm, num_reps)
Example #7
0
def werner(dim: int, alpha: Union[float, List[float]]) -> np.ndarray:
    r"""
    Produce a Werner state [WER]_.

    A Werner state is a state of the following form

    .. math::

        \begin{equation}
            \rho_{\alpha} = \frac{1}{d^2 - d\alpha} \left(\mathbb{I} \otimes
            \mathbb{I} - \alpha S \right) \in \mathbb{C}^d \otimes \mathbb{C}^d
        \end{equation}

    Yields a Werner state with parameter `alpha` acting on `(dim * dim)`-
    dimensional space. More specifically, `rho` is the density operator
    defined by (I - `alpha`*S) (normalized to have trace 1), where I is the
    density operator and S is the operator that swaps two copies of
    `dim`-dimensional space (see swap and swap_operator for example).

    If `alpha` is a vector with p!-1 entries, for some integer p > 1, then a
    multipartite Werner state is returned. This multipartite Werner state is
    the normalization of I - `alpha(1)*P(2)` - ... - `alpha(p!-1)*P(p!)`, where
    P(i) is the operator that permutes p subsystems according to the i-th
    permutation when they are written in lexicographical order (for example,
    the lexicographical ordering when p = 3 is:
    `[1, 2, 3], [1, 3, 2], [2, 1,3], [2, 3, 1], [3, 1, 2], [3, 2, 1],`

    so P(4) in this case equals permutation_operator(dim, [2, 3, 1]).

    Examples
    ==========

    Computing the qutrit Werner state with $\alpha = 1/2$ can be done in
    `toqito` as

    >>> from toqito.states.states.werner import werner
    >>> werner(3, 1 / 2)
    array([[ 0.06666667,  0.        ,  0.        ,  0.        ,  0.        ,
             0.        ,  0.        ,  0.        ,  0.        ],
           [ 0.        ,  0.13333333,  0.        , -0.06666667,  0.        ,
             0.        ,  0.        ,  0.        ,  0.        ],
           [ 0.        ,  0.        ,  0.13333333,  0.        ,  0.        ,
             0.        , -0.06666667,  0.        ,  0.        ],
           [ 0.        , -0.06666667,  0.        ,  0.13333333,  0.        ,
             0.        ,  0.        ,  0.        ,  0.        ],
           [ 0.        ,  0.        ,  0.        ,  0.        ,  0.06666667,
             0.        ,  0.        ,  0.        ,  0.        ],
           [ 0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
             0.13333333,  0.        , -0.06666667,  0.        ],
           [ 0.        ,  0.        , -0.06666667,  0.        ,  0.        ,
             0.        ,  0.13333333,  0.        ,  0.        ],
           [ 0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
            -0.06666667,  0.        ,  0.13333333,  0.        ],
           [ 0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
             0.        ,  0.        ,  0.        ,  0.06666667]])

    We may also compute multipartite Werner states in `toqito` as well.

    >>> from toqito.states.states.werner import werner
    >>> werner(2, [0.01, 0.02, 0.03, 0.04, 0.05])
    array([[ 0.12179487,  0.        ,  0.        ,  0.        ,  0.        ,
             0.        ,  0.        ,  0.        ],
           [ 0.        ,  0.12820513,  0.        ,  0.        , -0.00641026,
             0.        ,  0.        ,  0.        ],
           [ 0.        ,  0.        ,  0.12179487,  0.        ,  0.        ,
             0.        ,  0.        ,  0.        ],
           [ 0.        ,  0.        ,  0.        ,  0.12820513,  0.        ,
             0.        , -0.00641026,  0.        ],
           [ 0.        , -0.00641026,  0.        ,  0.        ,  0.12820513,
             0.        ,  0.        ,  0.        ],
           [ 0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
             0.12179487,  0.        ,  0.        ],
           [ 0.        ,  0.        ,  0.        , -0.00641026,  0.        ,
             0.        ,  0.12820513,  0.        ],
           [ 0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
             0.        ,  0.        ,  0.12179487]])

    References
    ==========
    .. [WER] R. F. Werner.
        Quantum states with Einstein-Podolsky-Rosen correlations admitting a
        hidden-variable model. Phys. Rev. A, 40(8):4277–4281

    :param dim: The dimension of the Werner state.
    :param alpha: Parameter to specify Werner state.
    :return: A Werner state of dimension `dim`.
    """
    # The total number of permutation operators.
    if isinstance(alpha, float):
        n_fac = 2
    else:
        n_fac = len(alpha) + 1

    # Multipartite Werner state.
    if n_fac > 2:
        # Compute the number of parties from `len(alpha)`.
        n_var = n_fac
        # We won't actually go all the way to `n_fac`.
        for i in range(2, n_fac):
            n_var = n_var // i
            if n_var == i + 1:
                break
            if n_var < i:
                raise ValueError(
                    "InvalidAlpha: The `alpha` vector must contain"
                    " p!-1 entries for some integer p > 1."
                )

        # Done error checking and computing the number of parties -- now
        # compute the Werner state.
        perms = list(itertools.permutations(np.arange(n_var)))
        sorted_perms = np.argsort(perms, axis=1) + 1

        for i in range(2, n_fac):
            rho = np.identity(dim ** n_var) - alpha[i - 1] * permutation_operator(
                dim, sorted_perms[i, :], False, True
            )
        rho = rho / np.trace(rho)
        return rho
    # Bipartite Werner state.
    return (np.identity(dim ** 2) - alpha * swap_operator(dim, True)) / (
        dim * (dim - alpha)
    )
Example #8
0
def symmetric_projection(
        dim: int,
        p_val: int = 2,
        partial: bool = False) -> [np.ndarray, sparse.lil_matrix]:
    r"""
    Produce the projection onto the symmetric subspace.

    Produces the orthogonal projection onto the symmetric subspace of `p`
    copies of `dim`-dimensional space. If `partial = True`, then the symmetric
    projection (PS) isn't the orthogonal projection itself, but rather a matrix
    whose columns form an orthonormal basis for the symmetric subspace (and
    hence the PS * PS' is the orthogonal projection onto the symmetric
    subspace.)

    Examples
    ==========

    The :math:`2`-dimensional symmetric projection with :math:`p=1` is given as
    :math:`2`-by-:math:`2` identity matrix

    .. math::
        \begin{pmatrix}
            1 & 0 \\
            0 & 1
        \end{pmatrix}.

    Using `toqito`, we can see this gives the proper result.

    >>> from toqito.perms.symmetric_projection import symmetric_projection
    >>> symmetric_projection(2, 1).todense()
    matrix([[1., 0.],
            [0., 1.]])

    When :math:`d = 2` and :math:`p = 2` we have that

    .. math::
        \begin{pmatrix}
            1 & 0 & 0 & 0 \\
            0 & 1/2 & 1/2 & 0 \\
            0 & 1/2 & 1/2 & 0 \\
            0 & 0 & 0 & 1
        \end{pmatrix}.

    Using `toqito` we can see this gives the proper result.

    >>> from toqito.perms.symmetric_projection import symmetric_projection
    >>> symmetric_projection(dim=2).todense()
    matrix([[1. , 0. , 0. , 0. ],
            [0. , 0.5, 0.5, 0. ],
            [0. , 0.5, 0.5, 0. ],
            [0. , 0. , 0. , 1. ]])

    :param dim: The dimension of the local systems.
    :param p_val: Default value of 2.
    :param partial: Default value of 0.
    :return: Projection onto the symmetric subspace.
    """
    dimp = dim**p_val

    if p_val == 1:
        return sparse.eye(dim)

    p_list = np.array(list(permutations(np.arange(1, p_val + 1))))
    p_fac = np.math.factorial(p_val)
    sym_proj = sparse.lil_matrix((dimp, dimp))

    for j in range(p_fac):
        sym_proj += permutation_operator(dim * np.ones(p_val), p_list[j, :],
                                         False, True)
    sym_proj = sym_proj / p_fac

    if partial:
        sym_proj = sym_proj.todense()
        sym_proj = sparse.lil_matrix(linalg.orth(sym_proj))
    return sym_proj