Source code for toqito.perms.permute_systems

"""Permute systems is used to permute subsystems within a quantum state or an operator."""

import functools
import operator

import numpy as np
from scipy import sparse

from toqito.perms import vec


[docs] def permute_systems( input_mat: np.ndarray, perm: np.ndarray | list[int], dim: np.ndarray | list[int] | None = None, row_only: bool = False, inv_perm: bool = False, ) -> np.ndarray: r"""Permute subsystems within a state or operator. Permutes the order of the subsystems of the vector or matrix `input_mat` according to the permutation vector `perm`, where the dimensions of the subsystems are given by the vector `dim`. If `input_mat` is non-square and not a vector, different row and column dimensions can be specified by putting the row dimensions in the first row of `dim` and the columns dimensions in the second row of `dim`. If `row_only = True`, then only the rows of `input_mat` are permuted, but not the columns -- this is equivalent to multiplying `input_mat` on the left by the corresponding permutation operator, but not on the right. If `row_only = False`, then `dim` only needs to contain the row dimensions of the subsystems, even if `input_mat` is not square. If `inv_perm = True`, then the inverse permutation of `perm` is applied instead of `perm` itself. Examples: For spaces \(\mathcal{A}\) and \(\mathcal{B}\) where \(\text{dim}(\mathcal{A}) = \text{dim}(\mathcal{B}) = 2\) we may consider an operator \(X \in \mathcal{A} \otimes \mathcal{B}\). Applying the `permute_systems` function with vector \([1,0]\) on \(X\), we may reorient the spaces such that \(X \in \mathcal{B} \otimes \mathcal{A}\). For example, if we define \(X \in \mathcal{A} \otimes \mathcal{B}\) as \[ X = \begin{pmatrix} 1 & 2 & 3 & 4 \\ 5 & 6 & 7 & 8 \\ 9 & 10 & 11 & 12 \\ 13 & 14 & 15 & 16 \end{pmatrix}, \] then applying the `permute_systems` function on \(X\) to obtain \(X \in \mathcal{B} \otimes \mathcal{A}\) yield the following matrix \[ X_{[1,0]} = \begin{pmatrix} 1 & 3 & 2 & 4 \\ 9 & 11 & 10 & 12 \\ 5 & 7 & 6 & 8 \\ 13 & 15 & 14 & 16 \end{pmatrix}. \] ```python exec="1" source="above" import numpy as np from toqito.perms import permute_systems test_input_mat = np.arange(1, 17).reshape(4, 4) print(permute_systems(test_input_mat, [1, 0])) ``` For spaces \(\mathcal{A}, \mathcal{B}\), and \(\mathcal{C}\) where \(\text{dim}(\mathcal{A}) = \text{dim}(\mathcal{B}) = \text{dim}(\mathcal{C}) = 2\) we may consider an operator \(X \in \mathcal{A} \otimes \mathcal{B} \otimes \mathcal{C}\). Applying the `permute_systems` function with vector \([1,2,0]\) on \(X\), we may reorient the spaces such that \(X \in \mathcal{B} \otimes \mathcal{C} \otimes \mathcal{A}\). For example, if we define \(X \in \mathcal{A} \otimes \mathcal{B} \otimes \mathcal{C}\) as \[ X = \begin{pmatrix} 1 & 2 & 3 & 4, 5 & 6 & 7 & 8 \\ 9 & 10 & 11 & 12 & 13 & 14 & 15 & 16 \\ 17 & 18 & 19 & 20 & 21 & 22 & 23 & 24 \\ 25 & 26 & 27 & 28 & 29 & 30 & 31 & 32 \\ 33 & 34 & 35 & 36 & 37 & 38 & 39 & 40 \\ 41 & 42 & 43 & 44 & 45 & 46 & 47 & 48 \\ 49 & 50 & 51 & 52 & 53 & 54 & 55 & 56 \\ 57 & 58 & 59 & 60 & 61 & 62 & 63 & 64 \end{pmatrix}, \] then applying the `permute_systems` function on \(X\) to obtain \(X \in \mathcal{B} \otimes \mathcal{C} \otimes \mathcal{C}\) yield the following matrix \[ X_{[1, 2, 0]} = \begin{pmatrix} 1 & 5 & 2 & 6 & 3 & 7 & 4, 8 \\ 33 & 37 & 34 & 38 & 35 & 39 & 36 & 40 \\ 9 & 13 & 10 & 14 & 11 & 15 & 12 & 16 \\ 41 & 45 & 42 & 46 & 43 & 47 & 44 & 48 \\ 17 & 21 & 18 & 22 & 19 & 23 & 20 & 24 \\ 49 & 53 & 50 & 54 & 51 & 55 & 52 & 56 \\ 25 & 29 & 26 & 30 & 27 & 31 & 28 & 32 \\ 57 & 61 & 58 & 62 & 59 & 63 & 60 & 64 \end{pmatrix}. \] ```python exec="1" source="above" import numpy as np from toqito.perms import permute_systems test_input_mat = np.arange(1, 65).reshape(8, 8) print(permute_systems(test_input_mat, [1, 2, 0])) ``` Raises: ValueError: If dimension does not match the number of subsystems. Args: input_mat: The vector or matrix. perm: A permutation vector. dim: The default has all subsystems of equal dimension. row_only: Default: `False` inv_perm: Default: `True` Returns: The matrix or vector that has been permuted. """ if len(input_mat.shape) == 1: input_mat_dims = (1, input_mat.shape[0]) else: input_mat_dims = input_mat.shape is_vec = np.min(input_mat_dims) == 1 num_sys = len(perm) if dim is None: x_tmp = input_mat_dims[0] ** (1 / num_sys) * np.ones(num_sys) y_tmp = input_mat_dims[1] ** (1 / num_sys) * np.ones(num_sys) dim_arr = np.array([x_tmp, y_tmp]) elif isinstance(dim, list): dim_arr = np.array(dim) else: dim_arr = dim if is_vec: # 1 if column vector if len(input_mat.shape) > 1: vec_orien = 0 # 2 if row vector else: vec_orien = 1 if len(dim_arr.shape) == 1: # Force dim to be a row vector. dim_tmp = dim_arr[:].T if is_vec: dim_arr = np.ones((2, len(dim_arr))) dim_arr[vec_orien, :] = dim_tmp else: dim_arr = np.array([[dim_tmp], [dim_tmp]]) prod_dim_r = int(np.prod(dim_arr[0, :])) prod_dim_c = int(np.prod(dim_arr[1, :])) if sorted(perm) != list(range(num_sys)): raise ValueError("InvalidPerm: `perm` must be a permutation vector.") if input_mat_dims[0] != prod_dim_r or (not row_only and input_mat_dims[1] != prod_dim_c): raise ValueError("InvalidDim: The dimensions specified in DIM do not agree with the size of X.") if is_vec: # If `input_mat` is a 1-by-X row vector, ensure we "flatten it" appropriately: if input_mat.shape[0] == 1: input_mat = input_mat[0] vec_orien = 1 # Rather than using subtraction to generate new indices, # it's better to use methods designed for handling permutations directly. # This avoids the risk of negative indices and is more straightforward. num_sys -= 1 # 0-indexing (Since we're using 0-indexing, we need to subtract 1 from the number of subsystems.) permuted_mat_1 = input_mat.reshape(dim_arr[vec_orien, ::-1].astype(int), order="F") if inv_perm: permuted_mat = vec(np.transpose(permuted_mat_1, np.argsort(num_sys - np.array(perm[::-1])))).T else: permuted_mat = vec(np.transpose(permuted_mat_1, num_sys - np.array(perm[::-1]))).T # We need to flatten out the array. permuted_mat = functools.reduce(operator.iconcat, permuted_mat, []) return np.array(permuted_mat) vec_arg = np.array(list(range(0, input_mat_dims[0]))) # If the dimensions are specified, ensure they are given to the # recursive calls as flattened lists. if len(dim_arr[0][:]) == 1: dim_arr = functools.reduce(operator.iconcat, dim_arr, []) row_perm = permute_systems(vec_arg, perm, dim_arr[0][:], False, inv_perm) # This condition is only necessary if the `input_mat` variable is sparse. if sparse.issparse(input_mat): input_mat = input_mat.toarray() permuted_mat = input_mat[row_perm, :] permuted_mat = np.array(permuted_mat) else: permuted_mat = input_mat[row_perm, :] if not row_only: vec_arg = np.array(list(range(0, input_mat_dims[1]))) col_perm = permute_systems(vec_arg, perm, dim_arr[1][:], False, inv_perm) permuted_mat = permuted_mat[:, col_perm] return permuted_mat