From 7d4a76605571e75cb842afda4edc9128cf371ebb Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Tue, 11 Jun 2019 16:22:28 +1000 Subject: [PATCH 01/33] move the random_operators file to the module operator_tools --- .../benchmarking/operator_tools/__init__.py | 2 + .../operator_tools/random_operators.py | 204 ++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 forest/benchmarking/operator_tools/__init__.py create mode 100644 forest/benchmarking/operator_tools/random_operators.py diff --git a/forest/benchmarking/operator_tools/__init__.py b/forest/benchmarking/operator_tools/__init__.py new file mode 100644 index 00000000..6489073e --- /dev/null +++ b/forest/benchmarking/operator_tools/__init__.py @@ -0,0 +1,2 @@ +#from .random_operators import * +__all__ = ["random_operators"] \ No newline at end of file diff --git a/forest/benchmarking/operator_tools/random_operators.py b/forest/benchmarking/operator_tools/random_operators.py new file mode 100644 index 00000000..fa57a1df --- /dev/null +++ b/forest/benchmarking/operator_tools/random_operators.py @@ -0,0 +1,204 @@ +"""A module for generating random quantum states and processes. + +Pseudocode for many of these routines can be found in the appendix of the paper: + +[BAYES] Practical Bayesian Tomography + Granade et al., + New Journal of Physics 18, 033024 (2016) + https://dx.doi.org/10.1088/1367-2630/18/3/033024 + https://arxiv.org/abs/1509.03770 +""" +from typing import Optional + +import numpy as np +from numpy import linalg as la +from scipy.linalg import sqrtm +from sympy.combinatorics import Permutation +from numpy.random import RandomState +from forest.benchmarking.utils import partial_trace + + +def ginibre_matrix_complex(D, K, rs: Optional[RandomState] = None): + """ + Given a scalars $D$ and $K$, returns a D × K matrix, + drawn from the complex Ginibre ensemble, i.e. (N(0, 1) + i · N(0, 1)). + + [IM] Induced measures in the space of mixed quantum states + Zyczkowski et al., + J. Phys A: Math. and Gen. 34, 7111 (2001) + https://doi.org/10.1088/0305-4470/34/35/335 + https://arxiv.org/abs/quant-ph/0012101 + + :param D: Hilbert space dimension (scalar). + :param K: Ultimately becomes the rank of a state (scalar). + :param rs: Optional random state. + :return: Returns a D × K matrix, drawn from the Ginibre ensemble. + """ + if rs is None: + rs = np.random + + return rs.randn(D, K) + 1j * rs.randn(D, K) + + +def haar_rand_unitary(dim, rs=None): + """ + Given a Hilbert space dimension D this function + returns a unitary operator U ∈ C^D×D drawn from the Haar measure. + + The error is of order 10^-16. + + [MEZ] How to generate random matrices from the classical compact groups + Mezzadri + Notices of the American Mathematical Society 54, 592 (2007). + http://www.ams.org/notices/200705/fea-mezzadri-web.pdf + https://arxiv.org/abs/math-ph/0609050 + + :param dim: Hilbert space dimension (scalar). + :param rs: Optional random state + :return: Returns a unitary operator U ∈ C^D×D drawn from the Haar measure. + """ + if rs is None: + rs = np.random + Z = ginibre_matrix_complex(D=dim, K=dim, rs=rs) # /np.sqrt(2) + Q, R = np.linalg.qr(Z) + diag = np.diagonal(R) + lamb = np.diag(diag) / np.absolute(diag) + return np.matmul(Q, lamb) + + +def haar_rand_state(dimension): + """ + Given a Hilbert space dimension $D$ this function returns a vector + representing a random pure state operator drawn from the Haar measure. + + :param dimension: Hilbert space dimension (scalar). + :return: Returns a Dx1 vector drawn from the Haar measure. + + """ + unitary = haar_rand_unitary(dimension) + fiducial_vec = np.zeros((dimension, 1)) + fiducial_vec[0] = 1 + return np.matmul(unitary, fiducial_vec) + + +def ginibre_state_matrix(D, K): + """ + Given a Hilbert space dimension $D$ and a desired rank $K$, returns + a D × D positive semidefinite matrix of rank K drawn from the Ginibre ensemble. + For D = K these are states drawn from the Hilbert-Schmidt measure. + + See reference [IM] for more details. + + :param D: Hilbert space dimension (scalar). + :param K: The rank of a state (scalar). + :return: Returns a D × K matrix, drawn from the Ginibre ensemble. + """ + if K > D: + raise ValueError("The rank of the state matrix cannot exceed the dimension.") + + A = ginibre_matrix_complex(D, K) + M = A.dot(np.transpose(np.conjugate(A))) + return M / np.trace(M) + + +def bures_measure_state_matrix(D): + """ + Given a Hilbert space dimension $D$, returns a D × D positive + semidefinite matrix drawn from the Bures measure. + + [OSZ] Random Bures mixed states and the distribution of their purity + Osipov et al., + J. Phys. A: Math. Theor. 43, 055302 (2010). + https://doi.org/10.1088/1751-8113/43/5/055302 + https://arxiv.org/abs/0909.5094 + + :param D: Hilbert space dimension (scalar). + :param K: The rank of a state (scalar). + :return: Returns a D × K matrix, drawn from the Ginibre ensemble. + """ + A = ginibre_matrix_complex(D, D) + U = haar_rand_unitary(D) + Udag = np.transpose(np.conjugate(U)) + Id = np.eye(D) + M = A.dot(np.transpose(np.conjugate(A))) + P = (Id + U).dot(M).dot(Id + Udag) + return P / np.trace(P) + + +def rand_map_with_BCSZ_dist(D, K): + """ + Given a Hilbert space dimension $D$ and a Kraus rank $K$, returns a + $D^2 × D^2$ Choi matrix $J(Λ)$ of a channel drawn from the BCSZ distribution + with Kraus rank $K$. + + [RQO] Random quantum operations, + Bruzda et al., + Physics Letters A 373, 320 (2009). + https://doi.org/10.1016/j.physleta.2008.11.043 + https://arxiv.org/abs/0804.2361 + + :param D: Hilbert space dimension (scalar). + :param K: The rank of a state (scalar). + :return: D^2 × D^2 Choi matrix, drawn from the BCSZ distribution with Kraus rank K. + """ + # TODO: this ^^ is CPTP, might want a flag that allows for just CP quantum operations. + X = ginibre_matrix_complex(D ** 2, K) + rho = X @ X.conj().T + rho_red = partial_trace(rho, [0], [D, D]) + # Note that Eqn. 8 of [RQO] uses a *row* stacking convention so in that case we would write + # Q = np.kron(np.eye(D), sqrtm(la.inv(rho_red))) + # But as we use column stacking we need: + Q = np.kron(sqrtm(la.inv(rho_red)), np.eye(D)) + Z = Q @ rho @ Q + return Z + + +def permute_tensor_factors(D, perm): + r""" + Return a permutation matrix of the given dimension. + + Given a Hilbert space dimension $D$ and an list representing the permutation $perm$ of the + tensor product Hilbert spaces, returns a $D^len(perm)$ by $D^len(perm)$ permutation matrix. + + E.g. 1) Suppose D=2 and perm=[0,1] + Returns the identity operator on two qubits + + 2) Suppose D=2 and perm=[1,0] + Returns the SWAP operator on two qubits which + maps A_1 \otimes A_2 --> A_2 \otimes A_1. + + See: Equations 5.11, 5.12, and 5.13 in + + [SCOTT] Optimizing quantum process tomography with unitary 2-designs + A. J. Scott, + J. Phys. A 41, 055308 (2008) + https://dx.doi.org/10.1088/1751-8113/41/5/055308 + https://arxiv.org/abs/0711.1017 + + This function is used in tests for other functions. However, it can also be useful when + thinking about higher moment (N>2) integrals over the Haar measure. + + :param D: Hilbert space dimension (scalar). + :param perm: A list representing the permutation of the tensor factors. + """ + dim_list = [D for i in range(2 * len(perm))] + + Id = np.eye(D ** len(perm), D ** len(perm)) + + P = Permutation(perm) + tran = P.transpositions + trans = tran() + + temp = np.reshape(Id, dim_list) + + # implement the permutation + + if P == []: + return Id + else: + for pdx in range(len(trans)): + tdx = trans[pdx] + answer = np.swapaxes(temp, tdx[0], tdx[1]) + temp = answer + + return np.reshape(answer, [D ** len(perm), D ** len(perm)]) From 5817e4c7b70b1ba4ef7be5b685139c9b619cb1e4 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Tue, 11 Jun 2019 16:38:12 +1000 Subject: [PATCH 02/33] move the superoperator tools --- .../operator_tools/superoperator_tools.py | 700 ++++++++++++++++++ 1 file changed, 700 insertions(+) create mode 100644 forest/benchmarking/operator_tools/superoperator_tools.py diff --git a/forest/benchmarking/operator_tools/superoperator_tools.py b/forest/benchmarking/operator_tools/superoperator_tools.py new file mode 100644 index 00000000..9a4956a4 --- /dev/null +++ b/forest/benchmarking/operator_tools/superoperator_tools.py @@ -0,0 +1,700 @@ +"""A module containing tools for working with superoperators. Eg. converting between different +representations of superoperators. + +We have arbitrarily decided to use a column stacking convention. + +For more information about the conventions used, look at the file in +/docs/Superoperator representations.md + +Further references include: + +[GRAPTN] Tensor networks and graphical calculus for open quantum systems + Wood et al. + Quant. Inf. Comp. 15, 0579-0811 (2015) + (no DOI) + https://arxiv.org/abs/1111.6950 + +[MATQO] On the Matrix Representation of Quantum Operations + Nambu et al., + arXiv: 0504091 (2005) + https://arxiv.org/abs/quant-ph/0504091 + +[DUAL] On duality between quantum maps and quantum states + Zyczkowski et al., + Open Syst. Inf. Dyn. 11, 3 (2004) + https://dx.doi.org/10.1023/B:OPSY.0000024753.05661.c2 + https://arxiv.org/abs/quant-ph/0401119 + +""" +from typing import Sequence, Tuple, List +import numpy as np +from forest.benchmarking.utils import n_qubit_pauli_basis, partial_trace + + +# ================================================================================================== +# Superoperator conversion tools +# ================================================================================================== + + +def vec(matrix: np.ndarray) -> np.ndarray: + """ + Vectorize, i.e. "vec", a matrix by column stacking. + + For example the 2 by 2 matrix A + + A = [[a, b] + [c, d]] becomes |A>> := vec(A) = (a, c, b, d)^T , + + where |A>> denotes the vec'ed version of A and T denotes transpose. + + :param matrix: A N (rows) by M (columns) numpy array. + :return: Returns a column vector with N by M rows. + """ + return np.asarray(matrix).T.reshape((-1, 1)) + + +def unvec(vector: np.ndarray, shape: Tuple[int, int] = None) -> np.ndarray: + """ + Take a column vector and turn it into a matrix. + + By default, the unvec'ed matrix is assumed to be square. Specifying shape = [N, M] will + produce a N by M matrix where N is the number of rows and M is the number of columns. + + Consider |A>> := vec(A) = (a, c, b, d)^T. `unvec(|A>>)` should return + + A = [[a, b] + [c, d]]. + + :param vector: A (N*M) by 1 numpy array. + :param shape: The shape of the output matrix; by default, the matrix is assumed to be square. + :return: Returns a N by M matrix. + """ + vector = np.asarray(vector) + if shape is None: + dim = int(np.sqrt(vector.size)) + shape = dim, dim + matrix = vector.reshape(*shape).T + return matrix + + +def kraus2chi(kraus_ops: Sequence[np.ndarray]) -> np.ndarray: + r""" + Convert a set of Kraus operators (representing a channel) to + a chi matrix which is also known as a process matrix. + + :param kraus_ops: A list or tuple of N Kraus operators + :return: Returns a dim**2 by dim**2 matrix. + """ + if isinstance(kraus_ops, np.ndarray): # handle input of single kraus op + if len(kraus_ops[0].shape) < 2: # first elem is not a matrix + kraus_ops = [kraus_ops] + + dim = np.asarray(kraus_ops[0]).shape[0] # kraus op is dim by dim matrix + c_vecs = [computational2pauli_basis_matrix(dim) @ vec(kraus) for kraus in kraus_ops] + chi_mat = sum([c_vec @ c_vec.conj().T for c_vec in c_vecs]) + return chi_mat + + +def kraus2superop(kraus_ops: Sequence[np.ndarray]) -> np.ndarray: + r""" + Convert a set of Kraus operators (representing a channel) to + a superoperator using the column stacking convention. + + Suppose the N Kraus operators M_i are dim by dim matrices. Then the + the superoperator is a (dim**2) \otimes (dim**2) matrix. Using the relation + column stacking relation, + vec(ABC) = (C^T\otimes A) vec(B), we can show + + super_operator = \sum_i^N ( M_i^\dagger )^T \otimes M_i + = \sum_i^N M_i^* \otimes M_i + + where A^* is the complex conjugate of a matrix A, A^T is the transpose, + and A^\dagger is the complex conjugate and transpose. + + Note: This function can also convert non-square Kraus operators to a superoperator, + these frequently arise in quantum measurement theory and quantum error correction. In that + situation consider a single Kraus operator that is M by N then the superoperator will be a + M**2 by N**2 matrix. + + :param kraus_ops: A tuple of N Kraus operators + :return: Returns a dim**2 by dim**2 matrix. + """ + if isinstance(kraus_ops, np.ndarray): # handle input of single kraus op + if len(kraus_ops[0].shape) < 2: # first elem is not a matrix + kraus_ops = [kraus_ops] + + rows, cols = np.asarray(kraus_ops[0]).shape + + # Standard case of square Kraus operators is if rows==cols. + # When representing a partial projection, e.g. a single measurement operator + # M_i = Id \otimes np.ndarray: + """ + Convert a set of Kraus operators (representing a channel) to + a Pauli-Liouville matrix (aka Pauli Transfer matrix). + + :param kraus_ops: A list of Kraus operators + :return: Returns dim**2 by dim**2 Pauli-Liouville matrix + """ + return superop2pauli_liouville(kraus2superop(kraus_ops)) + + +def kraus2choi(kraus_ops: Sequence[np.ndarray]) -> np.ndarray: + r""" + Convert a set of Kraus operators (representing a channel) to + a Choi matrix using the column stacking convention. + + Suppose the N Kraus operators M_i are dim by dim matrices. Then the + the Choi matrix is a dim**2 by dim**2 matrix + + choi_matrix = \sum_i^N |M_i>> (|M_i>>)^\dagger + = \sum_i^N |M_i>> <> = vec(M_i) + + :param kraus_ops: A list of N Kraus operators + :return: Returns a dim**2 by dim**2 matrix. + """ + if isinstance(kraus_ops, np.ndarray): # handle input of single kraus op + if len(kraus_ops[0].shape) < 2: # first elem is not a matrix + kraus_ops = [kraus_ops] + + return sum([vec(op) @ vec(op).conj().T for op in kraus_ops]) + + +def chi2pauli_liouville(chi_matrix: np.ndarray) -> np.ndarray: + r""" + Converts a chi matrix (aka a process matrix) to the Pauli Liouville representation. + + :param chi_matrix: a dim**2 by dim**2 process matrix + :return: dim**2 by dim**2 Pauli-Liouville matrix + """ + return choi2pauli_liouville(chi2choi(chi_matrix)) + + +def chi2kraus(chi_matrix: np.ndarray) -> List[np.ndarray]: + """ + Converts a chi matrix into a list of Kraus operators. (operators with small norm may be + excluded) + + :param chi_matrix: a dim**2 by dim**2 process matrix + :return: list of Kraus operators + """ + return pauli_liouville2kraus(chi2pauli_liouville(chi_matrix)) + + +def chi2superop(chi_matrix: np.ndarray) -> np.ndarray: + """ + Converts a chi matrix into a superoperator. + + :param chi_matrix: a dim**2 by dim**2 process matrix + :return: a dim**2 by dim**2 superoperator matrix + """ + return pauli_liouville2superop(chi2pauli_liouville(chi_matrix)) + + +def chi2choi(chi_matrix: np.ndarray) -> np.ndarray: + """ + Converts a chi matrix into a Choi matrix. + + :param chi_matrix: a dim**2 by dim**2 process matrix + :return: a dim**2 by dim**2 Choi matrix + """ + dim = int(np.sqrt(np.asarray(chi_matrix).shape[0])) + p2c = pauli2computational_basis_matrix(dim) + return p2c @ chi_matrix @ p2c.conj().T + + +def superop2kraus(superop: np.ndarray) -> List[np.ndarray]: + """ + Converts a superoperator into a list of Kraus operators. (operators with small norm may be excluded) + + :param superop: a dim**2 by dim**2 superoperator + :return: list of Kraus operators + """ + return choi2kraus(superop2choi(superop)) + + +def superop2chi(superop: np.ndarray) -> np.ndarray: + """ + Converts a superoperator into a list of Kraus operators. (operators with small norm may be excluded) + + :param superop: a dim**2 by dim**2 superoperator + :return: a dim**2 by dim**2 process matrix + """ + return kraus2chi(superop2kraus(superop)) + + +def superop2pauli_liouville(superop: np.ndarray) -> np.ndarray: + """ + Converts a superoperator into a pauli_liouville matrix. This is achieved by a linear change of basis. + + :param superop: a dim**2 by dim**2 superoperator + :return: dim**2 by dim**2 Pauli-Liouville matrix + """ + dim = int(np.sqrt(np.asarray(superop).shape[0])) + c2p_basis_transform = computational2pauli_basis_matrix(dim) + return c2p_basis_transform @ superop @ c2p_basis_transform.conj().T * dim + + +def superop2choi(superop: np.ndarray) -> np.ndarray: + """ + Convert a superoperator into a choi matrix. The operation acts equivalently to choi2superop, as it is a bijection. + + :param superop: a dim**2 by dim**2 superoperator + :return: dim**2 by dim**2 choi matrix + """ + dim = int(np.sqrt(np.asarray(superop).shape[0])) + return np.reshape(superop, [dim] * 4).swapaxes(0, 3).reshape([dim ** 2, dim ** 2]) + + +def pauli_liouville2kraus(pl_matrix: np.ndarray) -> List[np.ndarray]: + """ + Converts a pauli_liouville matrix into a list of Kraus operators. (operators with small norm may be excluded) + + :param pl_matrix: a dim**2 by dim**2 pauli_liouville matrix + :return: list of Kraus operators + """ + return choi2kraus(pauli_liouville2choi(pl_matrix)) + + +def pauli_liouville2chi(pl_matrix: np.ndarray) -> np.ndarray: + """ + Converts a pauli_liouville matrix into a chi matrix. (operators with small norm may be excluded) + + :param pl_matrix: a dim**2 by dim**2 pauli_liouville matrix + :return: a dim**2 by dim**2 process matrix + """ + return kraus2chi(pauli_liouville2kraus(pl_matrix)) + + +def pauli_liouville2superop(pl_matrix: np.ndarray) -> np.ndarray: + """ + Converts a pauli_liouville matrix into a superoperator. This is achieved by a linear change of basis. + + :param pl_matrix: a dim**2 by dim**2 Pauli-Liouville matrix + :return: dim**2 by dim**2 superoperator + """ + dim = int(np.sqrt(np.asarray(pl_matrix).shape[0])) + p2c_basis_transform = pauli2computational_basis_matrix(dim) + return p2c_basis_transform @ pl_matrix @ p2c_basis_transform.conj().T / dim + + +def pauli_liouville2choi(pl_matrix: np.ndarray) -> np.ndarray: + """ + Convert a Pauli-Liouville matrix into a choi matrix. + + :param pl_matrix: a dim**2 by dim**2 Pauli-Liouville matrix + :return: dim**2 by dim**2 choi matrix + """ + return superop2choi(pauli_liouville2superop(pl_matrix)) + + +def choi2kraus(choi: np.ndarray, tol: float = 1e-9) -> List[np.ndarray]: + """ + Converts a Choi matrix into a list of Kraus operators. (operators with small norm may be + excluded) + + :param choi: a dim**2 by dim**2 choi matrix + :param tol: optional threshold parameter for eigenvalues/kraus ops to be discarded + :return: list of Kraus operators + """ + eigvals, v = np.linalg.eigh(choi) + return [np.lib.scimath.sqrt(eigval) * unvec(np.array([evec]).T) for eigval, evec in + zip(eigvals, v.T) if abs(eigval) > tol] + + +def choi2chi(choi: np.ndarray) -> np.ndarray: + """ + Converts a Choi matrix into a chi matrix. (operators with small norm may be excluded) + :param choi: a dim**2 by dim**2 choi matrix + :return: a dim**2 by dim**2 process matrix + """ + return kraus2chi(choi2kraus(choi)) + + +def choi2superop(choi: np.ndarray) -> np.ndarray: + """ + Convert a choi matrix into a superoperator. The operation acts equivalently to superop2choi, as it is a bijection. + + :param choi: a dim**2 by dim**2 choi matrix + :return: dim**2 by dim**2 superoperator + """ + dim = int(np.sqrt(np.asarray(choi).shape[0])) + return np.reshape(choi, [dim] * 4).swapaxes(0, 3).reshape([dim ** 2, dim ** 2]) + + +def choi2pauli_liouville(choi: np.ndarray) -> np.ndarray: + """ + Convert a choi matrix into a Pauli-Liouville matrix. + + :param choi: a dim**2 by dim**2 choi matrix + :return: dim**2 by dim**2 Pauli-Liouville matrix + """ + return superop2pauli_liouville(choi2superop(choi)) + + +def pauli2computational_basis_matrix(dim) -> np.ndarray: + """ + Produces a basis transform matrix that converts from a pauli basis to the computational basis. + p2c_transform = sum_{k=1}^{dim**2} | sigma_k >> > + + :param dim: dimension of the hilbert space on which the operators act. + :return: A dim**2 by dim**2 basis transform matrix + """ + n_qubits = int(np.log2(dim)) + + conversion_mat = np.zeros((dim ** 2, dim ** 2), dtype=complex) + + for i, pauli in enumerate(n_qubit_pauli_basis(n_qubits)): + pauli_label = np.zeros((dim ** 2, 1)) + pauli_label[i] = 1. + pauli_mat = pauli[1] + conversion_mat += np.kron(vec(pauli_mat), pauli_label.T) + + return conversion_mat + + +def computational2pauli_basis_matrix(dim) -> np.ndarray: + """ + Produces a basis transform matrix that converts from a computational basis to a pauli basis. + Conjugate transpose of pauli2computational_basis_matrix with an extra dimensional factor. + c2p_transform = sum_{k=1}^{dim**2} | k > << sigma_k | + For example, + vec(sigma_z) = | sigma_z >> = [1, 0, 0, -1].T in the computational basis + c2p * | sigma_z >> = [0, 0, 0, 1].T + + :param dim: dimension of the hilbert space on which the operators act. + :return: A dim**2 by dim**2 basis transform matrix + """ + return pauli2computational_basis_matrix(dim).conj().T / dim + + +# ================================================================================================== +# Channel and Superoperator approximation tools +# ================================================================================================== +def pauli_twirl_chi_matrix(chi_matrix: np.ndarray) -> np.ndarray: + r""" + Implements a Pauli twirl of a chi matrix (aka a process matrix). + + See the folloiwng reference for more details + + [SPICC] Scalable protocol for identification of correctable codes + Silva et al., + PRA 78, 012347 2008 + http://dx.doi.org/10.1103/PhysRevA.78.012347 + https://arxiv.org/abs/0710.1900 + + Note: Pauli twirling a quantum channel can give rise to a channel that is less noisy; use with + care. + + :param chi_matrix: a dim**2 by dim**2 chi or process matrix + :return: dim**2 by dim**2 chi or process matrix + """ + return np.diag(chi_matrix.diagonal()) + + +# TODO: Honest approximations for Channels that act on one or MORE qubits. + +# ================================================================================================== +# Apply channel +# ================================================================================================== +def apply_kraus_ops_2_state(kraus_ops: Sequence[np.ndarray], state: np.ndarray) -> np.ndarray: + r""" + Apply a quantum channel, specified by Kraus operators, to state. + + The Kraus operators need not be square. + + :param kraus_ops: A list or tuple of N Kraus operators, each operator is M by dim ndarray + :param state: A dim by dim ndarray which is the density matrix for the state + :return: M by M ndarray which is the density matrix for the state after the action of kraus_ops + """ + if isinstance(kraus_ops, np.ndarray): # handle input of single kraus op + if len(kraus_ops[0].shape) < 2: # first elem is not a matrix + kraus_ops = [kraus_ops] + + dim, _ = state.shape + rows, cols = kraus_ops[0].shape + + if dim != cols: + raise ValueError("Dimensions of state and Kraus operator are incompatible") + + new_state = np.zeros((rows, rows)) + for M in kraus_ops: + new_state += M @ state @ np.transpose(M.conj()) + + return new_state + + +def apply_choi_matrix_2_state(choi: np.ndarray, state: np.ndarray) -> np.ndarray: + r""" + Apply a quantum channel, specified by a Choi matrix (using the column stacking convention), + to a state. + + The Choi matrix is a dim**2 by dim**2 matrix and the state rho is a dim by dim matrix. The + output state is + + rho_{out} = Tr_{A_{in}}[(rho^T \otimes Id) Choi_matrix ], + + where T denotes transposition and Tr_{A_{in}} is the partial trace over input Hilbert space H_{ + A_{in}}; the Choi matrix representing a process mapping rho in H_{A_{in}} to rho_{out} + in H_{B_{out}} is regarded as an operator on the space H_{A_{in}} \otimes H_{B_{out}}. + + + :param choi: a dim**2 by dim**2 matrix + :param state: A dim by dim ndarray which is the density matrix for the state + :return: a dim by dim matrix. + """ + dim = int(np.sqrt(np.asarray(choi).shape[0])) + dims = [dim, dim] + tot_matrix = np.kron(state.transpose(), np.identity(dim)) @ choi + return partial_trace(tot_matrix, [1], dims) + + +# ================================================================================================== +# Check physicality of Channels +# ================================================================================================== +def kraus_operators_are_valid(kraus_ops: Sequence[np.ndarray], + rtol: float = 1e-05, + atol: float = 1e-08)-> bool: + """ + Checks if a set of Kraus operators are valid. + + :param kraus_ops: A list of Kraus operators + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose + :return: True if the Kraus operators are valid with the given tolerance; False otherwise. + """ + if isinstance(kraus_ops, np.ndarray): # handle input of single kraus op + if len(kraus_ops[0].shape) < 2: # first elem is not a matrix + kraus_ops = [kraus_ops] + rows, _ = np.asarray(kraus_ops[0]).shape + # Standard case of square Kraus operators is if rows==cols. For non-square Kraus ops it is + # required that sum_i M_i^\dagger M_i = np.eye(rows,rows). + id_iff_valid = sum(np.transpose(op).conjugate().dot(op) for op in kraus_ops) + # TODO: check if each POVM element (i.e. M_i^\dagger M_i) is PSD + return np.allclose(id_iff_valid, np.eye(rows), rtol=rtol, atol=atol) + + +def choi_is_hermitian_preserving(choi: np.ndarray, rtol: float = 1e-05, + atol: float = 1e-08) -> bool: + """ + Checks if a quantum process, specified by a Choi matrix, is hermitian-preserving. + + :param choi: a dim**2 by dim**2 Choi matrix + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose + :return: Returns True if the quantum channel is hermitian preserving with the given tolerance; + False otherwise. + """ + # Equation 3.31 of [GRAPTN] + return np.allclose(choi, choi.conj().T, rtol=rtol, atol=atol) + + +def choi_is_trace_preserving(choi: np.ndarray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: + """ + Checks if a quantum process, specified by a Choi matrix, is trace-preserving. + + :param choi: A dim**2 by dim**2 Choi matrix + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose + :return: Returns True if the quantum channel is trace-preserving with the given tolerance; + False otherwise. + """ + rows, cols = choi.shape + dim = int(np.sqrt(rows)) + # the choi matrix acts on the Hilbert space H_{in} \otimes H_{out}. + # We want to trace out H_{out} and so keep the H_{in} space at index 0. + keep = [0] + id_iff_tp = partial_trace(choi, keep, [dim, dim]) + # Equation 3.33 of [GRAPTN] + return np.allclose(id_iff_tp, np.identity(dim), rtol=rtol, atol=atol) + + +def choi_is_completely_positive(choi: np.ndarray, limit: float = 1e-09) -> bool: + """ + Checks if a quantum process, specified by a Choi matrix, is completely positive. + + :param choi: A dim**2 by dim**2 Choi matrix + :param limit: A tolerance parameter, all eigenvalues must be greater than -|limit|. + :return: Returns True if the quantum channel is completely positive with the given tolerance; + False otherwise. + """ + evals, evecs = np.linalg.eig(choi) + # Equation 3.35 of [GRAPTN] + return all(x >= -abs(limit) for x in evals) + + +def choi_is_unital(choi: np.ndarray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: + """ + Checks if a quantum process, specified by a Choi matrix, is unital. + + A process is unital iff it maps the identity to itself. + + :param choi: A dim**2 by dim**2 Choi matrix + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose + :return: Returns True if the quantum channel is unital with the given tolerance; False + otherwise. + """ + rows, cols = choi.shape + dim = int(np.sqrt(rows)) + id_iff_unital = apply_choi_matrix_2_state(choi, np.identity(dim)) + return np.allclose(id_iff_unital, np.identity(dim), rtol=rtol, atol=atol) + + +def choi_is_unitary(choi: np.ndarray, limit: float = 1e-09) -> bool: + """ + Checks if a quantum process, specified by a Choi matrix, is unitary. + + :param choi: A dim**2 by dim**2 Choi matrix + :param limit: A tolerance parameter to discard Kraus operators with small norm. + :return: Returns True if the quantum channel is unitary with the given tolerance; False + otherwise. + """ + kraus_ops = choi2kraus(choi, tol=limit) + return len(kraus_ops) == 1 + +# ================================================================================================== +# Project Channels to CP, TNI, TP, and physical +# ================================================================================================== +def proj_choi_to_completely_positive(choi: np.ndarray) -> np.ndarray: + """ + Projects the Choi representation of a process into the nearest Choi matrix in the space of + completely positive maps. + + Equation 8 of [PGD] + + [PGD] Maximum-likelihood quantum process tomography via projected gradient descent + Knee et al., + Phys. Rev. A 98, 062336 (2018) + https://dx.doi.org/10.1103/PhysRevA.98.062336 + https://arxiv.org/abs/1803.10062 + + :param choi: Choi representation of a process + :return: closest Choi matrix in the space of completely positive maps + """ + hermitian = (choi + choi.conj().T) / 2 # enforce Hermiticity + evals, v = np.linalg.eigh(hermitian) + evals[evals < 0] = 0 # enforce completely positive by removing negative eigenvalues + diag = np.diag(evals) + return v @ diag @ v.conj().T + + +def proj_choi_to_trace_non_increasing(choi: np.ndarray) -> np.ndarray: + """ + Projects the Choi matrix of a process into the space of trace non-increasing maps. + + Equation 33 of [PGD] + + :param choi: Choi representation of a process + :return: Choi representation of the projected trace non-increasing process + """ + dim = int(np.sqrt(choi.shape[0])) + + # trace out the output Hilbert space + pt = partial_trace(choi, dims=[dim, dim], keep=[0]) + + hermitian = (pt + pt.conj().T) / 2 # enforce Hermiticity + d, v = np.linalg.eigh(hermitian) + d[d > 1] = 1 # enforce trace preserving + D = np.diag(d) + projection = v @ D @ v.conj().T + + trace_increasing_part = np.kron((pt - projection) / dim, np.eye(dim)) + + return choi - trace_increasing_part + + +def proj_choi_to_trace_preserving(choi: np.ndarray) -> np.ndarray: + """ + Projects the Choi representation of a process to the closest processes in the space of trace + preserving maps. + + Equation 12 of [PGD], but without vecing the Choi matrix. See choi_is_trace_preserving for + comparison. + + :param choi: Choi representation of a process + :return: Choi representation of the projected trace preserving process + """ + dim = int(np.sqrt(choi.shape[0])) + + # trace out the output Hilbert space, keep the input space at index 0 + pt = partial_trace(choi, dims=[dim, dim], keep=[0]) + # isolate the part the violates the condition we want, namely pt = Id + diff = pt - np.eye(dim) + # we want to subtract off the violation from the larger operator, so 'invert' the partial_trace + subtract = np.kron(diff/dim, np.eye(dim)) + return choi - subtract + + +def proj_choi_to_physical(choi, make_trace_preserving=True): + """ + Projects the given Choi matrix into the subspace of Completetly Positive and either + Trace Perserving (TP) or Trace-Non-Increasing maps. + + Uses Dykstra's algorithm with the stopping criterion presented in: + + [DYKALG] Dykstra’s algorithm and robust stopping criteria + Birgin et al., + (Springer US, Boston, MA, 2009), pp. 828–833, ISBN 978-0-387-74759-0. + https://doi.org/10.1007/978-0-387-74759-0_143 + + This method is suggested in [PGD] + + :param choi: the Choi representation estimate of a quantum process. + :param make_trace_preserving: default true, projects the estimate to a trace-preserving + process. If false the output process may only be trace non-increasing + :return: The Choi representation of the Completely Positive, Trace Preserving (CPTP) or Trace + Non-Increasing map that is closest to the given state. + """ + old_CP_change = np.zeros_like(choi) + old_TP_change = np.zeros_like(choi) + last_CP_projection = np.zeros_like(choi) + last_state = choi + + while True: + # Dykstra's algorithm + pre_CP = last_state - old_CP_change + CP_projection = proj_choi_to_completely_positive(pre_CP) + new_CP_change = CP_projection - pre_CP + + pre_TP = CP_projection - old_TP_change + if make_trace_preserving: + new_state = proj_choi_to_trace_preserving(pre_TP) + else: + new_state = proj_choi_to_trace_non_increasing(pre_TP) + new_TP_change = new_state - pre_TP + + CP_change_change = new_CP_change - old_CP_change + TP_change_change = new_TP_change - old_TP_change + state_change = new_state - last_state + + # stopping criterion + # norm(mat) is the frobenius norm + # norm(mat)**2 is thus equivalent to the dot product vec(mat) dot vec(mat) + if np.linalg.norm(CP_change_change) ** 2 + np.linalg.norm(TP_change_change) ** 2 \ + + 2 * abs(np.dot(vec(old_TP_change).conj().T, vec(state_change))) \ + + 2 * abs(np.dot(vec(old_CP_change).conj().T, + vec(CP_projection - last_CP_projection))) < 1e-4: + break + + # store results from this iteration + old_CP_change = new_CP_change + old_TP_change = new_TP_change + last_CP_projection = CP_projection + last_state = new_state + + return new_state From 3876fb729031468dfe00fb6861dd7a815e3f2a9d Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Tue, 11 Jun 2019 17:16:49 +1000 Subject: [PATCH 03/33] fix the legend not showing and add padding to the title --- forest/benchmarking/plotting/state_process.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/forest/benchmarking/plotting/state_process.py b/forest/benchmarking/plotting/state_process.py index 3ae51d17..bb589f3b 100644 --- a/forest/benchmarking/plotting/state_process.py +++ b/forest/benchmarking/plotting/state_process.py @@ -109,11 +109,12 @@ def plot_pauli_transfer_matrix(ptransfermatrix, ax, labels=None, title='', fonts ticklabs = cb.ax.get_yticklabels() cb.ax.set_yticklabels(ticklabs, ha='right') cb.ax.yaxis.set_tick_params(pad=35) + cb.draw_all() ax.set_xticks(range(dim_squared)) ax.set_xlabel("Input Pauli Operator", fontsize=fontsizes) ax.set_yticks(range(dim_squared)) ax.set_ylabel("Output Pauli Operator", fontsize=fontsizes) - ax.set_title(title, fontsize= int(np.floor(1.2*fontsizes))) + ax.set_title(title, fontsize= int(np.floor(1.2*fontsizes)), pad=15) ax.set_xticklabels(labels, rotation=45, fontsize=int(np.floor(0.7*fontsizes))) ax.set_yticklabels(labels, fontsize=int(np.floor(0.7*fontsizes))) ax.grid(False) From 4036a9e73781e316fba66f90ef324acb55f026c5 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Tue, 11 Jun 2019 17:38:19 +1000 Subject: [PATCH 04/33] superoperator_transformations --- .../superoperator_transformations.py | 381 ++++++++++++++++++ 1 file changed, 381 insertions(+) create mode 100644 forest/benchmarking/operator_tools/superoperator_transformations.py diff --git a/forest/benchmarking/operator_tools/superoperator_transformations.py b/forest/benchmarking/operator_tools/superoperator_transformations.py new file mode 100644 index 00000000..381a2567 --- /dev/null +++ b/forest/benchmarking/operator_tools/superoperator_transformations.py @@ -0,0 +1,381 @@ +"""A module containing tools for converting between different representations of superoperators. + +We have arbitrarily decided to use a column stacking convention. + +For more information about the conventions used, look at the file in +/docs/Superoperator representations.md + +Further references include: + +[GRAPTN] Tensor networks and graphical calculus for open quantum systems + Wood et al. + Quant. Inf. Comp. 15, 0579-0811 (2015) + (no DOI) + https://arxiv.org/abs/1111.6950 + +[MATQO] On the Matrix Representation of Quantum Operations + Nambu et al., + arXiv: 0504091 (2005) + https://arxiv.org/abs/quant-ph/0504091 + +[DUAL] On duality between quantum maps and quantum states + Zyczkowski et al., + Open Syst. Inf. Dyn. 11, 3 (2004) + https://dx.doi.org/10.1023/B:OPSY.0000024753.05661.c2 + https://arxiv.org/abs/quant-ph/0401119 + +""" +from typing import Sequence, Tuple, List +import numpy as np +from forest.benchmarking.utils import n_qubit_pauli_basis + + +# ================================================================================================== +# Superoperator conversion tools +# ================================================================================================== + + +def vec(matrix: np.ndarray) -> np.ndarray: + """ + Vectorize, i.e. "vec", a matrix by column stacking. + + For example the 2 by 2 matrix A + + A = [[a, b] + [c, d]] becomes |A>> := vec(A) = (a, c, b, d)^T , + + where |A>> denotes the vec'ed version of A and T denotes transpose. + + :param matrix: A N (rows) by M (columns) numpy array. + :return: Returns a column vector with N by M rows. + """ + return np.asarray(matrix).T.reshape((-1, 1)) + + +def unvec(vector: np.ndarray, shape: Tuple[int, int] = None) -> np.ndarray: + """ + Take a column vector and turn it into a matrix. + + By default, the unvec'ed matrix is assumed to be square. Specifying shape = [N, M] will + produce a N by M matrix where N is the number of rows and M is the number of columns. + + Consider |A>> := vec(A) = (a, c, b, d)^T. `unvec(|A>>)` should return + + A = [[a, b] + [c, d]]. + + :param vector: A (N*M) by 1 numpy array. + :param shape: The shape of the output matrix; by default, the matrix is assumed to be square. + :return: Returns a N by M matrix. + """ + vector = np.asarray(vector) + if shape is None: + dim = int(np.sqrt(vector.size)) + shape = dim, dim + matrix = vector.reshape(*shape).T + return matrix + + +def kraus2chi(kraus_ops: Sequence[np.ndarray]) -> np.ndarray: + r""" + Convert a set of Kraus operators (representing a channel) to + a chi matrix which is also known as a process matrix. + + :param kraus_ops: A list or tuple of N Kraus operators + :return: Returns a dim**2 by dim**2 matrix. + """ + if isinstance(kraus_ops, np.ndarray): # handle input of single kraus op + if len(kraus_ops[0].shape) < 2: # first elem is not a matrix + kraus_ops = [kraus_ops] + + dim = np.asarray(kraus_ops[0]).shape[0] # kraus op is dim by dim matrix + c_vecs = [computational2pauli_basis_matrix(dim) @ vec(kraus) for kraus in kraus_ops] + chi_mat = sum([c_vec @ c_vec.conj().T for c_vec in c_vecs]) + return chi_mat + + +def kraus2superop(kraus_ops: Sequence[np.ndarray]) -> np.ndarray: + r""" + Convert a set of Kraus operators (representing a channel) to + a superoperator using the column stacking convention. + + Suppose the N Kraus operators M_i are dim by dim matrices. Then the + the superoperator is a (dim**2) \otimes (dim**2) matrix. Using the relation + column stacking relation, + vec(ABC) = (C^T\otimes A) vec(B), we can show + + super_operator = \sum_i^N ( M_i^\dagger )^T \otimes M_i + = \sum_i^N M_i^* \otimes M_i + + where A^* is the complex conjugate of a matrix A, A^T is the transpose, + and A^\dagger is the complex conjugate and transpose. + + Note: This function can also convert non-square Kraus operators to a superoperator, + these frequently arise in quantum measurement theory and quantum error correction. In that + situation consider a single Kraus operator that is M by N then the superoperator will be a + M**2 by N**2 matrix. + + :param kraus_ops: A tuple of N Kraus operators + :return: Returns a dim**2 by dim**2 matrix. + """ + if isinstance(kraus_ops, np.ndarray): # handle input of single kraus op + if len(kraus_ops[0].shape) < 2: # first elem is not a matrix + kraus_ops = [kraus_ops] + + rows, cols = np.asarray(kraus_ops[0]).shape + + # Standard case of square Kraus operators is if rows==cols. + # When representing a partial projection, e.g. a single measurement operator + # M_i = Id \otimes np.ndarray: + """ + Convert a set of Kraus operators (representing a channel) to + a Pauli-Liouville matrix (aka Pauli Transfer matrix). + + :param kraus_ops: A list of Kraus operators + :return: Returns dim**2 by dim**2 Pauli-Liouville matrix + """ + return superop2pauli_liouville(kraus2superop(kraus_ops)) + + +def kraus2choi(kraus_ops: Sequence[np.ndarray]) -> np.ndarray: + r""" + Convert a set of Kraus operators (representing a channel) to + a Choi matrix using the column stacking convention. + + Suppose the N Kraus operators M_i are dim by dim matrices. Then the + the Choi matrix is a dim**2 by dim**2 matrix + + choi_matrix = \sum_i^N |M_i>> (|M_i>>)^\dagger + = \sum_i^N |M_i>> <> = vec(M_i) + + :param kraus_ops: A list of N Kraus operators + :return: Returns a dim**2 by dim**2 matrix. + """ + if isinstance(kraus_ops, np.ndarray): # handle input of single kraus op + if len(kraus_ops[0].shape) < 2: # first elem is not a matrix + kraus_ops = [kraus_ops] + + return sum([vec(op) @ vec(op).conj().T for op in kraus_ops]) + + +def chi2pauli_liouville(chi_matrix: np.ndarray) -> np.ndarray: + r""" + Converts a chi matrix (aka a process matrix) to the Pauli Liouville representation. + + :param chi_matrix: a dim**2 by dim**2 process matrix + :return: dim**2 by dim**2 Pauli-Liouville matrix + """ + return choi2pauli_liouville(chi2choi(chi_matrix)) + + +def chi2kraus(chi_matrix: np.ndarray) -> List[np.ndarray]: + """ + Converts a chi matrix into a list of Kraus operators. (operators with small norm may be + excluded) + + :param chi_matrix: a dim**2 by dim**2 process matrix + :return: list of Kraus operators + """ + return pauli_liouville2kraus(chi2pauli_liouville(chi_matrix)) + + +def chi2superop(chi_matrix: np.ndarray) -> np.ndarray: + """ + Converts a chi matrix into a superoperator. + + :param chi_matrix: a dim**2 by dim**2 process matrix + :return: a dim**2 by dim**2 superoperator matrix + """ + return pauli_liouville2superop(chi2pauli_liouville(chi_matrix)) + + +def chi2choi(chi_matrix: np.ndarray) -> np.ndarray: + """ + Converts a chi matrix into a Choi matrix. + + :param chi_matrix: a dim**2 by dim**2 process matrix + :return: a dim**2 by dim**2 Choi matrix + """ + dim = int(np.sqrt(np.asarray(chi_matrix).shape[0])) + p2c = pauli2computational_basis_matrix(dim) + return p2c @ chi_matrix @ p2c.conj().T + + +def superop2kraus(superop: np.ndarray) -> List[np.ndarray]: + """ + Converts a superoperator into a list of Kraus operators. (operators with small norm may be excluded) + + :param superop: a dim**2 by dim**2 superoperator + :return: list of Kraus operators + """ + return choi2kraus(superop2choi(superop)) + + +def superop2chi(superop: np.ndarray) -> np.ndarray: + """ + Converts a superoperator into a list of Kraus operators. (operators with small norm may be excluded) + + :param superop: a dim**2 by dim**2 superoperator + :return: a dim**2 by dim**2 process matrix + """ + return kraus2chi(superop2kraus(superop)) + + +def superop2pauli_liouville(superop: np.ndarray) -> np.ndarray: + """ + Converts a superoperator into a pauli_liouville matrix. This is achieved by a linear change of basis. + + :param superop: a dim**2 by dim**2 superoperator + :return: dim**2 by dim**2 Pauli-Liouville matrix + """ + dim = int(np.sqrt(np.asarray(superop).shape[0])) + c2p_basis_transform = computational2pauli_basis_matrix(dim) + return c2p_basis_transform @ superop @ c2p_basis_transform.conj().T * dim + + +def superop2choi(superop: np.ndarray) -> np.ndarray: + """ + Convert a superoperator into a choi matrix. The operation acts equivalently to choi2superop, as it is a bijection. + + :param superop: a dim**2 by dim**2 superoperator + :return: dim**2 by dim**2 choi matrix + """ + dim = int(np.sqrt(np.asarray(superop).shape[0])) + return np.reshape(superop, [dim] * 4).swapaxes(0, 3).reshape([dim ** 2, dim ** 2]) + + +def pauli_liouville2kraus(pl_matrix: np.ndarray) -> List[np.ndarray]: + """ + Converts a pauli_liouville matrix into a list of Kraus operators. (operators with small norm may be excluded) + + :param pl_matrix: a dim**2 by dim**2 pauli_liouville matrix + :return: list of Kraus operators + """ + return choi2kraus(pauli_liouville2choi(pl_matrix)) + + +def pauli_liouville2chi(pl_matrix: np.ndarray) -> np.ndarray: + """ + Converts a pauli_liouville matrix into a chi matrix. (operators with small norm may be excluded) + + :param pl_matrix: a dim**2 by dim**2 pauli_liouville matrix + :return: a dim**2 by dim**2 process matrix + """ + return kraus2chi(pauli_liouville2kraus(pl_matrix)) + + +def pauli_liouville2superop(pl_matrix: np.ndarray) -> np.ndarray: + """ + Converts a pauli_liouville matrix into a superoperator. This is achieved by a linear change of basis. + + :param pl_matrix: a dim**2 by dim**2 Pauli-Liouville matrix + :return: dim**2 by dim**2 superoperator + """ + dim = int(np.sqrt(np.asarray(pl_matrix).shape[0])) + p2c_basis_transform = pauli2computational_basis_matrix(dim) + return p2c_basis_transform @ pl_matrix @ p2c_basis_transform.conj().T / dim + + +def pauli_liouville2choi(pl_matrix: np.ndarray) -> np.ndarray: + """ + Convert a Pauli-Liouville matrix into a choi matrix. + + :param pl_matrix: a dim**2 by dim**2 Pauli-Liouville matrix + :return: dim**2 by dim**2 choi matrix + """ + return superop2choi(pauli_liouville2superop(pl_matrix)) + + +def choi2kraus(choi: np.ndarray, tol: float = 1e-9) -> List[np.ndarray]: + """ + Converts a Choi matrix into a list of Kraus operators. (operators with small norm may be + excluded) + + :param choi: a dim**2 by dim**2 choi matrix + :param tol: optional threshold parameter for eigenvalues/kraus ops to be discarded + :return: list of Kraus operators + """ + eigvals, v = np.linalg.eigh(choi) + return [np.lib.scimath.sqrt(eigval) * unvec(np.array([evec]).T) for eigval, evec in + zip(eigvals, v.T) if abs(eigval) > tol] + + +def choi2chi(choi: np.ndarray) -> np.ndarray: + """ + Converts a Choi matrix into a chi matrix. (operators with small norm may be excluded) + :param choi: a dim**2 by dim**2 choi matrix + :return: a dim**2 by dim**2 process matrix + """ + return kraus2chi(choi2kraus(choi)) + + +def choi2superop(choi: np.ndarray) -> np.ndarray: + """ + Convert a choi matrix into a superoperator. The operation acts equivalently to superop2choi, as it is a bijection. + + :param choi: a dim**2 by dim**2 choi matrix + :return: dim**2 by dim**2 superoperator + """ + dim = int(np.sqrt(np.asarray(choi).shape[0])) + return np.reshape(choi, [dim] * 4).swapaxes(0, 3).reshape([dim ** 2, dim ** 2]) + + +def choi2pauli_liouville(choi: np.ndarray) -> np.ndarray: + """ + Convert a choi matrix into a Pauli-Liouville matrix. + + :param choi: a dim**2 by dim**2 choi matrix + :return: dim**2 by dim**2 Pauli-Liouville matrix + """ + return superop2pauli_liouville(choi2superop(choi)) + + +def pauli2computational_basis_matrix(dim) -> np.ndarray: + """ + Produces a basis transform matrix that converts from a pauli basis to the computational basis. + p2c_transform = sum_{k=1}^{dim**2} | sigma_k >> > + + :param dim: dimension of the hilbert space on which the operators act. + :return: A dim**2 by dim**2 basis transform matrix + """ + n_qubits = int(np.log2(dim)) + + conversion_mat = np.zeros((dim ** 2, dim ** 2), dtype=complex) + + for i, pauli in enumerate(n_qubit_pauli_basis(n_qubits)): + pauli_label = np.zeros((dim ** 2, 1)) + pauli_label[i] = 1. + pauli_mat = pauli[1] + conversion_mat += np.kron(vec(pauli_mat), pauli_label.T) + + return conversion_mat + + +def computational2pauli_basis_matrix(dim) -> np.ndarray: + """ + Produces a basis transform matrix that converts from a computational basis to a pauli basis. + Conjugate transpose of pauli2computational_basis_matrix with an extra dimensional factor. + c2p_transform = sum_{k=1}^{dim**2} | k > << sigma_k | + For example, + vec(sigma_z) = | sigma_z >> = [1, 0, 0, -1].T in the computational basis + c2p * | sigma_z >> = [0, 0, 0, 1].T + + :param dim: dimension of the hilbert space on which the operators act. + :return: A dim**2 by dim**2 basis transform matrix + """ + return pauli2computational_basis_matrix(dim).conj().T / dim From bc349676b016f22532ad17685afa0204a56ad98a Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Tue, 11 Jun 2019 17:42:07 +1000 Subject: [PATCH 05/33] notebook update --- examples/random_operators.ipynb | 459 +++++++++++++++++++++++++++----- 1 file changed, 386 insertions(+), 73 deletions(-) diff --git a/examples/random_operators.ipynb b/examples/random_operators.ipynb index ce16698e..b57737cb 100644 --- a/examples/random_operators.ipynb +++ b/examples/random_operators.ipynb @@ -4,115 +4,293 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Random Operators: examples of random states and channels" + "# Random Operators: random quantum states and channels" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the context of `forest-benchmarking` the primary use of random operators is to test the estimation routines.\n", + "\n", + "For example you might modify a existing state or process tomography routine (or develop a new method) and want to test that your modification works. One way to do that would be to test it on a bunch of random quantum states or channels. " ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", - "from sympy.combinatorics import Permutation\n", - "import forest.benchmarking.random_operators as rand_ops\n", - "import numpy.linalg" + "import forest.benchmarking.operator_tools.random_operators as rand_ops" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Random Operators" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Complex Ginibre ensemble" + "### Complex Ginibre ensemble\n", + "\n", + "This is a subroutine for other methods in the module. The complex Ginibre ensemble is a random matrix where the real and imaginary parts of each entry, $G(n,m)$, are drawn in and IID fashion from $\\mathcal N(0,1)$ e.g.\n", + "\n", + "$$G(n,m) = X(n,m) + i Y(n,m)$$\n", + "\n", + "where $X(n,m), Y(n,m)\\sim \\mathcal N(0,1)$. For our purpose we allow for non square matricies." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 0.51783784+1.59579831j 0.22219084-0.08506946j]\n", + " [ 0.44832176+0.77277708j -0.68330944+0.43481616j]]\n", + "Notice that the above matrix is not Hermitian.\n", + "We can explicitly test if it is Hermitian = False\n" + ] + } + ], "source": [ - "rand_ops.ginibre_matrix_complex(2,2)" + "gini_2by2 = rand_ops.ginibre_matrix_complex(2,2)\n", + "print(gini_2by2)\n", + "\n", + "print('Notice that the above matrix is not Hermitian.')\n", + "print('We can explicitly test if it is Hermitian = ', np.all(gini_2by2.T.conj()==gini_2by2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Haar random unitary" + "### Haar random unitary\n", + "\n", + "Here you simply specify the dimesion of the Hilbert space. " ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 0.31784186+0.74235425j 0.04899764+0.58778052j]\n", + " [ 0.12099686-0.57727504j -0.40682771+0.69757043j]]\n" + ] + } + ], "source": [ "U = rand_ops.haar_rand_unitary(2)\n", + "print(U)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can test to see how unitary it is:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[1.+0.j 0.-0.j]\n", + " [0.+0.j 1.-0.j]]\n", + "[[1.+0.e+00j 0.-1.e-16j]\n", + " [0.+1.e-16j 1.-0.e+00j]]\n" + ] + } + ], + "source": [ "print(np.around(U.dot(np.transpose(np.conj(U))),decimals=15))\n", - "print(np.around(U.dot(np.transpose(np.conj(U))),decimals=16))\n", - "# only good to 16 decimal places..." + "print(np.around(U.dot(np.transpose(np.conj(U))),decimals=16))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Apparently it is only good to 16 decimal places." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Random States" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Haar random pure state " + "### Haar random pure state\n", + "\n", + "The simpliest random state is a state drawn from the Haar measure. It is a pure state, i.e. the purity is $P(\\rho)={\\rm Tr}[\\rho^2]=1$. These states are generated by applying a Haar random unitary to a fixed fuduial state, usually $|0\\ldots0\\rangle$." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The state vector is \n", + " [[0.30195045-0.7933957j]\n", + " [0.20713655-0.4862547j]]\n", + "It has shape (2, 1) and purity P = (1-0j)\n", + "\n", + "\n", + "Now lets look at a random pure state on two qubits\n", + "The state vector is \n", + " [[ 0.19914674+0.28997926j]\n", + " [-0.68868807+0.37357005j]\n", + " [ 0.48884613-0.10562828j]\n", + " [ 0.06850955+0.08709377j]]\n", + "It has shape (4, 1) .\n" + ] + } + ], "source": [ - "psi = rand_ops.haar_rand_state(2)\n", - "print(psi)" + "psi2 = rand_ops.haar_rand_state(2)\n", + "print('The state vector is \\n', psi2, )\n", + "print('It has shape', psi2.shape,'and purity P = ', np.round(np.trace(psi2@psi2.T.conj()@psi2@psi2.T.conj()),2))\n", + "print('\\n')\n", + "print('Now lets look at a random pure state on two qubits')\n", + "psi4 = rand_ops.haar_rand_state(4)\n", + "print('The state vector is \\n', psi4, )\n", + "print('It has shape', psi4.shape,'.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Ginibre State (mixed state with rank K)" + "For fun lets plot the state in the Pauli representation." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ - "# mixed single qubit state\n", - "print(np.around(rand_ops.ginibre_state_matrix(2,2),4))\n", - "print(\"----------------------\")\n", - "# mixed two qubit state\n", - "print(np.around(rand_ops.ginibre_state_matrix(4,2),4))" + "from forest.benchmarking.plotting.state_process import plot_pauli_rep_of_state\n", + "from forest.benchmarking.operator_tools.superoperator_transformations import computational2pauli_basis_matrix, vec \n", + "from forest.benchmarking.utils import n_qubit_pauli_basis\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# change of basis\n", + "n_qubits = 2\n", + "pl_basis_twoq = n_qubit_pauli_basis(n_qubits)\n", + "c2p_twoq = computational2pauli_basis_matrix(2*n_qubits)\n", + "\n", + "# turn a state vector into a state matrix\n", + "rho = psi4@psi4.T.conj()\n", + "# convert the state to the Pauli representation which should be real\n", + "state = np.real(c2p_twoq@vec(rho))\n", + "\n", + "fig, ax = plt.subplots()\n", + "plot_pauli_rep_of_state(state.transpose(), ax, pl_basis_twoq.labels, 'Random Two qubit state in Pauli representation')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Ginibre State (mixed state with rank K)\n", + "\n", + "This function lets us generate mixed states with a specific rank.\n", + "\n", + "Specifically, given a Hilbert space dimension $D$ and a desired rank $K$, this function \n", + "a D by D positive semidefinite matrix of rank $K$ drawn from the Ginibre ensemble. \n", + " \n", + "For $D = K$ these are states drawn from the **Hilbert-Schmidt measure**." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This is a mixed single qubit state drawn from Hilbert-Schmidt measure (as D=K)\n", + "[[0.2243-0.j 0.1617+0.0408j]\n", + " [0.1617-0.0408j 0.7757+0.j ]]\n", + "\n", + "\n", + "This is a mixed two qubit state with rank 2:\n", + "[[ 0.3435+0.j 0.0503+0.1182j -0.007 -0.0502j -0.1974+0.0158j]\n", + " [ 0.0503-0.1182j 0.3327-0.j 0.003 +0.1373j 0.14 -0.0304j]\n", + " [-0.007 +0.0502j 0.003 -0.1373j 0.0802-0.j -0.0364-0.1184j]\n", + " [-0.1974-0.0158j 0.14 +0.0304j -0.0364+0.1184j 0.2436-0.j ]]\n", + "\n", + "\n", + "Here are the eigenvalues: [0. +0.j 0.337-0.j 0.663-0.j 0. -0.j] . You can see only two are non zero.\n" + ] + } + ], "source": [ - "# you cant have Rank > Hilbert space Dim\n", - "rand_ops.ginibre_state_matrix(2,3)" + "print('This is a mixed single qubit state drawn from Hilbert-Schmidt measure (as D=K)')\n", + "print(np.around(rand_ops.ginibre_state_matrix(2,2),4))\n", + "print(\"\\n\")\n", + "print('This is a mixed two qubit state with rank 2:')\n", + "print(np.around(rand_ops.ginibre_state_matrix(4,2),4))\n", + "evals, evec = np.linalg.eig(rand_ops.ginibre_state_matrix(4,2))\n", + "print('\\n')\n", + "print('Here are the eigenvalues:', np.round(evals,3),'. You can see only two are non zero.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## State from Bures measure" + "### State from Bures measure" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0.41135882+0.j , -0.12971395+0.20328594j],\n", + " [-0.12971395-0.20328594j, 0.58864118+0.j ]])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "rand_ops.bures_measure_state_matrix(2)" ] @@ -121,27 +299,138 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Uniform ensemble of CPTP maps" + "## Random quantum Channels" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Uniform ensemble of CPTP maps (BCSZ distribution)\n", + "\n", + "Given a Hilbert space dimension $D$ and a Kraus rank $K$, this function returns a $D^2 × D^2$ Choi matrix $J(Λ)$ of a channel drawn from the BCSZ distribution with Kraus rank $K$." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Here is a random quantum channel on one qubit in Choi form:\n" + ] + }, + { + "data": { + "text/plain": [ + "array([[ 0.46089813+3.46944695e-18j, 0.27874737-3.47538997e-01j,\n", + " 0.07274096-1.05089882e-01j, -0.03975122-5.30473244e-02j],\n", + " [ 0.27874737+3.47538997e-01j, 0.53910187-2.60208521e-18j,\n", + " 0.23673647-2.42995364e-01j, -0.07274096+1.05089882e-01j],\n", + " [ 0.07274096+1.05089882e-01j, 0.23673647+2.42995364e-01j,\n", + " 0.66032833+1.38777878e-17j, -0.44807263-3.41252393e-02j],\n", + " [-0.03975122+5.30473244e-02j, -0.07274096-1.05089882e-01j,\n", + " -0.44807263+3.41252393e-02j, 0.33967167-1.73472348e-18j]])" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "# random quantum channel on one qubit in Choi form\n", - "choi = rand_ops.rand_map_with_BCSZ_dist(2,2)\n", - "choi" + "rand_choi = rand_ops.rand_map_with_BCSZ_dist(2,2)\n", + "print('Here is a random quantum channel on one qubit in Choi form:')\n", + "rand_choi" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "(4, 4)" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "choi.shape" + "rand_choi.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To convert to different superoperator representations we import the module `operator_tools.superoperator_transformations`" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "We can convert this channel to Kraus form and enumerate the Kraus operators.\n", + "Kraus OP #1 is: \n", + " [[0.53125657+0.j 0.03521529-0.36863168j]\n", + " [0.12883999+0.32986757j 0.00258408+0.41559292j]]\n", + "Kraus OP #2 is: \n", + " [[-0.42268734-0.j -0.12783115-0.71193967j]\n", + " [-0.49753153-0.40761732j 0.09729184+0.39683978j]]\n" + ] + } + ], + "source": [ + "import forest.benchmarking.operator_tools.superoperator_transformations as sot\n", + "print('We can convert this channel to Kraus form and enumerate the Kraus operators.')\n", + "\n", + "for idx, kraus_op in enumerate(sot.choi2kraus(rand_choi)):\n", + " print('Kraus OP #'+str(1+idx)+' is: \\n', kraus_op)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "rand_pl = sot.choi2pauli_liouville(rand_choi)\n", + "\n", + "\n", + "from forest.benchmarking.plotting.state_process import plot_pauli_transfer_matrix\n", + "n_qubits = 1\n", + "pl_basis_oneq = n_qubit_pauli_basis(n_qubits)\n", + "c2p_oneq = computational2pauli_basis_matrix(2*n_qubits)\n", + "\n", + "f, (ax1) = plt.subplots(1, 1, figsize=(5.5, 4.2))\n", + "plot_pauli_transfer_matrix(np.real(rand_pl), ax1, pl_basis_oneq.labels, 'The Pauli transfer matrix of a random CPTP channel')\n", + "plt.show()" ] }, { @@ -153,36 +442,47 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "# pick a hilbert space dimension\n", - "D = 2\n", - "# pick a way you want to permute the operators\n", - "perm =[1,2,0]\n", - "# Note: the number of elements in the permutation determines\n", - "# the number of Hilbert spaces you are considering." + "D = 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "^^ here the Identity permutation is P = [0,1,2] which maps (a,b,c) to (a,b,c).\n", - "The permutation P = [1,2,0] maps (a,b,c) to (b,c,a)." + "Lets consider a tensor product of three Hilbert spaces: $$\\mathcal H_a \\otimes \\mathcal H_b\\otimes \\mathcal H_c.$$ \n", + "\n", + "Next we need to pick a way you want to permute the operators; specified by a permutation in [cycle notation](https://en.wikipedia.org/wiki/Permutation#Cycle_notation). \n", + "\n", + "For example the Identity permutation is $P = [0,1,2]$ which maps $(a,b,c)$ to $(a,b,c)$.\n", + "The permutation $P = [1,2,0]$ maps $(a,b,c)$ to $(b,c,a)$, so lets try that." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "perm =[1,2,0]\n", + "# Note: the number of elements in the permutation determines \n", + "# the number of Hilbert spaces you are considering." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Create the basis states in the Hilbert space" + "Create the basis states in the Hilbert space" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -198,29 +498,32 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Create inital state and answer after applying the permutation [1,2,0]" + "Create inital state and answer after applying the permutation [1,2,0]" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ - "inital_vector = np.kron(np.kron(states[0],states[0]),states[1]) # before permuting anything\n", - "perm_vector = np.kron(np.kron(states[0],states[1]),states[0]) # apply the permutation by hand" + "# before permuting anything\n", + "inital_vector = np.kron(np.kron(states[0],states[0]),states[1]) \n", + "\n", + "# apply the permutation by hand\n", + "perm_vector = np.kron(np.kron(states[0],states[1]),states[0]) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### create permutation operator" + "**create permutation operator**" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -231,12 +534,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### check the permutation operator applied to the intial vector gives the correct answer" + "**check the permutation operator applied to the intial vector gives the correct answer**" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "metadata": {}, "outputs": [], "source": [ @@ -245,29 +548,39 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The inner product between the calculated and true answer is one [[1.]]\n" + ] + } + ], "source": [ - "np.matmul(perm_vector.T,answer)" + "print('The inner product between the calculated and true answer is one', np.matmul(perm_vector.T,answer))" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "", - "name": "" + "display_name": "Python 3", + "language": "python", + "name": "python3" }, "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", "name": "python", - "pygments_lexer": "ipython3" + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" } }, "nbformat": 4, From 8c7644c5f9ab62895822d953ea1154aee2ecce72 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Tue, 11 Jun 2019 18:18:07 +1000 Subject: [PATCH 06/33] type checking and docstring updates --- .../operator_tools/random_operators.py | 114 +++++++++--------- 1 file changed, 56 insertions(+), 58 deletions(-) diff --git a/forest/benchmarking/operator_tools/random_operators.py b/forest/benchmarking/operator_tools/random_operators.py index fa57a1df..04e8cdc4 100644 --- a/forest/benchmarking/operator_tools/random_operators.py +++ b/forest/benchmarking/operator_tools/random_operators.py @@ -8,7 +8,7 @@ https://dx.doi.org/10.1088/1367-2630/18/3/033024 https://arxiv.org/abs/1509.03770 """ -from typing import Optional +from typing import Optional, List import numpy as np from numpy import linalg as la @@ -18,10 +18,11 @@ from forest.benchmarking.utils import partial_trace -def ginibre_matrix_complex(D, K, rs: Optional[RandomState] = None): - """ - Given a scalars $D$ and $K$, returns a D × K matrix, - drawn from the complex Ginibre ensemble, i.e. (N(0, 1) + i · N(0, 1)). +def ginibre_matrix_complex(dim: int, k: int, rs: Optional[RandomState] = None) -> np.ndarray: + r""" + Given a scalars dim and k, returns a dim by k matrix, drawn from the complex Ginibre + ensemble, i.e. each element is distributed ~ [N(0, 1) + i · N(0, 1)]. Here X ~ N(0,1) + denotes a normally distributed random variable. [IM] Induced measures in the space of mixed quantum states Zyczkowski et al., @@ -29,21 +30,20 @@ def ginibre_matrix_complex(D, K, rs: Optional[RandomState] = None): https://doi.org/10.1088/0305-4470/34/35/335 https://arxiv.org/abs/quant-ph/0012101 - :param D: Hilbert space dimension (scalar). - :param K: Ultimately becomes the rank of a state (scalar). + :param dim: Hilbert space dimension. + :param k: Ultimately becomes the rank of a state. :param rs: Optional random state. - :return: Returns a D × K matrix, drawn from the Ginibre ensemble. + :return: Returns a dim by k matrix, drawn from the Ginibre ensemble. """ if rs is None: rs = np.random - - return rs.randn(D, K) + 1j * rs.randn(D, K) + return rs.randn(dim, k) + 1j * rs.randn(dim, k) -def haar_rand_unitary(dim, rs=None): +def haar_rand_unitary(dim: int, rs=None) -> np.ndarray: """ - Given a Hilbert space dimension D this function - returns a unitary operator U ∈ C^D×D drawn from the Haar measure. + Given a Hilbert space dimension dim this function + returns a unitary operator U ∈ C^(dim by dim) drawn from the Haar measure. The error is of order 10^-16. @@ -55,81 +55,78 @@ def haar_rand_unitary(dim, rs=None): :param dim: Hilbert space dimension (scalar). :param rs: Optional random state - :return: Returns a unitary operator U ∈ C^D×D drawn from the Haar measure. + :return: Returns a dim by dim unitary operator U drawn from the Haar measure. """ if rs is None: rs = np.random - Z = ginibre_matrix_complex(D=dim, K=dim, rs=rs) # /np.sqrt(2) + Z = ginibre_matrix_complex(dim=dim, k=dim, rs=rs) # /np.sqrt(2) Q, R = np.linalg.qr(Z) diag = np.diagonal(R) lamb = np.diag(diag) / np.absolute(diag) return np.matmul(Q, lamb) -def haar_rand_state(dimension): +def haar_rand_state(dim: int) -> np.ndarray: """ - Given a Hilbert space dimension $D$ this function returns a vector + Given a Hilbert space dimension dim this function returns a vector representing a random pure state operator drawn from the Haar measure. - :param dimension: Hilbert space dimension (scalar). - :return: Returns a Dx1 vector drawn from the Haar measure. + :param dim: Hilbert space dimension. + :return: Returns a dim by 1 vector drawn from the Haar measure. """ - unitary = haar_rand_unitary(dimension) - fiducial_vec = np.zeros((dimension, 1)) + unitary = haar_rand_unitary(dim) + fiducial_vec = np.zeros((dim, 1)) fiducial_vec[0] = 1 return np.matmul(unitary, fiducial_vec) -def ginibre_state_matrix(D, K): +def ginibre_state_matrix(dim: int, rank: int) -> np.ndarray: """ - Given a Hilbert space dimension $D$ and a desired rank $K$, returns - a D × D positive semidefinite matrix of rank K drawn from the Ginibre ensemble. - For D = K these are states drawn from the Hilbert-Schmidt measure. + Given a Hilbert space dimension dim and a desired rank K, returns a dim by dim positive + semidefinite matrix of rank K drawn from the Ginibre ensemble. For dim = K these are states + drawn from the Hilbert-Schmidt measure. See reference [IM] for more details. - :param D: Hilbert space dimension (scalar). - :param K: The rank of a state (scalar). - :return: Returns a D × K matrix, drawn from the Ginibre ensemble. + :param dim: Hilbert space dimension. + :param rank: The rank of a state. + :return: Returns a dim by rank matrix, drawn from the Ginibre ensemble. """ - if K > D: + if rank > dim: raise ValueError("The rank of the state matrix cannot exceed the dimension.") - - A = ginibre_matrix_complex(D, K) + A = ginibre_matrix_complex(dim, rank) M = A.dot(np.transpose(np.conjugate(A))) return M / np.trace(M) -def bures_measure_state_matrix(D): +def bures_measure_state_matrix(dim: int) -> np.ndarray: """ - Given a Hilbert space dimension $D$, returns a D × D positive - semidefinite matrix drawn from the Bures measure. + Given a Hilbert space dimension dim, returns a dim by dim positive semidefinite matrix drawn + from the Bures measure. [OSZ] Random Bures mixed states and the distribution of their purity Osipov et al., J. Phys. A: Math. Theor. 43, 055302 (2010). https://doi.org/10.1088/1751-8113/43/5/055302 https://arxiv.org/abs/0909.5094 - - :param D: Hilbert space dimension (scalar). - :param K: The rank of a state (scalar). - :return: Returns a D × K matrix, drawn from the Ginibre ensemble. + + :param dim: Hilbert space dimension. + :return: Returns a dim by dim matrix, drawn from the Bures measure. """ - A = ginibre_matrix_complex(D, D) - U = haar_rand_unitary(D) + A = ginibre_matrix_complex(dim, dim) + U = haar_rand_unitary(dim) Udag = np.transpose(np.conjugate(U)) - Id = np.eye(D) + Id = np.eye(dim) M = A.dot(np.transpose(np.conjugate(A))) P = (Id + U).dot(M).dot(Id + Udag) return P / np.trace(P) -def rand_map_with_BCSZ_dist(D, K): +def rand_map_with_BCSZ_dist(dim: int, kraus_rank: int) -> np.ndarray: """ - Given a Hilbert space dimension $D$ and a Kraus rank $K$, returns a - $D^2 × D^2$ Choi matrix $J(Λ)$ of a channel drawn from the BCSZ distribution - with Kraus rank $K$. + Given a Hilbert space dimension dim and a Kraus rank K, returns a $dim^2 by dim^2$ Choi + matrix $J(Λ)$ of a channel drawn from the BCSZ distribution with Kraus rank $K$. [RQO] Random quantum operations, Bruzda et al., @@ -137,28 +134,28 @@ def rand_map_with_BCSZ_dist(D, K): https://doi.org/10.1016/j.physleta.2008.11.043 https://arxiv.org/abs/0804.2361 - :param D: Hilbert space dimension (scalar). - :param K: The rank of a state (scalar). - :return: D^2 × D^2 Choi matrix, drawn from the BCSZ distribution with Kraus rank K. + :param dim: Hilbert space dimension. + :param kraus_rank: The number of Kraus operators in the operator sum description of the channel. + :return: dim^2 by dim^2 Choi matrix, drawn from the BCSZ distribution with Kraus rank K. """ # TODO: this ^^ is CPTP, might want a flag that allows for just CP quantum operations. - X = ginibre_matrix_complex(D ** 2, K) + X = ginibre_matrix_complex(dim ** 2, kraus_rank) rho = X @ X.conj().T - rho_red = partial_trace(rho, [0], [D, D]) + rho_red = partial_trace(rho, [0], [dim, dim]) # Note that Eqn. 8 of [RQO] uses a *row* stacking convention so in that case we would write # Q = np.kron(np.eye(D), sqrtm(la.inv(rho_red))) # But as we use column stacking we need: - Q = np.kron(sqrtm(la.inv(rho_red)), np.eye(D)) + Q = np.kron(sqrtm(la.inv(rho_red)), np.eye(dim)) Z = Q @ rho @ Q return Z -def permute_tensor_factors(D, perm): +def permute_tensor_factors(dim: int, perm: List[int]) -> np.ndarray: r""" Return a permutation matrix of the given dimension. - Given a Hilbert space dimension $D$ and an list representing the permutation $perm$ of the - tensor product Hilbert spaces, returns a $D^len(perm)$ by $D^len(perm)$ permutation matrix. + Given a Hilbert space dimension dim and an list representing the permutation perm of the + tensor product Hilbert spaces, returns a $dim^len(perm)$ by $dim^len(perm)$ permutation matrix. E.g. 1) Suppose D=2 and perm=[0,1] Returns the identity operator on two qubits @@ -178,12 +175,13 @@ def permute_tensor_factors(D, perm): This function is used in tests for other functions. However, it can also be useful when thinking about higher moment (N>2) integrals over the Haar measure. - :param D: Hilbert space dimension (scalar). + :param dim: Hilbert space dimension. :param perm: A list representing the permutation of the tensor factors. + :return: a matrix permuting the operators """ - dim_list = [D for i in range(2 * len(perm))] + dim_list = [dim for i in range(2 * len(perm))] - Id = np.eye(D ** len(perm), D ** len(perm)) + Id = np.eye(dim ** len(perm), dim ** len(perm)) P = Permutation(perm) tran = P.transpositions @@ -201,4 +199,4 @@ def permute_tensor_factors(D, perm): answer = np.swapaxes(temp, tdx[0], tdx[1]) temp = answer - return np.reshape(answer, [D ** len(perm), D ** len(perm)]) + return np.reshape(answer, [dim ** len(perm), dim ** len(perm)]) From 9eaf272f15cdc52d5697efff1fe2d98d4dd99c58 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Tue, 11 Jun 2019 18:18:38 +1000 Subject: [PATCH 07/33] add superoperator_transformations --- forest/benchmarking/operator_tools/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forest/benchmarking/operator_tools/__init__.py b/forest/benchmarking/operator_tools/__init__.py index 6489073e..33c728ea 100644 --- a/forest/benchmarking/operator_tools/__init__.py +++ b/forest/benchmarking/operator_tools/__init__.py @@ -1,2 +1,2 @@ #from .random_operators import * -__all__ = ["random_operators"] \ No newline at end of file +__all__ = ["random_operators", "superoperator_transformations"] \ No newline at end of file From 33da5f4295cc6c833c0c6cc02de80e0740a516d9 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Tue, 11 Jun 2019 19:48:58 +1000 Subject: [PATCH 08/33] tests --- forest/benchmarking/tests/test_random_operators.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/forest/benchmarking/tests/test_random_operators.py b/forest/benchmarking/tests/test_random_operators.py index b61d010c..203ad26f 100644 --- a/forest/benchmarking/tests/test_random_operators.py +++ b/forest/benchmarking/tests/test_random_operators.py @@ -1,3 +1,4 @@ +import pytest import numpy.random numpy.random.seed(1) # seed random number generation for all calls to rand_ops @@ -5,9 +6,9 @@ from sympy.combinatorics import Permutation from numpy import linalg as la import forest.benchmarking.distance_measures as dm -import forest.benchmarking.random_operators as rand_ops -from forest.benchmarking.superoperator_tools import choi_is_trace_preserving, \ - choi_is_completely_positive +import forest.benchmarking.operator_tools.random_operators as rand_ops +from forest.benchmarking.operator_tools.superoperator_tools import ( + choi_is_trace_preserving,choi_is_completely_positive) D2_SWAP = np.array([[1, 0, 0, 0], [0, 0, 1, 0], @@ -168,7 +169,7 @@ def test_random_unitaries_are_unitary(): assert np.allclose(avg_trace3, 3.0) assert np.allclose(avg_det3, 1.0) - +# ~ 11 sec; passed 2019/06/11 def test_random_unitaries_first_moment(): # the first moment should be proportional to P_21/D N_avg = 50000 @@ -203,6 +204,8 @@ def test_random_unitaries_first_moment(): # for dimensions 2 and 3 +# ~ 12 sec; passed 2019/06/11 +@pytest.mark.slow def test_random_unitaries_second_moment(): # the second moment should be proportional to # From dfb46425c5590f68487e0c7b6d421f3203e681f2 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Tue, 11 Jun 2019 19:49:56 +1000 Subject: [PATCH 09/33] superoperator_validation --- .../superoperator_validation.py | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 forest/benchmarking/operator_tools/superoperator_validation.py diff --git a/forest/benchmarking/operator_tools/superoperator_validation.py b/forest/benchmarking/operator_tools/superoperator_validation.py new file mode 100644 index 00000000..e48f287a --- /dev/null +++ b/forest/benchmarking/operator_tools/superoperator_validation.py @@ -0,0 +1,137 @@ +"""A module allowing one to check if superoperators or channels are physical. + +We have arbitrarily decided to use a column stacking convention. + +For more information about the conventions used, look at the file in +/docs/Superoperator representations.md + +Further references include: + +[GRAPTN] Tensor networks and graphical calculus for open quantum systems + Wood et al. + Quant. Inf. Comp. 15, 0579-0811 (2015) + (no DOI) + https://arxiv.org/abs/1111.6950 + +[MATQO] On the Matrix Representation of Quantum Operations + Nambu et al., + arXiv: 0504091 (2005) + https://arxiv.org/abs/quant-ph/0504091 + +[DUAL] On duality between quantum maps and quantum states + Zyczkowski et al., + Open Syst. Inf. Dyn. 11, 3 (2004) + https://dx.doi.org/10.1023/B:OPSY.0000024753.05661.c2 + https://arxiv.org/abs/quant-ph/0401119 + +""" +from typing import Sequence +import numpy as np +from forest.benchmarking.utils import partial_trace +from forest.benchmarking.operator_tools.superoperator_transformations import choi2kraus +from forest.benchmarking.operator_tools.superoperator_tools import apply_choi_matrix_2_state + + +# ================================================================================================== +# Check physicality of Channels +# ================================================================================================== +def kraus_operators_are_valid(kraus_ops: Sequence[np.ndarray], + rtol: float = 1e-05, + atol: float = 1e-08) -> bool: + """ + Checks if a set of Kraus operators are valid. + + :param kraus_ops: A list of Kraus operators + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose + :return: True if the Kraus operators are valid with the given tolerance; False otherwise. + """ + if isinstance(kraus_ops, np.ndarray): # handle input of single kraus op + if len(kraus_ops[0].shape) < 2: # first elem is not a matrix + kraus_ops = [kraus_ops] + rows, _ = np.asarray(kraus_ops[0]).shape + # Standard case of square Kraus operators is if rows==cols. For non-square Kraus ops it is + # required that sum_i M_i^\dagger M_i = np.eye(rows,rows). + id_iff_valid = sum(np.transpose(op).conjugate().dot(op) for op in kraus_ops) + # TODO: check if each POVM element (i.e. M_i^\dagger M_i) is PSD + return np.allclose(id_iff_valid, np.eye(rows), rtol=rtol, atol=atol) + + +def choi_is_hermitian_preserving(choi: np.ndarray, rtol: float = 1e-05, + atol: float = 1e-08) -> bool: + """ + Checks if a quantum process, specified by a Choi matrix, is hermitian-preserving. + + :param choi: a dim**2 by dim**2 Choi matrix + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose + :return: Returns True if the quantum channel is hermitian preserving with the given tolerance; + False otherwise. + """ + # Equation 3.31 of [GRAPTN] + return np.allclose(choi, choi.conj().T, rtol=rtol, atol=atol) + + +def choi_is_trace_preserving(choi: np.ndarray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: + """ + Checks if a quantum process, specified by a Choi matrix, is trace-preserving. + + :param choi: A dim**2 by dim**2 Choi matrix + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose + :return: Returns True if the quantum channel is trace-preserving with the given tolerance; + False otherwise. + """ + rows, cols = choi.shape + dim = int(np.sqrt(rows)) + # the choi matrix acts on the Hilbert space H_{in} \otimes H_{out}. + # We want to trace out H_{out} and so keep the H_{in} space at index 0. + keep = [0] + id_iff_tp = partial_trace(choi, keep, [dim, dim]) + # Equation 3.33 of [GRAPTN] + return np.allclose(id_iff_tp, np.identity(dim), rtol=rtol, atol=atol) + + +def choi_is_completely_positive(choi: np.ndarray, limit: float = 1e-09) -> bool: + """ + Checks if a quantum process, specified by a Choi matrix, is completely positive. + + :param choi: A dim**2 by dim**2 Choi matrix + :param limit: A tolerance parameter, all eigenvalues must be greater than -|limit|. + :return: Returns True if the quantum channel is completely positive with the given tolerance; + False otherwise. + """ + evals, evecs = np.linalg.eig(choi) + # Equation 3.35 of [GRAPTN] + return all(x >= -abs(limit) for x in evals) + + +def choi_is_unital(choi: np.ndarray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: + """ + Checks if a quantum process, specified by a Choi matrix, is unital. + + A process is unital iff it maps the identity to itself. + + :param choi: A dim**2 by dim**2 Choi matrix + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose + :return: Returns True if the quantum channel is unital with the given tolerance; False + otherwise. + """ + rows, cols = choi.shape + dim = int(np.sqrt(rows)) + id_iff_unital = apply_choi_matrix_2_state(choi, np.identity(dim)) + return np.allclose(id_iff_unital, np.identity(dim), rtol=rtol, atol=atol) + + +def choi_is_unitary(choi: np.ndarray, limit: float = 1e-09) -> bool: + """ + Checks if a quantum process, specified by a Choi matrix, is unitary. + + :param choi: A dim**2 by dim**2 Choi matrix + :param limit: A tolerance parameter to discard Kraus operators with small norm. + :return: Returns True if the quantum channel is unitary with the given tolerance; False + otherwise. + """ + kraus_ops = choi2kraus(choi, tol=limit) + return len(kraus_ops) == 1 From 18a2bc98966b11b4614d1542a982c26720eebac3 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Tue, 11 Jun 2019 19:55:00 +1000 Subject: [PATCH 10/33] apply superop --- .../operator_tools/apply_superoperator.py | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 forest/benchmarking/operator_tools/apply_superoperator.py diff --git a/forest/benchmarking/operator_tools/apply_superoperator.py b/forest/benchmarking/operator_tools/apply_superoperator.py new file mode 100644 index 00000000..012fae44 --- /dev/null +++ b/forest/benchmarking/operator_tools/apply_superoperator.py @@ -0,0 +1,85 @@ +"""A module containing tools for applying superoperators to states. + +We have arbitrarily decided to use a column stacking convention. + +For more information about the conventions used, look at the file in +/docs/Superoperator representations.md + +Further references include: + +[GRAPTN] Tensor networks and graphical calculus for open quantum systems + Wood et al. + Quant. Inf. Comp. 15, 0579-0811 (2015) + (no DOI) + https://arxiv.org/abs/1111.6950 + +[MATQO] On the Matrix Representation of Quantum Operations + Nambu et al., + arXiv: 0504091 (2005) + https://arxiv.org/abs/quant-ph/0504091 + +[DUAL] On duality between quantum maps and quantum states + Zyczkowski et al., + Open Syst. Inf. Dyn. 11, 3 (2004) + https://dx.doi.org/10.1023/B:OPSY.0000024753.05661.c2 + https://arxiv.org/abs/quant-ph/0401119 + +""" +from typing import Sequence +import numpy as np +from forest.benchmarking.utils import partial_trace + + +# ================================================================================================== +# Apply channel +# ================================================================================================== +def apply_kraus_ops_2_state(kraus_ops: Sequence[np.ndarray], state: np.ndarray) -> np.ndarray: + r""" + Apply a quantum channel, specified by Kraus operators, to state. + + The Kraus operators need not be square. + + :param kraus_ops: A list or tuple of N Kraus operators, each operator is M by dim ndarray + :param state: A dim by dim ndarray which is the density matrix for the state + :return: M by M ndarray which is the density matrix for the state after the action of kraus_ops + """ + if isinstance(kraus_ops, np.ndarray): # handle input of single kraus op + if len(kraus_ops[0].shape) < 2: # first elem is not a matrix + kraus_ops = [kraus_ops] + + dim, _ = state.shape + rows, cols = kraus_ops[0].shape + + if dim != cols: + raise ValueError("Dimensions of state and Kraus operator are incompatible") + + new_state = np.zeros((rows, rows)) + for M in kraus_ops: + new_state += M @ state @ np.transpose(M.conj()) + + return new_state + + +def apply_choi_matrix_2_state(choi: np.ndarray, state: np.ndarray) -> np.ndarray: + r""" + Apply a quantum channel, specified by a Choi matrix (using the column stacking convention), + to a state. + + The Choi matrix is a dim**2 by dim**2 matrix and the state rho is a dim by dim matrix. The + output state is + + rho_{out} = Tr_{A_{in}}[(rho^T \otimes Id) Choi_matrix ], + + where T denotes transposition and Tr_{A_{in}} is the partial trace over input Hilbert space H_{ + A_{in}}; the Choi matrix representing a process mapping rho in H_{A_{in}} to rho_{out} + in H_{B_{out}} is regarded as an operator on the space H_{A_{in}} \otimes H_{B_{out}}. + + + :param choi: a dim**2 by dim**2 matrix + :param state: A dim by dim ndarray which is the density matrix for the state + :return: a dim by dim matrix. + """ + dim = int(np.sqrt(np.asarray(choi).shape[0])) + dims = [dim, dim] + tot_matrix = np.kron(state.transpose(), np.identity(dim)) @ choi + return partial_trace(tot_matrix, [1], dims) From a885f011223f383068eca61e9eeb80f5fd6b546e Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Wed, 12 Jun 2019 08:58:32 +1000 Subject: [PATCH 11/33] proj superops --- .../operator_tools/project_superoperators.py | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 forest/benchmarking/operator_tools/project_superoperators.py diff --git a/forest/benchmarking/operator_tools/project_superoperators.py b/forest/benchmarking/operator_tools/project_superoperators.py new file mode 100644 index 00000000..316091ae --- /dev/null +++ b/forest/benchmarking/operator_tools/project_superoperators.py @@ -0,0 +1,163 @@ +"""A module containing tools for projecting superoperators to CP, TNI, TP, and physical. + +We have arbitrarily decided to use a column stacking convention. + +For more information about the conventions used, look at the file in +/docs/Superoperator representations.md + +Further references include: + +[GRAPTN] Tensor networks and graphical calculus for open quantum systems + Wood et al. + Quant. Inf. Comp. 15, 0579-0811 (2015) + (no DOI) + https://arxiv.org/abs/1111.6950 + +[MATQO] On the Matrix Representation of Quantum Operations + Nambu et al., + arXiv: 0504091 (2005) + https://arxiv.org/abs/quant-ph/0504091 + +[DUAL] On duality between quantum maps and quantum states + Zyczkowski et al., + Open Syst. Inf. Dyn. 11, 3 (2004) + https://dx.doi.org/10.1023/B:OPSY.0000024753.05661.c2 + https://arxiv.org/abs/quant-ph/0401119 + +""" +from typing import Sequence, Tuple, List +import numpy as np +from forest.benchmarking.utils import n_qubit_pauli_basis, partial_trace + + +# ================================================================================================== +# Project Channels to CP, TNI, TP, and physical +# ================================================================================================== +def proj_choi_to_completely_positive(choi: np.ndarray) -> np.ndarray: + """ + Projects the Choi representation of a process into the nearest Choi matrix in the space of + completely positive maps. + + Equation 8 of [PGD] + + [PGD] Maximum-likelihood quantum process tomography via projected gradient descent + Knee et al., + Phys. Rev. A 98, 062336 (2018) + https://dx.doi.org/10.1103/PhysRevA.98.062336 + https://arxiv.org/abs/1803.10062 + + :param choi: Choi representation of a process + :return: closest Choi matrix in the space of completely positive maps + """ + hermitian = (choi + choi.conj().T) / 2 # enforce Hermiticity + evals, v = np.linalg.eigh(hermitian) + evals[evals < 0] = 0 # enforce completely positive by removing negative eigenvalues + diag = np.diag(evals) + return v @ diag @ v.conj().T + + +def proj_choi_to_trace_non_increasing(choi: np.ndarray) -> np.ndarray: + """ + Projects the Choi matrix of a process into the space of trace non-increasing maps. + + Equation 33 of [PGD] + + :param choi: Choi representation of a process + :return: Choi representation of the projected trace non-increasing process + """ + dim = int(np.sqrt(choi.shape[0])) + + # trace out the output Hilbert space + pt = partial_trace(choi, dims=[dim, dim], keep=[0]) + + hermitian = (pt + pt.conj().T) / 2 # enforce Hermiticity + d, v = np.linalg.eigh(hermitian) + d[d > 1] = 1 # enforce trace preserving + D = np.diag(d) + projection = v @ D @ v.conj().T + + trace_increasing_part = np.kron((pt - projection) / dim, np.eye(dim)) + + return choi - trace_increasing_part + + +def proj_choi_to_trace_preserving(choi: np.ndarray) -> np.ndarray: + """ + Projects the Choi representation of a process to the closest processes in the space of trace + preserving maps. + + Equation 12 of [PGD], but without vecing the Choi matrix. See choi_is_trace_preserving for + comparison. + + :param choi: Choi representation of a process + :return: Choi representation of the projected trace preserving process + """ + dim = int(np.sqrt(choi.shape[0])) + + # trace out the output Hilbert space, keep the input space at index 0 + pt = partial_trace(choi, dims=[dim, dim], keep=[0]) + # isolate the part the violates the condition we want, namely pt = Id + diff = pt - np.eye(dim) + # we want to subtract off the violation from the larger operator, so 'invert' the partial_trace + subtract = np.kron(diff/dim, np.eye(dim)) + return choi - subtract + + +def proj_choi_to_physical(choi: np.ndarray, make_trace_preserving: bool = True) -> np.ndarray: + """ + Projects the given Choi matrix into the subspace of Completetly Positive and either + Trace Perserving (TP) or Trace-Non-Increasing maps. + + Uses Dykstra's algorithm with the stopping criterion presented in: + + [DYKALG] Dykstra’s algorithm and robust stopping criteria + Birgin et al., + (Springer US, Boston, MA, 2009), pp. 828–833, ISBN 978-0-387-74759-0. + https://doi.org/10.1007/978-0-387-74759-0_143 + + This method is suggested in [PGD] + + :param choi: the Choi representation estimate of a quantum process. + :param make_trace_preserving: default true, projects the estimate to a trace-preserving + process. If false the output process may only be trace non-increasing + :return: The Choi representation of the Completely Positive, Trace Preserving (CPTP) or Trace + Non-Increasing map that is closest to the given state. + """ + old_CP_change = np.zeros_like(choi) + old_TP_change = np.zeros_like(choi) + last_CP_projection = np.zeros_like(choi) + last_state = choi + + while True: + # Dykstra's algorithm + pre_CP = last_state - old_CP_change + CP_projection = proj_choi_to_completely_positive(pre_CP) + new_CP_change = CP_projection - pre_CP + + pre_TP = CP_projection - old_TP_change + if make_trace_preserving: + new_state = proj_choi_to_trace_preserving(pre_TP) + else: + new_state = proj_choi_to_trace_non_increasing(pre_TP) + new_TP_change = new_state - pre_TP + + CP_change_change = new_CP_change - old_CP_change + TP_change_change = new_TP_change - old_TP_change + state_change = new_state - last_state + + # stopping criterion + # norm(mat) is the frobenius norm + # norm(mat)**2 is thus equivalent to the dot product vec(mat) dot vec(mat) + if np.linalg.norm(CP_change_change) ** 2 + np.linalg.norm(TP_change_change) ** 2 \ + + 2 * abs(np.dot(vec(old_TP_change).conj().T, vec(state_change))) \ + + 2 * abs(np.dot(vec(old_CP_change).conj().T, + vec(CP_projection - last_CP_projection))) < 1e-4: + break + + # store results from this iteration + old_CP_change = new_CP_change + old_TP_change = new_TP_change + last_CP_projection = CP_projection + last_state = new_state + + return new_state From 520792bbf3a7ad7ddcdc455ce9122504703fae42 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Wed, 12 Jun 2019 09:00:38 +1000 Subject: [PATCH 12/33] unused imports --- forest/benchmarking/operator_tools/project_superoperators.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/forest/benchmarking/operator_tools/project_superoperators.py b/forest/benchmarking/operator_tools/project_superoperators.py index 316091ae..0a65252a 100644 --- a/forest/benchmarking/operator_tools/project_superoperators.py +++ b/forest/benchmarking/operator_tools/project_superoperators.py @@ -25,9 +25,8 @@ https://arxiv.org/abs/quant-ph/0401119 """ -from typing import Sequence, Tuple, List import numpy as np -from forest.benchmarking.utils import n_qubit_pauli_basis, partial_trace +from forest.benchmarking.utils import partial_trace # ================================================================================================== From 676d2032d7a5d09ec056223ab5ff4e6caf9ef817 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Wed, 12 Jun 2019 09:07:42 +1000 Subject: [PATCH 13/33] add and remove files --- .../operator_tools/channel_approximation.py | 55 ++ .../operator_tools/superoperator_tools.py | 700 ------------------ ...alidation.py => validate_superoperator.py} | 0 3 files changed, 55 insertions(+), 700 deletions(-) create mode 100644 forest/benchmarking/operator_tools/channel_approximation.py delete mode 100644 forest/benchmarking/operator_tools/superoperator_tools.py rename forest/benchmarking/operator_tools/{superoperator_validation.py => validate_superoperator.py} (100%) diff --git a/forest/benchmarking/operator_tools/channel_approximation.py b/forest/benchmarking/operator_tools/channel_approximation.py new file mode 100644 index 00000000..b8ebefe5 --- /dev/null +++ b/forest/benchmarking/operator_tools/channel_approximation.py @@ -0,0 +1,55 @@ +"""A module containing tools for approximating channels. + +We have arbitrarily decided to use a column stacking convention. + +For more information about the conventions used, look at the file in +/docs/Superoperator representations.md + +Further references include: + +[GRAPTN] Tensor networks and graphical calculus for open quantum systems + Wood et al. + Quant. Inf. Comp. 15, 0579-0811 (2015) + (no DOI) + https://arxiv.org/abs/1111.6950 + +[MATQO] On the Matrix Representation of Quantum Operations + Nambu et al., + arXiv: 0504091 (2005) + https://arxiv.org/abs/quant-ph/0504091 + +[DUAL] On duality between quantum maps and quantum states + Zyczkowski et al., + Open Syst. Inf. Dyn. 11, 3 (2004) + https://dx.doi.org/10.1023/B:OPSY.0000024753.05661.c2 + https://arxiv.org/abs/quant-ph/0401119 + +""" +import numpy as np + + +# ================================================================================================== +# Channel and Superoperator approximation tools +# ================================================================================================== +def pauli_twirl_chi_matrix(chi_matrix: np.ndarray) -> np.ndarray: + r""" + Implements a Pauli twirl of a chi matrix (aka a process matrix). + + See the folloiwng reference for more details + + [SPICC] Scalable protocol for identification of correctable codes + Silva et al., + PRA 78, 012347 2008 + http://dx.doi.org/10.1103/PhysRevA.78.012347 + https://arxiv.org/abs/0710.1900 + + Note: Pauli twirling a quantum channel can give rise to a channel that is less noisy; use with + care. + + :param chi_matrix: a dim**2 by dim**2 chi or process matrix + :return: dim**2 by dim**2 chi or process matrix + """ + return np.diag(chi_matrix.diagonal()) + + +# TODO: Honest approximations for Channels that act on one or MORE qubits. diff --git a/forest/benchmarking/operator_tools/superoperator_tools.py b/forest/benchmarking/operator_tools/superoperator_tools.py deleted file mode 100644 index 9a4956a4..00000000 --- a/forest/benchmarking/operator_tools/superoperator_tools.py +++ /dev/null @@ -1,700 +0,0 @@ -"""A module containing tools for working with superoperators. Eg. converting between different -representations of superoperators. - -We have arbitrarily decided to use a column stacking convention. - -For more information about the conventions used, look at the file in -/docs/Superoperator representations.md - -Further references include: - -[GRAPTN] Tensor networks and graphical calculus for open quantum systems - Wood et al. - Quant. Inf. Comp. 15, 0579-0811 (2015) - (no DOI) - https://arxiv.org/abs/1111.6950 - -[MATQO] On the Matrix Representation of Quantum Operations - Nambu et al., - arXiv: 0504091 (2005) - https://arxiv.org/abs/quant-ph/0504091 - -[DUAL] On duality between quantum maps and quantum states - Zyczkowski et al., - Open Syst. Inf. Dyn. 11, 3 (2004) - https://dx.doi.org/10.1023/B:OPSY.0000024753.05661.c2 - https://arxiv.org/abs/quant-ph/0401119 - -""" -from typing import Sequence, Tuple, List -import numpy as np -from forest.benchmarking.utils import n_qubit_pauli_basis, partial_trace - - -# ================================================================================================== -# Superoperator conversion tools -# ================================================================================================== - - -def vec(matrix: np.ndarray) -> np.ndarray: - """ - Vectorize, i.e. "vec", a matrix by column stacking. - - For example the 2 by 2 matrix A - - A = [[a, b] - [c, d]] becomes |A>> := vec(A) = (a, c, b, d)^T , - - where |A>> denotes the vec'ed version of A and T denotes transpose. - - :param matrix: A N (rows) by M (columns) numpy array. - :return: Returns a column vector with N by M rows. - """ - return np.asarray(matrix).T.reshape((-1, 1)) - - -def unvec(vector: np.ndarray, shape: Tuple[int, int] = None) -> np.ndarray: - """ - Take a column vector and turn it into a matrix. - - By default, the unvec'ed matrix is assumed to be square. Specifying shape = [N, M] will - produce a N by M matrix where N is the number of rows and M is the number of columns. - - Consider |A>> := vec(A) = (a, c, b, d)^T. `unvec(|A>>)` should return - - A = [[a, b] - [c, d]]. - - :param vector: A (N*M) by 1 numpy array. - :param shape: The shape of the output matrix; by default, the matrix is assumed to be square. - :return: Returns a N by M matrix. - """ - vector = np.asarray(vector) - if shape is None: - dim = int(np.sqrt(vector.size)) - shape = dim, dim - matrix = vector.reshape(*shape).T - return matrix - - -def kraus2chi(kraus_ops: Sequence[np.ndarray]) -> np.ndarray: - r""" - Convert a set of Kraus operators (representing a channel) to - a chi matrix which is also known as a process matrix. - - :param kraus_ops: A list or tuple of N Kraus operators - :return: Returns a dim**2 by dim**2 matrix. - """ - if isinstance(kraus_ops, np.ndarray): # handle input of single kraus op - if len(kraus_ops[0].shape) < 2: # first elem is not a matrix - kraus_ops = [kraus_ops] - - dim = np.asarray(kraus_ops[0]).shape[0] # kraus op is dim by dim matrix - c_vecs = [computational2pauli_basis_matrix(dim) @ vec(kraus) for kraus in kraus_ops] - chi_mat = sum([c_vec @ c_vec.conj().T for c_vec in c_vecs]) - return chi_mat - - -def kraus2superop(kraus_ops: Sequence[np.ndarray]) -> np.ndarray: - r""" - Convert a set of Kraus operators (representing a channel) to - a superoperator using the column stacking convention. - - Suppose the N Kraus operators M_i are dim by dim matrices. Then the - the superoperator is a (dim**2) \otimes (dim**2) matrix. Using the relation - column stacking relation, - vec(ABC) = (C^T\otimes A) vec(B), we can show - - super_operator = \sum_i^N ( M_i^\dagger )^T \otimes M_i - = \sum_i^N M_i^* \otimes M_i - - where A^* is the complex conjugate of a matrix A, A^T is the transpose, - and A^\dagger is the complex conjugate and transpose. - - Note: This function can also convert non-square Kraus operators to a superoperator, - these frequently arise in quantum measurement theory and quantum error correction. In that - situation consider a single Kraus operator that is M by N then the superoperator will be a - M**2 by N**2 matrix. - - :param kraus_ops: A tuple of N Kraus operators - :return: Returns a dim**2 by dim**2 matrix. - """ - if isinstance(kraus_ops, np.ndarray): # handle input of single kraus op - if len(kraus_ops[0].shape) < 2: # first elem is not a matrix - kraus_ops = [kraus_ops] - - rows, cols = np.asarray(kraus_ops[0]).shape - - # Standard case of square Kraus operators is if rows==cols. - # When representing a partial projection, e.g. a single measurement operator - # M_i = Id \otimes np.ndarray: - """ - Convert a set of Kraus operators (representing a channel) to - a Pauli-Liouville matrix (aka Pauli Transfer matrix). - - :param kraus_ops: A list of Kraus operators - :return: Returns dim**2 by dim**2 Pauli-Liouville matrix - """ - return superop2pauli_liouville(kraus2superop(kraus_ops)) - - -def kraus2choi(kraus_ops: Sequence[np.ndarray]) -> np.ndarray: - r""" - Convert a set of Kraus operators (representing a channel) to - a Choi matrix using the column stacking convention. - - Suppose the N Kraus operators M_i are dim by dim matrices. Then the - the Choi matrix is a dim**2 by dim**2 matrix - - choi_matrix = \sum_i^N |M_i>> (|M_i>>)^\dagger - = \sum_i^N |M_i>> <> = vec(M_i) - - :param kraus_ops: A list of N Kraus operators - :return: Returns a dim**2 by dim**2 matrix. - """ - if isinstance(kraus_ops, np.ndarray): # handle input of single kraus op - if len(kraus_ops[0].shape) < 2: # first elem is not a matrix - kraus_ops = [kraus_ops] - - return sum([vec(op) @ vec(op).conj().T for op in kraus_ops]) - - -def chi2pauli_liouville(chi_matrix: np.ndarray) -> np.ndarray: - r""" - Converts a chi matrix (aka a process matrix) to the Pauli Liouville representation. - - :param chi_matrix: a dim**2 by dim**2 process matrix - :return: dim**2 by dim**2 Pauli-Liouville matrix - """ - return choi2pauli_liouville(chi2choi(chi_matrix)) - - -def chi2kraus(chi_matrix: np.ndarray) -> List[np.ndarray]: - """ - Converts a chi matrix into a list of Kraus operators. (operators with small norm may be - excluded) - - :param chi_matrix: a dim**2 by dim**2 process matrix - :return: list of Kraus operators - """ - return pauli_liouville2kraus(chi2pauli_liouville(chi_matrix)) - - -def chi2superop(chi_matrix: np.ndarray) -> np.ndarray: - """ - Converts a chi matrix into a superoperator. - - :param chi_matrix: a dim**2 by dim**2 process matrix - :return: a dim**2 by dim**2 superoperator matrix - """ - return pauli_liouville2superop(chi2pauli_liouville(chi_matrix)) - - -def chi2choi(chi_matrix: np.ndarray) -> np.ndarray: - """ - Converts a chi matrix into a Choi matrix. - - :param chi_matrix: a dim**2 by dim**2 process matrix - :return: a dim**2 by dim**2 Choi matrix - """ - dim = int(np.sqrt(np.asarray(chi_matrix).shape[0])) - p2c = pauli2computational_basis_matrix(dim) - return p2c @ chi_matrix @ p2c.conj().T - - -def superop2kraus(superop: np.ndarray) -> List[np.ndarray]: - """ - Converts a superoperator into a list of Kraus operators. (operators with small norm may be excluded) - - :param superop: a dim**2 by dim**2 superoperator - :return: list of Kraus operators - """ - return choi2kraus(superop2choi(superop)) - - -def superop2chi(superop: np.ndarray) -> np.ndarray: - """ - Converts a superoperator into a list of Kraus operators. (operators with small norm may be excluded) - - :param superop: a dim**2 by dim**2 superoperator - :return: a dim**2 by dim**2 process matrix - """ - return kraus2chi(superop2kraus(superop)) - - -def superop2pauli_liouville(superop: np.ndarray) -> np.ndarray: - """ - Converts a superoperator into a pauli_liouville matrix. This is achieved by a linear change of basis. - - :param superop: a dim**2 by dim**2 superoperator - :return: dim**2 by dim**2 Pauli-Liouville matrix - """ - dim = int(np.sqrt(np.asarray(superop).shape[0])) - c2p_basis_transform = computational2pauli_basis_matrix(dim) - return c2p_basis_transform @ superop @ c2p_basis_transform.conj().T * dim - - -def superop2choi(superop: np.ndarray) -> np.ndarray: - """ - Convert a superoperator into a choi matrix. The operation acts equivalently to choi2superop, as it is a bijection. - - :param superop: a dim**2 by dim**2 superoperator - :return: dim**2 by dim**2 choi matrix - """ - dim = int(np.sqrt(np.asarray(superop).shape[0])) - return np.reshape(superop, [dim] * 4).swapaxes(0, 3).reshape([dim ** 2, dim ** 2]) - - -def pauli_liouville2kraus(pl_matrix: np.ndarray) -> List[np.ndarray]: - """ - Converts a pauli_liouville matrix into a list of Kraus operators. (operators with small norm may be excluded) - - :param pl_matrix: a dim**2 by dim**2 pauli_liouville matrix - :return: list of Kraus operators - """ - return choi2kraus(pauli_liouville2choi(pl_matrix)) - - -def pauli_liouville2chi(pl_matrix: np.ndarray) -> np.ndarray: - """ - Converts a pauli_liouville matrix into a chi matrix. (operators with small norm may be excluded) - - :param pl_matrix: a dim**2 by dim**2 pauli_liouville matrix - :return: a dim**2 by dim**2 process matrix - """ - return kraus2chi(pauli_liouville2kraus(pl_matrix)) - - -def pauli_liouville2superop(pl_matrix: np.ndarray) -> np.ndarray: - """ - Converts a pauli_liouville matrix into a superoperator. This is achieved by a linear change of basis. - - :param pl_matrix: a dim**2 by dim**2 Pauli-Liouville matrix - :return: dim**2 by dim**2 superoperator - """ - dim = int(np.sqrt(np.asarray(pl_matrix).shape[0])) - p2c_basis_transform = pauli2computational_basis_matrix(dim) - return p2c_basis_transform @ pl_matrix @ p2c_basis_transform.conj().T / dim - - -def pauli_liouville2choi(pl_matrix: np.ndarray) -> np.ndarray: - """ - Convert a Pauli-Liouville matrix into a choi matrix. - - :param pl_matrix: a dim**2 by dim**2 Pauli-Liouville matrix - :return: dim**2 by dim**2 choi matrix - """ - return superop2choi(pauli_liouville2superop(pl_matrix)) - - -def choi2kraus(choi: np.ndarray, tol: float = 1e-9) -> List[np.ndarray]: - """ - Converts a Choi matrix into a list of Kraus operators. (operators with small norm may be - excluded) - - :param choi: a dim**2 by dim**2 choi matrix - :param tol: optional threshold parameter for eigenvalues/kraus ops to be discarded - :return: list of Kraus operators - """ - eigvals, v = np.linalg.eigh(choi) - return [np.lib.scimath.sqrt(eigval) * unvec(np.array([evec]).T) for eigval, evec in - zip(eigvals, v.T) if abs(eigval) > tol] - - -def choi2chi(choi: np.ndarray) -> np.ndarray: - """ - Converts a Choi matrix into a chi matrix. (operators with small norm may be excluded) - :param choi: a dim**2 by dim**2 choi matrix - :return: a dim**2 by dim**2 process matrix - """ - return kraus2chi(choi2kraus(choi)) - - -def choi2superop(choi: np.ndarray) -> np.ndarray: - """ - Convert a choi matrix into a superoperator. The operation acts equivalently to superop2choi, as it is a bijection. - - :param choi: a dim**2 by dim**2 choi matrix - :return: dim**2 by dim**2 superoperator - """ - dim = int(np.sqrt(np.asarray(choi).shape[0])) - return np.reshape(choi, [dim] * 4).swapaxes(0, 3).reshape([dim ** 2, dim ** 2]) - - -def choi2pauli_liouville(choi: np.ndarray) -> np.ndarray: - """ - Convert a choi matrix into a Pauli-Liouville matrix. - - :param choi: a dim**2 by dim**2 choi matrix - :return: dim**2 by dim**2 Pauli-Liouville matrix - """ - return superop2pauli_liouville(choi2superop(choi)) - - -def pauli2computational_basis_matrix(dim) -> np.ndarray: - """ - Produces a basis transform matrix that converts from a pauli basis to the computational basis. - p2c_transform = sum_{k=1}^{dim**2} | sigma_k >> > - - :param dim: dimension of the hilbert space on which the operators act. - :return: A dim**2 by dim**2 basis transform matrix - """ - n_qubits = int(np.log2(dim)) - - conversion_mat = np.zeros((dim ** 2, dim ** 2), dtype=complex) - - for i, pauli in enumerate(n_qubit_pauli_basis(n_qubits)): - pauli_label = np.zeros((dim ** 2, 1)) - pauli_label[i] = 1. - pauli_mat = pauli[1] - conversion_mat += np.kron(vec(pauli_mat), pauli_label.T) - - return conversion_mat - - -def computational2pauli_basis_matrix(dim) -> np.ndarray: - """ - Produces a basis transform matrix that converts from a computational basis to a pauli basis. - Conjugate transpose of pauli2computational_basis_matrix with an extra dimensional factor. - c2p_transform = sum_{k=1}^{dim**2} | k > << sigma_k | - For example, - vec(sigma_z) = | sigma_z >> = [1, 0, 0, -1].T in the computational basis - c2p * | sigma_z >> = [0, 0, 0, 1].T - - :param dim: dimension of the hilbert space on which the operators act. - :return: A dim**2 by dim**2 basis transform matrix - """ - return pauli2computational_basis_matrix(dim).conj().T / dim - - -# ================================================================================================== -# Channel and Superoperator approximation tools -# ================================================================================================== -def pauli_twirl_chi_matrix(chi_matrix: np.ndarray) -> np.ndarray: - r""" - Implements a Pauli twirl of a chi matrix (aka a process matrix). - - See the folloiwng reference for more details - - [SPICC] Scalable protocol for identification of correctable codes - Silva et al., - PRA 78, 012347 2008 - http://dx.doi.org/10.1103/PhysRevA.78.012347 - https://arxiv.org/abs/0710.1900 - - Note: Pauli twirling a quantum channel can give rise to a channel that is less noisy; use with - care. - - :param chi_matrix: a dim**2 by dim**2 chi or process matrix - :return: dim**2 by dim**2 chi or process matrix - """ - return np.diag(chi_matrix.diagonal()) - - -# TODO: Honest approximations for Channels that act on one or MORE qubits. - -# ================================================================================================== -# Apply channel -# ================================================================================================== -def apply_kraus_ops_2_state(kraus_ops: Sequence[np.ndarray], state: np.ndarray) -> np.ndarray: - r""" - Apply a quantum channel, specified by Kraus operators, to state. - - The Kraus operators need not be square. - - :param kraus_ops: A list or tuple of N Kraus operators, each operator is M by dim ndarray - :param state: A dim by dim ndarray which is the density matrix for the state - :return: M by M ndarray which is the density matrix for the state after the action of kraus_ops - """ - if isinstance(kraus_ops, np.ndarray): # handle input of single kraus op - if len(kraus_ops[0].shape) < 2: # first elem is not a matrix - kraus_ops = [kraus_ops] - - dim, _ = state.shape - rows, cols = kraus_ops[0].shape - - if dim != cols: - raise ValueError("Dimensions of state and Kraus operator are incompatible") - - new_state = np.zeros((rows, rows)) - for M in kraus_ops: - new_state += M @ state @ np.transpose(M.conj()) - - return new_state - - -def apply_choi_matrix_2_state(choi: np.ndarray, state: np.ndarray) -> np.ndarray: - r""" - Apply a quantum channel, specified by a Choi matrix (using the column stacking convention), - to a state. - - The Choi matrix is a dim**2 by dim**2 matrix and the state rho is a dim by dim matrix. The - output state is - - rho_{out} = Tr_{A_{in}}[(rho^T \otimes Id) Choi_matrix ], - - where T denotes transposition and Tr_{A_{in}} is the partial trace over input Hilbert space H_{ - A_{in}}; the Choi matrix representing a process mapping rho in H_{A_{in}} to rho_{out} - in H_{B_{out}} is regarded as an operator on the space H_{A_{in}} \otimes H_{B_{out}}. - - - :param choi: a dim**2 by dim**2 matrix - :param state: A dim by dim ndarray which is the density matrix for the state - :return: a dim by dim matrix. - """ - dim = int(np.sqrt(np.asarray(choi).shape[0])) - dims = [dim, dim] - tot_matrix = np.kron(state.transpose(), np.identity(dim)) @ choi - return partial_trace(tot_matrix, [1], dims) - - -# ================================================================================================== -# Check physicality of Channels -# ================================================================================================== -def kraus_operators_are_valid(kraus_ops: Sequence[np.ndarray], - rtol: float = 1e-05, - atol: float = 1e-08)-> bool: - """ - Checks if a set of Kraus operators are valid. - - :param kraus_ops: A list of Kraus operators - :param rtol: The relative tolerance parameter in np.allclose - :param atol: The absolute tolerance parameter in np.allclose - :return: True if the Kraus operators are valid with the given tolerance; False otherwise. - """ - if isinstance(kraus_ops, np.ndarray): # handle input of single kraus op - if len(kraus_ops[0].shape) < 2: # first elem is not a matrix - kraus_ops = [kraus_ops] - rows, _ = np.asarray(kraus_ops[0]).shape - # Standard case of square Kraus operators is if rows==cols. For non-square Kraus ops it is - # required that sum_i M_i^\dagger M_i = np.eye(rows,rows). - id_iff_valid = sum(np.transpose(op).conjugate().dot(op) for op in kraus_ops) - # TODO: check if each POVM element (i.e. M_i^\dagger M_i) is PSD - return np.allclose(id_iff_valid, np.eye(rows), rtol=rtol, atol=atol) - - -def choi_is_hermitian_preserving(choi: np.ndarray, rtol: float = 1e-05, - atol: float = 1e-08) -> bool: - """ - Checks if a quantum process, specified by a Choi matrix, is hermitian-preserving. - - :param choi: a dim**2 by dim**2 Choi matrix - :param rtol: The relative tolerance parameter in np.allclose - :param atol: The absolute tolerance parameter in np.allclose - :return: Returns True if the quantum channel is hermitian preserving with the given tolerance; - False otherwise. - """ - # Equation 3.31 of [GRAPTN] - return np.allclose(choi, choi.conj().T, rtol=rtol, atol=atol) - - -def choi_is_trace_preserving(choi: np.ndarray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: - """ - Checks if a quantum process, specified by a Choi matrix, is trace-preserving. - - :param choi: A dim**2 by dim**2 Choi matrix - :param rtol: The relative tolerance parameter in np.allclose - :param atol: The absolute tolerance parameter in np.allclose - :return: Returns True if the quantum channel is trace-preserving with the given tolerance; - False otherwise. - """ - rows, cols = choi.shape - dim = int(np.sqrt(rows)) - # the choi matrix acts on the Hilbert space H_{in} \otimes H_{out}. - # We want to trace out H_{out} and so keep the H_{in} space at index 0. - keep = [0] - id_iff_tp = partial_trace(choi, keep, [dim, dim]) - # Equation 3.33 of [GRAPTN] - return np.allclose(id_iff_tp, np.identity(dim), rtol=rtol, atol=atol) - - -def choi_is_completely_positive(choi: np.ndarray, limit: float = 1e-09) -> bool: - """ - Checks if a quantum process, specified by a Choi matrix, is completely positive. - - :param choi: A dim**2 by dim**2 Choi matrix - :param limit: A tolerance parameter, all eigenvalues must be greater than -|limit|. - :return: Returns True if the quantum channel is completely positive with the given tolerance; - False otherwise. - """ - evals, evecs = np.linalg.eig(choi) - # Equation 3.35 of [GRAPTN] - return all(x >= -abs(limit) for x in evals) - - -def choi_is_unital(choi: np.ndarray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: - """ - Checks if a quantum process, specified by a Choi matrix, is unital. - - A process is unital iff it maps the identity to itself. - - :param choi: A dim**2 by dim**2 Choi matrix - :param rtol: The relative tolerance parameter in np.allclose - :param atol: The absolute tolerance parameter in np.allclose - :return: Returns True if the quantum channel is unital with the given tolerance; False - otherwise. - """ - rows, cols = choi.shape - dim = int(np.sqrt(rows)) - id_iff_unital = apply_choi_matrix_2_state(choi, np.identity(dim)) - return np.allclose(id_iff_unital, np.identity(dim), rtol=rtol, atol=atol) - - -def choi_is_unitary(choi: np.ndarray, limit: float = 1e-09) -> bool: - """ - Checks if a quantum process, specified by a Choi matrix, is unitary. - - :param choi: A dim**2 by dim**2 Choi matrix - :param limit: A tolerance parameter to discard Kraus operators with small norm. - :return: Returns True if the quantum channel is unitary with the given tolerance; False - otherwise. - """ - kraus_ops = choi2kraus(choi, tol=limit) - return len(kraus_ops) == 1 - -# ================================================================================================== -# Project Channels to CP, TNI, TP, and physical -# ================================================================================================== -def proj_choi_to_completely_positive(choi: np.ndarray) -> np.ndarray: - """ - Projects the Choi representation of a process into the nearest Choi matrix in the space of - completely positive maps. - - Equation 8 of [PGD] - - [PGD] Maximum-likelihood quantum process tomography via projected gradient descent - Knee et al., - Phys. Rev. A 98, 062336 (2018) - https://dx.doi.org/10.1103/PhysRevA.98.062336 - https://arxiv.org/abs/1803.10062 - - :param choi: Choi representation of a process - :return: closest Choi matrix in the space of completely positive maps - """ - hermitian = (choi + choi.conj().T) / 2 # enforce Hermiticity - evals, v = np.linalg.eigh(hermitian) - evals[evals < 0] = 0 # enforce completely positive by removing negative eigenvalues - diag = np.diag(evals) - return v @ diag @ v.conj().T - - -def proj_choi_to_trace_non_increasing(choi: np.ndarray) -> np.ndarray: - """ - Projects the Choi matrix of a process into the space of trace non-increasing maps. - - Equation 33 of [PGD] - - :param choi: Choi representation of a process - :return: Choi representation of the projected trace non-increasing process - """ - dim = int(np.sqrt(choi.shape[0])) - - # trace out the output Hilbert space - pt = partial_trace(choi, dims=[dim, dim], keep=[0]) - - hermitian = (pt + pt.conj().T) / 2 # enforce Hermiticity - d, v = np.linalg.eigh(hermitian) - d[d > 1] = 1 # enforce trace preserving - D = np.diag(d) - projection = v @ D @ v.conj().T - - trace_increasing_part = np.kron((pt - projection) / dim, np.eye(dim)) - - return choi - trace_increasing_part - - -def proj_choi_to_trace_preserving(choi: np.ndarray) -> np.ndarray: - """ - Projects the Choi representation of a process to the closest processes in the space of trace - preserving maps. - - Equation 12 of [PGD], but without vecing the Choi matrix. See choi_is_trace_preserving for - comparison. - - :param choi: Choi representation of a process - :return: Choi representation of the projected trace preserving process - """ - dim = int(np.sqrt(choi.shape[0])) - - # trace out the output Hilbert space, keep the input space at index 0 - pt = partial_trace(choi, dims=[dim, dim], keep=[0]) - # isolate the part the violates the condition we want, namely pt = Id - diff = pt - np.eye(dim) - # we want to subtract off the violation from the larger operator, so 'invert' the partial_trace - subtract = np.kron(diff/dim, np.eye(dim)) - return choi - subtract - - -def proj_choi_to_physical(choi, make_trace_preserving=True): - """ - Projects the given Choi matrix into the subspace of Completetly Positive and either - Trace Perserving (TP) or Trace-Non-Increasing maps. - - Uses Dykstra's algorithm with the stopping criterion presented in: - - [DYKALG] Dykstra’s algorithm and robust stopping criteria - Birgin et al., - (Springer US, Boston, MA, 2009), pp. 828–833, ISBN 978-0-387-74759-0. - https://doi.org/10.1007/978-0-387-74759-0_143 - - This method is suggested in [PGD] - - :param choi: the Choi representation estimate of a quantum process. - :param make_trace_preserving: default true, projects the estimate to a trace-preserving - process. If false the output process may only be trace non-increasing - :return: The Choi representation of the Completely Positive, Trace Preserving (CPTP) or Trace - Non-Increasing map that is closest to the given state. - """ - old_CP_change = np.zeros_like(choi) - old_TP_change = np.zeros_like(choi) - last_CP_projection = np.zeros_like(choi) - last_state = choi - - while True: - # Dykstra's algorithm - pre_CP = last_state - old_CP_change - CP_projection = proj_choi_to_completely_positive(pre_CP) - new_CP_change = CP_projection - pre_CP - - pre_TP = CP_projection - old_TP_change - if make_trace_preserving: - new_state = proj_choi_to_trace_preserving(pre_TP) - else: - new_state = proj_choi_to_trace_non_increasing(pre_TP) - new_TP_change = new_state - pre_TP - - CP_change_change = new_CP_change - old_CP_change - TP_change_change = new_TP_change - old_TP_change - state_change = new_state - last_state - - # stopping criterion - # norm(mat) is the frobenius norm - # norm(mat)**2 is thus equivalent to the dot product vec(mat) dot vec(mat) - if np.linalg.norm(CP_change_change) ** 2 + np.linalg.norm(TP_change_change) ** 2 \ - + 2 * abs(np.dot(vec(old_TP_change).conj().T, vec(state_change))) \ - + 2 * abs(np.dot(vec(old_CP_change).conj().T, - vec(CP_projection - last_CP_projection))) < 1e-4: - break - - # store results from this iteration - old_CP_change = new_CP_change - old_TP_change = new_TP_change - last_CP_projection = CP_projection - last_state = new_state - - return new_state diff --git a/forest/benchmarking/operator_tools/superoperator_validation.py b/forest/benchmarking/operator_tools/validate_superoperator.py similarity index 100% rename from forest/benchmarking/operator_tools/superoperator_validation.py rename to forest/benchmarking/operator_tools/validate_superoperator.py From 905a97f0d366ae3887aa4114e4c266179c28bc73 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Wed, 12 Jun 2019 10:48:23 +1000 Subject: [PATCH 14/33] validate operators --- .../operator_tools/validate_operator.py | 203 ++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 forest/benchmarking/operator_tools/validate_operator.py diff --git a/forest/benchmarking/operator_tools/validate_operator.py b/forest/benchmarking/operator_tools/validate_operator.py new file mode 100644 index 00000000..de4a60da --- /dev/null +++ b/forest/benchmarking/operator_tools/validate_operator.py @@ -0,0 +1,203 @@ +"""A module allowing one to check if superoperators or channels are physical. + +We have arbitrarily decided to use a column stacking convention. + +For more information about the conventions used, look at the file in +/docs/Superoperator representations.md + +Further references include: + +[GRAPTN] Tensor networks and graphical calculus for open quantum systems + Wood et al. + Quant. Inf. Comp. 15, 0579-0811 (2015) + (no DOI) + https://arxiv.org/abs/1111.6950 + +[MATQO] On the Matrix Representation of Quantum Operations + Nambu et al., + arXiv: 0504091 (2005) + https://arxiv.org/abs/quant-ph/0504091 + +[DUAL] On duality between quantum maps and quantum states + Zyczkowski et al., + Open Syst. Inf. Dyn. 11, 3 (2004) + https://dx.doi.org/10.1023/B:OPSY.0000024753.05661.c2 + https://arxiv.org/abs/quant-ph/0401119 + +""" +from typing import Sequence +import numpy as np +from forest.benchmarking.utils import partial_trace +from forest.benchmarking.operator_tools.superoperator_transformations import choi2kraus +from forest.benchmarking.operator_tools.superoperator_tools import apply_choi_matrix_2_state + + +# ================================================================================================== +# Check physicality of operators or matrices +# ================================================================================================== +def is_square_matrix(matrix: np.ndarray) -> bool: + """ + Checks if a matrix is square. + + :param matrix: a M by N matrix. + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose + :return: True if the matrix is square; False otherwise. + """ + if len(matrix.shape) != 2: + raise ValueError("The object is not a matrix.") + rows, cols = matrix.shape + return rows == cols + + +def is_symmetric_matrix(matrix: np.ndarray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: + """ + Checks if a square matrix is symmetric. That is + + A is symmetric iff $A = A ^T$, + + where $T$ denotes transpose. + + :param matrix: a M by M matrix. + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose + :return: True if the matrix is symmetric; False otherwise. + """ + if not is_square_matrix(matrix): + raise ValueError("The matrix is not square.") + return np.allclose(matrix, matrix.T, rtol=rtol, atol=atol) + + +def is_identity_matrix(matrix: np.ndarray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: + """ + Checks if a square matrix is the identity matrix. + + :param matrix: a M by M matrix. + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose + :return: True if the matrix is the identity matrix; False otherwise. + """ + if not is_square_matrix(matrix): + raise ValueError("The matrix is not square.") + Id = np.eye(len(matrix)) + return np.allclose(matrix, Id, rtol=rtol, atol=atol) + + +def is_idempotent_matrix(matrix: np.ndarray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: + """ + Checks if a square matrix A is idempotent. That is + + A is idempotent iff $A^2 = A.$ + + :param matrix: a M by M matrix. + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose + :return: True if the matrix is the idempotent; False otherwise. + """ + if not is_square_matrix(matrix): + raise ValueError("The matrix is not square.") + return np.allclose(matrix, matrix @ matrix, rtol=rtol, atol=atol) + + +def is_normal_matrix(matrix: np.ndarray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: + r""" + Checks if a square matrix A is normal. That is + + A is normal iff $A^{\dagger} A = A A^{\dagger}$, + + where $\dagger$ denotes conjugate transpose. + + :param matrix: a M by M matrix. + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose + :return: True if the matrix is normal; False otherwise. + """ + if not is_square_matrix(matrix): + raise ValueError("The matrix is not square.") + AB = matrix.T.conj() @ matrix + BA = matrix @ matrix.T.conj() + return np.allclose(AB, BA, rtol=rtol, atol=atol) + + +def is_hermitian_matrix(matrix: np.ndarray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: + r""" + Checks if a square matrix A is Hermitian. That is + + A is Hermitian iff $A = A^{\dagger}$, + + where $\dagger$ denotes conjugate transpose. + + :param matrix: a M by M matrix. + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose + :return: True if the matrix is Hermitian; False otherwise. + """ + if not is_square_matrix(matrix): + raise ValueError("The matrix is not square.") + return np.allclose(matrix, matrix.T.conj(), rtol=rtol, atol=atol) + + +def is_unitary_matrix(matrix: np.ndarray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: + r""" + Checks if a square matrix A is unitary. That is + + A is unitary iff $A^{\dagger} A = A A^{\dagger}$ = Id, + + where $\dagger$ denotes conjugate transpose and Id denotes the identity. + + :param matrix: a M by M matrix. + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose + :return: True if the matrix is normal; False otherwise. + """ + if not is_square_matrix(matrix): + raise ValueError("The matrix is not square.") + AB = matrix.T.conj() @ matrix + BA = matrix @ matrix.T.conj() + Id = np.eye(len(matrix)) + return np.allclose(AB, Id, rtol=rtol, atol=atol) and np.allclose(BA, Id, rtol=rtol, atol=atol) + + +def is_positive_definite_matrix(matrix: np.ndarray, + rtol: float = 1e-05, + atol: float = 1e-08) -> bool: + r""" + Checks if a square Hermitian matrix A is positive definite. That is + + A is positive definite iff eig(A) > 0. + + In this numerical implementation we check if each eigenvalue obeys eig(A) > -|atol|, + the strict condition can be recoved by setting `atol = 0`. + + :param matrix: a M by M Hermitian matrix. + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose + :return: True if the matrix is normal; False otherwise. + """ + if not is_hermitian_matrix(matrix, rtol, atol): + raise ValueError("The matrix is not Hermitian.") + evals, _ = np.linalg.eigh(matrix) + return all(x > -abs(atol) for x in evals) + + +def is_positive_semidefinite_matrix(matrix: np.ndarray, + rtol: float = 1e-05, + atol: float = 1e-08) -> bool: + r""" + Checks if a square Hermitian matrix A is positive semi-definite. That is + + A is positive semi-definite iff eig(A) >= 0. + + In this numerical implementation we check if each eigenvalue obeys + + eig(A) >= -|atol|. + + :param matrix: a M by M Hermitian matrix. + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose + :return: True if the matrix is normal; False otherwise. + """ + if not is_hermitian_matrix(matrix, rtol, atol): + raise ValueError("The matrix is not Hermitian.") + evals, _ = np.linalg.eigh(matrix) + return all(x >= -abs(atol) for x in evals) From 9ebc58f15164d44bb57091c7666a28f4e6bb120b Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Wed, 12 Jun 2019 11:42:42 +1000 Subject: [PATCH 15/33] improvements --- .../benchmarking/operator_tools/__init__.py | 5 ++- .../operator_tools/apply_superoperator.py | 3 -- .../operator_tools/channel_approximation.py | 4 -- .../operator_tools/project_superoperators.py | 3 -- .../operator_tools/validate_operator.py | 2 +- .../operator_tools/validate_superoperator.py | 44 +++++++++++++------ 6 files changed, 35 insertions(+), 26 deletions(-) diff --git a/forest/benchmarking/operator_tools/__init__.py b/forest/benchmarking/operator_tools/__init__.py index 33c728ea..07c38657 100644 --- a/forest/benchmarking/operator_tools/__init__.py +++ b/forest/benchmarking/operator_tools/__init__.py @@ -1,2 +1,3 @@ -#from .random_operators import * -__all__ = ["random_operators", "superoperator_transformations"] \ No newline at end of file +__all__ = ["apply_superoperator", "channel_approximation", "project_superoperators", + "random_operators", "superoperator_transformations", "validate_operator", + "validate_superoperator" ] \ No newline at end of file diff --git a/forest/benchmarking/operator_tools/apply_superoperator.py b/forest/benchmarking/operator_tools/apply_superoperator.py index 012fae44..b687b238 100644 --- a/forest/benchmarking/operator_tools/apply_superoperator.py +++ b/forest/benchmarking/operator_tools/apply_superoperator.py @@ -30,9 +30,6 @@ from forest.benchmarking.utils import partial_trace -# ================================================================================================== -# Apply channel -# ================================================================================================== def apply_kraus_ops_2_state(kraus_ops: Sequence[np.ndarray], state: np.ndarray) -> np.ndarray: r""" Apply a quantum channel, specified by Kraus operators, to state. diff --git a/forest/benchmarking/operator_tools/channel_approximation.py b/forest/benchmarking/operator_tools/channel_approximation.py index b8ebefe5..0e6c7d20 100644 --- a/forest/benchmarking/operator_tools/channel_approximation.py +++ b/forest/benchmarking/operator_tools/channel_approximation.py @@ -27,10 +27,6 @@ """ import numpy as np - -# ================================================================================================== -# Channel and Superoperator approximation tools -# ================================================================================================== def pauli_twirl_chi_matrix(chi_matrix: np.ndarray) -> np.ndarray: r""" Implements a Pauli twirl of a chi matrix (aka a process matrix). diff --git a/forest/benchmarking/operator_tools/project_superoperators.py b/forest/benchmarking/operator_tools/project_superoperators.py index 0a65252a..3284dd58 100644 --- a/forest/benchmarking/operator_tools/project_superoperators.py +++ b/forest/benchmarking/operator_tools/project_superoperators.py @@ -29,9 +29,6 @@ from forest.benchmarking.utils import partial_trace -# ================================================================================================== -# Project Channels to CP, TNI, TP, and physical -# ================================================================================================== def proj_choi_to_completely_positive(choi: np.ndarray) -> np.ndarray: """ Projects the Choi representation of a process into the nearest Choi matrix in the space of diff --git a/forest/benchmarking/operator_tools/validate_operator.py b/forest/benchmarking/operator_tools/validate_operator.py index de4a60da..4347aca7 100644 --- a/forest/benchmarking/operator_tools/validate_operator.py +++ b/forest/benchmarking/operator_tools/validate_operator.py @@ -33,7 +33,7 @@ # ================================================================================================== -# Check physicality of operators or matrices +# Check properties of operators or matrices # ================================================================================================== def is_square_matrix(matrix: np.ndarray) -> bool: """ diff --git a/forest/benchmarking/operator_tools/validate_superoperator.py b/forest/benchmarking/operator_tools/validate_superoperator.py index e48f287a..00c8ce6e 100644 --- a/forest/benchmarking/operator_tools/validate_superoperator.py +++ b/forest/benchmarking/operator_tools/validate_superoperator.py @@ -29,7 +29,9 @@ import numpy as np from forest.benchmarking.utils import partial_trace from forest.benchmarking.operator_tools.superoperator_transformations import choi2kraus -from forest.benchmarking.operator_tools.superoperator_tools import apply_choi_matrix_2_state +from forest.benchmarking.operator_tools.validate_operator import ( + is_positive_semidefinite_matrix, is_identity_matrix, is_hermitian_matrix) +from forest.benchmarking.operator_tools.apply_superoperator import apply_choi_matrix_2_state # ================================================================================================== @@ -52,9 +54,10 @@ def kraus_operators_are_valid(kraus_ops: Sequence[np.ndarray], rows, _ = np.asarray(kraus_ops[0]).shape # Standard case of square Kraus operators is if rows==cols. For non-square Kraus ops it is # required that sum_i M_i^\dagger M_i = np.eye(rows,rows). - id_iff_valid = sum(np.transpose(op).conjugate().dot(op) for op in kraus_ops) - # TODO: check if each POVM element (i.e. M_i^\dagger M_i) is PSD - return np.allclose(id_iff_valid, np.eye(rows), rtol=rtol, atol=atol) + POVM = [np.transpose(op).conjugate().dot(op) for op in kraus_ops] + all_POVM_elements_are_PSD = all(is_positive_semidefinite_matrix(elem) for elem in POVM) + POVM_elements_sum_to_Id = is_identity_matrix(sum(POVM), rtol, atol) + return all_POVM_elements_are_PSD and POVM_elements_sum_to_Id def choi_is_hermitian_preserving(choi: np.ndarray, rtol: float = 1e-05, @@ -69,12 +72,12 @@ def choi_is_hermitian_preserving(choi: np.ndarray, rtol: float = 1e-05, False otherwise. """ # Equation 3.31 of [GRAPTN] - return np.allclose(choi, choi.conj().T, rtol=rtol, atol=atol) + return is_hermitian_matrix(choi, rtol, atol) def choi_is_trace_preserving(choi: np.ndarray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: """ - Checks if a quantum process, specified by a Choi matrix, is trace-preserving. + Checks if a quantum process, specified by a Choi matrix, is trace-preserving (TP). :param choi: A dim**2 by dim**2 Choi matrix :param rtol: The relative tolerance parameter in np.allclose @@ -89,21 +92,36 @@ def choi_is_trace_preserving(choi: np.ndarray, rtol: float = 1e-05, atol: float keep = [0] id_iff_tp = partial_trace(choi, keep, [dim, dim]) # Equation 3.33 of [GRAPTN] - return np.allclose(id_iff_tp, np.identity(dim), rtol=rtol, atol=atol) + return is_identity_matrix(id_iff_tp, rtol, atol) -def choi_is_completely_positive(choi: np.ndarray, limit: float = 1e-09) -> bool: +def choi_is_completely_positive(choi: np.ndarray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: """ - Checks if a quantum process, specified by a Choi matrix, is completely positive. + Checks if a quantum process, specified by a Choi matrix, is completely positive (CP). :param choi: A dim**2 by dim**2 Choi matrix - :param limit: A tolerance parameter, all eigenvalues must be greater than -|limit|. + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose :return: Returns True if the quantum channel is completely positive with the given tolerance; False otherwise. """ - evals, evecs = np.linalg.eig(choi) # Equation 3.35 of [GRAPTN] - return all(x >= -abs(limit) for x in evals) + return is_positive_semidefinite_matrix(choi, rtol, atol) + + +def choi_is_cptp(choi: np.ndarray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: + """ + Checks if a quantum process, specified by a Choi matrix, is completely positive and + trace-preserving (CPTP). + + :param choi: A dim**2 by dim**2 Choi matrix + :param rtol: The relative tolerance parameter in np.allclose + :param atol: The absolute tolerance parameter in np.allclose + :return: Returns True if the quantum channel is CPTP with the given tolerance; False otherwise. + """ + choi_is_TP = choi_is_trace_preserving(choi, rtol, atol) + choi_is_CP = choi_is_completely_positive(choi, rtol, atol) + return choi_is_CP and choi_is_TP def choi_is_unital(choi: np.ndarray, rtol: float = 1e-05, atol: float = 1e-08) -> bool: @@ -121,7 +139,7 @@ def choi_is_unital(choi: np.ndarray, rtol: float = 1e-05, atol: float = 1e-08) - rows, cols = choi.shape dim = int(np.sqrt(rows)) id_iff_unital = apply_choi_matrix_2_state(choi, np.identity(dim)) - return np.allclose(id_iff_unital, np.identity(dim), rtol=rtol, atol=atol) + return is_identity_matrix(id_iff_unital, rtol, atol) def choi_is_unitary(choi: np.ndarray, limit: float = 1e-09) -> bool: From 00bcd947c1484af836a68d00d352d057ecbef805 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Wed, 12 Jun 2019 12:40:35 +1000 Subject: [PATCH 16/33] test superop transforms --- .../test_superoperator_transformations.py | 287 ++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 forest/benchmarking/tests/test_superoperator_transformations.py diff --git a/forest/benchmarking/tests/test_superoperator_transformations.py b/forest/benchmarking/tests/test_superoperator_transformations.py new file mode 100644 index 00000000..65757516 --- /dev/null +++ b/forest/benchmarking/tests/test_superoperator_transformations.py @@ -0,0 +1,287 @@ +import numpy as np +from pyquil.gate_matrices import X, Y, Z, H +from forest.benchmarking.superoperator_tools import * + + +# Test philosophy: +# Using the by hand calculations found in the docs we check conversion +# between one qubit channels with one Kraus operator (Hadamard) and two +# Kraus operators (the amplitude damping channel). Additionally we check +# a few two qubit channel conversions to get additional confidence. + +def amplitude_damping_kraus(p): + Ad0 = np.asarray([[1, 0], [0, np.sqrt(1 - p)]]) + Ad1 = np.asarray([[0, np.sqrt(p)], [0, 0]]) + return [Ad0, Ad1] + + +def amplitude_damping_chi(p): + poly1 = (1 + np.sqrt(1 - p)) ** 2 + poly2 = (-1 + np.sqrt(1 - p)) ** 2 + ad_pro = 0.25 * np.asarray([[poly1, 0, 0, p], + [0, p, -1j * p, 0], + [0, 1j * p, p, 0], + [p, 0, 0, poly2]]) + return ad_pro + + +def amplitude_damping_pauli(p): + poly1 = np.sqrt(1 - p) + ad_pau = np.asarray([[1, 0, 0, 0], + [0, poly1, 0, 0], + [0, 0, poly1, 0], + [p, 0, 0, 1 - p]]) + return ad_pau + + +def amplitude_damping_super(p): + poly1 = np.sqrt(1 - p) + ad_sup = np.asarray([[1, 0, 0, p], + [0, poly1, 0, 0], + [0, 0, poly1, 0], + [0, 0, 0, 1 - p]]) + return ad_sup + + +def amplitude_damping_choi(p): + poly1 = np.sqrt(1 - p) + ad_choi = np.asarray([[1, 0, 0, poly1], + [0, 0, 0, 0], + [0, 0, p, 0], + [poly1, 0, 0, 1 - p]]) + return ad_choi + + +HADChi = 0.5 * np.asarray([[0, 0, 0, 0], + [0, 1, 0, 1], + [0, 0, 0, 0], + [0, 1, 0, 1]]) + +HADPauli = 1.0 * np.asarray([[1, 0, 0, 0], + [0, 0, 0, 1], + [0, 0, -1, 0], + [0, 1, 0, 0]]) + +HADSuper = 0.5 * np.asarray([[1, 1, 1, 1], + [1, -1, 1, -1], + [1, 1, -1, -1], + [1, -1, -1, 1]]) + +HADChoi = 0.5 * np.asarray([[1, 1, 1, -1], + [1, 1, 1, -1], + [1, 1, 1, -1], + [-1, -1, -1, 1]]) + + +# Single Qubit Pauli Channel +def one_q_pauli_channel_chi(px, py, pz): + p = (px + py + pz) + pp_chi = np.asarray([[1 - p, 0, 0, 0], + [0, px, 0, 0], + [0, 0, py, 0], + [0, 0, 0, pz]]) + return pp_chi + + +# Pauli twirled Amplitude damping channel +def analytical_pauli_twirl_of_AD_chi(p): + # see equation 7 of https://arxiv.org/pdf/1701.03708.pdf + poly1 = (2 + 2 * np.sqrt(1 - p) - p) / 4 + poly2 = p / 4 + poly3 = (2 - 2 * np.sqrt(1 - p) - p) / 4 + pp_chi = np.asarray([[poly1, 0, 0, 0], + [0, poly2, 0, 0], + [0, 0, poly2, 0], + [0, 0, 0, poly3]]) + return pp_chi + + +# I \otimes Z channel or gate (two qubits) +two_qubit_paulis = n_qubit_pauli_basis(2) +IZKraus = two_qubit_paulis.ops_by_label['IZ'] +IZSuper = np.diag([1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1]) + +# one and zero state as a density matrix +ONE_STATE = np.asarray([[0, 0], [0, 1]]) +ZERO_STATE = np.asarray([[1, 0], [0, 0]]) + +# Amplitude damping Kraus operators with p = 0.1 +AdKrausOps = amplitude_damping_kraus(.1) + +# Use Kraus operators to find output of channel i.e. +# rho_out = A_0 rho A_0^\dag + A_1 rho A_1^\dag. +rho_out = np.matmul(np.matmul(AdKrausOps[0], ONE_STATE), AdKrausOps[0].transpose().conj()) + \ + np.matmul(np.matmul(AdKrausOps[1], ONE_STATE), AdKrausOps[1].transpose().conj()) + + +def test_vec(): + A = np.asarray([[1, 2], [3, 4]]) + B = np.asarray([[1, 2, 5], [3, 4, 6]]) + np.testing.assert_array_equal(np.array([[1], [3], [2], [4]]), vec(A)) + np.testing.assert_array_equal(np.array([[1], [3], [2], [4], [5], [6]]), vec(B)) + + +def test_unvec(): + A = np.asarray([[1, 2], [3, 4]]) + C = np.asarray([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + np.testing.assert_array_equal(A, unvec(vec(A))) + np.testing.assert_array_equal(C, unvec(vec(C))) + + +def test_kraus_ops_sum_to_identity(): + # Check kraus ops sum to identity + p = np.random.rand() + Ad0, Ad1 = amplitude_damping_kraus(p) + np.testing.assert_array_almost_equal_nulp(np.matmul(Ad0.transpose().conj(), Ad0) + + np.matmul(Ad1.transpose().conj(), Ad1), np.eye(2)) + + +def test_kraus2chi(): + assert np.allclose(HADChi, kraus2chi(H)) + p = np.random.rand() + AdKraus = amplitude_damping_kraus(p) + AdChi = amplitude_damping_chi(p) + assert np.allclose(AdChi, kraus2chi(AdKraus)) + assert np.allclose(superop2chi(IZSuper), kraus2chi(IZKraus)) + + +def test_kraus2pauli_liouville(): + p = np.random.rand() + AdKraus = amplitude_damping_kraus(p) + AdPauli = amplitude_damping_pauli(p) + assert np.allclose(kraus2pauli_liouville(AdKraus), AdPauli) + assert np.allclose(kraus2pauli_liouville(H), HADPauli) + + +def test_kraus2superop(): + p = np.random.rand() + AdKraus = amplitude_damping_kraus(p) + AdSuper = amplitude_damping_super(p) + np.testing.assert_array_almost_equal_nulp(kraus2superop(AdKraus), AdSuper) + # test application of super operator is the same as application of Kraus ops + ONE_STATE_VEC = vec(ONE_STATE) + np.testing.assert_array_almost_equal_nulp(unvec(np.matmul(kraus2superop(AdKrausOps), + ONE_STATE_VEC)), rho_out) + assert np.allclose(kraus2superop(H), HADSuper) + assert np.allclose(kraus2superop(IZKraus), IZSuper) + # Below here tests non square Kraus operators + # In this example The Kraus operator is M_0 = I \otimes <0| where <0| = (1,0) + Idd = np.asarray([[1, 0], [0, 1]]) + M0 = np.kron(Idd, np.asarray([[1, 0]])) + attempt = kraus2superop(M0) + answer = np.kron(M0.conj(), M0) + assert np.allclose(answer, attempt) + + +def test_kraus2choi(): + p = np.random.rand() + AdKraus = amplitude_damping_kraus(p) + AdChoi = amplitude_damping_choi(p) + assert np.allclose(kraus2choi(AdKraus), AdChoi) + assert np.allclose(kraus2choi(H), HADChoi) + + +def test_chi2pauli_liouville(): + p = np.random.rand() + AdChi = amplitude_damping_chi(p) + AdPauli = amplitude_damping_pauli(p) + assert np.allclose(AdPauli, chi2pauli_liouville(AdChi)) + assert np.allclose(HADPauli, chi2pauli_liouville(HADChi)) + + +def test_basis_transform_p_to_c(): + xz_pauli_basis = np.zeros((16, 1)) + xz_pauli_basis[7] = [1.] + assert np.allclose(unvec(pauli2computational_basis_matrix(4) @ xz_pauli_basis), np.kron(X, Z)) + + +def test_basis_transform_c_to_p(): + xz_pauli_basis = np.zeros((16, 1)) + xz_pauli_basis[7] = [1.] + assert np.allclose(computational2pauli_basis_matrix(4) @ vec(np.kron(X, Z)), xz_pauli_basis) + + +def test_pl_to_choi(): + for i, pauli in enumerate(n_qubit_pauli_basis(2)): + pl = kraus2pauli_liouville(pauli[1]) + choi = kraus2choi(pauli[1]) + assert np.allclose(choi, pauli_liouville2choi(pl)) + + pl = kraus2pauli_liouville(H) + choi = kraus2choi(H) + assert np.allclose(choi, pauli_liouville2choi(pl)) + + +def test_superop_to_kraus(): + assert np.allclose(superop2kraus(IZSuper), IZKraus) + p = np.random.rand() + AdSuper = amplitude_damping_super(p) + AdKraus = amplitude_damping_kraus(p) + kraus_ops = superop2kraus(AdSuper) + + # the order of the Kraus ops matters + # TODO: fix the sign problem in Kraus operators + assert np.allclose([np.abs(kraus_ops[1]), np.abs(kraus_ops[0])], AdKraus) + + +def test_superop_to_choi(): + for i, pauli in enumerate(n_qubit_pauli_basis(2)): + superop = kraus2superop(pauli[1]) + choi = kraus2choi(pauli[1]) + assert np.allclose(choi, superop2choi(superop)) + p = np.random.rand() + AdSuper = amplitude_damping_super(p) + AdChoi = amplitude_damping_choi(p) + assert np.allclose(AdChoi, superop2choi(AdSuper)) + superop = kraus2superop(H) + choi = kraus2choi(H) + assert np.allclose(choi, superop2choi(superop)) + + +def test_superop_to_pl(): + p = np.random.rand() + AdSuper = amplitude_damping_super(p) + AdPauli = amplitude_damping_pauli(p) + assert np.allclose(AdPauli, superop2pauli_liouville(AdSuper)) + AdKraus = amplitude_damping_kraus(p) + superop = kraus2superop(AdKraus) + pauli = kraus2pauli_liouville(AdKraus) + assert np.allclose(pauli, superop2pauli_liouville(superop)) + + +def test_pauli_liouville_to_superop(): + p = np.random.rand() + AdSuper = amplitude_damping_super(p) + AdPauli = amplitude_damping_pauli(p) + assert np.allclose(AdSuper, pauli_liouville2superop(AdPauli)) + AdKraus = amplitude_damping_kraus(p) + superop = kraus2superop(AdKraus) + pauli = kraus2pauli_liouville(AdKraus) + assert np.allclose(superop, pauli_liouville2superop(pauli)) + + +def test_choi_to_kraus(): + for i, pauli in enumerate(n_qubit_pauli_basis(2)): + choi = kraus2choi(pauli[1]) + kraus = choi2kraus(choi) + assert np.allclose(choi, kraus2choi(kraus)) + id_choi = np.array([[1, 0, 0, 1], [0, 0, 0, 0], [0, 0, 0, 0], [1, 0, 0, 1]]) + assert np.allclose(kraus2choi(choi2kraus(id_choi)), id_choi) + for kraus in choi2kraus(id_choi): + assert np.allclose(abs(kraus), np.eye(2)) or np.allclose(kraus, np.zeros((2, 2))) + + +def test_choi_to_super(): + p = np.random.rand() + AdSuper = amplitude_damping_super(p) + AdChoi = amplitude_damping_choi(p) + assert np.allclose(AdSuper, choi2superop(AdChoi)) + + +def test_choi_pl_bijectivity(): + assert np.allclose(choi2superop(choi2superop(np.eye(4))), np.eye(4)) + assert np.allclose(superop2choi(superop2choi(np.eye(4))), np.eye(4)) + h_choi = kraus2choi(H) + h_superop = kraus2superop(H) + assert np.allclose(choi2superop(choi2superop(h_choi)), h_choi) + assert np.allclose(superop2choi(superop2choi(h_superop)), h_superop) From e4ea578e4b95cab299ff9e08bf2de53c984dd091 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Wed, 12 Jun 2019 12:49:00 +1000 Subject: [PATCH 17/33] test apply superop --- .../tests/test_apply_superoperator.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 forest/benchmarking/tests/test_apply_superoperator.py diff --git a/forest/benchmarking/tests/test_apply_superoperator.py b/forest/benchmarking/tests/test_apply_superoperator.py new file mode 100644 index 00000000..430c72a9 --- /dev/null +++ b/forest/benchmarking/tests/test_apply_superoperator.py @@ -0,0 +1,29 @@ +import numpy as np +from forest.benchmarking.tests.test_superoperator_transformations import ( + amplitude_damping_kraus, amplitude_damping_choi, ONE_STATE, ZERO_STATE, rho_out) +from forest.benchmarking.operator_tools.apply_superoperator import (apply_kraus_ops_2_state, + apply_choi_matrix_2_state) + + +def test_apply_kraus_ops_2_state(): + AD_kraus = amplitude_damping_kraus(0.1) + # rho_out was calculated by hand + assert np.allclose(rho_out, apply_kraus_ops_2_state(AD_kraus, ONE_STATE)) + + +def test_apply_non_square_kraus_ops_2_state(): + Id = np.eye(2) + bra_zero = np.asarray([[1], [0]]) + bra_one = np.asarray([[0], [1]]) + state_one = np.kron(Id / 2, ONE_STATE) + state_zero = np.kron(Id / 2, ZERO_STATE) + Kraus1 = np.kron(Id, bra_one.transpose()) + Kraus0 = np.kron(Id, bra_zero.transpose()) + assert np.allclose(apply_kraus_ops_2_state(Kraus0, state_zero), Id / 2) + assert np.allclose(apply_kraus_ops_2_state(Kraus1, state_one), Id / 2) + assert np.allclose(apply_kraus_ops_2_state(Kraus0, state_one), 0) + + +def test_apply_choi_matrix_2_state(): + choi = amplitude_damping_choi(0.1) + assert np.allclose(rho_out, apply_choi_matrix_2_state(choi, ONE_STATE)) From 6599a786dec731950ece887075945d428e623f71 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Wed, 12 Jun 2019 13:01:21 +1000 Subject: [PATCH 18/33] test channel approx --- .../tests/test_channel_approximation.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 forest/benchmarking/tests/test_channel_approximation.py diff --git a/forest/benchmarking/tests/test_channel_approximation.py b/forest/benchmarking/tests/test_channel_approximation.py new file mode 100644 index 00000000..ac5f2e79 --- /dev/null +++ b/forest/benchmarking/tests/test_channel_approximation.py @@ -0,0 +1,34 @@ +import numpy as np +from forest.benchmarking.operator_tools.channel_approximation import pauli_twirl_chi_matrix +from forest.benchmarking.tests.test_superoperator_transformations import (one_q_pauli_channel_chi, + amplitude_damping_chi) + + +# Pauli twirled Amplitude damping channel +def analytical_pauli_twirl_of_AD_chi(p): + # see equation 7 of https://arxiv.org/pdf/1701.03708.pdf + poly1 = (2 + 2 * np.sqrt(1 - p) - p) / 4 + poly2 = p / 4 + poly3 = (2 - 2 * np.sqrt(1 - p) - p) / 4 + pp_chi = np.asarray([[poly1, 0, 0, 0], + [0, poly2, 0, 0], + [0, 0, poly2, 0], + [0, 0, 0, poly3]]) + return pp_chi + + +def test_pauli_twirl_of_pauli_channel(): + # diagonal channel so should not change anything + px = np.random.rand() + py = np.random.rand() + pz = np.random.rand() + pauli_chan_chi_matrix = one_q_pauli_channel_chi(px, py, pz) + pauli_twirled_chi_matrix = pauli_twirl_chi_matrix(pauli_chan_chi_matrix) + assert np.allclose(pauli_chan_chi_matrix, pauli_twirled_chi_matrix) + + +def test_pauli_twirl_of_amp_damp(): + p = np.random.rand() + ana_chi = analytical_pauli_twirl_of_AD_chi(p) + num_chi = pauli_twirl_chi_matrix(amplitude_damping_chi(p)) + assert np.allclose(ana_chi, num_chi) From f7023e1a1b137082dd9f6b5b25c963ac13ab8b68 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Wed, 12 Jun 2019 14:41:56 +1000 Subject: [PATCH 19/33] first pass at operator_tools refactor --- .../benchmarking/operator_tools/__init__.py | 10 +- .../operator_tools/project_superoperators.py | 28 +- .../superoperator_transformations.py | 5 - .../operator_tools/validate_operator.py | 34 +- .../tests/test_project_superoperators.py | 92 ++++ .../tests/test_random_operators.py | 2 +- .../tests/test_superoperator_tools.py | 485 ------------------ .../tests/test_validate_operators.py | 77 +++ .../tests/test_validate_superoperator.py | 60 +++ 9 files changed, 245 insertions(+), 548 deletions(-) create mode 100644 forest/benchmarking/tests/test_project_superoperators.py delete mode 100644 forest/benchmarking/tests/test_superoperator_tools.py create mode 100644 forest/benchmarking/tests/test_validate_operators.py create mode 100644 forest/benchmarking/tests/test_validate_superoperator.py diff --git a/forest/benchmarking/operator_tools/__init__.py b/forest/benchmarking/operator_tools/__init__.py index 07c38657..6094e682 100644 --- a/forest/benchmarking/operator_tools/__init__.py +++ b/forest/benchmarking/operator_tools/__init__.py @@ -1,3 +1,7 @@ -__all__ = ["apply_superoperator", "channel_approximation", "project_superoperators", - "random_operators", "superoperator_transformations", "validate_operator", - "validate_superoperator" ] \ No newline at end of file +from .apply_superoperator import * +from .channel_approximation import * +from .project_superoperators import * +from .superoperator_transformations import * +from .random_operators import * +from .validate_operator import * +from .validate_superoperator import * diff --git a/forest/benchmarking/operator_tools/project_superoperators.py b/forest/benchmarking/operator_tools/project_superoperators.py index 3284dd58..71fc6922 100644 --- a/forest/benchmarking/operator_tools/project_superoperators.py +++ b/forest/benchmarking/operator_tools/project_superoperators.py @@ -2,31 +2,17 @@ We have arbitrarily decided to use a column stacking convention. -For more information about the conventions used, look at the file in -/docs/Superoperator representations.md - -Further references include: - -[GRAPTN] Tensor networks and graphical calculus for open quantum systems - Wood et al. - Quant. Inf. Comp. 15, 0579-0811 (2015) - (no DOI) - https://arxiv.org/abs/1111.6950 - -[MATQO] On the Matrix Representation of Quantum Operations - Nambu et al., - arXiv: 0504091 (2005) - https://arxiv.org/abs/quant-ph/0504091 - -[DUAL] On duality between quantum maps and quantum states - Zyczkowski et al., - Open Syst. Inf. Dyn. 11, 3 (2004) - https://dx.doi.org/10.1023/B:OPSY.0000024753.05661.c2 - https://arxiv.org/abs/quant-ph/0401119 +A good reference for these methods is: +[PGD] Maximum-likelihood quantum process tomography via projected gradient descent + Knee et al., + Phys. Rev. A 98, 062336 (2018) + https://dx.doi.org/10.1103/PhysRevA.98.062336 + https://arxiv.org/abs/1803.10062 """ import numpy as np from forest.benchmarking.utils import partial_trace +from forest.benchmarking.operator_tools.superoperator_transformations import vec def proj_choi_to_completely_positive(choi: np.ndarray) -> np.ndarray: diff --git a/forest/benchmarking/operator_tools/superoperator_transformations.py b/forest/benchmarking/operator_tools/superoperator_transformations.py index 381a2567..512a69cc 100644 --- a/forest/benchmarking/operator_tools/superoperator_transformations.py +++ b/forest/benchmarking/operator_tools/superoperator_transformations.py @@ -30,11 +30,6 @@ from forest.benchmarking.utils import n_qubit_pauli_basis -# ================================================================================================== -# Superoperator conversion tools -# ================================================================================================== - - def vec(matrix: np.ndarray) -> np.ndarray: """ Vectorize, i.e. "vec", a matrix by column stacking. diff --git a/forest/benchmarking/operator_tools/validate_operator.py b/forest/benchmarking/operator_tools/validate_operator.py index 4347aca7..ec2bd740 100644 --- a/forest/benchmarking/operator_tools/validate_operator.py +++ b/forest/benchmarking/operator_tools/validate_operator.py @@ -1,40 +1,8 @@ -"""A module allowing one to check if superoperators or channels are physical. - -We have arbitrarily decided to use a column stacking convention. - -For more information about the conventions used, look at the file in -/docs/Superoperator representations.md - -Further references include: - -[GRAPTN] Tensor networks and graphical calculus for open quantum systems - Wood et al. - Quant. Inf. Comp. 15, 0579-0811 (2015) - (no DOI) - https://arxiv.org/abs/1111.6950 - -[MATQO] On the Matrix Representation of Quantum Operations - Nambu et al., - arXiv: 0504091 (2005) - https://arxiv.org/abs/quant-ph/0504091 - -[DUAL] On duality between quantum maps and quantum states - Zyczkowski et al., - Open Syst. Inf. Dyn. 11, 3 (2004) - https://dx.doi.org/10.1023/B:OPSY.0000024753.05661.c2 - https://arxiv.org/abs/quant-ph/0401119 - +"""A module allowing one to check properties of operators or matrices. """ -from typing import Sequence import numpy as np -from forest.benchmarking.utils import partial_trace -from forest.benchmarking.operator_tools.superoperator_transformations import choi2kraus -from forest.benchmarking.operator_tools.superoperator_tools import apply_choi_matrix_2_state -# ================================================================================================== -# Check properties of operators or matrices -# ================================================================================================== def is_square_matrix(matrix: np.ndarray) -> bool: """ Checks if a matrix is square. diff --git a/forest/benchmarking/tests/test_project_superoperators.py b/forest/benchmarking/tests/test_project_superoperators.py new file mode 100644 index 00000000..a4d6b809 --- /dev/null +++ b/forest/benchmarking/tests/test_project_superoperators.py @@ -0,0 +1,92 @@ +import numpy as np +from pyquil.gate_matrices import X, Y, Z, H +from forest.benchmarking.operator_tools.superoperator_transformations import kraus2choi +from forest.benchmarking.operator_tools.validate_superoperator import * +from forest.benchmarking.operator_tools.project_superoperators import * + + +def test_proj_to_cp(): + state = np.eye(2) + assert np.allclose(state, proj_choi_to_completely_positive(state)) + + state = np.array([[1.5, 0], [0, 10]]) + assert choi_is_completely_positive(state) + assert np.allclose(state, proj_choi_to_completely_positive(state)) + + state = -Z + cp_state = proj_choi_to_completely_positive(state) + target = np.array([[0, 0], [0, 1.]]) + assert choi_is_completely_positive(cp_state) + assert np.allclose(target, cp_state) + + state = X + cp_state = proj_choi_to_completely_positive(state) + target = np.array([[.5, .5], [.5, .5]]) + assert choi_is_completely_positive(cp_state) + assert np.allclose(target, cp_state) + + state = Y + cp_state = proj_choi_to_completely_positive(state) + target = np.array([[.5, -.5j], [.5j, .5]]) + assert choi_is_completely_positive(cp_state) + assert np.allclose(target, cp_state) + + choi = kraus2choi(np.kron(X, Z)) + assert choi_is_completely_positive(choi) + assert np.allclose(choi, proj_choi_to_completely_positive(choi)) + + +def test_proj_to_tp(): + # Identity process is trace preserving, so no change + choi = kraus2choi(np.eye(2)) + assert np.allclose(choi, proj_choi_to_trace_preserving(choi)) + + # Bit flip process is trace preserving, so no change + choi = kraus2choi(X) + assert np.allclose(choi, proj_choi_to_trace_preserving(choi)) + + # start with a non-trace-preserving choi. + choi = kraus2choi(X - np.eye(2)*.01) + assert choi_is_trace_preserving(proj_choi_to_trace_preserving(choi)) + + +def test_proj_to_tni(): + # Bit flip process is trace preserving, so no change + choi = kraus2choi(X) + assert np.allclose(choi, proj_choi_to_trace_non_increasing(choi)) + + choi = np.array( + [[0., 0., 0., 0.], [0., 1.01, 1.01, 0.], [0., 1., 1., 0.], [0., 0., 0., 0.]]) + assert choi_is_trace_preserving(proj_choi_to_trace_non_increasing(choi)) + + # start with a non-trace-preserving choi. + choi = kraus2choi(np.kron(X - np.eye(2) * .01, np.eye(2))) + choi_tni = proj_choi_to_trace_non_increasing(choi) + plusplus = np.array([[1, 1, 1, 1]]).T / 2 + rho_pp = plusplus @ plusplus.T + output = apply_choi_matrix_2_state(choi_tni, rho_pp) + assert 0 < np.trace(output) <= 1 + + +def test_proj_to_cptp(): + # Identity process is cptp, so no change + choi = kraus2choi(np.eye(2)) + assert np.allclose(choi, proj_choi_to_physical(choi)) + + # Bit flip process is cptp, so no change + choi = kraus2choi(X) + assert np.allclose(choi, proj_choi_to_physical(choi)) + + # Small perturbation shouldn't change too much + choi = np.array([[1.001, 0., 0., .99], [0., 0., 0., 0.], [0., 0., 0., 0.], + [1.004, 0., 0., 1.01]]) + assert np.allclose(choi, proj_choi_to_physical(choi), atol=1e-2) + + # Ensure final product is cptp with arbitrary perturbation + choi = np.array([[1.1, 0.2, -0.4, .9], [.5, 0., 0., 0.], [0., 0., 0., 0.], + [1.4, 0., 0., .8]]) + physical_choi = proj_choi_to_physical(choi) + assert choi_is_trace_preserving(physical_choi) + assert choi_is_completely_positive(physical_choi, atol=1e-1) + assert choi_is_cptp(physical_choi, atol=1e-1) + diff --git a/forest/benchmarking/tests/test_random_operators.py b/forest/benchmarking/tests/test_random_operators.py index 203ad26f..4accd00d 100644 --- a/forest/benchmarking/tests/test_random_operators.py +++ b/forest/benchmarking/tests/test_random_operators.py @@ -7,7 +7,7 @@ from numpy import linalg as la import forest.benchmarking.distance_measures as dm import forest.benchmarking.operator_tools.random_operators as rand_ops -from forest.benchmarking.operator_tools.superoperator_tools import ( +from forest.benchmarking.operator_tools.validate_superoperator import ( choi_is_trace_preserving,choi_is_completely_positive) D2_SWAP = np.array([[1, 0, 0, 0], diff --git a/forest/benchmarking/tests/test_superoperator_tools.py b/forest/benchmarking/tests/test_superoperator_tools.py deleted file mode 100644 index 4e59a316..00000000 --- a/forest/benchmarking/tests/test_superoperator_tools.py +++ /dev/null @@ -1,485 +0,0 @@ -import numpy as np -from pyquil.gate_matrices import X, Y, Z, H -from forest.benchmarking.superoperator_tools import * -import forest.benchmarking.random_operators as rand_ops - - -# Test philosophy: -# Using the by hand calculations found in the docs we check conversion -# between one qubit channels with one Kraus operator (Hadamard) and two -# Kraus operators (the amplitude damping channel). Additionally we check -# a few two qubit channel conversions to get additional confidence. - -def amplitude_damping_kraus(p): - Ad0 = np.asarray([[1, 0], [0, np.sqrt(1 - p)]]) - Ad1 = np.asarray([[0, np.sqrt(p)], [0, 0]]) - return [Ad0, Ad1] - - -def amplitude_damping_chi(p): - poly1 = (1 + np.sqrt(1 - p)) ** 2 - poly2 = (-1 + np.sqrt(1 - p)) ** 2 - ad_pro = 0.25 * np.asarray([[poly1, 0, 0, p], - [0, p, -1j * p, 0], - [0, 1j * p, p, 0], - [p, 0, 0, poly2]]) - return ad_pro - - -def amplitude_damping_pauli(p): - poly1 = np.sqrt(1 - p) - ad_pau = np.asarray([[1, 0, 0, 0], - [0, poly1, 0, 0], - [0, 0, poly1, 0], - [p, 0, 0, 1 - p]]) - return ad_pau - - -def amplitude_damping_super(p): - poly1 = np.sqrt(1 - p) - ad_sup = np.asarray([[1, 0, 0, p], - [0, poly1, 0, 0], - [0, 0, poly1, 0], - [0, 0, 0, 1 - p]]) - return ad_sup - - -def amplitude_damping_choi(p): - poly1 = np.sqrt(1 - p) - ad_choi = np.asarray([[1, 0, 0, poly1], - [0, 0, 0, 0], - [0, 0, p, 0], - [poly1, 0, 0, 1 - p]]) - return ad_choi - - -HADChi = 0.5 * np.asarray([[0, 0, 0, 0], - [0, 1, 0, 1], - [0, 0, 0, 0], - [0, 1, 0, 1]]) - -HADPauli = 1.0 * np.asarray([[1, 0, 0, 0], - [0, 0, 0, 1], - [0, 0, -1, 0], - [0, 1, 0, 0]]) - -HADSuper = 0.5 * np.asarray([[1, 1, 1, 1], - [1, -1, 1, -1], - [1, 1, -1, -1], - [1, -1, -1, 1]]) - -HADChoi = 0.5 * np.asarray([[1, 1, 1, -1], - [1, 1, 1, -1], - [1, 1, 1, -1], - [-1, -1, -1, 1]]) - - -# Single Qubit Pauli Channel -def one_q_pauli_channel_chi(px, py, pz): - p = (px + py + pz) - pp_chi = np.asarray([[1 - p, 0, 0, 0], - [0, px, 0, 0], - [0, 0, py, 0], - [0, 0, 0, pz]]) - return pp_chi - - -# Pauli twirled Amplitude damping channel -def analytical_pauli_twirl_of_AD_chi(p): - # see equation 7 of https://arxiv.org/pdf/1701.03708.pdf - poly1 = (2 + 2 * np.sqrt(1 - p) - p) / 4 - poly2 = p / 4 - poly3 = (2 - 2 * np.sqrt(1 - p) - p) / 4 - pp_chi = np.asarray([[poly1, 0, 0, 0], - [0, poly2, 0, 0], - [0, 0, poly2, 0], - [0, 0, 0, poly3]]) - return pp_chi - - -# I \otimes Z channel or gate (two qubits) -two_qubit_paulis = n_qubit_pauli_basis(2) -IZKraus = two_qubit_paulis.ops_by_label['IZ'] -IZSuper = np.diag([1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1]) - -# one and zero state as a density matrix -ONE_STATE = np.asarray([[0, 0], [0, 1]]) -ZERO_STATE = np.asarray([[1, 0], [0, 0]]) - -# Amplitude damping Kraus operators with p = 0.1 -AdKrausOps = amplitude_damping_kraus(.1) - -# Use Kraus operators to find output of channel i.e. -# rho_out = A_0 rho A_0^\dag + A_1 rho A_1^\dag. -rho_out = np.matmul(np.matmul(AdKrausOps[0], ONE_STATE), AdKrausOps[0].transpose().conj()) + \ - np.matmul(np.matmul(AdKrausOps[1], ONE_STATE), AdKrausOps[1].transpose().conj()) - - -# =================================================================================================== -# Test superoperator conversion tools -# =================================================================================================== - -def test_vec(): - A = np.asarray([[1, 2], [3, 4]]) - B = np.asarray([[1, 2, 5], [3, 4, 6]]) - np.testing.assert_array_equal(np.array([[1], [3], [2], [4]]), vec(A)) - np.testing.assert_array_equal(np.array([[1], [3], [2], [4], [5], [6]]), vec(B)) - - -def test_unvec(): - A = np.asarray([[1, 2], [3, 4]]) - C = np.asarray([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) - np.testing.assert_array_equal(A, unvec(vec(A))) - np.testing.assert_array_equal(C, unvec(vec(C))) - - -def test_kraus_ops_sum_to_identity(): - # Check kraus ops sum to identity - p = np.random.rand() - Ad0, Ad1 = amplitude_damping_kraus(p) - np.testing.assert_array_almost_equal_nulp(np.matmul(Ad0.transpose().conj(), Ad0) - + np.matmul(Ad1.transpose().conj(), Ad1), np.eye(2)) - - -def test_kraus2chi(): - assert np.allclose(HADChi, kraus2chi(H)) - p = np.random.rand() - AdKraus = amplitude_damping_kraus(p) - AdChi = amplitude_damping_chi(p) - assert np.allclose(AdChi, kraus2chi(AdKraus)) - assert np.allclose(superop2chi(IZSuper), kraus2chi(IZKraus)) - - -def test_kraus2pauli_liouville(): - p = np.random.rand() - AdKraus = amplitude_damping_kraus(p) - AdPauli = amplitude_damping_pauli(p) - assert np.allclose(kraus2pauli_liouville(AdKraus), AdPauli) - assert np.allclose(kraus2pauli_liouville(H), HADPauli) - - -def test_kraus2superop(): - p = np.random.rand() - AdKraus = amplitude_damping_kraus(p) - AdSuper = amplitude_damping_super(p) - np.testing.assert_array_almost_equal_nulp(kraus2superop(AdKraus), AdSuper) - # test application of super operator is the same as application of Kraus ops - ONE_STATE_VEC = vec(ONE_STATE) - np.testing.assert_array_almost_equal_nulp(unvec(np.matmul(kraus2superop(AdKrausOps), - ONE_STATE_VEC)), rho_out) - assert np.allclose(kraus2superop(H), HADSuper) - assert np.allclose(kraus2superop(IZKraus), IZSuper) - # Below here tests non square Kraus operators - # In this example The Kraus operator is M_0 = I \otimes <0| where <0| = (1,0) - Idd = np.asarray([[1, 0], [0, 1]]) - M0 = np.kron(Idd, np.asarray([[1, 0]])) - attempt = kraus2superop(M0) - answer = np.kron(M0.conj(), M0) - assert np.allclose(answer, attempt) - - -def test_kraus2choi(): - p = np.random.rand() - AdKraus = amplitude_damping_kraus(p) - AdChoi = amplitude_damping_choi(p) - assert np.allclose(kraus2choi(AdKraus), AdChoi) - assert np.allclose(kraus2choi(H), HADChoi) - - -def test_chi2pauli_liouville(): - p = np.random.rand() - AdChi = amplitude_damping_chi(p) - AdPauli = amplitude_damping_pauli(p) - assert np.allclose(AdPauli, chi2pauli_liouville(AdChi)) - assert np.allclose(HADPauli, chi2pauli_liouville(HADChi)) - - -def test_basis_transform_p_to_c(): - xz_pauli_basis = np.zeros((16, 1)) - xz_pauli_basis[7] = [1.] - assert np.allclose(unvec(pauli2computational_basis_matrix(4) @ xz_pauli_basis), np.kron(X, Z)) - - -def test_basis_transform_c_to_p(): - xz_pauli_basis = np.zeros((16, 1)) - xz_pauli_basis[7] = [1.] - assert np.allclose(computational2pauli_basis_matrix(4) @ vec(np.kron(X, Z)), xz_pauli_basis) - - -def test_pl_to_choi(): - for i, pauli in enumerate(n_qubit_pauli_basis(2)): - pl = kraus2pauli_liouville(pauli[1]) - choi = kraus2choi(pauli[1]) - assert np.allclose(choi, pauli_liouville2choi(pl)) - - pl = kraus2pauli_liouville(H) - choi = kraus2choi(H) - assert np.allclose(choi, pauli_liouville2choi(pl)) - - -def test_superop_to_kraus(): - assert np.allclose(superop2kraus(IZSuper), IZKraus) - p = np.random.rand() - AdSuper = amplitude_damping_super(p) - AdKraus = amplitude_damping_kraus(p) - kraus_ops = superop2kraus(AdSuper) - - # the order of the Kraus ops matters - # TODO: fix the sign problem in Kraus operators - assert np.allclose([np.abs(kraus_ops[1]), np.abs(kraus_ops[0])], AdKraus) - - -def test_superop_to_choi(): - for i, pauli in enumerate(n_qubit_pauli_basis(2)): - superop = kraus2superop(pauli[1]) - choi = kraus2choi(pauli[1]) - assert np.allclose(choi, superop2choi(superop)) - p = np.random.rand() - AdSuper = amplitude_damping_super(p) - AdChoi = amplitude_damping_choi(p) - assert np.allclose(AdChoi, superop2choi(AdSuper)) - superop = kraus2superop(H) - choi = kraus2choi(H) - assert np.allclose(choi, superop2choi(superop)) - - -def test_superop_to_pl(): - p = np.random.rand() - AdSuper = amplitude_damping_super(p) - AdPauli = amplitude_damping_pauli(p) - assert np.allclose(AdPauli, superop2pauli_liouville(AdSuper)) - AdKraus = amplitude_damping_kraus(p) - superop = kraus2superop(AdKraus) - pauli = kraus2pauli_liouville(AdKraus) - assert np.allclose(pauli, superop2pauli_liouville(superop)) - - -def test_pauli_liouville_to_superop(): - p = np.random.rand() - AdSuper = amplitude_damping_super(p) - AdPauli = amplitude_damping_pauli(p) - assert np.allclose(AdSuper, pauli_liouville2superop(AdPauli)) - AdKraus = amplitude_damping_kraus(p) - superop = kraus2superop(AdKraus) - pauli = kraus2pauli_liouville(AdKraus) - assert np.allclose(superop, pauli_liouville2superop(pauli)) - - -def test_choi_to_kraus(): - for i, pauli in enumerate(n_qubit_pauli_basis(2)): - choi = kraus2choi(pauli[1]) - kraus = choi2kraus(choi) - assert np.allclose(choi, kraus2choi(kraus)) - id_choi = np.array([[1, 0, 0, 1], [0, 0, 0, 0], [0, 0, 0, 0], [1, 0, 0, 1]]) - assert np.allclose(kraus2choi(choi2kraus(id_choi)), id_choi) - for kraus in choi2kraus(id_choi): - assert np.allclose(abs(kraus), np.eye(2)) or np.allclose(kraus, np.zeros((2, 2))) - - -def test_choi_to_super(): - p = np.random.rand() - AdSuper = amplitude_damping_super(p) - AdChoi = amplitude_damping_choi(p) - assert np.allclose(AdSuper, choi2superop(AdChoi)) - - -def test_choi_pl_bijectivity(): - assert np.allclose(choi2superop(choi2superop(np.eye(4))), np.eye(4)) - assert np.allclose(superop2choi(superop2choi(np.eye(4))), np.eye(4)) - h_choi = kraus2choi(H) - h_superop = kraus2superop(H) - assert np.allclose(choi2superop(choi2superop(h_choi)), h_choi) - assert np.allclose(superop2choi(superop2choi(h_superop)), h_superop) - - -# =================================================================================================== -# Test channel and superoperator approximation tools -# =================================================================================================== - - -def test_pauli_twirl_of_pauli_channel(): - # diagonal channel so should not change anything - px = np.random.rand() - py = np.random.rand() - pz = np.random.rand() - pauli_chan_chi_matrix = one_q_pauli_channel_chi(px, py, pz) - pauli_twirled_chi_matrix = pauli_twirl_chi_matrix(pauli_chan_chi_matrix) - assert np.allclose(pauli_chan_chi_matrix, pauli_twirled_chi_matrix) - - -def test_pauli_twirl_of_amp_damp(): - p = np.random.rand() - ana_chi = analytical_pauli_twirl_of_AD_chi(p) - num_chi = pauli_twirl_chi_matrix(amplitude_damping_chi(p)) - assert np.allclose(ana_chi, num_chi) - - -# ================================================================================================== -# Test apply channel -# ================================================================================================== - - -def test_apply_kraus_ops_2_state(): - AD_kraus = amplitude_damping_kraus(0.1) - assert np.allclose(rho_out, apply_kraus_ops_2_state(AD_kraus, ONE_STATE)) - - -def test_apply_non_square_kraus_ops_2_state(): - Id = np.eye(2) - bra_zero = np.asarray([[1], [0]]) - bra_one = np.asarray([[0], [1]]) - state_one = np.kron(Id / 2, ONE_STATE) - state_zero = np.kron(Id / 2, ZERO_STATE) - Kraus1 = np.kron(Id, bra_one.transpose()) - Kraus0 = np.kron(Id, bra_zero.transpose()) - assert np.allclose(apply_kraus_ops_2_state(Kraus0, state_zero), Id / 2) - assert np.allclose(apply_kraus_ops_2_state(Kraus1, state_one), Id / 2) - assert np.allclose(apply_kraus_ops_2_state(Kraus0, state_one), 0) - - -def test_apply_choi_matrix_2_state(): - choi = amplitude_damping_choi(0.1) - assert np.allclose(rho_out, apply_choi_matrix_2_state(choi, ONE_STATE)) - - -# ================================================================================================== -# Test physicality of Channels -# ================================================================================================== -def test_kraus_operators_are_valid(): - assert kraus_operators_are_valid(amplitude_damping_kraus(np.random.rand())) - assert kraus_operators_are_valid(H) - assert not kraus_operators_are_valid(AdKrausOps[0]) - -def test_choi_is_hermitian_preserving(): - D = 2 - K = 2 - choi = rand_ops.rand_map_with_BCSZ_dist(D, K) - assert choi_is_hermitian_preserving(choi) - - -def test_choi_is_trace_preserving(): - D = 2 - K = 2 - choi = rand_ops.rand_map_with_BCSZ_dist(D, K) - assert choi_is_trace_preserving(choi) - - -def test_choi_is_completely_positive(): - D = 2 - K = 2 - choi = rand_ops.rand_map_with_BCSZ_dist(D, K) - assert choi_is_completely_positive(choi) - D = 3 - K = 2 - choi = rand_ops.rand_map_with_BCSZ_dist(D, K) - assert choi_is_completely_positive(choi) - - -def test_choi_is_unital(): - px = np.random.rand() - py = np.random.rand() - pz = np.random.rand() - choi = chi2choi(one_q_pauli_channel_chi(px, py, pz)) - assert choi_is_unital(choi) - assert choi_is_unital(HADChoi) - assert not choi_is_unital(amplitude_damping_choi(0.1)) - - -def test_choi_is_unitary(): - px = np.random.rand() - py = np.random.rand() - pz = np.random.rand() - choi = chi2choi(one_q_pauli_channel_chi(px, py, pz)) - assert not choi_is_unitary(choi) - assert choi_is_unital(choi) - assert choi_is_unitary(HADChoi) - assert not choi_is_unitary(amplitude_damping_choi(0.1)) - -# ================================================================================================== -# Test project Channels to CP, TNI, TP, and physical -# ================================================================================================== - -def test_proj_to_cp(): - state = np.eye(2) - assert np.allclose(state, proj_choi_to_completely_positive(state)) - - state = np.array([[1.5, 0], [0, 10]]) - assert choi_is_completely_positive(state) - assert np.allclose(state, proj_choi_to_completely_positive(state)) - - state = -Z - cp_state = proj_choi_to_completely_positive(state) - target = np.array([[0, 0], [0, 1.]]) - assert choi_is_completely_positive(cp_state) - assert np.allclose(target, cp_state) - - state = X - cp_state = proj_choi_to_completely_positive(state) - target = np.array([[.5, .5], [.5, .5]]) - assert choi_is_completely_positive(cp_state) - assert np.allclose(target, cp_state) - - state = Y - cp_state = proj_choi_to_completely_positive(state) - target = np.array([[.5, -.5j], [.5j, .5]]) - assert choi_is_completely_positive(cp_state) - assert np.allclose(target, cp_state) - - choi = kraus2choi(np.kron(X, Z)) - assert choi_is_completely_positive(choi) - assert np.allclose(choi, proj_choi_to_completely_positive(choi)) - - -def test_proj_to_tp(): - # Identity process is trace preserving, so no change - choi = kraus2choi(np.eye(2)) - assert np.allclose(choi, proj_choi_to_trace_preserving(choi)) - - # Bit flip process is trace preserving, so no change - choi = kraus2choi(X) - assert np.allclose(choi, proj_choi_to_trace_preserving(choi)) - - # start with a non-trace-preserving choi. - choi = kraus2choi(X - np.eye(2)*.01) - assert choi_is_trace_preserving(proj_choi_to_trace_preserving(choi)) - - -def test_proj_to_tni(): - # Bit flip process is trace preserving, so no change - choi = kraus2choi(X) - assert np.allclose(choi, proj_choi_to_trace_non_increasing(choi)) - - choi = np.array( - [[0., 0., 0., 0.], [0., 1.01, 1.01, 0.], [0., 1., 1., 0.], [0., 0., 0., 0.]]) - assert choi_is_trace_preserving(proj_choi_to_trace_non_increasing(choi)) - - # start with a non-trace-preserving choi. - choi = kraus2choi(np.kron(X - np.eye(2) * .01, np.eye(2))) - choi_tni = proj_choi_to_trace_non_increasing(choi) - plusplus = np.array([[1, 1, 1, 1]]).T / 2 - rho_pp = plusplus @ plusplus.T - output = apply_choi_matrix_2_state(choi_tni, rho_pp) - assert 0 < np.trace(output) <= 1 - - -def test_proj_to_cptp(): - # Identity process is cptp, so no change - choi = kraus2choi(np.eye(2)) - assert np.allclose(choi, proj_choi_to_physical(choi)) - - # Bit flip process is cptp, so no change - choi = kraus2choi(X) - assert np.allclose(choi, proj_choi_to_physical(choi)) - - # Small perturbation shouldn't change too much - choi = np.array([[1.001, 0., 0., .99], [0., 0., 0., 0.], [0., 0., 0., 0.], - [1.004, 0., 0., 1.01]]) - assert np.allclose(choi, proj_choi_to_physical(choi), atol=.01) - - # Ensure final product is cptp with arbitrary perturbation - choi = np.array([[1.1, 0.2, -0.4, .9], [.5, 0., 0., 0.], [0., 0., 0., 0.], - [1.4, 0., 0., .8]]) - physical_choi = proj_choi_to_physical(choi) - assert choi_is_trace_preserving(physical_choi) - assert choi_is_completely_positive(physical_choi, limit=1e-1) - diff --git a/forest/benchmarking/tests/test_validate_operators.py b/forest/benchmarking/tests/test_validate_operators.py new file mode 100644 index 00000000..837fbac4 --- /dev/null +++ b/forest/benchmarking/tests/test_validate_operators.py @@ -0,0 +1,77 @@ +import pytest +import numpy as np +from pyquil.gate_matrices import X, Y, Z, H +from forest.benchmarking.operator_tools.random_operators import haar_rand_unitary +from forest.benchmarking.operator_tools.validate_operator import * + + +# Matrix below is from https://en.wikipedia.org/wiki/Normal_matrix +# it is normal but NOT unitary or Hermitian +NORMAL = np.array([[1, 1, 0], [0, 1, 1], [1, 0, 1]]) + +# not symmetric +SIGMA_MINUS = (X + 1j*Y)/2 # np.array([[0, 1], [0, 0]]) + +# idempotent +PROJ_ZERO = np.array([[1, 0], [0, 0]]) + + +def test_is_square_matrix(): + assert is_square_matrix(np.eye(3)) + with pytest.raises(ValueError): + is_square_matrix(np.ndarray(shape=(2, 2, 2))) + assert not is_square_matrix(np.array([[1, 0]])) + + +def test_is_symmetric_matrix(): + assert is_symmetric_matrix(X) + assert not is_symmetric_matrix(SIGMA_MINUS) + with pytest.raises(ValueError): + is_symmetric_matrix(np.ndarray(shape=(2, 2, 2))) + with pytest.raises(ValueError): + is_symmetric_matrix(np.array([[1, 0]])) + + +def test_is_identity_matrix(): + assert not is_identity_matrix(Z) + assert is_identity_matrix(np.eye(3)) + with pytest.raises(ValueError): + is_identity_matrix(np.ndarray(shape=(2, 2, 2))) + with pytest.raises(ValueError): + is_identity_matrix(np.array([[1, 0]])) + + +def test_is_idempotent_matrix(): + assert not is_idempotent_matrix(SIGMA_MINUS) + assert is_idempotent_matrix(PROJ_ZERO) + assert is_idempotent_matrix(np.array([[0, 0, 0], [0, 1, 0], [0, 0, 0]])) + + +def test_is_normal_matrix(): + assert is_normal_matrix(NORMAL) + assert not is_normal_matrix(SIGMA_MINUS) + + +def test_is_hermitian_matrix(): + assert not is_hermitian_matrix(NORMAL) + assert is_hermitian_matrix(X) + assert is_hermitian_matrix(Y) + + +def test_is_unitary_matrix(): + assert not is_unitary_matrix(NORMAL) + assert is_unitary_matrix(Y) + assert is_unitary_matrix(haar_rand_unitary(4)) + + +def test_is_positive_definite_matrix(): + # not atol is = 1e-08 + assert not is_positive_definite_matrix(np.array([[-1e-08, 0], [0, 0.1]])) + assert is_positive_definite_matrix(np.array([[0.5e-08, 0], [0, 0.1]])) + + +def test_is_positive_semidefinite_matrix(): + # not atol is = 1e-08 + assert not is_positive_semidefinite_matrix(np.array([[-1e-07, 0], [0, 0.1]])) + assert is_positive_semidefinite_matrix(np.array([[-1e-08, 0], [0, 0.1]])) + assert is_positive_semidefinite_matrix(np.array([[0.5e-08, 0], [0, 0.1]])) diff --git a/forest/benchmarking/tests/test_validate_superoperator.py b/forest/benchmarking/tests/test_validate_superoperator.py new file mode 100644 index 00000000..e209fe78 --- /dev/null +++ b/forest/benchmarking/tests/test_validate_superoperator.py @@ -0,0 +1,60 @@ +from pyquil.gate_matrices import H +import forest.benchmarking.operator_tools.random_operators as rand_ops +from forest.benchmarking.operator_tools.superoperator_transformations import chi2choi +from forest.benchmarking.tests.test_superoperator_transformations import ( + amplitude_damping_kraus, AdKrausOps, HADChoi, amplitude_damping_choi, one_q_pauli_channel_chi) +from forest.benchmarking.operator_tools.validate_superoperator import * + + +def test_kraus_operators_are_valid(): + assert kraus_operators_are_valid(amplitude_damping_kraus(np.random.rand())) + assert kraus_operators_are_valid(H) + assert not kraus_operators_are_valid(AdKrausOps[0]) + + +def test_choi_is_hermitian_preserving(): + D = 2 + K = 2 + choi = rand_ops.rand_map_with_BCSZ_dist(D, K) + assert choi_is_hermitian_preserving(choi) + + +def test_choi_is_trace_preserving(): + D = 2 + K = 2 + choi = rand_ops.rand_map_with_BCSZ_dist(D, K) + assert choi_is_trace_preserving(choi) + + +def test_choi_is_completely_positive(): + D = 2 + K = 2 + choi = rand_ops.rand_map_with_BCSZ_dist(D, K) + assert choi_is_completely_positive(choi) + D = 3 + K = 2 + choi = rand_ops.rand_map_with_BCSZ_dist(D, K) + assert choi_is_completely_positive(choi) + + +def test_choi_is_unital(): + px = np.random.rand() + py = np.random.rand() + pz = np.random.rand() + norm = px + py + pz + choi = chi2choi(one_q_pauli_channel_chi(px/norm, py/norm, pz/norm)) + assert choi_is_unital(choi) + assert choi_is_unital(HADChoi) + assert not choi_is_unital(amplitude_damping_choi(0.1)) + + +def test_choi_is_unitary(): + px = np.random.rand() + py = np.random.rand() + pz = np.random.rand() + norm = px + py + pz + choi = chi2choi(one_q_pauli_channel_chi(px/norm, py/norm, pz/norm)) + assert not choi_is_unitary(choi) + assert choi_is_unital(choi) + assert choi_is_unitary(HADChoi) + assert not choi_is_unitary(amplitude_damping_choi(0.1)) From 47ba97cba877b933e70902d8e7b9daf86a6661e4 Mon Sep 17 00:00:00 2001 From: Kyle Date: Wed, 12 Jun 2019 13:00:06 -0400 Subject: [PATCH 20/33] Spruce up permute_tesnor_factor and expand functionality. --- .../operator_tools/random_operators.py | 53 +++++++++++-------- .../tests/test_random_operators.py | 15 ++++++ 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/forest/benchmarking/operator_tools/random_operators.py b/forest/benchmarking/operator_tools/random_operators.py index 04e8cdc4..5f64082a 100644 --- a/forest/benchmarking/operator_tools/random_operators.py +++ b/forest/benchmarking/operator_tools/random_operators.py @@ -8,7 +8,7 @@ https://dx.doi.org/10.1088/1367-2630/18/3/033024 https://arxiv.org/abs/1509.03770 """ -from typing import Optional, List +from typing import Optional, List, Union import numpy as np from numpy import linalg as la @@ -150,20 +150,24 @@ def rand_map_with_BCSZ_dist(dim: int, kraus_rank: int) -> np.ndarray: return Z -def permute_tensor_factors(dim: int, perm: List[int]) -> np.ndarray: +def permute_tensor_factors(dims: Union[int, List[int]], perm: List[int]) -> np.ndarray: r""" - Return a permutation matrix of the given dimension. + Return a permutation matrix that appropriately swaps spaces of the given dimension(s). Given a Hilbert space dimension dim and an list representing the permutation perm of the tensor product Hilbert spaces, returns a $dim^len(perm)$ by $dim^len(perm)$ permutation matrix. - E.g. 1) Suppose D=2 and perm=[0,1] + E.g. 1) Suppose dims=2 and perm=[0,1] Returns the identity operator on two qubits - 2) Suppose D=2 and perm=[1,0] + 2) Suppose dims=2 and perm=[1,0] Returns the SWAP operator on two qubits which maps A_1 \otimes A_2 --> A_2 \otimes A_1. + 3) Suppose dims=[2, 4] and perm=[1,0] + Returns the SWAP operator on three qubits which + maps A_1 \otimes (A_2 \otimes A_3) -> (A_2 \otimes A_3) + See: Equations 5.11, 5.12, and 5.13 in [SCOTT] Optimizing quantum process tomography with unitary 2-designs @@ -175,28 +179,31 @@ def permute_tensor_factors(dim: int, perm: List[int]) -> np.ndarray: This function is used in tests for other functions. However, it can also be useful when thinking about higher moment (N>2) integrals over the Haar measure. - :param dim: Hilbert space dimension. + :param dims: The dimension of each Hilbert space factor given in order of the pre-permuted + factorization of the total space. If an int is specified then each space in the + permutation is assumed to have this dimension. :param perm: A list representing the permutation of the tensor factors. - :return: a matrix permuting the operators + :return: a permutation matrix that permutes the factors of the given dimension. """ - dim_list = [dim for i in range(2 * len(perm))] - - Id = np.eye(dim ** len(perm), dim ** len(perm)) + if isinstance(dims, int): + dim_list = [dims for _ in range(2 * len(perm))] + total_dim = dims ** len(perm) + else: + assert len(dims) == len(perm), "Please specify the dimension of each factor to be permuted." + dim_list = [dim for _ in range(2) for dim in dims] + total_dim = np.prod(dims) - P = Permutation(perm) - tran = P.transpositions - trans = tran() + transpositions = Permutation(perm).transpositions() - temp = np.reshape(Id, dim_list) + # start with identity + perm_matrix = np.eye(total_dim, total_dim) - # implement the permutation + # reshape for easy implementation of transpositions + perm_matrix = np.reshape(perm_matrix, dim_list) - if P == []: - return Id - else: - for pdx in range(len(trans)): - tdx = trans[pdx] - answer = np.swapaxes(temp, tdx[0], tdx[1]) - temp = answer + # build up the permutation one transposition at a time + for swap in transpositions: + perm_matrix = np.swapaxes(perm_matrix, swap[0], swap[1]) - return np.reshape(answer, [dim ** len(perm), dim ** len(perm)]) + # reshape to act on total_dim space + return np.reshape(perm_matrix, [total_dim, total_dim]) diff --git a/forest/benchmarking/tests/test_random_operators.py b/forest/benchmarking/tests/test_random_operators.py index 4ee3efc0..032b3daa 100644 --- a/forest/benchmarking/tests/test_random_operators.py +++ b/forest/benchmarking/tests/test_random_operators.py @@ -30,6 +30,10 @@ # ================================================================================================= # Test: Permute tensor factors # ================================================================================================= +def test_permute_tensor_factor_id(): + np.testing.assert_array_equal(rand_ops.permute_tensor_factors(2, [0, 1, 2]), np.eye(8)) + + def test_permute_tensor_factor_SWAP(): # test the Dimension two and three SWAP operators D2 = 2 @@ -64,6 +68,17 @@ def test_permute_tensor_factor_three_qubits(): assert np.dot(fn_ans3.T, by_hand_ans3) == 1.0 +def test_permute_tensor_factor_diff_size_spaces(): + dims = [2, 4, 8] + perm = [2, 1, 0] + + alt_dims = 2 + alt_perm = [3, 4, 5, 1, 2, 0] + + np.testing.assert_array_equal(rand_ops.permute_tensor_factors(dims, perm), + rand_ops.permute_tensor_factors(alt_dims, alt_perm)) + + def test_permute_tensor_factor_four_qubit_permutation_operator(): # Test the generation of the permutation operator from the symbolic list # given to us by SymPy and if that is the same thing from Andrew Scott's papers. From 854a0cfaff4516750f450a38d188870fae618b8b Mon Sep 17 00:00:00 2001 From: Kyle Date: Wed, 12 Jun 2019 13:28:45 -0400 Subject: [PATCH 21/33] Move project_state_matrix_to_physical out of tomography. --- .../operator_tools/channel_approximation.py | 1 + .../operator_tools/project_state_matrix.py | 52 ++++++++++++++++++ forest/benchmarking/tomography.py | 53 +------------------ 3 files changed, 55 insertions(+), 51 deletions(-) create mode 100644 forest/benchmarking/operator_tools/project_state_matrix.py diff --git a/forest/benchmarking/operator_tools/channel_approximation.py b/forest/benchmarking/operator_tools/channel_approximation.py index 0e6c7d20..f3aa9cf8 100644 --- a/forest/benchmarking/operator_tools/channel_approximation.py +++ b/forest/benchmarking/operator_tools/channel_approximation.py @@ -27,6 +27,7 @@ """ import numpy as np + def pauli_twirl_chi_matrix(chi_matrix: np.ndarray) -> np.ndarray: r""" Implements a Pauli twirl of a chi matrix (aka a process matrix). diff --git a/forest/benchmarking/operator_tools/project_state_matrix.py b/forest/benchmarking/operator_tools/project_state_matrix.py new file mode 100644 index 00000000..aab9e87d --- /dev/null +++ b/forest/benchmarking/operator_tools/project_state_matrix.py @@ -0,0 +1,52 @@ +import numpy as np +import functools +from scipy.linalg import eigh + + +def project_density_matrix_to_physical(rho: np.ndarray) -> np.ndarray: + """ + Project a possibly unphysical estimated density matrix to the closest (with respect to the + 2-norm) positive semi-definite matrix with trace 1, that is a valid quantum state. + + This is the so called "wizard" method. It is described in the following reference: + + [MLEWIZ] Efficient Method for Computing the Maximum-Likelihood Quantum State from + Measurements with Additive Gaussian Noise + Smolin et al., + Phys. Rev. Lett. 108, 070502 (2012) + https://doi.org/10.1103/PhysRevLett.108.070502 + https://arxiv.org/abs/1106.5458 + + :param rho: the density (state) matrix with shape (N, N) + :return rho_projected: The closest positive semi-definite trace 1 matrix to rho. + """ + # Rescale to trace 1 if the matrix is not already + rho_impure = rho / np.trace(rho) + + dimension = rho_impure.shape[0] # the dimension of the Hilbert space + [eigvals, eigvecs] = eigh(rho_impure) + + # If matrix is already trace one PSD, we are done + if np.min(eigvals) >= 0: + return rho_impure + + # Otherwise, continue finding closest trace one, PSD matrix + eigvals = list(eigvals) + eigvals.reverse() + eigvals_new = [0.0] * len(eigvals) + + i = dimension + accumulator = 0.0 # Accumulator + while eigvals[i - 1] + accumulator / float(i) < 0: + accumulator += eigvals[i - 1] + i -= 1 + for j in range(i): + eigvals_new[j] = eigvals[j] + accumulator / float(i) + eigvals_new.reverse() + + # Reconstruct the matrix + rho_projected = functools.reduce(np.dot, (eigvecs, + np.diag(eigvals_new), + np.conj(eigvecs.T))) + + return rho_projected diff --git a/forest/benchmarking/tomography.py b/forest/benchmarking/tomography.py index 64992884..8bc8d059 100644 --- a/forest/benchmarking/tomography.py +++ b/forest/benchmarking/tomography.py @@ -5,7 +5,7 @@ import warnings import numpy as np -from scipy.linalg import logm, pinv, eigh +from scipy.linalg import logm, pinv from pyquil import Program from pyquil.unitary_tools import lifted_pauli as pauli2matrix, lifted_state_operator as state2matrix @@ -13,6 +13,7 @@ import forest.benchmarking.distance_measures as dm from forest.benchmarking.utils import all_traceless_pauli_terms from forest.benchmarking.superoperator_tools import vec, unvec, proj_choi_to_physical +from forest.benchmarking.operator_tools.project_state_matrix import project_density_matrix from forest.benchmarking.observable_estimation import ExperimentSetting, ObservablesExperiment, \ ExperimentResult, SIC0, SIC1, SIC2, SIC3, plusX, minusX, plusY, minusY, plusZ, minusZ, \ TensorProductState, zeros_state @@ -331,56 +332,6 @@ def state_log_likelihood(state, results, qubits) -> float: return ll -def project_density_matrix(rho) -> np.ndarray: - """ - Project a possibly unphysical estimated density matrix to the closest (with respect to the - 2-norm) positive semi-definite matrix with trace 1, that is a valid quantum state. - - This is the so called "wizard" method. It is described in the following reference: - - [MLEWIZ] Efficient Method for Computing the Maximum-Likelihood Quantum State from - Measurements with Additive Gaussian Noise - Smolin et al., - Phys. Rev. Lett. 108, 070502 (2012) - https://doi.org/10.1103/PhysRevLett.108.070502 - https://arxiv.org/abs/1106.5458 - - :param rho: Numpy array containing the density matrix with dimension (N, N) - :return rho_projected: The closest positive semi-definite trace 1 matrix to rho. - """ - - # Rescale to trace 1 if the matrix is not already - rho_impure = rho / np.trace(rho) - - dimension = rho_impure.shape[0] # the dimension of the Hilbert space - [eigvals, eigvecs] = eigh(rho_impure) - - # If matrix is already trace one PSD, we are done - if np.min(eigvals) >= 0: - return rho_impure - - # Otherwise, continue finding closest trace one, PSD matrix - eigvals = list(eigvals) - eigvals.reverse() - eigvals_new = [0.0] * len(eigvals) - - i = dimension - accumulator = 0.0 # Accumulator - while eigvals[i - 1] + accumulator / float(i) < 0: - accumulator += eigvals[i - 1] - i -= 1 - for j in range(i): - eigvals_new[j] = eigvals[j] + accumulator / float(i) - eigvals_new.reverse() - - # Reconstruct the matrix - rho_projected = functools.reduce(np.dot, (eigvecs, - np.diag(eigvals_new), - np.conj(eigvecs.T))) - - return rho_projected - - def _resample_expectations_with_beta(results, prior_counts=1): """Resample expectation values by constructing a beta distribution and sampling from it. From 380d32187759c253441ce706dcc9b4f4783c25f0 Mon Sep 17 00:00:00 2001 From: Kyle Date: Wed, 12 Jun 2019 16:31:49 -0400 Subject: [PATCH 22/33] Fix import --- forest/benchmarking/tomography.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forest/benchmarking/tomography.py b/forest/benchmarking/tomography.py index 8bc8d059..3e7b7b29 100644 --- a/forest/benchmarking/tomography.py +++ b/forest/benchmarking/tomography.py @@ -13,7 +13,7 @@ import forest.benchmarking.distance_measures as dm from forest.benchmarking.utils import all_traceless_pauli_terms from forest.benchmarking.superoperator_tools import vec, unvec, proj_choi_to_physical -from forest.benchmarking.operator_tools.project_state_matrix import project_density_matrix +from forest.benchmarking.operator_tools.project_state_matrix import project_density_matrix_to_physical from forest.benchmarking.observable_estimation import ExperimentSetting, ObservablesExperiment, \ ExperimentResult, SIC0, SIC1, SIC2, SIC3, plusX, minusX, plusY, minusY, plusZ, minusZ, \ TensorProductState, zeros_state @@ -385,7 +385,7 @@ def estimate_variance(results: List[ExperimentResult], functional is measured. Not applicable if functional is ``dm.purity``. :param n_resamples: The number of times to resample. :param project_to_physical: Whether to project the estimated state to a physical one - with :py:func:`project_density_matrix`. + with :py:func:`project_density_matrix_to_physical`. """ if functional != dm.purity: if target_state is None: @@ -398,7 +398,7 @@ def estimate_variance(results: List[ExperimentResult], rho = tomo_estimator(resampled_results, qubits) if project_to_physical: - rho = project_density_matrix(rho) + rho = project_density_matrix_to_physical(rho) # Calculate functional of the state if functional == dm.purity: From 59596cfb6a12e310ae9e93a1c2cf2b45bad9f563 Mon Sep 17 00:00:00 2001 From: Kyle Date: Wed, 12 Jun 2019 17:24:33 -0400 Subject: [PATCH 23/33] Update names and imports for project_state_matrix --- examples/tomography_state.ipynb | 6 +++--- .../operator_tools/project_state_matrix.py | 2 +- .../tests/test_project_state_matrix.py | 14 ++++++++++++++ .../benchmarking/tests/test_state_tomography.py | 15 +-------------- forest/benchmarking/tomography.py | 8 +++++--- 5 files changed, 24 insertions(+), 21 deletions(-) create mode 100644 forest/benchmarking/tests/test_project_state_matrix.py diff --git a/examples/tomography_state.ipynb b/examples/tomography_state.ipynb index b09f503d..a1f2df6c 100644 --- a/examples/tomography_state.ipynb +++ b/examples/tomography_state.ipynb @@ -265,10 +265,10 @@ "metadata": {}, "outputs": [], "source": [ - "from forest.benchmarking.tomography import project_density_matrix\n", + "from forest.benchmarking.operator_tools.project_state_matrix import project_state_matrix_to_physical\n", "rho_unphys = np.random.uniform(-1, 1, (2, 2)) \\\n", " * np.exp(1.j * np.random.uniform(-np.pi, np.pi, (2, 2)))\n", - "rho_phys = project_density_matrix(rho_unphys)\n", + "rho_phys = project_state_matrix_to_physical(rho_unphys)\n", "\n", "fig, (ax1, ax2) = plt.subplots(1, 2)\n", "hinton(rho_unphys, ax=ax1)\n", @@ -288,7 +288,7 @@ "# https://doi.org/10.1103/PhysRevLett.108.070502\n", "\n", "eigs = np.diag(np.array(list(reversed([3.0/5, 1.0/2, 7.0/20, 1.0/10, -11.0/20]))))\n", - "phys = project_density_matrix(eigs)\n", + "phys = project_state_matrix_to_physical(eigs)\n", "np.allclose(phys, np.diag([0, 0, 1.0/5, 7.0/20, 9.0/20]))" ] }, diff --git a/forest/benchmarking/operator_tools/project_state_matrix.py b/forest/benchmarking/operator_tools/project_state_matrix.py index aab9e87d..132ae76a 100644 --- a/forest/benchmarking/operator_tools/project_state_matrix.py +++ b/forest/benchmarking/operator_tools/project_state_matrix.py @@ -3,7 +3,7 @@ from scipy.linalg import eigh -def project_density_matrix_to_physical(rho: np.ndarray) -> np.ndarray: +def project_state_matrix_to_physical(rho: np.ndarray) -> np.ndarray: """ Project a possibly unphysical estimated density matrix to the closest (with respect to the 2-norm) positive semi-definite matrix with trace 1, that is a valid quantum state. diff --git a/forest/benchmarking/tests/test_project_state_matrix.py b/forest/benchmarking/tests/test_project_state_matrix.py new file mode 100644 index 00000000..5d6a7273 --- /dev/null +++ b/forest/benchmarking/tests/test_project_state_matrix.py @@ -0,0 +1,14 @@ +import numpy as np +from forest.benchmarking.operator_tools.project_state_matrix import project_state_matrix_to_physical + + +def test_project_state_matrix(): + """ + Test the wizard method. Example from fig 1 of maximum likelihood minimum effort + https://doi.org/10.1103/PhysRevLett.108.070502 + + :return: + """ + eigs = np.diag(np.array(list(reversed([3.0 / 5, 1.0 / 2, 7.0 / 20, 1.0 / 10, -11.0 / 20])))) + phys = project_state_matrix_to_physical(eigs) + assert np.allclose(phys, np.diag([0, 0, 1.0 / 5, 7.0 / 20, 9.0 / 20])) diff --git a/forest/benchmarking/tests/test_state_tomography.py b/forest/benchmarking/tests/test_state_tomography.py index 1d41706f..97cfae42 100644 --- a/forest/benchmarking/tests/test_state_tomography.py +++ b/forest/benchmarking/tests/test_state_tomography.py @@ -6,8 +6,7 @@ from forest.benchmarking.compilation import basic_compile from forest.benchmarking.random_operators import haar_rand_unitary from forest.benchmarking.tomography import generate_state_tomography_experiment, _R, \ - iterative_mle_state_estimate, project_density_matrix, estimate_variance, \ - linear_inv_state_estimate + iterative_mle_state_estimate, estimate_variance, linear_inv_state_estimate from pyquil.api import ForestConnection, QuantumComputer, QVM from pyquil.api._compiler import _extract_attribute_dictionary_from_program from pyquil.api._qac import AbstractCompiler @@ -262,18 +261,6 @@ def test_hedged_two_qubit(two_q_tomo_fixture): np.testing.assert_allclose(rho_true, rho_est, atol=0.02) -def test_project_density_matrix(): - """ - Test the wizard method. Example from fig 1 of maximum likelihood minimum effort - https://doi.org/10.1103/PhysRevLett.108.070502 - - :return: - """ - eigs = np.diag(np.array(list(reversed([3.0 / 5, 1.0 / 2, 7.0 / 20, 1.0 / 10, -11.0 / 20])))) - phys = project_density_matrix(eigs) - assert np.allclose(phys, np.diag([0, 0, 1.0 / 5, 7.0 / 20, 9.0 / 20])) - - def test_variance_bootstrap(two_q_tomo_fixture): qubits = [0, 1] results, rho_true = two_q_tomo_fixture diff --git a/forest/benchmarking/tomography.py b/forest/benchmarking/tomography.py index 3e7b7b29..9c5b5c39 100644 --- a/forest/benchmarking/tomography.py +++ b/forest/benchmarking/tomography.py @@ -13,7 +13,7 @@ import forest.benchmarking.distance_measures as dm from forest.benchmarking.utils import all_traceless_pauli_terms from forest.benchmarking.superoperator_tools import vec, unvec, proj_choi_to_physical -from forest.benchmarking.operator_tools.project_state_matrix import project_density_matrix_to_physical +from forest.benchmarking.operator_tools.project_state_matrix import project_state_matrix_to_physical from forest.benchmarking.observable_estimation import ExperimentSetting, ObservablesExperiment, \ ExperimentResult, SIC0, SIC1, SIC2, SIC3, plusX, minusX, plusY, minusY, plusZ, minusZ, \ TensorProductState, zeros_state @@ -22,6 +22,8 @@ OPTIMAL = "optimal" FRO = 'fro' +def project_density_matrix(): + return None # ================================================================================================== # Generate state and process tomography experiments @@ -385,7 +387,7 @@ def estimate_variance(results: List[ExperimentResult], functional is measured. Not applicable if functional is ``dm.purity``. :param n_resamples: The number of times to resample. :param project_to_physical: Whether to project the estimated state to a physical one - with :py:func:`project_density_matrix_to_physical`. + with :py:func:`project_state_matrix_to_physical`. """ if functional != dm.purity: if target_state is None: @@ -398,7 +400,7 @@ def estimate_variance(results: List[ExperimentResult], rho = tomo_estimator(resampled_results, qubits) if project_to_physical: - rho = project_density_matrix_to_physical(rho) + rho = project_state_matrix_to_physical(rho) # Calculate functional of the state if functional == dm.purity: From 455bb581d81f1c1a5d3a7af221feb3914fa9a2da Mon Sep 17 00:00:00 2001 From: Kyle Date: Wed, 12 Jun 2019 17:27:52 -0400 Subject: [PATCH 24/33] Remove nonsense code. --- forest/benchmarking/tomography.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/forest/benchmarking/tomography.py b/forest/benchmarking/tomography.py index 9c5b5c39..675e9724 100644 --- a/forest/benchmarking/tomography.py +++ b/forest/benchmarking/tomography.py @@ -22,8 +22,6 @@ OPTIMAL = "optimal" FRO = 'fro' -def project_density_matrix(): - return None # ================================================================================================== # Generate state and process tomography experiments From d00db8f3b850f23a89039160d01646b33eb47ea0 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Thu, 13 Jun 2019 14:49:57 +1000 Subject: [PATCH 25/33] compose kraus + tests --- .../operator_tools/compose_superoperators.py | 42 +++++++++++++++++++ .../tests/test_compose_superoperators.py | 32 ++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 forest/benchmarking/operator_tools/compose_superoperators.py create mode 100644 forest/benchmarking/tests/test_compose_superoperators.py diff --git a/forest/benchmarking/operator_tools/compose_superoperators.py b/forest/benchmarking/operator_tools/compose_superoperators.py new file mode 100644 index 00000000..d4ba2ecc --- /dev/null +++ b/forest/benchmarking/operator_tools/compose_superoperators.py @@ -0,0 +1,42 @@ +"""A module containing tools for composing superoperators. +""" +from typing import Sequence +import numpy as np + + +def tensor_channel_kraus(k2: Sequence[np.ndarray], + k1: Sequence[np.ndarray]) -> Sequence[np.ndarray]: + r""" + Given the Kraus representaion for two channels, $\mathcal E$ and $\mathcal F$, acting on + different systems this function returns the Kraus operators representing the composition of + these independent channels. + + Suppose $\mathcal E$ and $\mathcal F$ both have one Kraus operator K_1 = X and K_2 = H, + that is they are unitary. Then, with respect to the tensor product structure + + $H_2 \otimes H_1$ + + of the individual systems this function returns $K_{\rm tot} = H \otimes X$. + + :param k1: The list of Kraus operators on the first system. + :param k2: The list of Kraus operators on the second system. + :return: A list of tensored Kraus operators. + """ + # TODO: make this function work for an arbitrary number of Kraus operators + return [np.kron(k2l, k1j) for k1j in k1 for k2l in k2] + + +def compose_channel_kraus(k2: Sequence[np.ndarray], + k1: Sequence[np.ndarray]) -> Sequence[np.ndarray]: + r""" + Given two channels, K_1 and K_2, acting on the same system in the Kraus representation this + function return the Kraus operators representing the composition of the channels. + + It is assumed that K_1 is applied first then K_2 is applied. + + :param k2: The list of Kraus operators that are applied second. + :param k1: The list of Kraus operators that are applied first. + :return: A combinatorially generated list of composed Kraus operators. + """ + # TODO: make this function work for an arbitrary number of Kraus operators + return [np.dot(k2l, k1j) for k1j in k1 for k2l in k2] diff --git a/forest/benchmarking/tests/test_compose_superoperators.py b/forest/benchmarking/tests/test_compose_superoperators.py new file mode 100644 index 00000000..0f53aa4a --- /dev/null +++ b/forest/benchmarking/tests/test_compose_superoperators.py @@ -0,0 +1,32 @@ +import numpy as np +from pyquil.gate_matrices import I, X, H +from forest.benchmarking.tests.test_superoperator_transformations import amplitude_damping_kraus +from forest.benchmarking.operator_tools.superoperator_transformations import kraus2superop +from forest.benchmarking.operator_tools.compose_superoperators import (compose_channel_kraus, + tensor_channel_kraus) + + +def bit_flip_kraus(p): + M0 = np.sqrt(1 - p) * I + M1 = np.sqrt(p) * X + return [M0, M1] + + +AD_kraus = amplitude_damping_kraus(0.1) +BitFlip_kraus = bit_flip_kraus(0.2) +BitFlip_super = kraus2superop(BitFlip_kraus) +AD_super = kraus2superop(AD_kraus) + + +def test_compose_channel_kraus(): + function_output = kraus2superop(compose_channel_kraus(AD_kraus, BitFlip_kraus)) + independent_answer = AD_super @ BitFlip_super + assert np.allclose(function_output, independent_answer) + + +def test_tensor_channel_kraus(): + function_output = tensor_channel_kraus([X], [H]) + independent_answer = np.kron(X,H) + wrong_answer = np.kron(H,X) + assert np.allclose(function_output, independent_answer) + assert not np.allclose(function_output, wrong_answer) From ec1438208500817f80eadc42ac850cd2465b83e4 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Thu, 13 Jun 2019 14:50:39 +1000 Subject: [PATCH 26/33] improve --- examples/random_operators.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/random_operators.ipynb b/examples/random_operators.ipynb index b57737cb..7b092d97 100644 --- a/examples/random_operators.ipynb +++ b/examples/random_operators.ipynb @@ -11,9 +11,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In the context of `forest-benchmarking` the primary use of random operators is to test the estimation routines.\n", + "In this notebook we explore a submodule of `operator_tools` called `random_operators`.\n", "\n", - "For example you might modify a existing state or process tomography routine (or develop a new method) and want to test that your modification works. One way to do that would be to test it on a bunch of random quantum states or channels. " + "In the context of forest benchmarking the primary use of random operators is to test the estimation routines. For example you might modify a existing state or process tomography routine (or develop a new method) and want to test that your modification works. One way to do that would be to test it on a bunch of random quantum states or channels. " ] }, { From e49af7d49570ea36685149a1005159d827955216 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Thu, 13 Jun 2019 14:57:53 +1000 Subject: [PATCH 27/33] big update --- examples/superoperator_tools.ipynb | 1075 +++++++++++++++++++++++++--- 1 file changed, 979 insertions(+), 96 deletions(-) diff --git a/examples/superoperator_tools.ipynb b/examples/superoperator_tools.ipynb index 13cff7af..044e36ba 100644 --- a/examples/superoperator_tools.ipynb +++ b/examples/superoperator_tools.ipynb @@ -4,265 +4,1148 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Superoperator conversion: example conversion between representations" + "# Superoperator tools\n", + "\n", + "In this notebook we explore a the submodules of `operator_tools` that enable easy manipulation of the various quantum channel representations.\n", + "\n", + "To summarize the functionality:\n", + "- vectorization and conversions between different repesentations of quantum channels\n", + "- apply quantm operations\n", + "- compose quantum operations\n", + "- validate that quantum channels are physical\n", + "- project unphysical channels to physical channels" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Breif motivation and introduction" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Perfect the gates of **reversible classical computation** are described by permutation matricies, e.g. the [Toffoli gate](https://en.wikipedia.org/wiki/Toffoli_gate), while the input states are vectors. Noisy classical gates could be modeled as a perfect gate followed by a noise channel, e.g. [binary symmetric channel](https://en.wikipedia.org/wiki/Binary_symmetric_channel), on all the bits.\n", + "\n", + "Perfect gates in **quantum computation** are described by unitary matricies and states are described by vectors, e.g.\n", + "$$\n", + "|\\psi\\rangle = U |\\psi_0\\rangle.\n", + "$$\n", + "\n", + "Modeling **noisy quantum computation** often makes use of [mixed states](https://en.wikipedia.org/wiki/Density_matrix) and quantum operations or quantum noise channels.\n", + "\n", + "Interestingly there are a number of ways to represent quantum noise channels, and depending on your task some can be more convenient than others. The simplest case to illustrate this point is to consider a mixed initial state $\\rho$ undergoing unitary evolution\n", + "\n", + "$$ \\rho' = U \\rho U^\\dagger.$$\n", + "\n", + "The fact that the unitary has to act on both sides of the inital state means it is a [*superoperator*](https://en.wikipedia.org/wiki/Superoperator), that is an object that can act on operators like the state matrix. \n", + "\n", + "\n", + "\n", + "It turns out using a special matrix multiiplication identity we can write this as\n", + "$$ |\\rho'\\rangle \\rangle = \\mathcal U |\\rho\\rangle\\rangle,\n", + "$$\n", + "where $\\mathcal U = U^*\\otimes U$ and $|\\rho\\rangle\\rangle = {\\rm vec}(\\rho')$. The nice thing about this is it looks like the pure state case. This is be cause the operator (the state) has become a vector and the superoperator (the left right action of $U$) has become an operator. \n", + "\n", + "\n", + "\n", + "**More information** \n", + "Below we will assume that you are already an expert in these topics. If you are unfamilar with these topics we recomend the following references\n", + "- chapter 8 of [Mike_N_Ike] which is on *Quantum noise and quantum operations*. \n", + "- chapter 3 of John Preskill's lecture notes [Physics 219/Computer Science 219](http://www.theory.caltech.edu/people/preskill/ph219/chap3_15.pdf)\n", + "- the file in `/docs/Superoperator representations.md` \n", + "- for and intuitive but advanced treatment see [GRAPTN]\n", + "\n", + "\n", + "\n", + "[Mike_N_Ike] Quantum Computation and Quantum Information \n", + " Michael A. Nielsen & Isaac L. Chuang \n", + " Cambridge: Cambridge University Press (2000) \n", + "\n", + "\n", + "[GRAPTN] Tensor networks and graphical calculus for open quantum systems \n", + " Christopher Wood et al. \n", + " Quant. Inf. Comp. 15, 0579-0811 (2015) \n", + " (no DOI) \n", + " https://arxiv.org/abs/1111.6950 " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conversion between different descriptions of quantum channels\n", + "\n", + "We intentionally chose not to make quantum channels python objects with methods that would automatically transform between represtations. \n", + "\n", + "The functions to convert between different representations are called things like `kraus2chi`, `kraus2choi`, `pauli_liouville2choi` etc.\n", + "\n", + "This assumes the user does not do silly things like input a choi matrix to a function `chi2choi`." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", - "from forest.benchmarking.superoperator_tools import vec, unvec" + "from pyquil.gate_matrices import I, X, Y, Z, H, CNOT" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define some channels" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "def amplitude_damping_kraus(p):\n", + " Ad0 = np.asarray([[1, 0], [0, np.sqrt(1 - p)]])\n", + " Ad1 = np.asarray([[0, np.sqrt(p)], [0, 0]])\n", + " return [Ad0, Ad1]\n", + "\n", + "def bit_flip_kraus(p):\n", + " M0 = np.sqrt(1 - p) * I\n", + " M1 = np.sqrt(p) * X\n", + " return [M0, M1]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define some states" + ] + }, + { + "cell_type": "code", + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ - "I = np.asarray([[1, 0], [0, 1]])\n", - "X = np.asarray([[0, 1], [1, 0]])\n", - "Y = np.asarray([[0, -1.0j], [1.0j, 0]])\n", - "Z = np.asarray([[1, 0], [0, -1]])\n", - "H = np.asarray([[1, 1], [1, -1]]) / np.sqrt(2)\n", - "CNOT = np.array([[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]])\n", "one_state = np.asarray([[0,0],[0,1]])\n", - "zero_state = np.asarray([[1,0],[0,0]])" + "zero_state = np.asarray([[1,0],[0,0]])\n", + "rho_mixed = np.asarray([[0.9,0],[0,0.1]])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# vec and unvec " + "### vec and unvec \n", + "\n", + "We can vectorize i.e. `vec` and unvec matricies.\n", + "\n", + "We chose a column staking convention so that the matrix\n", + "$$\n", + "A = \\begin{pmatrix} 1 & 2\\\\ 3 & 4\\end{pmatrix}\n", + "$$\n", + "becomes\n", + "$$\n", + "|A\\rangle\\rangle = {\\rm vec}(A) = \\begin{pmatrix} 1\\\\ 3\\\\ 2\\\\ 4\\end{pmatrix}.\n", + "$$\n", + "\n", + "Let's check that" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ - "A = np.asarray([[1, 2], [3, 4]])" + "from forest.benchmarking.operator_tools import vec, unvec" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], + "source": [ + "A = np.asarray([[1, 2], [3, 4]])" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[1 2]\n", + " [3 4]]\n", + " \n", + "[[1]\n", + " [3]\n", + " [2]\n", + " [4]]\n", + " \n", + "Does the story check out? True\n" + ] + } + ], "source": [ "print(A)\n", "print(\" \")\n", "print(vec(A))\n", "print(\" \")\n", - "print(unvec(vec(A))==A)" + "print('Does the story check out? ', np.all(unvec(vec(A))==A))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# Apply Kraus operators to the excited state" + "### Kraus to $\\chi$ matrix (aka chi or process matrix)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ - "one_state = np.asarray([[0,0],[0,1]])\n", - "one = vec(one_state)\n", - "Ad0 = np.asarray([[1, 0], [0, np.sqrt(1-0.1)]])\n", - "Ad1 = np.asarray([[0, np.sqrt(0.1)], [0, 0]])\n", - "ko = [Ad0, Ad1]\n", - "print(ko)\n", - "# check kraus ops sum to ID\n", - "np.matmul(Ad0.transpose(), Ad0) + np.matmul(Ad1.transpose(), Ad1)" + "from forest.benchmarking.operator_tools import kraus2chi" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Use Kraus operators to find out put of channel i.e.\n", - "\n", - "$\\rho_{out} = A_0 \\rho A_0^\\dagger + A_1 \\rho A_1^\\dagger$\n", - "\n", - "Except we only transpose as these matrices have real entries" + "Lets do a unitary gate first, say the Hadamard" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The Kraus operator is:\n", + " [[ 0.707 0.707]\n", + " [ 0.707 -0.707]]\n", + "\n", + "\n", + "The Chi matrix is:\n", + " [[0. +0.j 0. +0.j 0. +0.j 0. +0.j]\n", + " [0. +0.j 0.5+0.j 0. +0.j 0.5+0.j]\n", + " [0. +0.j 0. +0.j 0. +0.j 0. +0.j]\n", + " [0. +0.j 0.5+0.j 0. +0.j 0.5+0.j]]\n" + ] + } + ], "source": [ - "rho_out = np.matmul(np.matmul(Ad0, one_state), Ad0.transpose()) \\\n", - " + np.matmul(np.matmul(Ad1, one_state), Ad1.transpose())\n", - "print(rho_out)" + "print('The Kraus operator is:\\n', np.round(H,3))\n", + "print('\\n')\n", + "print('The Chi matrix is:\\n', kraus2chi(H))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Kraus to superoperator" + "Now consider the Amplitude damping channel" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The Kraus operators are:\n", + " [[[1. 0. ]\n", + " [0. 0.949]]\n", + "\n", + " [[0. 0.316]\n", + " [0. 0. ]]]\n", + "\n", + "\n", + "The Chi matrix is:\n", + " [[0.949+0.j 0. +0.j 0. +0.j 0.025+0.j ]\n", + " [0. +0.j 0.025+0.j 0. -0.025j 0. +0.j ]\n", + " [0. +0.j 0. +0.025j 0.025+0.j 0. +0.j ]\n", + " [0.025+0.j 0. +0.j 0. +0.j 0.001+0.j ]]\n" + ] + } + ], + "source": [ + "AD_kraus = amplitude_damping_kraus(0.1)\n", + "\n", + "print('The Kraus operators are:\\n', np.round(AD_kraus,3))\n", + "print('\\n')\n", + "print('The Chi matrix is:\\n', np.round(kraus2chi(AD_kraus),3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Do the case of one Kraus operator i.e. a unitary." + "### Kraus to Pauli Liouville aka the \"Pauli Transfer Matrix\"" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ - "from forest.benchmarking.superoperator_tools import kraus2superop" + "from forest.benchmarking.operator_tools import kraus2pauli_liouville" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],\n", + " [ 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],\n", + " [ 0.+0.j, 0.+0.j, -1.+0.j, 0.+0.j],\n", + " [ 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j]])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Hpaulirep = kraus2pauli_liouville(H)\n", + "Hpaulirep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can visualize this using the tools from the plotting module." + ] + }, + { + "cell_type": "code", + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ + "from forest.benchmarking.plotting.state_process import plot_pauli_transfer_matrix\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "f, (ax1) = plt.subplots(1, 1, figsize=(5, 4.2))\n", + "\n", + "plot_pauli_transfer_matrix(Hpaulirep,ax=ax1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The above figure is a graphical representaiton of:\n", + " \n", + "(out operator) = H (in operator) H\n", + "\n", + "Z = H X H \n", + "-Y = H Y H \n", + "X = H Z H " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Evolving states using quantum channels\n", + "\n", + "In many in the superoperator representations evolution coresponds to mutiplying the vec'ed state by the superoperator. E.g." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The vec'ed answer is [[0.5+0.j]\n", + " [0.5+0.j]\n", + " [0.5+0.j]\n", + " [0.5+0.j]]\n", + "\n", + "\n", + "The unvec'ed answer is\n", + " [[0.5 0.5]\n", + " [0.5 0.5]]\n", + "\n", + "\n", + "Let's compare it to the normal calculation\n", + " [[0.5 0.5]\n", + " [0.5 0.5]]\n" + ] + } + ], + "source": [ + "from forest.benchmarking.superoperator_tools import kraus2superop\n", + "\n", "zero_state_vec = vec(zero_state)\n", - "print(zero_state)\n", - "print(\"\")\n", - "print(zero_state_vec)" + "\n", + "answer_vec = np.matmul(kraus2superop([H]), zero_state_vec)\n", + "\n", + "print('The vec\\'ed answer is', answer_vec)\n", + "print('\\n')\n", + "print('The unvec\\'ed answer is\\n', np.real(unvec(answer_vec)))\n", + "print('\\n')\n", + "print('Let\\'s compare it to the normal calculation\\n', H @ zero_state @ H)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For representations like this are no inbuilt functions in forest benchmarking. \n", + "\n", + "However it is more painful in the Choi and Kraus representation.\n", + "\n", + "Consider the amplitude damping channel where we need to perform the following calcualtion to find out put of channel \n", + "$\\rho_{out} = A_0 \\rho A_0^\\dagger + A_1 \\rho A_1^\\dagger.$\n", + "We provide helper functions to do these calculations." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ - "# Hadamard * |0>\n", - "unvec(np.matmul(kraus2superop([H]), zero_state_vec))" + "from forest.benchmarking.operator_tools import apply_kraus_ops_2_state, apply_choi_matrix_2_state, kraus2choi" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0.1, 0. ],\n", + " [0. , 0.9]])" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "apply_kraus_ops_2_state(AD_kraus, one_state)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Do the case of amplitude damping i.e. two Kraus operators." + "In the Choi representation we get the same answer:" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0.1, 0. ],\n", + " [0. , 0.9]])" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "# super op makes sense\n", - "one_state_vec = vec(one_state) \n", - "print(\"vec'ed version \\n\", np.matmul(kraus2superop(ko), one_state_vec))\n", - "print(\" \")\n", - "print(\"unvec'ed i.e. rho_out \\n\", unvec(np.matmul(kraus2superop(ko), one_state_vec)))" + "AD_choi = kraus2choi(AD_kraus)\n", + "\n", + "apply_choi_matrix_2_state(AD_choi, one_state)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Compose quantum channels\n", + "\n", + "Composing channels is useful when describing larger circuits. In some representations e.g. in the superoperator or Liouville representation it is just matrix multiplication e.g." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ - "print(kraus2superop([X]))" + "from forest.benchmarking.operator_tools import superop2kraus, kraus2superop" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hadamard squared as a superoperator:\n", + " [[ 1.+0.j -0.+0.j -0.+0.j 0.+0.j]\n", + " [-0.+0.j 1.+0.j 0.+0.j -0.+0.j]\n", + " [-0.+0.j 0.+0.j 1.+0.j -0.+0.j]\n", + " [ 0.+0.j -0.+0.j -0.+0.j 1.+0.j]]\n", + "\n", + " As a Kraus operator:\n", + " [[[ 1.+0.j -0.+0.j]\n", + " [ 0.+0.j 1.+0.j]]]\n" + ] + } + ], + "source": [ + "H_super = kraus2superop(H)\n", + "\n", + "H_squared_super = H_super @ H_super\n", + "\n", + "print('Hadamard squared as a superoperator:\\n', np.round(H_squared_super,2))\n", + "\n", + "print('\\n As a Kraus operator:\\n', np.round(superop2kraus(H_squared_super),2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# Kraus to Choi" + "Composing channels in the Kraus represntaion is more difficult. Consider composing two channels $\\mathcal A$ (with Kraus operators $[A_0, A_1]$) and $\\mathcal B$ (with Kraus operators $[B_0, B_1]$). The composition is \n", + "\n", + "$$\\begin{align}\n", + "\\mathcal B(\\mathcal A(\\rho)) & = \\sum_i \\sum_j B_j A_i \\rho A_i^\\dagger B_j^\\dagger \n", + "\\end{align}\n", + "$$" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ - "from scipy.linalg import expm\n", - "from forest.benchmarking.superoperator_tools import kraus2choi\n", - "from forest.benchmarking.utils import partial_trace" + "from forest.benchmarking.operator_tools import compose_channel_kraus, superop2kraus" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0.82 +0.j, 0. +0.j, 0. +0.j, 0.28 +0.j],\n", + " [0. +0.j, 0.75894664+0.j, 0.18973666+0.j, 0. +0.j],\n", + " [0. +0.j, 0.18973666+0.j, 0.75894664+0.j, 0. +0.j],\n", + " [0.18 +0.j, 0. +0.j, 0. +0.j, 0.72 +0.j]])" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "choi = kraus2choi(ko)\n", - "print(choi)" + "BitFlip_kraus = bit_flip_kraus(0.2)\n", + "\n", + "kraus2superop(compose_channel_kraus(AD_kraus, BitFlip_kraus))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is the same as if we do" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0.82 +0.j, 0. +0.j, 0. +0.j, 0.28 +0.j],\n", + " [0. +0.j, 0.75894664+0.j, 0.18973666+0.j, 0. +0.j],\n", + " [0. +0.j, 0.18973666+0.j, 0.75894664+0.j, 0. +0.j],\n", + " [0.18 +0.j, 0. +0.j, 0. +0.j, 0.72 +0.j]])" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "prody = np.matmul(choi, np.kron(one_state.transpose(),I) )\n", - "print(partial_trace(prody, [1], [2,2]))\n", - "print(\"\\n which is the same as rho_out\")\n" + "BitFlip_super = kraus2superop(BitFlip_kraus)\n", + "AD_super = kraus2superop(AD_kraus)\n", + "\n", + "AD_super @ BitFlip_super" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also compose independent channels easily\n", + "\n", + "Composing channels in the Kraus represntaion is more difficult. Consider composing the same two channels $\\mathcal A$ and $\\mathcal B$. However this time they act on different Hilbert spaces. With respect to the tensor product structure $H_2 \\otimes H_1$ the Kraus operators are $[A_0\\otimes I, A_1\\otimes I]$ and $[I \\otimes B_0, I \\otimes B_1]$.\n", + "\n", + "In this case the order of the operations commutes \n", + "$$\\begin{align}\n", + "\\mathcal A(\\mathcal B(\\rho))= \\mathcal B(\\mathcal A(\\rho)) & = \\sum_i \\sum_j A_i\\otimes B_j \\rho A_i^\\dagger\\otimes B_j^\\dagger \n", + "\\end{align}\n", + "$$\n", + "\n", + "In forest benchmarking you can specify the two channels without the Identity tensored on and it will take care of it for you:" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 23, "metadata": {}, "outputs": [], "source": [ - "choiCNOT = kraus2choi([CNOT])\n", - "print(choiCNOT)" + "from forest.benchmarking.operator_tools import tensor_channel_kraus" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[0.894, 0. , 0. , 0. ],\n", + " [0. , 0.894, 0. , 0. ],\n", + " [0. , 0. , 0.849, 0. ],\n", + " [0. , 0. , 0. , 0.849]],\n", + "\n", + " [[0. , 0. , 0.283, 0. ],\n", + " [0. , 0. , 0. , 0.283],\n", + " [0. , 0. , 0. , 0. ],\n", + " [0. , 0. , 0. , 0. ]],\n", + "\n", + " [[0. , 0.447, 0. , 0. ],\n", + " [0.447, 0. , 0. , 0. ],\n", + " [0. , 0. , 0. , 0.424],\n", + " [0. , 0. , 0.424, 0. ]],\n", + "\n", + " [[0. , 0. , 0. , 0.141],\n", + " [0. , 0. , 0.141, 0. ],\n", + " [0. , 0. , 0. , 0. ],\n", + " [0. , 0. , 0. , 0. ]]])" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.round(tensor_channel_kraus(AD_kraus,BitFlip_kraus),3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Validate quantum channels are physical\n", + "\n", + "When doing process tomography sometimes the estimates returned various estimation methods can result in unphysical processes.\n", + "\n", + "The functions below can be used to check if the estimates are physical.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As a starting point, we might want to check if a process, specified by Kraus operators is valid. \n", + "\n", + "Unless a process is unitary you need more than one Kraus operator to be a valid quantum operation." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from forest.benchmarking.operator_tools import kraus_operators_are_valid\n", + "\n", + "kraus_operators_are_valid(AD_kraus[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However a full set is valid:" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kraus_operators_are_valid(AD_kraus)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also validate other properties of quantum channels such as completely positivity and trace preservation. This is done on the **Choi** choi representation, so you many need to convert your quantum operation to the Choi representaiton.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 27, "metadata": {}, "outputs": [], "source": [ - "two_q_id = np.kron(I,I)\n", + "from forest.benchmarking.operator_tools import (choi_is_unitary, \n", + " choi_is_unital, \n", + " choi_is_trace_preserving, \n", + " choi_is_completely_positive, \n", + " choi_is_cptp)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "False \n", + "\n", + "False\n" + ] + } + ], + "source": [ + "# amplitude damping is not unitary\n", + "print(choi_is_unitary(AD_choi),'\\n')\n", "\n", - "print(\"Lets see if the CNOT action is correct\")\n", - "print(\" \")\n", + "# amplitude damping is not unital\n", + "print(choi_is_unital(AD_choi))" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True \n", + "\n", + "True \n", + "\n", + "True\n" + ] + } + ], + "source": [ + "# amplitude damping is trace preserving (TP)\n", + "print(choi_is_trace_preserving(AD_choi),'\\n')\n", + "\n", + "# amplitude damping is completely positive (CP)\n", + "print(choi_is_completely_positive(AD_choi), '\\n')\n", + "\n", + "# amplitude damping is CPTP\n", + "print(choi_is_cptp(AD_choi))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Project unphysical channels to physical channels\n", + "\n", + "When doing process tomography often the estimates returned by maximum likelihood estimation or linear inversion methods can result in unphysical processes.\n", "\n", - "print(\"one one state\")\n", - "input_state = np.kron(one_state, one_state)\n", - "prody = np.matmul(choiCNOT, np.kron(two_q_id, input_state.transpose()))\n", - "print(partial_trace(prody, [0,1], [2,2,2,2]))\n", + "The functions below can be used to project the unphysical estimates back to physical estimates." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [], + "source": [ + "from forest.benchmarking.operator_tools.project_superoperators import (proj_choi_to_completely_positive,\n", + " proj_choi_to_trace_non_increasing,\n", + " proj_choi_to_trace_preserving,\n", + " proj_choi_to_physical)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [], + "source": [ + "neg_Id_choi = -kraus2choi(I)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.]])" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "proj_choi_to_completely_positive(neg_Id_choi)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[-1., -0., -0., -1.],\n", + " [-0., -0., -0., -0.],\n", + " [-0., -0., -0., -0.],\n", + " [-1., -0., -0., -1.]])" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "proj_choi_to_trace_non_increasing(neg_Id_choi)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0., 0., -0., -1.],\n", + " [ 0., 1., -0., -0.],\n", + " [-0., -0., 1., 0.],\n", + " [-1., -0., 0., 0.]])" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "proj_choi_to_trace_preserving(neg_Id_choi)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0.33398437, 0. , 0. , -0.33203125],\n", + " [ 0. , 0.66601562, 0. , 0. ],\n", + " [ 0. , 0. , 0.66601562, 0. ],\n", + " [-0.33203125, 0. , 0. , 0.33398437]])" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "proj_choi_to_physical(neg_Id_choi)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Validate operators\n", "\n", - "print(\"one zero state\")\n", - "input_stated = np.kron(one_state, zero_state)\n", - "prody = np.matmul(choiCNOT, np.kron(two_q_id, input_stated.transpose()))\n", - "print(partial_trace(prody, [0,1], [2,2,2,2]))\n", + "A lot of the work in validating the physicality of quantum channels comes down to validating properties of matricies:" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [], + "source": [ + "from forest.benchmarking.operator_tools.validate_operator import (is_square_matrix, \n", + " is_identity_matrix, \n", + " is_idempotent_matrix, \n", + " is_unitary_matrix, \n", + " is_positive_semidefinite_matrix)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# a vector is not square\n", + "is_square_matrix(np.array([[1], [0]]))" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[[1. 1.]\n", + " [1. 1.]]\n", + "\n", + " [[1. 1.]\n", + " [1. 1.]]]\n" + ] + }, + { + "ename": "ValueError", + "evalue": "The object is not a matrix.", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtensor\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0mis_square_matrix\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtensor\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m~/forest-benchmarking/forest/benchmarking/operator_tools/validate_operator.py\u001b[0m in \u001b[0;36mis_square_matrix\u001b[0;34m(matrix)\u001b[0m\n\u001b[1;32m 14\u001b[0m \"\"\"\n\u001b[1;32m 15\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmatrix\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 16\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"The object is not a matrix.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 17\u001b[0m \u001b[0mrows\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcols\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmatrix\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrows\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mcols\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mValueError\u001b[0m: The object is not a matrix." + ] + } + ], + "source": [ + "# a tensor is not a matrix\n", "\n", - "print(\"zero one state\")\n", - "input_stated = np.kron(zero_state,one_state)\n", - "prody = np.matmul(choiCNOT, np.kron(two_q_id, input_stated.transpose()))\n", - "print(partial_trace(prody, [0,1], [2,2,2,2]))\n", + "tensor = np.ones(8).reshape(2,2,2)\n", + "print(tensor)\n", "\n", - "print(\"zero zero state\")\n", - "input_stated = np.kron(zero_state, zero_state)\n", - "prody = np.matmul(choiCNOT, np.kron(two_q_id, input_stated.transpose()))\n", - "print(partial_trace(prody, [0,1], [2,2,2,2]))\n", + "is_square_matrix(tensor)" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "is_identity_matrix(X)" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "projector_zero = np.array([[1, 0], [0, 0]])\n", "\n", - "print(\"\\n Story checks out.\")" + "is_idempotent_matrix(projector_zero)" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "is_unitary_matrix(AD_kraus[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "is_positive_semidefinite_matrix(I)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -281,7 +1164,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.3" + "version": "3.7.3" } }, "nbformat": 4, From 733227184c83d1f9eec43bf4194728e1b9e69fdf Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Thu, 13 Jun 2019 14:58:13 +1000 Subject: [PATCH 28/33] format improvments --- examples/random_operators.ipynb | 131 ++++++++++++++++---------------- 1 file changed, 67 insertions(+), 64 deletions(-) diff --git a/examples/random_operators.ipynb b/examples/random_operators.ipynb index 7b092d97..8d21e87c 100644 --- a/examples/random_operators.ipynb +++ b/examples/random_operators.ipynb @@ -55,8 +55,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "[[ 0.51783784+1.59579831j 0.22219084-0.08506946j]\n", - " [ 0.44832176+0.77277708j -0.68330944+0.43481616j]]\n", + "[[ 2.324+1.337j -0.897-0.495j]\n", + " [ 0.403-0.158j -0.118+1.419j]]\n", "Notice that the above matrix is not Hermitian.\n", "We can explicitly test if it is Hermitian = False\n" ] @@ -64,7 +64,7 @@ ], "source": [ "gini_2by2 = rand_ops.ginibre_matrix_complex(2,2)\n", - "print(gini_2by2)\n", + "print(np.round(gini_2by2,3))\n", "\n", "print('Notice that the above matrix is not Hermitian.')\n", "print('We can explicitly test if it is Hermitian = ', np.all(gini_2by2.T.conj()==gini_2by2))" @@ -88,8 +88,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "[[ 0.31784186+0.74235425j 0.04899764+0.58778052j]\n", - " [ 0.12099686-0.57727504j -0.40682771+0.69757043j]]\n" + "[[-0.34910551-0.81440349j 0.13456504-0.44358151j]\n", + " [-0.42579218-0.18323024j 0.39726838+0.79202622j]]\n" ] } ], @@ -114,10 +114,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "[[1.+0.j 0.-0.j]\n", - " [0.+0.j 1.-0.j]]\n", - "[[1.+0.e+00j 0.-1.e-16j]\n", - " [0.+1.e-16j 1.-0.e+00j]]\n" + "[[1.-0.j 0.+0.j]\n", + " [0.-0.j 1.-0.j]]\n", + "[[1.e+00-0.e+00j 1.e-16+1.e-16j]\n", + " [1.e-16-1.e-16j 1.e+00-0.e+00j]]\n" ] } ], @@ -159,29 +159,29 @@ "output_type": "stream", "text": [ "The state vector is \n", - " [[0.30195045-0.7933957j]\n", - " [0.20713655-0.4862547j]]\n", - "It has shape (2, 1) and purity P = (1-0j)\n", + " [[ 0.034-0.278j]\n", + " [-0.904-0.321j]]\n", + "It has shape (2, 1) and purity P = 1.0\n", "\n", "\n", - "Now lets look at a random pure state on two qubits\n", + "Now lets look at a random pure state on two qubits.\n", "The state vector is \n", - " [[ 0.19914674+0.28997926j]\n", - " [-0.68868807+0.37357005j]\n", - " [ 0.48884613-0.10562828j]\n", - " [ 0.06850955+0.08709377j]]\n", + " [[0.306+0.687j]\n", + " [0.069+0.004j]\n", + " [0.21 -0.111j]\n", + " [0.149-0.592j]]\n", "It has shape (4, 1) .\n" ] } ], "source": [ "psi2 = rand_ops.haar_rand_state(2)\n", - "print('The state vector is \\n', psi2, )\n", - "print('It has shape', psi2.shape,'and purity P = ', np.round(np.trace(psi2@psi2.T.conj()@psi2@psi2.T.conj()),2))\n", + "print('The state vector is \\n', np.round(psi2,3))\n", + "print('It has shape', psi2.shape,'and purity P = ', np.real(np.round(np.trace(psi2@psi2.T.conj()@psi2@psi2.T.conj()),2)))\n", "print('\\n')\n", - "print('Now lets look at a random pure state on two qubits')\n", + "print('Now lets look at a random pure state on two qubits.')\n", "psi4 = rand_ops.haar_rand_state(4)\n", - "print('The state vector is \\n', psi4, )\n", + "print('The state vector is \\n', np.round(psi4,3), )\n", "print('It has shape', psi4.shape,'.')" ] }, @@ -233,7 +233,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -241,27 +241,27 @@ "output_type": "stream", "text": [ "This is a mixed single qubit state drawn from Hilbert-Schmidt measure (as D=K)\n", - "[[0.2243-0.j 0.1617+0.0408j]\n", - " [0.1617-0.0408j 0.7757+0.j ]]\n", + "[[0.438-0.j 0.158+0.055j]\n", + " [0.158-0.055j 0.562+0.j ]]\n", "\n", "\n", "This is a mixed two qubit state with rank 2:\n", - "[[ 0.3435+0.j 0.0503+0.1182j -0.007 -0.0502j -0.1974+0.0158j]\n", - " [ 0.0503-0.1182j 0.3327-0.j 0.003 +0.1373j 0.14 -0.0304j]\n", - " [-0.007 +0.0502j 0.003 -0.1373j 0.0802-0.j -0.0364-0.1184j]\n", - " [-0.1974-0.0158j 0.14 +0.0304j -0.0364+0.1184j 0.2436-0.j ]]\n", + "[[ 0.42 +0.j -0.303-0.242j 0.222-0.005j 0.059-0.135j]\n", + " [-0.303+0.242j 0.369-0.j -0.147+0.113j 0.033+0.135j]\n", + " [ 0.222+0.005j -0.147-0.113j 0.158-0.j 0.024-0.072j]\n", + " [ 0.059+0.135j 0.033-0.135j 0.024+0.072j 0.053-0.j ]]\n", "\n", "\n", - "Here are the eigenvalues: [0. +0.j 0.337-0.j 0.663-0.j 0. -0.j] . You can see only two are non zero.\n" + "Here are the eigenvalues: [ 0. +0.j 0.269-0.j 0.731-0.j -0. +0.j] . You can see only two are non zero.\n" ] } ], "source": [ "print('This is a mixed single qubit state drawn from Hilbert-Schmidt measure (as D=K)')\n", - "print(np.around(rand_ops.ginibre_state_matrix(2,2),4))\n", + "print(np.around(rand_ops.ginibre_state_matrix(2,2),3))\n", "print(\"\\n\")\n", "print('This is a mixed two qubit state with rank 2:')\n", - "print(np.around(rand_ops.ginibre_state_matrix(4,2),4))\n", + "print(np.around(rand_ops.ginibre_state_matrix(4,2),3))\n", "evals, evec = np.linalg.eig(rand_ops.ginibre_state_matrix(4,2))\n", "print('\\n')\n", "print('Here are the eigenvalues:', np.round(evals,3),'. You can see only two are non zero.')" @@ -276,23 +276,23 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "array([[ 0.41135882+0.j , -0.12971395+0.20328594j],\n", - " [-0.12971395-0.20328594j, 0.58864118+0.j ]])" + "array([[0.2619+0.j , 0.3212+0.0241j],\n", + " [0.3212-0.0241j, 0.7381-0.j ]])" ] }, - "execution_count": 11, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "rand_ops.bures_measure_state_matrix(2)" + "np.round(rand_ops.bures_measure_state_matrix(2),4)" ] }, { @@ -313,7 +313,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -326,17 +326,13 @@ { "data": { "text/plain": [ - "array([[ 0.46089813+3.46944695e-18j, 0.27874737-3.47538997e-01j,\n", - " 0.07274096-1.05089882e-01j, -0.03975122-5.30473244e-02j],\n", - " [ 0.27874737+3.47538997e-01j, 0.53910187-2.60208521e-18j,\n", - " 0.23673647-2.42995364e-01j, -0.07274096+1.05089882e-01j],\n", - " [ 0.07274096+1.05089882e-01j, 0.23673647+2.42995364e-01j,\n", - " 0.66032833+1.38777878e-17j, -0.44807263-3.41252393e-02j],\n", - " [-0.03975122+5.30473244e-02j, -0.07274096-1.05089882e-01j,\n", - " -0.44807263+3.41252393e-02j, 0.33967167-1.73472348e-18j]])" + "array([[ 0.296+0.j , -0.043-0.412j, 0.165+0.108j, -0.146-0.019j],\n", + " [-0.043+0.412j, 0.704-0.j , 0.031+0.169j, -0.165-0.108j],\n", + " [ 0.165-0.108j, 0.031-0.169j, 0.49 -0.j , -0.477+0.12j ],\n", + " [-0.146+0.019j, -0.165+0.108j, -0.477-0.12j , 0.51 -0.j ]])" ] }, - "execution_count": 12, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -344,12 +340,12 @@ "source": [ "rand_choi = rand_ops.rand_map_with_BCSZ_dist(2,2)\n", "print('Here is a random quantum channel on one qubit in Choi form:')\n", - "rand_choi" + "np.round(rand_choi,3)" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -358,7 +354,7 @@ "(4, 4)" ] }, - "execution_count": 13, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -376,7 +372,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -385,11 +381,11 @@ "text": [ "We can convert this channel to Kraus form and enumerate the Kraus operators.\n", "Kraus OP #1 is: \n", - " [[0.53125657+0.j 0.03521529-0.36863168j]\n", - " [0.12883999+0.32986757j 0.00258408+0.41559292j]]\n", + " [[ 0.326+0.j -0.278-0.253j]\n", + " [-0.328+0.435j 0.308+0.265j]]\n", "Kraus OP #2 is: \n", - " [[-0.42268734-0.j -0.12783115-0.71193967j]\n", - " [-0.49753153-0.40761732j 0.09729184+0.39683978j]]\n" + " [[-0.435-0.j -0.588+0.059j]\n", + " [-0.147-0.621j 0.566+0.154j]]\n" ] } ], @@ -398,17 +394,17 @@ "print('We can convert this channel to Kraus form and enumerate the Kraus operators.')\n", "\n", "for idx, kraus_op in enumerate(sot.choi2kraus(rand_choi)):\n", - " print('Kraus OP #'+str(1+idx)+' is: \\n', kraus_op)" + " print('Kraus OP #'+str(1+idx)+' is: \\n', np.round(kraus_op,3))" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -442,7 +438,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -464,7 +460,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -482,7 +478,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -503,7 +499,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -523,7 +519,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ @@ -539,7 +535,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -548,7 +544,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "metadata": {}, "outputs": [ { @@ -562,6 +558,13 @@ "source": [ "print('The inner product between the calculated and true answer is one', np.matmul(perm_vector.T,answer))" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { From 047a9db6b24349145ff8da675d14e643a2f051f1 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Thu, 13 Jun 2019 14:58:25 +1000 Subject: [PATCH 29/33] update --- forest/benchmarking/operator_tools/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/forest/benchmarking/operator_tools/__init__.py b/forest/benchmarking/operator_tools/__init__.py index 6094e682..f576bcb4 100644 --- a/forest/benchmarking/operator_tools/__init__.py +++ b/forest/benchmarking/operator_tools/__init__.py @@ -1,5 +1,6 @@ from .apply_superoperator import * from .channel_approximation import * +from .compose_superoperators import * from .project_superoperators import * from .superoperator_transformations import * from .random_operators import * From 9d9c2b9fb4ff7e4a981aa1644b0bbc1777b5234f Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Thu, 13 Jun 2019 15:29:24 +1000 Subject: [PATCH 30/33] to fix the failed test add # NBVAL_IGNORE_OUTPUT --- examples/superoperator_tools.ipynb | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/examples/superoperator_tools.ipynb b/examples/superoperator_tools.ipynb index 044e36ba..5f257511 100644 --- a/examples/superoperator_tools.ipynb +++ b/examples/superoperator_tools.ipynb @@ -1022,34 +1022,25 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 1, "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[[1. 1.]\n", - " [1. 1.]]\n", - "\n", - " [[1. 1.]\n", - " [1. 1.]]]\n" - ] - }, - { - "ename": "ValueError", - "evalue": "The object is not a matrix.", + "ename": "NameError", + "evalue": "name 'np' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtensor\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0mis_square_matrix\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtensor\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m~/forest-benchmarking/forest/benchmarking/operator_tools/validate_operator.py\u001b[0m in \u001b[0;36mis_square_matrix\u001b[0;34m(matrix)\u001b[0m\n\u001b[1;32m 14\u001b[0m \"\"\"\n\u001b[1;32m 15\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmatrix\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 16\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"The object is not a matrix.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 17\u001b[0m \u001b[0mrows\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcols\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmatrix\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrows\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mcols\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mValueError\u001b[0m: The object is not a matrix." + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;31m# a tensor is not a matrix\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0mtensor\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mones\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m8\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreshape\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 7\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtensor\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mNameError\u001b[0m: name 'np' is not defined" ] } ], "source": [ + "# NBVAL_IGNORE_OUTPUT\n", + "# the line above is for testing purposes, do not remove.\n", + "\n", "# a tensor is not a matrix\n", "\n", "tensor = np.ones(8).reshape(2,2,2)\n", From 7b8aec3577ea9a4240fde977636d51ccbabcd735 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Thu, 13 Jun 2019 16:20:26 +1000 Subject: [PATCH 31/33] it should have been # NBVAL_RAISES_EXCEPTION --- examples/superoperator_tools.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/superoperator_tools.ipynb b/examples/superoperator_tools.ipynb index 5f257511..a2794dd4 100644 --- a/examples/superoperator_tools.ipynb +++ b/examples/superoperator_tools.ipynb @@ -1038,7 +1038,7 @@ } ], "source": [ - "# NBVAL_IGNORE_OUTPUT\n", + "# NBVAL_RAISES_EXCEPTION\n", "# the line above is for testing purposes, do not remove.\n", "\n", "# a tensor is not a matrix\n", From 38280f74c4ef2f1eb5159daf61a6504008e8366c Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 14 Jun 2019 15:36:15 -0400 Subject: [PATCH 32/33] Typo fixing. --- examples/random_operators.ipynb | 260 +++--------- examples/superoperator_tools.ipynb | 608 ++++++----------------------- 2 files changed, 158 insertions(+), 710 deletions(-) diff --git a/examples/random_operators.ipynb b/examples/random_operators.ipynb index 8d21e87c..f2df678c 100644 --- a/examples/random_operators.ipynb +++ b/examples/random_operators.ipynb @@ -13,12 +13,12 @@ "source": [ "In this notebook we explore a submodule of `operator_tools` called `random_operators`.\n", "\n", - "In the context of forest benchmarking the primary use of random operators is to test the estimation routines. For example you might modify a existing state or process tomography routine (or develop a new method) and want to test that your modification works. One way to do that would be to test it on a bunch of random quantum states or channels. " + "In the context of forest benchmarking the primary use of random operators is to test the estimation routines. For example you might modify an existing state or process tomography routine (or develop a new method) and want to test that your modification works. One way to do that would be to test it on a bunch of random quantum states or channels. " ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -39,7 +39,7 @@ "source": [ "### Complex Ginibre ensemble\n", "\n", - "This is a subroutine for other methods in the module. The complex Ginibre ensemble is a random matrix where the real and imaginary parts of each entry, $G(n,m)$, are drawn in and IID fashion from $\\mathcal N(0,1)$ e.g.\n", + "This is a subroutine for other methods in the module. The complex Ginibre ensemble is a random matrix where the real and imaginary parts of each entry, $G(n,m)$, are drawn in an IID fashion from $\\mathcal N(0,1)$ e.g.\n", "\n", "$$G(n,m) = X(n,m) + i Y(n,m)$$\n", "\n", @@ -48,26 +48,15 @@ }, { "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[ 2.324+1.337j -0.897-0.495j]\n", - " [ 0.403-0.158j -0.118+1.419j]]\n", - "Notice that the above matrix is not Hermitian.\n", - "We can explicitly test if it is Hermitian = False\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "gini_2by2 = rand_ops.ginibre_matrix_complex(2,2)\n", "print(np.round(gini_2by2,3))\n", "\n", "print('Notice that the above matrix is not Hermitian.')\n", - "print('We can explicitly test if it is Hermitian = ', np.all(gini_2by2.T.conj()==gini_2by2))" + "print('We can explicitly test if it is Hermitian: ', np.all(gini_2by2.T.conj()==gini_2by2))" ] }, { @@ -81,18 +70,9 @@ }, { "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[-0.34910551-0.81440349j 0.13456504-0.44358151j]\n", - " [-0.42579218-0.18323024j 0.39726838+0.79202622j]]\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "U = rand_ops.haar_rand_unitary(2)\n", "print(U)" @@ -102,25 +82,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can test to see how unitary it is:" + "We can test to see how close to unitary it is:" ] }, { "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[1.-0.j 0.+0.j]\n", - " [0.-0.j 1.-0.j]]\n", - "[[1.e+00-0.e+00j 1.e-16+1.e-16j]\n", - " [1.e-16-1.e-16j 1.e+00-0.e+00j]]\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "print(np.around(U.dot(np.transpose(np.conj(U))),decimals=15))\n", "print(np.around(U.dot(np.transpose(np.conj(U))),decimals=16))" @@ -151,29 +120,9 @@ }, { "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The state vector is \n", - " [[ 0.034-0.278j]\n", - " [-0.904-0.321j]]\n", - "It has shape (2, 1) and purity P = 1.0\n", - "\n", - "\n", - "Now lets look at a random pure state on two qubits.\n", - "The state vector is \n", - " [[0.306+0.687j]\n", - " [0.069+0.004j]\n", - " [0.21 -0.111j]\n", - " [0.149-0.592j]]\n", - "It has shape (4, 1) .\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "psi2 = rand_ops.haar_rand_state(2)\n", "print('The state vector is \\n', np.round(psi2,3))\n", @@ -194,7 +143,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -226,36 +175,16 @@ "This function lets us generate mixed states with a specific rank.\n", "\n", "Specifically, given a Hilbert space dimension $D$ and a desired rank $K$, this function \n", - "a D by D positive semidefinite matrix of rank $K$ drawn from the Ginibre ensemble. \n", + "gives a D by D positive semidefinite matrix of rank $K$ drawn from the Ginibre ensemble. \n", " \n", "For $D = K$ these are states drawn from the **Hilbert-Schmidt measure**." ] }, { "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "This is a mixed single qubit state drawn from Hilbert-Schmidt measure (as D=K)\n", - "[[0.438-0.j 0.158+0.055j]\n", - " [0.158-0.055j 0.562+0.j ]]\n", - "\n", - "\n", - "This is a mixed two qubit state with rank 2:\n", - "[[ 0.42 +0.j -0.303-0.242j 0.222-0.005j 0.059-0.135j]\n", - " [-0.303+0.242j 0.369-0.j -0.147+0.113j 0.033+0.135j]\n", - " [ 0.222+0.005j -0.147-0.113j 0.158-0.j 0.024-0.072j]\n", - " [ 0.059+0.135j 0.033-0.135j 0.024+0.072j 0.053-0.j ]]\n", - "\n", - "\n", - "Here are the eigenvalues: [ 0. +0.j 0.269-0.j 0.731-0.j -0. +0.j] . You can see only two are non zero.\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "print('This is a mixed single qubit state drawn from Hilbert-Schmidt measure (as D=K)')\n", "print(np.around(rand_ops.ginibre_state_matrix(2,2),3))\n", @@ -276,21 +205,9 @@ }, { "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[0.2619+0.j , 0.3212+0.0241j],\n", - " [0.3212-0.0241j, 0.7381-0.j ]])" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "np.round(rand_ops.bures_measure_state_matrix(2),4)" ] @@ -313,30 +230,9 @@ }, { "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Here is a random quantum channel on one qubit in Choi form:\n" - ] - }, - { - "data": { - "text/plain": [ - "array([[ 0.296+0.j , -0.043-0.412j, 0.165+0.108j, -0.146-0.019j],\n", - " [-0.043+0.412j, 0.704-0.j , 0.031+0.169j, -0.165-0.108j],\n", - " [ 0.165-0.108j, 0.031-0.169j, 0.49 -0.j , -0.477+0.12j ],\n", - " [-0.146+0.019j, -0.165+0.108j, -0.477-0.12j , 0.51 -0.j ]])" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "rand_choi = rand_ops.rand_map_with_BCSZ_dist(2,2)\n", "print('Here is a random quantum channel on one qubit in Choi form:')\n", @@ -345,20 +241,9 @@ }, { "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(4, 4)" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "rand_choi.shape" ] @@ -372,26 +257,12 @@ }, { "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "We can convert this channel to Kraus form and enumerate the Kraus operators.\n", - "Kraus OP #1 is: \n", - " [[ 0.326+0.j -0.278-0.253j]\n", - " [-0.328+0.435j 0.308+0.265j]]\n", - "Kraus OP #2 is: \n", - " [[-0.435-0.j -0.588+0.059j]\n", - " [-0.147-0.621j 0.566+0.154j]]\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "import forest.benchmarking.operator_tools.superoperator_transformations as sot\n", - "print('We can convert this channel to Kraus form and enumerate the Kraus operators.')\n", + "print('We can convert this channel to Kraus form and enumerate the Kraus operators. We expect there to be two Kraus ops, consistent with the rank we specified.')\n", "\n", "for idx, kraus_op in enumerate(sot.choi2kraus(rand_choi)):\n", " print('Kraus OP #'+str(1+idx)+' is: \\n', np.round(kraus_op,3))" @@ -399,26 +270,12 @@ }, { "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "rand_pl = sot.choi2pauli_liouville(rand_choi)\n", "\n", - "\n", "from forest.benchmarking.plotting.state_process import plot_pauli_transfer_matrix\n", "n_qubits = 1\n", "pl_basis_oneq = n_qubit_pauli_basis(n_qubits)\n", @@ -438,7 +295,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -460,7 +317,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -478,7 +335,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -499,7 +356,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -519,7 +376,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -535,7 +392,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -544,17 +401,9 @@ }, { "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The inner product between the calculated and true answer is one [[1.]]\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "print('The inner product between the calculated and true answer is one', np.matmul(perm_vector.T,answer))" ] @@ -568,22 +417,9 @@ } ], "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.3" + "pygments_lexer": "ipython3" } }, "nbformat": 4, diff --git a/examples/superoperator_tools.ipynb b/examples/superoperator_tools.ipynb index a2794dd4..9d965d75 100644 --- a/examples/superoperator_tools.ipynb +++ b/examples/superoperator_tools.ipynb @@ -6,11 +6,11 @@ "source": [ "# Superoperator tools\n", "\n", - "In this notebook we explore a the submodules of `operator_tools` that enable easy manipulation of the various quantum channel representations.\n", + "In this notebook we explore the submodules of `operator_tools` that enable easy manipulation of the various quantum channel representations.\n", "\n", "To summarize the functionality:\n", "- vectorization and conversions between different repesentations of quantum channels\n", - "- apply quantm operations\n", + "- apply quantum operations\n", "- compose quantum operations\n", "- validate that quantum channels are physical\n", "- project unphysical channels to physical channels" @@ -27,9 +27,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Perfect the gates of **reversible classical computation** are described by permutation matricies, e.g. the [Toffoli gate](https://en.wikipedia.org/wiki/Toffoli_gate), while the input states are vectors. Noisy classical gates could be modeled as a perfect gate followed by a noise channel, e.g. [binary symmetric channel](https://en.wikipedia.org/wiki/Binary_symmetric_channel), on all the bits.\n", + "Perfect gates in **reversible classical computation** are described by permutation matricies, e.g. the [Toffoli gate](https://en.wikipedia.org/wiki/Toffoli_gate), while the input states are vectors. A noisy classical gate could be modeled as a perfect gate followed by a noise channel, e.g. [binary symmetric channel](https://en.wikipedia.org/wiki/Binary_symmetric_channel), on all the bits in the state vector.\n", "\n", - "Perfect gates in **quantum computation** are described by unitary matricies and states are described by vectors, e.g.\n", + "Perfect gates in **quantum computation** are described by unitary matricies and states are described by complex vectors, e.g.\n", "$$\n", "|\\psi\\rangle = U |\\psi_0\\rangle.\n", "$$\n", @@ -47,7 +47,7 @@ "It turns out using a special matrix multiiplication identity we can write this as\n", "$$ |\\rho'\\rangle \\rangle = \\mathcal U |\\rho\\rangle\\rangle,\n", "$$\n", - "where $\\mathcal U = U^*\\otimes U$ and $|\\rho\\rangle\\rangle = {\\rm vec}(\\rho')$. The nice thing about this is it looks like the pure state case. This is be cause the operator (the state) has become a vector and the superoperator (the left right action of $U$) has become an operator. \n", + "where $\\mathcal U = U^*\\otimes U$ and $|\\rho\\rangle\\rangle = {\\rm vec}(\\rho)$. The nice thing about this is it looks like the pure state case. This is because the operator (the state) has become a vector and the superoperator (the left right action of $U$) has become an operator. \n", "\n", "\n", "\n", @@ -56,7 +56,7 @@ "- chapter 8 of [Mike_N_Ike] which is on *Quantum noise and quantum operations*. \n", "- chapter 3 of John Preskill's lecture notes [Physics 219/Computer Science 219](http://www.theory.caltech.edu/people/preskill/ph219/chap3_15.pdf)\n", "- the file in `/docs/Superoperator representations.md` \n", - "- for and intuitive but advanced treatment see [GRAPTN]\n", + "- for an intuitive but advanced treatment see [GRAPTN]\n", "\n", "\n", "\n", @@ -87,7 +87,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -104,7 +104,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -128,7 +128,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -145,7 +145,7 @@ "\n", "We can vectorize i.e. `vec` and unvec matricies.\n", "\n", - "We chose a column staking convention so that the matrix\n", + "We chose a column stacking convention so that the matrix\n", "$$\n", "A = \\begin{pmatrix} 1 & 2\\\\ 3 & 4\\end{pmatrix}\n", "$$\n", @@ -159,7 +159,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -168,7 +168,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -177,25 +177,9 @@ }, { "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[1 2]\n", - " [3 4]]\n", - " \n", - "[[1]\n", - " [3]\n", - " [2]\n", - " [4]]\n", - " \n", - "Does the story check out? True\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "print(A)\n", "print(\" \")\n", @@ -213,7 +197,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -229,26 +213,9 @@ }, { "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The Kraus operator is:\n", - " [[ 0.707 0.707]\n", - " [ 0.707 -0.707]]\n", - "\n", - "\n", - "The Chi matrix is:\n", - " [[0. +0.j 0. +0.j 0. +0.j 0. +0.j]\n", - " [0. +0.j 0.5+0.j 0. +0.j 0.5+0.j]\n", - " [0. +0.j 0. +0.j 0. +0.j 0. +0.j]\n", - " [0. +0.j 0.5+0.j 0. +0.j 0.5+0.j]]\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "print('The Kraus operator is:\\n', np.round(H,3))\n", "print('\\n')\n", @@ -264,29 +231,9 @@ }, { "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The Kraus operators are:\n", - " [[[1. 0. ]\n", - " [0. 0.949]]\n", - "\n", - " [[0. 0.316]\n", - " [0. 0. ]]]\n", - "\n", - "\n", - "The Chi matrix is:\n", - " [[0.949+0.j 0. +0.j 0. +0.j 0.025+0.j ]\n", - " [0. +0.j 0.025+0.j 0. -0.025j 0. +0.j ]\n", - " [0. +0.j 0. +0.025j 0.025+0.j 0. +0.j ]\n", - " [0.025+0.j 0. +0.j 0. +0.j 0.001+0.j ]]\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "AD_kraus = amplitude_damping_kraus(0.1)\n", "\n", @@ -304,7 +251,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -313,23 +260,9 @@ }, { "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[ 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],\n", - " [ 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],\n", - " [ 0.+0.j, 0.+0.j, -1.+0.j, 0.+0.j],\n", - " [ 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j]])" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "Hpaulirep = kraus2pauli_liouville(H)\n", "Hpaulirep" @@ -344,7 +277,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -354,32 +287,9 @@ }, { "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "f, (ax1) = plt.subplots(1, 1, figsize=(5, 4.2))\n", "\n", @@ -405,35 +315,14 @@ "source": [ "### Evolving states using quantum channels\n", "\n", - "In many in the superoperator representations evolution coresponds to mutiplying the vec'ed state by the superoperator. E.g." + "In many superoperator representations evolution coresponds to mutiplying the vec'ed state by the superoperator. E.g." ] }, { "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The vec'ed answer is [[0.5+0.j]\n", - " [0.5+0.j]\n", - " [0.5+0.j]\n", - " [0.5+0.j]]\n", - "\n", - "\n", - "The unvec'ed answer is\n", - " [[0.5 0.5]\n", - " [0.5 0.5]]\n", - "\n", - "\n", - "Let's compare it to the normal calculation\n", - " [[0.5 0.5]\n", - " [0.5 0.5]]\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "from forest.benchmarking.superoperator_tools import kraus2superop\n", "\n", @@ -452,9 +341,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "For representations like this are no inbuilt functions in forest benchmarking. \n", + "For representations with this simple application there are no inbuilt functions in forest benchmarking. \n", "\n", - "However it is more painful in the Choi and Kraus representation.\n", + "However applying a channel is more painful in the Choi and Kraus representation.\n", "\n", "Consider the amplitude damping channel where we need to perform the following calcualtion to find out put of channel \n", "$\\rho_{out} = A_0 \\rho A_0^\\dagger + A_1 \\rho A_1^\\dagger.$\n", @@ -463,7 +352,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -472,21 +361,9 @@ }, { "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[0.1, 0. ],\n", - " [0. , 0.9]])" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "apply_kraus_ops_2_state(AD_kraus, one_state)" ] @@ -500,21 +377,9 @@ }, { "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[0.1, 0. ],\n", - " [0. , 0.9]])" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "AD_choi = kraus2choi(AD_kraus)\n", "\n", @@ -532,7 +397,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -541,25 +406,9 @@ }, { "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Hadamard squared as a superoperator:\n", - " [[ 1.+0.j -0.+0.j -0.+0.j 0.+0.j]\n", - " [-0.+0.j 1.+0.j 0.+0.j -0.+0.j]\n", - " [-0.+0.j 0.+0.j 1.+0.j -0.+0.j]\n", - " [ 0.+0.j -0.+0.j -0.+0.j 1.+0.j]]\n", - "\n", - " As a Kraus operator:\n", - " [[[ 1.+0.j -0.+0.j]\n", - " [ 0.+0.j 1.+0.j]]]\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "H_super = kraus2superop(H)\n", "\n", @@ -584,7 +433,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -593,23 +442,9 @@ }, { "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[0.82 +0.j, 0. +0.j, 0. +0.j, 0.28 +0.j],\n", - " [0. +0.j, 0.75894664+0.j, 0.18973666+0.j, 0. +0.j],\n", - " [0. +0.j, 0.18973666+0.j, 0.75894664+0.j, 0. +0.j],\n", - " [0.18 +0.j, 0. +0.j, 0. +0.j, 0.72 +0.j]])" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "BitFlip_kraus = bit_flip_kraus(0.2)\n", "\n", @@ -625,23 +460,9 @@ }, { "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[0.82 +0.j, 0. +0.j, 0. +0.j, 0.28 +0.j],\n", - " [0. +0.j, 0.75894664+0.j, 0.18973666+0.j, 0. +0.j],\n", - " [0. +0.j, 0.18973666+0.j, 0.75894664+0.j, 0. +0.j],\n", - " [0.18 +0.j, 0. +0.j, 0. +0.j, 0.72 +0.j]])" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "BitFlip_super = kraus2superop(BitFlip_kraus)\n", "AD_super = kraus2superop(AD_kraus)\n", @@ -653,9 +474,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can also compose independent channels easily\n", + "We can also easily compose channels acting on independent spaces.\n", "\n", - "Composing channels in the Kraus represntaion is more difficult. Consider composing the same two channels $\\mathcal A$ and $\\mathcal B$. However this time they act on different Hilbert spaces. With respect to the tensor product structure $H_2 \\otimes H_1$ the Kraus operators are $[A_0\\otimes I, A_1\\otimes I]$ and $[I \\otimes B_0, I \\otimes B_1]$.\n", + "Consider composing the same two channels as above, $\\mathcal A$ and $\\mathcal B$. However this time they act on different Hilbert spaces. With respect to the tensor product structure $H_2 \\otimes H_1$ the Kraus operators are $[A_0\\otimes I, A_1\\otimes I]$ and $[I \\otimes B_0, I \\otimes B_1]$.\n", "\n", "In this case the order of the operations commutes \n", "$$\\begin{align}\n", @@ -668,7 +489,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -677,38 +498,9 @@ }, { "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[[0.894, 0. , 0. , 0. ],\n", - " [0. , 0.894, 0. , 0. ],\n", - " [0. , 0. , 0.849, 0. ],\n", - " [0. , 0. , 0. , 0.849]],\n", - "\n", - " [[0. , 0. , 0.283, 0. ],\n", - " [0. , 0. , 0. , 0.283],\n", - " [0. , 0. , 0. , 0. ],\n", - " [0. , 0. , 0. , 0. ]],\n", - "\n", - " [[0. , 0.447, 0. , 0. ],\n", - " [0.447, 0. , 0. , 0. ],\n", - " [0. , 0. , 0. , 0.424],\n", - " [0. , 0. , 0.424, 0. ]],\n", - "\n", - " [[0. , 0. , 0. , 0.141],\n", - " [0. , 0. , 0.141, 0. ],\n", - " [0. , 0. , 0. , 0. ],\n", - " [0. , 0. , 0. , 0. ]]])" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "np.round(tensor_channel_kraus(AD_kraus,BitFlip_kraus),3)" ] @@ -719,7 +511,7 @@ "source": [ "### Validate quantum channels are physical\n", "\n", - "When doing process tomography sometimes the estimates returned various estimation methods can result in unphysical processes.\n", + "When doing process tomography sometimes the estimates returned by various estimation methods can result in unphysical processes.\n", "\n", "The functions below can be used to check if the estimates are physical.\n" ] @@ -728,27 +520,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As a starting point, we might want to check if a process, specified by Kraus operators is valid. \n", + "As a starting point, we might want to check if a process specified by Kraus operators is valid. \n", "\n", "Unless a process is unitary you need more than one Kraus operator to be a valid quantum operation." ] }, { "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "False" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "from forest.benchmarking.operator_tools import kraus_operators_are_valid\n", "\n", @@ -764,20 +545,9 @@ }, { "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "kraus_operators_are_valid(AD_kraus)" ] @@ -786,12 +556,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can also validate other properties of quantum channels such as completely positivity and trace preservation. This is done on the **Choi** choi representation, so you many need to convert your quantum operation to the Choi representaiton.\n" + "We can also validate other properties of quantum channels such as completely positivity and trace preservation. This is done on the **Choi** representation, so you many need to convert your quantum operation to the Choi representaiton first.\n" ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -804,19 +574,9 @@ }, { "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "False \n", - "\n", - "False\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "# amplitude damping is not unitary\n", "print(choi_is_unitary(AD_choi),'\\n')\n", @@ -827,21 +587,9 @@ }, { "cell_type": "code", - "execution_count": 29, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "True \n", - "\n", - "True \n", - "\n", - "True\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "# amplitude damping is trace preserving (TP)\n", "print(choi_is_trace_preserving(AD_choi),'\\n')\n", @@ -866,7 +614,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -878,7 +626,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -887,92 +635,36 @@ }, { "cell_type": "code", - "execution_count": 32, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.]])" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "proj_choi_to_completely_positive(neg_Id_choi)" ] }, { "cell_type": "code", - "execution_count": 33, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[-1., -0., -0., -1.],\n", - " [-0., -0., -0., -0.],\n", - " [-0., -0., -0., -0.],\n", - " [-1., -0., -0., -1.]])" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "proj_choi_to_trace_non_increasing(neg_Id_choi)" ] }, { "cell_type": "code", - "execution_count": 34, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[ 0., 0., -0., -1.],\n", - " [ 0., 1., -0., -0.],\n", - " [-0., -0., 1., 0.],\n", - " [-1., -0., 0., 0.]])" - ] - }, - "execution_count": 34, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "proj_choi_to_trace_preserving(neg_Id_choi)" ] }, { "cell_type": "code", - "execution_count": 35, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[ 0.33398437, 0. , 0. , -0.33203125],\n", - " [ 0. , 0.66601562, 0. , 0. ],\n", - " [ 0. , 0. , 0.66601562, 0. ],\n", - " [-0.33203125, 0. , 0. , 0.33398437]])" - ] - }, - "execution_count": 35, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "proj_choi_to_physical(neg_Id_choi)" ] @@ -988,7 +680,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1001,20 +693,9 @@ }, { "cell_type": "code", - "execution_count": 37, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "False" - ] - }, - "execution_count": 37, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "# a vector is not square\n", "is_square_matrix(np.array([[1], [0]]))" @@ -1022,21 +703,9 @@ }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'np' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;31m# a tensor is not a matrix\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0mtensor\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mones\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m8\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreshape\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 7\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtensor\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mNameError\u001b[0m: name 'np' is not defined" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "# NBVAL_RAISES_EXCEPTION\n", "# the line above is for testing purposes, do not remove.\n", @@ -1051,40 +720,18 @@ }, { "cell_type": "code", - "execution_count": 39, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "False" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "is_identity_matrix(X)" ] }, { "cell_type": "code", - "execution_count": 40, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "projector_zero = np.array([[1, 0], [0, 0]])\n", "\n", @@ -1093,40 +740,18 @@ }, { "cell_type": "code", - "execution_count": 41, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "False" - ] - }, - "execution_count": 41, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "is_unitary_matrix(AD_kraus[0])" ] }, { "cell_type": "code", - "execution_count": 42, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 42, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "is_positive_semidefinite_matrix(I)" ] @@ -1140,22 +765,9 @@ } ], "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.3" + "pygments_lexer": "ipython3" } }, "nbformat": 4, From b76999edd39152aa50e2c204b388d92f4ff572f8 Mon Sep 17 00:00:00 2001 From: Joshua Combes Date: Sat, 15 Jun 2019 06:49:01 +1000 Subject: [PATCH 33/33] matplotlib >= 3.1.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 906b2298..e0f400ee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ cvxpy>=1.0.0 dataclasses; python_version < "3.7" tqdm gitpython -matplotlib +matplotlib>=3.1.0 # test dependencies flake8