def test_permutation_operator_dim_2_2_perm_2_1(): """Test permutation operator when dim is [2, 2] and perm is [2, 1]""" res = permutation_operator([2, 2], [2, 1]) expected_res = np.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]]) bool_mat = np.isclose(res, expected_res) np.testing.assert_equal(np.all(bool_mat), True)
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_permutation_operator_standard_swap(): """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, 1]) bool_mat = np.isclose(res, expected_res) np.testing.assert_equal(np.all(bool_mat), True)
def test_permutation_operator_dim_2_perm_1_3_2(): """Test permutation operator when dim is 2 and perm is [1, 3, 2].""" res = permutation_operator(2, [1, 3, 2]) expected_res = np.array([[ [1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1], ]]) bool_mat = np.isclose(res, expected_res) np.testing.assert_equal(np.all(bool_mat), True)
def test_permutation_operator_sparse_option(): """Sparse swap operator on two qutrits.""" res = permutation_operator(3, [2, 1], False, True) expected_res = np.array([ [1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1], ]) bool_mat = np.isclose(res, expected_res) np.testing.assert_equal(np.all(bool_mat), True)
def test_permutation_operator_dim_2_2_perm_1_2(): """Test permutation operator when dim is [2, 2] and perm is [1, 2]""" res = permutation_operator([2, 2], [1, 2]) expected_res = np.identity(4) bool_mat = np.isclose(res, expected_res) np.testing.assert_equal(np.all(bool_mat), True)
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 :code:`p_param` copies of :code:`dim`-dimensional space. If :code:`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:: A_{2,1} = \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix}. Using :code:`toqito`, we can see this gives the proper result. >>> from toqito.perms import antisymmetric_projection >>> antisymmetric_projection(2, 1).todense() [[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:: A_{2, 3} = \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 :code:`toqito` we can see this gives the proper result. >>> from toqito.perms import antisymmetric_projection >>> antisymmetric_projection(2, 3).todense() [[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
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 [CJKLZ14]_. For a complex Euclidean space :math:`\mathcal{X}` and a positive integer :math:`n`, the projection onto the symmetric subspace is given by .. math:: \frac{1}{n!} \sum_{\pi \in S_n} W_{\pi} where :math:`W_{\pi}` is the swap operator and where :math:`S_n` is the symmetric group on :math:`n` symbols. Produces the orthogonal projection onto the symmetric subspace of :code:`p_val` 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). This function was adapted from the QETLAB package. 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 :code:`toqito`, we can see this gives the proper result. >>> from toqito.perms import symmetric_projection >>> symmetric_projection(2, 1).todense() [[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 :code:`toqito` we can see this gives the proper result. >>> from toqito.perms import symmetric_projection >>> symmetric_projection(dim=2).todense() [[1. , 0. , 0. , 0. ], [0. , 0.5, 0.5, 0. ], [0. , 0.5, 0.5, 0. ], [0. , 0. , 0. , 1. ]] References ========== .. [CJKLZ14] J. Chen, Z. Ji, D. Kribs, N. Lütkenhaus, and B. Zeng. "Symmetric extension of two-qubit states". Physical Review A 90.3 (2014): 032318. https://arxiv.org/abs/1310.3530 E-print: arXiv:1310.3530 [quant-ph] :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 np.eye(dim) p_list = np.array(list(permutations(np.arange(1, p_val + 1)))) p_fac = np.math.factorial(p_val) sym_proj = np.zeros((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 = linalg.orth(sym_proj) return sym_proj
def optimal_clone( states: List[np.ndarray], probs: List[float], num_reps: int = 1, strategy: bool = False, ) -> Union[float, np.ndarray]: 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:: \begin{equation} Q = \frac{1}{4} \left(|000 \rangle \langle 000| + |111 \rangle \langle 111| + |+++ \rangle + \langle +++| + |--- \rangle \langle ---| \right) \end{equation} We can see that the optimal value we obtain in solving the SDP is 3/4. >>> from toqito.state_opt import optimal_clone >>> from toqito.states import basis >>> import numpy as np >>> e_0, e_1 = basis(2, 0), basis(2, 1) >>> e_p = (e_0 + e_1) / np.sqrt(2) >>> e_m = (e_0 - e_1) / np.sqrt(2) >>> >>> states = [e_0, e_1, e_p, e_m] >>> probs = [1 / 4, 1 / 4, 1 / 4, 1 / 4] >>> wiesner = optimal_clone(states, probs) 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 :return: The optimal probability with of counterfeiting quantum money. """ dim = len(states[0])**3 # Construct the following operator: # ___ ___ # Q = ∑_{k=1}^N p_k |ψ_k ⊗ ψ_k ⊗ ψ_k> <ψ_k ⊗ ψ_k ⊗ ψ_k| q_a = np.zeros((dim, dim)) for k, state in enumerate(states): q_a += (probs[k] * tensor(state, state, state.conj()) * tensor(state, state, state.conj()).conj().T) # 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) if strategy: return primal_problem(q_a, pperm, num_reps) return dual_problem(q_a, pperm, num_reps)
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. For a complex Euclidean space :math:`\mathcal{X}` and a positive integer :math:`n`, the projection onto the symmetric subspace is given by .. math:: \frac{1}{n!} \sum_{\pi \in S_n} W_{\pi} where :math:`W_{\pi}` is the swap operator and where :math:`` is the . 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 import symmetric_projection >>> symmetric_projection(2, 1).todense() [[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 import symmetric_projection >>> symmetric_projection(dim=2).todense() [[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
def werner(dim: int, alpha: Union[float, List[float]]) -> np.ndarray: r""" Produce a Werner state [Wer89]_. 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 :code:`alpha` acting on :code:`(dim * dim)`- dimensional space. More specifically, :math:`\rho` is the density operator defined by :math:`(\mathbb{I} - `alpha` S)` (normalized to have trace 1), where :math:`\mathbb{I}` is the density operator and :math:`S` is the operator that swaps two copies of :code:`dim`-dimensional space (see swap and swap_operator for example). If :code:`alpha` is a vector with :math:`p!-1` entries, for some integer :math:`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 :math:`\alpha = 1/2` can be done in :code:`toqito` as >>> from toqito.states import werner >>> werner(3, 1 / 2) [[ 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 :code:`toqito` as well. >>> from toqito.states import werner >>> werner(2, [0.01, 0.02, 0.03, 0.04, 0.05]) [[ 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 ========== .. [Wer89] R. F. Werner. Quantum states with Einstein-Podolsky-Rosen correlations admitting a hidden-variable model. Phys. Rev. A, 40(8):4277–4281. 1989 :param dim: The dimension of the Werner state. :param alpha: Parameter to specify Werner state. :return: A Werner state of dimension :code:`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))
def test_permutation_operator_sparse_option(): """Sparse swap operator on two qutrits.""" res = permutation_operator(3, [2, 1], False, True) np.testing.assert_equal(res[0][0], 1)