From 7c3e5c44c5a2b6498ec3a254f98e9ab9452966c9 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Mon, 6 Feb 2023 15:07:26 +0400 Subject: [PATCH 01/36] Initial commit --- src/qibo/gates/channels.py | 80 +++++++++++++++++++++++---- src/qibo/tests/test_gates_channels.py | 17 ++---- 2 files changed, 74 insertions(+), 23 deletions(-) diff --git a/src/qibo/gates/channels.py b/src/qibo/gates/channels.py index d1c67a925c..ca0de5e6cb 100644 --- a/src/qibo/gates/channels.py +++ b/src/qibo/gates/channels.py @@ -101,24 +101,31 @@ def __init__(self, ops): self.coefficients = len(self.gates) * (1,) self.coefficient_sum = 1 - def to_superop(self, backend=None): - """Returns the Liouville representation of the Kraus channel. + + def to_choi(self, order: str = "row", backend=None): + """Returns the Choi representation of the Kraus operator. Args: - backend (``qibo.backends.abstract.Backend``, optional): backend - to be used in the execution. If ``None``, it uses - ``GlobalBackend()``. Defaults to ``None``. + order (str, optional): If ``"row"``, vectorization of Kraus operators + is done row-wise. If ``column``, column-vectorization is performed. + If ``system``, block-wise vectorization is performed. + Defaults to ``"row"``. + backend (``qibo.backends.abstract.Backend``, optional): backend to be + used in the execution. If ``None``, it uses ``GlobalBackend()``. + Defaults to ``None``. Returns: - Liouville representation of the channel. + Choi representation of the Kraus channel. """ import numpy as np + from qibo.quantum_info import vectorization + if backend is None: # pragma: no cover from qibo.backends import GlobalBackend backend = GlobalBackend() - + self.nqubits = 1 + max(self.target_qubits) if self.name != "KrausChannel": @@ -129,16 +136,69 @@ def to_superop(self, backend=None): self.gates += (I(*self.target_qubits),) super_op = np.zeros((4**self.nqubits, 4**self.nqubits), dtype="complex") - super_op = backend.cast(super_op, dtype=super_op.dtype) for coeff, gate in zip(self.coefficients, self.gates): kraus_op = FusedGate(*range(self.nqubits)) kraus_op.append(gate) kraus_op = kraus_op.asmatrix(backend) - kraus_op = coeff * np.kron(np.conj(kraus_op), kraus_op) - super_op += backend.cast(kraus_op, dtype=kraus_op.dtype) + kraus_op = vectorization(kraus_op, order=order) + super_op += coeff * np.outer(kraus_op, np.conj(kraus_op)) + del kraus_op + + super_op = backend.cast(super_op, dtype=super_op.dtype) return super_op + + def to_superop(self, order: str = "column", backend=None): + """Returns the Liouville representation of the Kraus channel. + + Args: + order (str, optional): If ``"row"``, vectorization of Kraus operators + is done row-wise. If ``column``, column-vectorization is performed. + If ``system``, block-wise vectorization is performed. + Defaults to ``"row"``. + backend (``qibo.backends.abstract.Backend``, optional): backend + to be used in the execution. If ``None``, it uses + ``GlobalBackend()``. Defaults to ``None``. + + Returns: + Liouville representation of the channel. + """ + import numpy as np + + if backend is None: # pragma: no cover + from qibo.backends import GlobalBackend + + backend = GlobalBackend() + + super_op = self.to_choi(order=order, backend=backend) + + d = 2 ** self.nqubits + + super_op = np.reshape(super_op, [d] * 4) + super_op = np.swapaxes(super_op, 0, 3) + super_op = np.reshape(super_op, [d**2, d**2]) + + # self.nqubits = 1 + max(self.target_qubits) + + # if self.name != "KrausChannel": + # p0 = 1 + # for coeff in self.coefficients: + # p0 = p0 - coeff + # self.coefficients += (p0,) + # self.gates += (I(*self.target_qubits),) + + # super_op = np.zeros((4**self.nqubits, 4**self.nqubits), dtype="complex") + # super_op = backend.cast(super_op, dtype=super_op.dtype) + # for coeff, gate in zip(self.coefficients, self.gates): + # kraus_op = FusedGate(*range(self.nqubits)) + # kraus_op.append(gate) + # kraus_op = kraus_op.asmatrix(backend) + # kraus_op = coeff * np.kron(np.conj(kraus_op), kraus_op) + # super_op += backend.cast(kraus_op, dtype=kraus_op.dtype) + + return backend.cast(super_op, dtype=super_op.dtype) + def to_pauli_liouville(self, normalize: bool = False, backend=None): """Returns the Liouville representation of the Kraus channel in the Pauli basis. diff --git a/src/qibo/tests/test_gates_channels.py b/src/qibo/tests/test_gates_channels.py index d846b7baef..bd8a278152 100644 --- a/src/qibo/tests/test_gates_channels.py +++ b/src/qibo/tests/test_gates_channels.py @@ -38,24 +38,15 @@ def test_krauss_channel_errors(backend): [0.4 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.6 + 0.0j], ] ) + test_superop = backend.cast(test_superop, dtype=test_superop.dtype) test_pauli = np.diag([2.0, -0.4, -2.0, 0.4]) + test_pauli = backend.cast(test_pauli, dtype=test_pauli.dtype) channel = gates.KrausChannel([((0,), a1), ((0,), a2)]) - assert ( - np.linalg.norm( - backend.to_numpy(channel.to_superop(backend=backend)) - test_superop - ) - < PRECISION_TOL - ) - assert ( - np.linalg.norm( - backend.to_numpy(channel.to_pauli_liouville(backend=backend)) - test_pauli - ) - < PRECISION_TOL - ) - + backend.assert_allclose(backend.calculate_norm(channel.to_superop(backend=backend) - test_superop) < PRECISION_TOL, True) + backend.assert_allclose(backend.calculate_norm(channel.to_pauli_liouville(backend=backend) - test_pauli) < PRECISION_TOL, True) def test_depolarizing_channel_errors(): with pytest.raises(ValueError): From 6e954972b4d04eb9f2039bc494f088d67658a5da Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 11:08:43 +0000 Subject: [PATCH 02/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/gates/channels.py | 6 ++---- src/qibo/tests/test_gates_channels.py | 13 +++++++++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/qibo/gates/channels.py b/src/qibo/gates/channels.py index ca0de5e6cb..c52eea1f09 100644 --- a/src/qibo/gates/channels.py +++ b/src/qibo/gates/channels.py @@ -101,7 +101,6 @@ def __init__(self, ops): self.coefficients = len(self.gates) * (1,) self.coefficient_sum = 1 - def to_choi(self, order: str = "row", backend=None): """Returns the Choi representation of the Kraus operator. @@ -125,7 +124,7 @@ def to_choi(self, order: str = "row", backend=None): from qibo.backends import GlobalBackend backend = GlobalBackend() - + self.nqubits = 1 + max(self.target_qubits) if self.name != "KrausChannel": @@ -148,7 +147,6 @@ def to_choi(self, order: str = "row", backend=None): return super_op - def to_superop(self, order: str = "column", backend=None): """Returns the Liouville representation of the Kraus channel. @@ -173,7 +171,7 @@ def to_superop(self, order: str = "column", backend=None): super_op = self.to_choi(order=order, backend=backend) - d = 2 ** self.nqubits + d = 2**self.nqubits super_op = np.reshape(super_op, [d] * 4) super_op = np.swapaxes(super_op, 0, 3) diff --git a/src/qibo/tests/test_gates_channels.py b/src/qibo/tests/test_gates_channels.py index bd8a278152..19d50686aa 100644 --- a/src/qibo/tests/test_gates_channels.py +++ b/src/qibo/tests/test_gates_channels.py @@ -45,8 +45,17 @@ def test_krauss_channel_errors(backend): channel = gates.KrausChannel([((0,), a1), ((0,), a2)]) - backend.assert_allclose(backend.calculate_norm(channel.to_superop(backend=backend) - test_superop) < PRECISION_TOL, True) - backend.assert_allclose(backend.calculate_norm(channel.to_pauli_liouville(backend=backend) - test_pauli) < PRECISION_TOL, True) + backend.assert_allclose( + backend.calculate_norm(channel.to_superop(backend=backend) - test_superop) + < PRECISION_TOL, + True, + ) + backend.assert_allclose( + backend.calculate_norm(channel.to_pauli_liouville(backend=backend) - test_pauli) + < PRECISION_TOL, + True, + ) + def test_depolarizing_channel_errors(): with pytest.raises(ValueError): From 1f34bdf38df135a3daf2045a8260354336513004 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Tue, 7 Feb 2023 13:29:01 +0400 Subject: [PATCH 03/36] create superoperator_transformations.py --- src/qibo/quantum_info/__init__.py | 1 + .../superoperator_transformations.py | 266 ++++++++++++++++++ 2 files changed, 267 insertions(+) create mode 100644 src/qibo/quantum_info/superoperator_transformations.py diff --git a/src/qibo/quantum_info/__init__.py b/src/qibo/quantum_info/__init__.py index 0ae43b9249..3a40341e68 100644 --- a/src/qibo/quantum_info/__init__.py +++ b/src/qibo/quantum_info/__init__.py @@ -1,4 +1,5 @@ from qibo.quantum_info.basis import * from qibo.quantum_info.metrics import * from qibo.quantum_info.random_ensembles import * +from qibo.quantum_info.superoperator_transformations import * from qibo.quantum_info.utils import * diff --git a/src/qibo/quantum_info/superoperator_transformations.py b/src/qibo/quantum_info/superoperator_transformations.py new file mode 100644 index 0000000000..2efbf0bac5 --- /dev/null +++ b/src/qibo/quantum_info/superoperator_transformations.py @@ -0,0 +1,266 @@ +import numpy as np + +from qibo.config import raise_error +from qibo.gates.abstract import Gate +from qibo.gates.gates import Unitary +from qibo.gates.special import FusedGate + +def vectorization(state, order: str = "row"): + """Returns state :math:`\\rho` in its Liouville + representation :math:`\\ket{\\rho}`. + + Args: + state: state vector or density matrix. + order (str, optional): If ``"row"``, vectorization is performed + row-wise. If ``"column"``, vectorization is performed + column-wise. If ``"system"``, a block-vectorization is + performed. Default is ``"row"``. + + Returns: + Liouville representation of ``state``. + """ + + if ( + (len(state.shape) >= 3) + or (len(state) == 0) + or (len(state.shape) == 2 and state.shape[0] != state.shape[1]) + ): + raise_error( + TypeError, + f"Object must have dims either (k,) or (k,k), but have dims {state.shape}.", + ) + + if not isinstance(order, str): + raise_error( + TypeError, f"order must be type str, but it is type {type(order)} instead." + ) + else: + if (order != "row") and (order != "column") and (order != "system"): + raise_error( + ValueError, + f"order must be either 'row' or 'column' or 'system', but it is {order}.", + ) + + if len(state.shape) == 1: + state = np.outer(state, np.conj(state)) + + if order == "row": + state = np.reshape(state, (1, -1), order="C")[0] + elif order == "column": + state = np.reshape(state, (1, -1), order="F")[0] + else: + d = len(state) + nqubits = int(np.log2(d)) + + new_axis = [] + for x in range(nqubits): + new_axis += [x + nqubits, x] + state = np.reshape( + np.transpose(np.reshape(state, [2] * 2 * nqubits), axes=new_axis), -1 + ) + + return state + + +def unvectorization(state, order: str = "row"): + """Returns state :math:`\\rho` from its Liouville + representation :math:`\\ket{\\rho}`. + + Args: + state: :func:`vectorization` of a quantum state. + order (str, optional): If ``"row"``, unvectorization is performed + row-wise. If ``"column"``, unvectorization is performed + column-wise. If ``"system"``, system-wise vectorization is + performed. Default is ``"row"``. + + Returns: + Density matrix of ``state``. + """ + + if len(state.shape) != 1: + raise_error( + TypeError, + f"Object must have dims (k,), but have dims {state.shape}.", + ) + + if not isinstance(order, str): + raise_error( + TypeError, f"order must be type str, but it is type {type(order)} instead." + ) + else: + if (order != "row") and (order != "column") and (order != "system"): + raise_error( + ValueError, + f"order must be either 'row' or 'column' or 'system', but it is {order}.", + ) + + d = int(np.sqrt(len(state))) + + if (order == "row") or (order == "column"): + order = "C" if order == "row" else "F" + state = np.reshape(state, (d, d), order=order) + else: + nqubits = int(np.log2(d)) + axes_old = list(np.arange(0, 2 * nqubits)) + state = np.reshape( + np.transpose( + np.reshape(state, [2] * 2 * nqubits), + axes=axes_old[1::2] + axes_old[0::2], + ), + [2**nqubits] * 2, + ) + + return state + + +def liouville_to_choi(super_op): + """Convert Liouville representation of quantum channel + to its Choi representation. + + Args: + super_op: Liouville representation of quanutm channel. + + Returns: + ndarray: Choi representation of quantum channel. + """ + + return _reshuffling(super_op) + + +def choi_to_liouville(choi_super_op): + """Convert Choi representation of quantum channel + to its Liouville representation. + + Args: + choi_super_op: Choi representation of quanutm channel. + + Returns: + ndarray: Liouville representation of quantum channel. + """ + + return _reshuffling(choi_super_op) + +def choi_to_kraus(choi_super_op, precision_tol: float = None): + """Convert Choi representation of a quantum channel into Kraus operators. + + Args: + choi_super_op: Choi representation of a quantum channel. + precision_tol (float, optional): Precision tolerance for eigenvalues + found in the spectral decomposition problem. Any eigenvalue + :math:`\\lambda < \\text{precision_tol}` is set to 0 (zero). + If ``None``, ``precision_tol`` defaults to + ``qibo.config.PRECISION_TOL=1e-8``. Defaults to ``None``. + + Returns: + ndarray: Kraus operators of quantum channel. + ndarray: coefficients of Kraus operators. + """ + + if precision_tol is None: # pragma: no cover + from qibo.config import PRECISION_TOL + + precision_tol = PRECISION_TOL + + # using eigh because Choi representation is, + # in theory, always Hermitian + eigenvalues, eigenvectors = np.linalg.eigh(choi_super_op) + eigenvectors = np.transpose(eigenvectors) + + + kraus_ops, coefficients = list(), list() + for eig, kraus in zip(eigenvalues, eigenvectors): + if np.abs(eig) > precision_tol: + kraus_ops.append(unvectorization(kraus)) + coefficients.append(np.sqrt(eig)) + + kraus_ops = np.array(kraus_ops) + coefficients = np.array(coefficients) + + return kraus_ops, coefficients + + +def kraus_to_choi(kraus_ops): + """Convert Kraus representation of quantum channel + to its Choi representation. + + Args: + ops (list): List of Kraus operators as pairs ``(qubits, Ak)`` where + ``qubits`` refers the qubit ids that :math:`A_k` acts on and + :math:`A_k` is the corresponding matrix as a ``np.ndarray``. + + Returns: + ndarray: Choi representation of the Kraus channel. + """ + + from qibo.backends import NumpyBackend + + backend = NumpyBackend() + + gates, target_qubits = _set_gate_and_target_qubits(kraus_ops) + nqubits = 1 + max(target_qubits) + d = 2 ** nqubits + + super_op = np.zeros((d**2, d**2), dtype="complex") + for gate in gates: + kraus_op = FusedGate(*range(nqubits)) + kraus_op.append(gate) + kraus_op = kraus_op.asmatrix(backend) + kraus_op = vectorization(kraus_op) + super_op += np.outer(kraus_op, np.conj(kraus_op)) + del kraus_op + + return super_op + + +def kraus_to_liouville(kraus_ops, coefficients=None): + + super_op = kraus_to_choi(kraus_ops, coefficients) + super_op = choi_to_liouville(super_op) + + return super_op + + +def liouville_to_kraus(super_op, precision_tol: float = None): + + choi_super_op = liouville_to_choi(super_op) + kraus_ops, coefficients = choi_to_kraus(choi_super_op, precision_tol) + + return kraus_ops, coefficients + + +def _reshuffling(super_operator): + + d = int(np.sqrt(super_operator.shape[0])) + + super_operator = np.reshape(super_operator, [d] * 4) + super_operator = np.swapaxes(super_operator, 0, 3) + super_operator = np.reshape(super_operator, [d**2, d**2]) + + return super_operator + + +def _set_gate_and_target_qubits(kraus_ops): + + if isinstance(kraus_ops[0], Gate): + gates = tuple(kraus_ops) + target_qubits = tuple( + sorted({q for gate in kraus_ops for q in gate.target_qubits}) + ) + else: + gates, qubitset = [], set() + for qubits, matrix in kraus_ops: + rank = 2 ** len(qubits) + shape = tuple(matrix.shape) + if shape != (rank, rank): + raise_error( + ValueError, + "Invalid Krauss operator shape {} for " + "acting on {} qubits." + "".format(shape, len(qubits)), + ) + qubitset.update(qubits) + gates.append(Unitary(matrix, *list(qubits))) + gates = tuple(gates) + target_qubits = tuple(sorted(qubitset)) + + return gates, target_qubits \ No newline at end of file From bd266ac702fa21b4e57ed04933e69f0c8999d83c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 09:29:18 +0000 Subject: [PATCH 04/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../superoperator_transformations.py | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/qibo/quantum_info/superoperator_transformations.py b/src/qibo/quantum_info/superoperator_transformations.py index 2efbf0bac5..2593efeea9 100644 --- a/src/qibo/quantum_info/superoperator_transformations.py +++ b/src/qibo/quantum_info/superoperator_transformations.py @@ -5,6 +5,7 @@ from qibo.gates.gates import Unitary from qibo.gates.special import FusedGate + def vectorization(state, order: str = "row"): """Returns state :math:`\\rho` in its Liouville representation :math:`\\ket{\\rho}`. @@ -140,15 +141,16 @@ def choi_to_liouville(choi_super_op): return _reshuffling(choi_super_op) + def choi_to_kraus(choi_super_op, precision_tol: float = None): """Convert Choi representation of a quantum channel into Kraus operators. Args: choi_super_op: Choi representation of a quantum channel. - precision_tol (float, optional): Precision tolerance for eigenvalues - found in the spectral decomposition problem. Any eigenvalue + precision_tol (float, optional): Precision tolerance for eigenvalues + found in the spectral decomposition problem. Any eigenvalue :math:`\\lambda < \\text{precision_tol}` is set to 0 (zero). - If ``None``, ``precision_tol`` defaults to + If ``None``, ``precision_tol`` defaults to ``qibo.config.PRECISION_TOL=1e-8``. Defaults to ``None``. Returns: @@ -156,17 +158,16 @@ def choi_to_kraus(choi_super_op, precision_tol: float = None): ndarray: coefficients of Kraus operators. """ - if precision_tol is None: # pragma: no cover + if precision_tol is None: # pragma: no cover from qibo.config import PRECISION_TOL precision_tol = PRECISION_TOL - # using eigh because Choi representation is, + # using eigh because Choi representation is, # in theory, always Hermitian eigenvalues, eigenvectors = np.linalg.eigh(choi_super_op) eigenvectors = np.transpose(eigenvectors) - kraus_ops, coefficients = list(), list() for eig, kraus in zip(eigenvalues, eigenvectors): if np.abs(eig) > precision_tol: @@ -185,7 +186,7 @@ def kraus_to_choi(kraus_ops): Args: ops (list): List of Kraus operators as pairs ``(qubits, Ak)`` where - ``qubits`` refers the qubit ids that :math:`A_k` acts on and + ``qubits`` refers the qubit ids that :math:`A_k` acts on and :math:`A_k` is the corresponding matrix as a ``np.ndarray``. Returns: @@ -198,7 +199,7 @@ def kraus_to_choi(kraus_ops): gates, target_qubits = _set_gate_and_target_qubits(kraus_ops) nqubits = 1 + max(target_qubits) - d = 2 ** nqubits + d = 2**nqubits super_op = np.zeros((d**2, d**2), dtype="complex") for gate in gates: @@ -213,7 +214,6 @@ def kraus_to_choi(kraus_ops): def kraus_to_liouville(kraus_ops, coefficients=None): - super_op = kraus_to_choi(kraus_ops, coefficients) super_op = choi_to_liouville(super_op) @@ -221,7 +221,6 @@ def kraus_to_liouville(kraus_ops, coefficients=None): def liouville_to_kraus(super_op, precision_tol: float = None): - choi_super_op = liouville_to_choi(super_op) kraus_ops, coefficients = choi_to_kraus(choi_super_op, precision_tol) @@ -229,7 +228,6 @@ def liouville_to_kraus(super_op, precision_tol: float = None): def _reshuffling(super_operator): - d = int(np.sqrt(super_operator.shape[0])) super_operator = np.reshape(super_operator, [d] * 4) @@ -240,7 +238,6 @@ def _reshuffling(super_operator): def _set_gate_and_target_qubits(kraus_ops): - if isinstance(kraus_ops[0], Gate): gates = tuple(kraus_ops) target_qubits = tuple( @@ -263,4 +260,4 @@ def _set_gate_and_target_qubits(kraus_ops): gates = tuple(gates) target_qubits = tuple(sorted(qubitset)) - return gates, target_qubits \ No newline at end of file + return gates, target_qubits From 7fefed6945a16b184ad473ae147cd28b79230e4f Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Tue, 7 Feb 2023 14:05:15 +0400 Subject: [PATCH 05/36] documentation --- .../superoperator_transformations.py | 79 +++++++++++++++---- 1 file changed, 63 insertions(+), 16 deletions(-) diff --git a/src/qibo/quantum_info/superoperator_transformations.py b/src/qibo/quantum_info/superoperator_transformations.py index 2593efeea9..1bc30297d3 100644 --- a/src/qibo/quantum_info/superoperator_transformations.py +++ b/src/qibo/quantum_info/superoperator_transformations.py @@ -18,7 +18,7 @@ def vectorization(state, order: str = "row"): performed. Default is ``"row"``. Returns: - Liouville representation of ``state``. + ndarray: Liouville representation of ``state``. """ if ( @@ -75,7 +75,7 @@ def unvectorization(state, order: str = "row"): performed. Default is ``"row"``. Returns: - Density matrix of ``state``. + ndarray: Density matrix of ``state``. """ if len(state.shape) != 1: @@ -154,8 +154,8 @@ def choi_to_kraus(choi_super_op, precision_tol: float = None): ``qibo.config.PRECISION_TOL=1e-8``. Defaults to ``None``. Returns: - ndarray: Kraus operators of quantum channel. - ndarray: coefficients of Kraus operators. + (ndarray, ndarray): Kraus operators of quantum channel and their + respective coefficients. """ if precision_tol is None: # pragma: no cover @@ -164,7 +164,7 @@ def choi_to_kraus(choi_super_op, precision_tol: float = None): precision_tol = PRECISION_TOL # using eigh because Choi representation is, - # in theory, always Hermitian + # in theory, always Hermitian eigenvalues, eigenvectors = np.linalg.eigh(choi_super_op) eigenvectors = np.transpose(eigenvectors) @@ -185,9 +185,9 @@ def kraus_to_choi(kraus_ops): to its Choi representation. Args: - ops (list): List of Kraus operators as pairs ``(qubits, Ak)`` where - ``qubits`` refers the qubit ids that :math:`A_k` acts on and - :math:`A_k` is the corresponding matrix as a ``np.ndarray``. + kraus_ops (list): List of Kraus operators as pairs ``(qubits, Ak)`` + where ``qubits`` refers the qubit ids that :math:`A_k` acts on + and :math:`A_k` is the corresponding matrix as a ``np.ndarray``. Returns: ndarray: Choi representation of the Kraus channel. @@ -213,31 +213,78 @@ def kraus_to_choi(kraus_ops): return super_op -def kraus_to_liouville(kraus_ops, coefficients=None): - super_op = kraus_to_choi(kraus_ops, coefficients) +def kraus_to_liouville(kraus_ops): + """Convert from Kraus representation to Liouville representation. + + Args: + kraus_ops (list): List of Kraus operators as pairs ``(qubits, Ak)`` + where ``qubits`` refers the qubit ids that :math:`A_k` acts on + and :math:`A_k` is the corresponding matrix as a ``np.ndarray``. + + Returns: + ndarray: Liouville representation of quantum channel. + """ + super_op = kraus_to_choi(kraus_ops) super_op = choi_to_liouville(super_op) return super_op def liouville_to_kraus(super_op, precision_tol: float = None): + """Convert Liouville representation of a quantum channel to + its Kraus representation. It uses the Choi representation as + an intermediate step. + + Args: + super_op (ndarray): Liouville representation of quantum channel. + precision_tol (float, optional): Precision tolerance for eigenvalues + found in the spectral decomposition problem. Any eigenvalue + :math:`\\lambda < \\text{precision_tol}` is set to 0 (zero). + If ``None``, ``precision_tol`` defaults to + ``qibo.config.PRECISION_TOL=1e-8``. Defaults to None. + + Returns: + (ndarray, ndarray): Kraus operators of quantum channel and their + respective coefficients. + """ choi_super_op = liouville_to_choi(super_op) kraus_ops, coefficients = choi_to_kraus(choi_super_op, precision_tol) return kraus_ops, coefficients -def _reshuffling(super_operator): - d = int(np.sqrt(super_operator.shape[0])) +def _reshuffling(super_op): + """Reshuffling operation used to convert Lioville representation + of quantum channels to their Choi representation (and vice-versa). - super_operator = np.reshape(super_operator, [d] * 4) - super_operator = np.swapaxes(super_operator, 0, 3) - super_operator = np.reshape(super_operator, [d**2, d**2]) + Args: + super_op (ndarray): Liouville (Choi) representation of a + quantum channel. - return super_operator + Returns: + ndarray: Choi (Liouville) representation of the quantum channel. + """ + d = int(np.sqrt(super_op.shape[0])) + + super_op = np.reshape(super_op, [d] * 4) + super_op = np.swapaxes(super_op, 0, 3) + super_op = np.reshape(super_op, [d**2, d**2]) + + return super_op def _set_gate_and_target_qubits(kraus_ops): + """Returns Kraus operators as a set of gates acting on + their respective ``target qubits``. + + Args: + kraus_ops (list): List of Kraus operators as pairs ``(qubits, Ak)`` + where ``qubits`` refers the qubit ids that :math:`A_k` acts on + and :math:`A_k` is the corresponding matrix as a ``np.ndarray``. + + Returns: + (tuple, tuple): gates and their respective target qubits. + """ if isinstance(kraus_ops[0], Gate): gates = tuple(kraus_ops) target_qubits = tuple( From d246ecda3d085b8b2022e824f1f2cd8dfbde6967 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 10:05:33 +0000 Subject: [PATCH 06/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../quantum_info/superoperator_transformations.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/qibo/quantum_info/superoperator_transformations.py b/src/qibo/quantum_info/superoperator_transformations.py index 1bc30297d3..dee0d3f94f 100644 --- a/src/qibo/quantum_info/superoperator_transformations.py +++ b/src/qibo/quantum_info/superoperator_transformations.py @@ -154,7 +154,7 @@ def choi_to_kraus(choi_super_op, precision_tol: float = None): ``qibo.config.PRECISION_TOL=1e-8``. Defaults to ``None``. Returns: - (ndarray, ndarray): Kraus operators of quantum channel and their + (ndarray, ndarray): Kraus operators of quantum channel and their respective coefficients. """ @@ -185,7 +185,7 @@ def kraus_to_choi(kraus_ops): to its Choi representation. Args: - kraus_ops (list): List of Kraus operators as pairs ``(qubits, Ak)`` + kraus_ops (list): List of Kraus operators as pairs ``(qubits, Ak)`` where ``qubits`` refers the qubit ids that :math:`A_k` acts on and :math:`A_k` is the corresponding matrix as a ``np.ndarray``. @@ -217,7 +217,7 @@ def kraus_to_liouville(kraus_ops): """Convert from Kraus representation to Liouville representation. Args: - kraus_ops (list): List of Kraus operators as pairs ``(qubits, Ak)`` + kraus_ops (list): List of Kraus operators as pairs ``(qubits, Ak)`` where ``qubits`` refers the qubit ids that :math:`A_k` acts on and :math:`A_k` is the corresponding matrix as a ``np.ndarray``. @@ -244,7 +244,7 @@ def liouville_to_kraus(super_op, precision_tol: float = None): ``qibo.config.PRECISION_TOL=1e-8``. Defaults to None. Returns: - (ndarray, ndarray): Kraus operators of quantum channel and their + (ndarray, ndarray): Kraus operators of quantum channel and their respective coefficients. """ choi_super_op = liouville_to_choi(super_op) @@ -258,7 +258,7 @@ def _reshuffling(super_op): of quantum channels to their Choi representation (and vice-versa). Args: - super_op (ndarray): Liouville (Choi) representation of a + super_op (ndarray): Liouville (Choi) representation of a quantum channel. Returns: @@ -278,7 +278,7 @@ def _set_gate_and_target_qubits(kraus_ops): their respective ``target qubits``. Args: - kraus_ops (list): List of Kraus operators as pairs ``(qubits, Ak)`` + kraus_ops (list): List of Kraus operators as pairs ``(qubits, Ak)`` where ``qubits`` refers the qubit ids that :math:`A_k` acts on and :math:`A_k` is the corresponding matrix as a ``np.ndarray``. From 3a56990cd25f9180121c49e7eceaef30057149f7 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Tue, 7 Feb 2023 14:06:55 +0400 Subject: [PATCH 07/36] moving `vec` and `unvec` to superoperator_transformations.py --- src/qibo/quantum_info/basis.py | 109 +-------------------------------- 1 file changed, 1 insertion(+), 108 deletions(-) diff --git a/src/qibo/quantum_info/basis.py b/src/qibo/quantum_info/basis.py index cf71f55d31..7e929f4c3f 100644 --- a/src/qibo/quantum_info/basis.py +++ b/src/qibo/quantum_info/basis.py @@ -5,114 +5,7 @@ from qibo import matrices from qibo.config import raise_error - - -def vectorization(state, order: str = "row"): - """Returns state :math:`\\rho` in its Liouville - representation :math:`\\ket{\\rho}`. - - Args: - state: state vector or density matrix. - order (str, optional): If ``"row"``, vectorization is performed - row-wise. If ``"column"``, vectorization is performed - column-wise. If ``"system"``, a block-vectorization is - performed. Default is ``"row"``. - - Returns: - Liouville representation of ``state``. - """ - - if ( - (len(state.shape) >= 3) - or (len(state) == 0) - or (len(state.shape) == 2 and state.shape[0] != state.shape[1]) - ): - raise_error( - TypeError, - f"Object must have dims either (k,) or (k,k), but have dims {state.shape}.", - ) - - if not isinstance(order, str): - raise_error( - TypeError, f"order must be type str, but it is type {type(order)} instead." - ) - else: - if (order != "row") and (order != "column") and (order != "system"): - raise_error( - ValueError, - f"order must be either 'row' or 'column' or 'system', but it is {order}.", - ) - - if len(state.shape) == 1: - state = np.outer(state, np.conj(state)) - - if order == "row": - state = np.reshape(state, (1, -1), order="C")[0] - elif order == "column": - state = np.reshape(state, (1, -1), order="F")[0] - else: - d = len(state) - nqubits = int(np.log2(d)) - - new_axis = [] - for x in range(nqubits): - new_axis += [x + nqubits, x] - state = np.reshape( - np.transpose(np.reshape(state, [2] * 2 * nqubits), axes=new_axis), -1 - ) - - return state - - -def unvectorization(state, order: str = "row"): - """Returns state :math:`\\rho` from its Liouville - representation :math:`\\ket{\\rho}`. - - Args: - state: :func:`vectorization` of a quantum state. - order (str, optional): If ``"row"``, unvectorization is performed - row-wise. If ``"column"``, unvectorization is performed - column-wise. If ``"system"``, system-wise vectorization is - performed. Default is ``"row"``. - - Returns: - Density matrix of ``state``. - """ - - if len(state.shape) != 1: - raise_error( - TypeError, - f"Object must have dims (k,), but have dims {state.shape}.", - ) - - if not isinstance(order, str): - raise_error( - TypeError, f"order must be type str, but it is type {type(order)} instead." - ) - else: - if (order != "row") and (order != "column") and (order != "system"): - raise_error( - ValueError, - f"order must be either 'row' or 'column' or 'system', but it is {order}.", - ) - - d = int(np.sqrt(len(state))) - - if (order == "row") or (order == "column"): - order = "C" if order == "row" else "F" - state = np.reshape(state, (d, d), order=order) - else: - nqubits = int(np.log2(d)) - axes_old = list(np.arange(0, 2 * nqubits)) - state = np.reshape( - np.transpose( - np.reshape(state, [2] * 2 * nqubits), - axes=axes_old[1::2] + axes_old[0::2], - ), - [2**nqubits] * 2, - ) - - return state +from qibo.quantum_info.superoperator_transformations import vectorization def pauli_basis( From b66743764330fb4dbba82ac10ccec4e09040aca9 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Tue, 7 Feb 2023 14:07:26 +0400 Subject: [PATCH 08/36] reformulating `to_superop()` --- src/qibo/gates/channels.py | 52 +++++++++----------------------------- 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/src/qibo/gates/channels.py b/src/qibo/gates/channels.py index c52eea1f09..1d6bc177e0 100644 --- a/src/qibo/gates/channels.py +++ b/src/qibo/gates/channels.py @@ -101,14 +101,10 @@ def __init__(self, ops): self.coefficients = len(self.gates) * (1,) self.coefficient_sum = 1 - def to_choi(self, order: str = "row", backend=None): - """Returns the Choi representation of the Kraus operator. + def to_choi(self, backend=None): + """Returns the Choi representation of the Kraus channel. Args: - order (str, optional): If ``"row"``, vectorization of Kraus operators - is done row-wise. If ``column``, column-vectorization is performed. - If ``system``, block-wise vectorization is performed. - Defaults to ``"row"``. backend (``qibo.backends.abstract.Backend``, optional): backend to be used in the execution. If ``None``, it uses ``GlobalBackend()``. Defaults to ``None``. @@ -118,7 +114,7 @@ def to_choi(self, order: str = "row", backend=None): """ import numpy as np - from qibo.quantum_info import vectorization + from qibo.quantum_info.superoperator_transformations import vectorization if backend is None: # pragma: no cover from qibo.backends import GlobalBackend @@ -139,22 +135,18 @@ def to_choi(self, order: str = "row", backend=None): kraus_op = FusedGate(*range(self.nqubits)) kraus_op.append(gate) kraus_op = kraus_op.asmatrix(backend) - kraus_op = vectorization(kraus_op, order=order) - super_op += coeff * np.outer(kraus_op, np.conj(kraus_op)) + kraus_op = vectorization(coeff * kraus_op) + super_op += np.outer(kraus_op, np.conj(kraus_op)) del kraus_op super_op = backend.cast(super_op, dtype=super_op.dtype) return super_op - def to_superop(self, order: str = "column", backend=None): + def to_superop(self, backend=None): """Returns the Liouville representation of the Kraus channel. Args: - order (str, optional): If ``"row"``, vectorization of Kraus operators - is done row-wise. If ``column``, column-vectorization is performed. - If ``system``, block-wise vectorization is performed. - Defaults to ``"row"``. backend (``qibo.backends.abstract.Backend``, optional): backend to be used in the execution. If ``None``, it uses ``GlobalBackend()``. Defaults to ``None``. @@ -164,38 +156,18 @@ def to_superop(self, order: str = "column", backend=None): """ import numpy as np + from qibo.quantum_info.superoperator_transformations import choi_to_liouville + if backend is None: # pragma: no cover from qibo.backends import GlobalBackend backend = GlobalBackend() - super_op = self.to_choi(order=order, backend=backend) - - d = 2**self.nqubits - - super_op = np.reshape(super_op, [d] * 4) - super_op = np.swapaxes(super_op, 0, 3) - super_op = np.reshape(super_op, [d**2, d**2]) - - # self.nqubits = 1 + max(self.target_qubits) - - # if self.name != "KrausChannel": - # p0 = 1 - # for coeff in self.coefficients: - # p0 = p0 - coeff - # self.coefficients += (p0,) - # self.gates += (I(*self.target_qubits),) - - # super_op = np.zeros((4**self.nqubits, 4**self.nqubits), dtype="complex") - # super_op = backend.cast(super_op, dtype=super_op.dtype) - # for coeff, gate in zip(self.coefficients, self.gates): - # kraus_op = FusedGate(*range(self.nqubits)) - # kraus_op.append(gate) - # kraus_op = kraus_op.asmatrix(backend) - # kraus_op = coeff * np.kron(np.conj(kraus_op), kraus_op) - # super_op += backend.cast(kraus_op, dtype=kraus_op.dtype) + super_op = self.to_choi(backend=backend) + super_op = choi_to_liouville(super_op) + super_op = backend.cast(super_op, dtype=super_op.dtype) - return backend.cast(super_op, dtype=super_op.dtype) + return super_op def to_pauli_liouville(self, normalize: bool = False, backend=None): """Returns the Liouville representation of the Kraus channel From 523288b3b77f2dee107f1e42ccc4e387b3bce9c7 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Tue, 7 Feb 2023 14:24:02 +0400 Subject: [PATCH 09/36] documentation --- doc/source/api-reference/qibo.rst | 65 +++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/doc/source/api-reference/qibo.rst b/doc/source/api-reference/qibo.rst index f99abaa032..6902c0b55e 100644 --- a/doc/source/api-reference/qibo.rst +++ b/doc/source/api-reference/qibo.rst @@ -1018,18 +1018,6 @@ Basis Set of functions related to basis and basis transformations. -Vectorization -""""""""""""" - -.. autofunction:: qibo.quantum_info.vectorization - - -Unvectorization -""""""""""""""" - -.. autofunction:: qibo.quantum_info.unvectorization - - Pauli basis """"""""""" @@ -1168,6 +1156,59 @@ Random stochastic matrix .. autofunction:: qibo.quantum_info.random_stochastic_matrix +Superoperator Transformations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Functions used to convert superoperators among their possible representations. + +Vectorization +""""""""""""" + +.. autofunction:: qibo.quantum_info.vectorization + + +Unvectorization +""""""""""""""" + +.. autofunction:: qibo.quantum_info.unvectorization + + +Liouville to Choi +""""""""""""""""" + +.. autofunction:: qibo.quantum_info.liouville_to_choi + + +Choi to Liouville +""""""""""""""""" + +.. autofunction:: qibo.quantum_info.choi_to_liouville + + +Choi to Kraus +""""""""""""" + +.. autofunction:: qibo.quantum_info.choi_to_kraus + + +Kraus to Choi +""""""""""""" + +.. autofunction:: qibo.quantum_info.kraus_to_choi + + +Kraus to Liouville +"""""""""""""""""" + +.. autofunction:: qibo.quantum_info.kraus_to_liouville + + +Liouville to Kraus +"""""""""""""""""" + +.. autofunction:: qibo.quantum_info.liouville_to_kraus + + Utility Functions ^^^^^^^^^^^^^^^^^ From dce90918c4284a2d2c47e61c27fffadad7ac30fb Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Tue, 7 Feb 2023 14:28:12 +0400 Subject: [PATCH 10/36] rename function + add choin test --- src/qibo/tests/test_gates_channels.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/qibo/tests/test_gates_channels.py b/src/qibo/tests/test_gates_channels.py index 19d50686aa..526a5775d8 100644 --- a/src/qibo/tests/test_gates_channels.py +++ b/src/qibo/tests/test_gates_channels.py @@ -24,7 +24,7 @@ def test_general_channel(backend): backend.assert_allclose(final_rho, target_rho) -def test_krauss_channel_errors(backend): +def test_kraus_channel_errors(backend): a1 = np.sqrt(0.4) * np.array([[0, 1], [1, 0]]) a2 = np.sqrt(0.6) * np.array([[1, 0], [0, -1]]) with pytest.raises(ValueError): @@ -38,9 +38,11 @@ def test_krauss_channel_errors(backend): [0.4 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.6 + 0.0j], ] ) - test_superop = backend.cast(test_superop, dtype=test_superop.dtype) - + test_choi = np.reshape(test_superop, [2] * 4).swapaxes(0, 3).reshape([4, 4]) test_pauli = np.diag([2.0, -0.4, -2.0, 0.4]) + + test_superop = backend.cast(test_superop, dtype=test_superop.dtype) + test_choi = backend.cast(test_choi, dtype=test_choi.dtype) test_pauli = backend.cast(test_pauli, dtype=test_pauli.dtype) channel = gates.KrausChannel([((0,), a1), ((0,), a2)]) @@ -50,6 +52,11 @@ def test_krauss_channel_errors(backend): < PRECISION_TOL, True, ) + backend.assert_allclose( + backend.calculate_norm(channel.to_choi(backend=backend) - test_choi) + < PRECISION_TOL, + True, + ) backend.assert_allclose( backend.calculate_norm(channel.to_pauli_liouville(backend=backend) - test_pauli) < PRECISION_TOL, From 3457187d40907ed48a2b38a16c904e31db747a1e Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Tue, 7 Feb 2023 14:30:44 +0400 Subject: [PATCH 11/36] remove `vec` and `unvec` tests --- src/qibo/tests/test_quantum_info_basis.py | 134 ---------------------- 1 file changed, 134 deletions(-) diff --git a/src/qibo/tests/test_quantum_info_basis.py b/src/qibo/tests/test_quantum_info_basis.py index 9f54e203f2..e4fe9cc415 100644 --- a/src/qibo/tests/test_quantum_info_basis.py +++ b/src/qibo/tests/test_quantum_info_basis.py @@ -6,140 +6,6 @@ from qibo.quantum_info import * -@pytest.mark.parametrize("order", ["row", "column", "system"]) -@pytest.mark.parametrize("nqubits", [1, 2, 3]) -def test_vectorization(nqubits, order): - with pytest.raises(TypeError): - vectorization(np.array([[0.0, 0.0], [0.0, 0.0], [0.0, 0.0]])) - with pytest.raises(TypeError): - vectorization( - np.array([[[0.0, 0.0], [0.0, 0.0]], [[0.0, 0.0]]], dtype="object") - ) - with pytest.raises(TypeError): - vectorization(np.array([])) - with pytest.raises(TypeError): - vectorization(random_statevector(4), order=1) - with pytest.raises(ValueError): - vectorization(random_statevector(4), order="1") - - d = 2**nqubits - - if nqubits == 1: - if order == "system" or order == "column": - matrix_test = [0, 2, 1, 3] - else: - matrix_test = [0, 1, 2, 3] - elif nqubits == 2: - if order == "row": - matrix_test = np.arange(d**2) - elif order == "column": - matrix_test = np.arange(d**2) - matrix_test = np.reshape(matrix_test, (d, d)) - matrix_test = np.reshape(matrix_test, (1, -1), order="F")[0] - else: - matrix_test = [0, 4, 1, 5, 8, 12, 9, 13, 2, 6, 3, 7, 10, 14, 11, 15] - else: - if order == "row": - matrix_test = np.arange(d**2) - elif order == "column": - matrix_test = np.arange(d**2) - matrix_test = np.reshape(matrix_test, (d, d)) - matrix_test = np.reshape(matrix_test, (1, -1), order="F")[0] - else: - matrix_test = [ - 0, - 8, - 1, - 9, - 16, - 24, - 17, - 25, - 2, - 10, - 3, - 11, - 18, - 26, - 19, - 27, - 32, - 40, - 33, - 41, - 48, - 56, - 49, - 57, - 34, - 42, - 35, - 43, - 50, - 58, - 51, - 59, - 4, - 12, - 5, - 13, - 20, - 28, - 21, - 29, - 6, - 14, - 7, - 15, - 22, - 30, - 23, - 31, - 36, - 44, - 37, - 45, - 52, - 60, - 53, - 61, - 38, - 46, - 39, - 47, - 54, - 62, - 55, - 63, - ] - matrix_test = np.array(matrix_test) - - d = 2**nqubits - matrix = np.arange(d**2).reshape((d, d)) - matrix = vectorization(matrix, order) - - assert np.linalg.norm(matrix - matrix_test) < PRECISION_TOL - - -@pytest.mark.parametrize("order", ["row", "column", "system"]) -@pytest.mark.parametrize("nqubits", [2, 3, 4, 5]) -def test_unvectorization(nqubits, order): - with pytest.raises(TypeError): - unvectorization(random_density_matrix(2**nqubits)) - with pytest.raises(TypeError): - unvectorization(random_statevector(4**nqubits), order=1) - with pytest.raises(ValueError): - unvectorization(random_statevector(4**2), order="1") - - d = 2**nqubits - matrix_test = random_density_matrix(d) - - matrix = vectorization(matrix_test, order) - matrix = unvectorization(matrix, order) - - assert np.linalg.norm(matrix_test - matrix) < PRECISION_TOL - - @pytest.mark.parametrize("order", ["row", "column", "system"]) @pytest.mark.parametrize("vectorize", [False, True]) @pytest.mark.parametrize("normalize", [False, True]) From b4b96277955194e7faf11366eae828137f7a7692 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Tue, 7 Feb 2023 18:25:21 +0400 Subject: [PATCH 12/36] add `precision_tol`checks --- .../quantum_info/superoperator_transformations.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/qibo/quantum_info/superoperator_transformations.py b/src/qibo/quantum_info/superoperator_transformations.py index dee0d3f94f..6533769f96 100644 --- a/src/qibo/quantum_info/superoperator_transformations.py +++ b/src/qibo/quantum_info/superoperator_transformations.py @@ -158,6 +158,18 @@ def choi_to_kraus(choi_super_op, precision_tol: float = None): respective coefficients. """ + if precision_tol is not None and not isinstance(precision_tol, float): + raise_error( + TypeError, + f"precision_tol must be type float, but it is type {type(precision_tol)}" + ) + + if precision_tol is not None and precision_tol < 0: + raise_error( + ValueError, + f"precision_tol must be a non-negative float, but it is {precision_tol}." + ) + if precision_tol is None: # pragma: no cover from qibo.config import PRECISION_TOL @@ -273,7 +285,7 @@ def _reshuffling(super_op): return super_op -def _set_gate_and_target_qubits(kraus_ops): +def _set_gate_and_target_qubits(kraus_ops): # pragma: no cover """Returns Kraus operators as a set of gates acting on their respective ``target qubits``. From b435b1e5fe910ea473c6f4e4e81b4faf04cdcb92 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 14:25:46 +0000 Subject: [PATCH 13/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/quantum_info/superoperator_transformations.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qibo/quantum_info/superoperator_transformations.py b/src/qibo/quantum_info/superoperator_transformations.py index 6533769f96..da6fc58c86 100644 --- a/src/qibo/quantum_info/superoperator_transformations.py +++ b/src/qibo/quantum_info/superoperator_transformations.py @@ -161,13 +161,13 @@ def choi_to_kraus(choi_super_op, precision_tol: float = None): if precision_tol is not None and not isinstance(precision_tol, float): raise_error( TypeError, - f"precision_tol must be type float, but it is type {type(precision_tol)}" + f"precision_tol must be type float, but it is type {type(precision_tol)}", ) - + if precision_tol is not None and precision_tol < 0: raise_error( ValueError, - f"precision_tol must be a non-negative float, but it is {precision_tol}." + f"precision_tol must be a non-negative float, but it is {precision_tol}.", ) if precision_tol is None: # pragma: no cover From af7ae0100542efd87ed8f9fb4c4e56a4a9a9b604 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Tue, 7 Feb 2023 18:25:51 +0400 Subject: [PATCH 14/36] delete file --- examples/data3sat/.DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 examples/data3sat/.DS_Store diff --git a/examples/data3sat/.DS_Store b/examples/data3sat/.DS_Store deleted file mode 100644 index 8597a5bdbfe38771a2213da0cbc1a5d3101a0854..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKO-lno3{BbkRl0W-t61S zWcLkhHblgmpZ%I>MMMj@;OrJdU~*r4Vl!v%fUJ9b@4r3{yW{aNS)0UlDj;WfLu-1V z9evX1{DiJC{S?#BG5r$JHBMRMl$FAimBN&j!jz9u$|2f$h&H&+1I(vN1yX@j-~t6u zv&9xm!yHqAR3H^NDInj60vBwK-B3Rr7_ zuzlNZ@Je1=Codh($ From fa8d2cb972d30e99443f31898cc3ec9a0432adb7 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Tue, 7 Feb 2023 18:30:07 +0400 Subject: [PATCH 15/36] using `qibo.matrices`in tests --- src/qibo/tests/test_gates_channels.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/qibo/tests/test_gates_channels.py b/src/qibo/tests/test_gates_channels.py index 526a5775d8..804c75be09 100644 --- a/src/qibo/tests/test_gates_channels.py +++ b/src/qibo/tests/test_gates_channels.py @@ -2,13 +2,13 @@ import numpy as np import pytest -from qibo import gates +from qibo import gates, matrices from qibo.config import PRECISION_TOL from qibo.tests.utils import random_density_matrix def test_general_channel(backend): - a1 = np.sqrt(0.4) * np.array([[0, 1], [1, 0]]) + a1 = np.sqrt(0.4) * matrices.X a2 = np.sqrt(0.6) * np.array( [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]] ) @@ -25,8 +25,8 @@ def test_general_channel(backend): def test_kraus_channel_errors(backend): - a1 = np.sqrt(0.4) * np.array([[0, 1], [1, 0]]) - a2 = np.sqrt(0.6) * np.array([[1, 0], [0, -1]]) + a1 = np.sqrt(0.4) * matrices.X + a2 = np.sqrt(0.6) * matrices.Z with pytest.raises(ValueError): gate = gates.KrausChannel([((0, 1), a1)]) @@ -73,7 +73,7 @@ def test_controlled_by_channel_error(): with pytest.raises(ValueError): gates.PauliNoiseChannel(0, px=0.5).controlled_by(1) - a1 = np.sqrt(0.4) * np.array([[0, 1], [1, 0]]) + a1 = np.sqrt(0.4) * matrices.X a2 = np.sqrt(0.6) * np.array( [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]] ) @@ -83,7 +83,7 @@ def test_controlled_by_channel_error(): def test_unitary_channel(backend): - a1 = np.array([[0, 1], [1, 0]]) + a1 = matrices.X a2 = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]) probs = [0.4, 0.3] matrices = [((0,), a1), ((2, 3), a2)] @@ -120,7 +120,7 @@ def test_unitary_channel_probability_tolerance(backend): def test_unitary_channel_errors(): """Check errors raised by ``gates.UnitaryChannel``.""" - a1 = np.array([[0, 1], [1, 0]]) + a1 = matrices.X a2 = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]) probs = [0.4, 0.3] matrices = [((0,), a1), ((2, 3), a2)] From e78755b326cfddca93fb868b2f4eba979aef2ab1 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Tue, 7 Feb 2023 18:48:14 +0400 Subject: [PATCH 16/36] create tests for superoperator_transformations.py` --- ...ntum_info_superoperator_transformations.py | 232 ++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 src/qibo/tests/test_quantum_info_superoperator_transformations.py diff --git a/src/qibo/tests/test_quantum_info_superoperator_transformations.py b/src/qibo/tests/test_quantum_info_superoperator_transformations.py new file mode 100644 index 0000000000..2715c78bc2 --- /dev/null +++ b/src/qibo/tests/test_quantum_info_superoperator_transformations.py @@ -0,0 +1,232 @@ +import numpy as np +import pytest + +from qibo import matrices +from qibo.config import PRECISION_TOL +from qibo.quantum_info import * + + +@pytest.mark.parametrize("order", ["row", "column", "system"]) +@pytest.mark.parametrize("nqubits", [1, 2, 3]) +def test_vectorization(nqubits, order): + with pytest.raises(TypeError): + vectorization(np.array([[0.0, 0.0], [0.0, 0.0], [0.0, 0.0]])) + with pytest.raises(TypeError): + vectorization( + np.array([[[0.0, 0.0], [0.0, 0.0]], [[0.0, 0.0]]], dtype="object") + ) + with pytest.raises(TypeError): + vectorization(np.array([])) + with pytest.raises(TypeError): + vectorization(random_statevector(4), order=1) + with pytest.raises(ValueError): + vectorization(random_statevector(4), order="1") + + d = 2**nqubits + + if nqubits == 1: + if order == "system" or order == "column": + matrix_test = [0, 2, 1, 3] + else: + matrix_test = [0, 1, 2, 3] + elif nqubits == 2: + if order == "row": + matrix_test = np.arange(d**2) + elif order == "column": + matrix_test = np.arange(d**2) + matrix_test = np.reshape(matrix_test, (d, d)) + matrix_test = np.reshape(matrix_test, (1, -1), order="F")[0] + else: + matrix_test = [0, 4, 1, 5, 8, 12, 9, 13, 2, 6, 3, 7, 10, 14, 11, 15] + else: + if order == "row": + matrix_test = np.arange(d**2) + elif order == "column": + matrix_test = np.arange(d**2) + matrix_test = np.reshape(matrix_test, (d, d)) + matrix_test = np.reshape(matrix_test, (1, -1), order="F")[0] + else: + matrix_test = [ + 0, + 8, + 1, + 9, + 16, + 24, + 17, + 25, + 2, + 10, + 3, + 11, + 18, + 26, + 19, + 27, + 32, + 40, + 33, + 41, + 48, + 56, + 49, + 57, + 34, + 42, + 35, + 43, + 50, + 58, + 51, + 59, + 4, + 12, + 5, + 13, + 20, + 28, + 21, + 29, + 6, + 14, + 7, + 15, + 22, + 30, + 23, + 31, + 36, + 44, + 37, + 45, + 52, + 60, + 53, + 61, + 38, + 46, + 39, + 47, + 54, + 62, + 55, + 63, + ] + matrix_test = np.array(matrix_test) + + d = 2**nqubits + matrix = np.arange(d**2).reshape((d, d)) + matrix = vectorization(matrix, order) + + assert np.linalg.norm(matrix - matrix_test) < PRECISION_TOL + + +@pytest.mark.parametrize("order", ["row", "column", "system"]) +@pytest.mark.parametrize("nqubits", [2, 3, 4, 5]) +def test_unvectorization(nqubits, order): + with pytest.raises(TypeError): + unvectorization(random_density_matrix(2**nqubits)) + with pytest.raises(TypeError): + unvectorization(random_statevector(4**nqubits), order=1) + with pytest.raises(ValueError): + unvectorization(random_statevector(4**2), order="1") + + d = 2**nqubits + matrix_test = random_density_matrix(d) + + matrix = vectorization(matrix_test, order) + matrix = unvectorization(matrix, order) + + assert np.linalg.norm(matrix_test - matrix) < PRECISION_TOL + +test_a0 = np.sqrt(0.4) * matrices.X +test_a1 = np.sqrt(0.6) * matrices.Z +test_kraus = [((0,), test_a0), ((0,), test_a1)] +test_superop = np.array( + [ + [0.6 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.4 + 0.0j], + [0.0 + 0.0j, -0.6 + 0.0j, 0.4 + 0.0j, 0.0 + 0.0j], + [0.0 + 0.0j, 0.4 + 0.0j, -0.6 + 0.0j, 0.0 + 0.0j], + [0.4 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.6 + 0.0j], + ] +) +test_choi = np.reshape(test_superop, [2] * 4).swapaxes(0, 3).reshape([4, 4]) + + +def test_liouville_to_choi(): + choi = liouville_to_choi(test_superop) + + assert np.linalg.norm(choi - test_choi) < PRECISION_TOL, True + + +def test_choi_to_liouville(): + liouville = choi_to_liouville(test_choi) + + assert np.linalg.norm(liouville - test_superop) < PRECISION_TOL, True + + +def test_choi_to_kraus(): + with pytest.raises(TypeError): + choi_to_kraus(test_choi, '1e-8') + with pytest.raises(ValueError): + choi_to_kraus(test_choi, -1.0 * 1e-8) + + kraus_ops, coefficients = choi_to_kraus(test_choi) + + a0 = coefficients[0] * kraus_ops[0] + a1 = coefficients[1] * kraus_ops[1] + + state = random_density_matrix(2) + + evolution_a0 = a0 @ state @ a0.T.conj() + evolution_a1 = a1 @ state @ a1.T.conj() + + test_evolution_a0 = test_a0 @ state @ test_a0.T.conj() + test_evolution_a1 = test_a1 @ state @ test_a1.T.conj() + + assert np.linalg.norm(evolution_a0 - test_evolution_a0) < PRECISION_TOL, True + assert np.linalg.norm(evolution_a1 - test_evolution_a1) < PRECISION_TOL, True + + +def test_kraus_to_choi(): + choi = kraus_to_choi(test_kraus) + + assert np.linalg.norm(choi - test_choi) < PRECISION_TOL, True + + +def test_kraus_to_liouville(): + liouville = kraus_to_liouville(test_kraus) + + assert np.linalg.norm(liouville - test_superop) < PRECISION_TOL, True + + +def test_liouville_to_kraus(): + kraus_ops, coefficients = liouville_to_kraus(test_superop) + + a0 = coefficients[0] * kraus_ops[0] + a1 = coefficients[1] * kraus_ops[1] + + state = random_density_matrix(2) + + evolution_a0 = a0 @ state @ a0.T.conj() + evolution_a1 = a1 @ state @ a1.T.conj() + + test_evolution_a0 = test_a0 @ state @ test_a0.T.conj() + test_evolution_a1 = test_a1 @ state @ test_a1.T.conj() + + assert np.linalg.norm(evolution_a0 - test_evolution_a0) < PRECISION_TOL, True + assert np.linalg.norm(evolution_a1 - test_evolution_a1) < PRECISION_TOL, True + + +def test_reshuffling(): + from qibo.quantum_info.superoperator_transformations import _reshuffling + + reshuffled = _reshuffling(test_superop) + reshuffled = _reshuffling(reshuffled) + + assert np.linalg.norm(reshuffled - test_superop) < PRECISION_TOL, True + + reshuffled = _reshuffling(test_choi) + reshuffled = _reshuffling(reshuffled) + + assert np.linalg.norm(reshuffled - test_choi) < PRECISION_TOL, True From e1d4d12c8d7be576dfdc06a7d3870d2da40e7014 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 14:48:32 +0000 Subject: [PATCH 17/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../tests/test_quantum_info_superoperator_transformations.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/qibo/tests/test_quantum_info_superoperator_transformations.py b/src/qibo/tests/test_quantum_info_superoperator_transformations.py index 2715c78bc2..056d15169b 100644 --- a/src/qibo/tests/test_quantum_info_superoperator_transformations.py +++ b/src/qibo/tests/test_quantum_info_superoperator_transformations.py @@ -139,6 +139,7 @@ def test_unvectorization(nqubits, order): assert np.linalg.norm(matrix_test - matrix) < PRECISION_TOL + test_a0 = np.sqrt(0.4) * matrices.X test_a1 = np.sqrt(0.6) * matrices.Z test_kraus = [((0,), test_a0), ((0,), test_a1)] @@ -167,7 +168,7 @@ def test_choi_to_liouville(): def test_choi_to_kraus(): with pytest.raises(TypeError): - choi_to_kraus(test_choi, '1e-8') + choi_to_kraus(test_choi, "1e-8") with pytest.raises(ValueError): choi_to_kraus(test_choi, -1.0 * 1e-8) From fe6ea08ee05d26e1c57d2ec1c27213f1506405c7 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Tue, 7 Feb 2023 19:07:36 +0400 Subject: [PATCH 18/36] undoing previous changes --- src/qibo/gates/channels.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/qibo/gates/channels.py b/src/qibo/gates/channels.py index 1d6bc177e0..cc5f5869fd 100644 --- a/src/qibo/gates/channels.py +++ b/src/qibo/gates/channels.py @@ -192,7 +192,23 @@ def to_pauli_liouville(self, normalize: bool = False, backend=None): backend = GlobalBackend() - super_op = self.to_superop(backend=backend) + self.nqubits = 1 + max(self.target_qubits) + + if self.name != "KrausChannel": + p0 = 1 + for coeff in self.coefficients: + p0 = p0 - coeff + self.coefficients += (p0,) + self.gates += (I(*self.target_qubits),) + + super_op = np.zeros((4**self.nqubits, 4**self.nqubits), dtype="complex") + super_op = backend.cast(super_op, dtype=super_op.dtype) + for coeff, gate in zip(self.coefficients, self.gates): + kraus_op = FusedGate(*range(self.nqubits)) + kraus_op.append(gate) + kraus_op = kraus_op.asmatrix(backend) + kraus_op = coeff * np.kron(np.conj(kraus_op), kraus_op) + super_op += backend.cast(kraus_op, dtype=kraus_op.dtype) # unitary that transforms from comp basis to pauli basis U = backend.cast(comp_basis_to_pauli(self.nqubits, normalize)) From 565ea4602d0a673eb578df1b42ca7a3d692efde2 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Tue, 7 Feb 2023 19:08:02 +0400 Subject: [PATCH 19/36] renaming variables --- src/qibo/tests/test_gates_channels.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/qibo/tests/test_gates_channels.py b/src/qibo/tests/test_gates_channels.py index 804c75be09..b7af17a862 100644 --- a/src/qibo/tests/test_gates_channels.py +++ b/src/qibo/tests/test_gates_channels.py @@ -86,9 +86,9 @@ def test_unitary_channel(backend): a1 = matrices.X a2 = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]) probs = [0.4, 0.3] - matrices = [((0,), a1), ((2, 3), a2)] + matrices_ = [((0,), a1), ((2, 3), a2)] initial_state = random_density_matrix(4) - channel = gates.UnitaryChannel(probs, matrices) + channel = gates.UnitaryChannel(probs, matrices_) final_state = backend.apply_channel_density_matrix( channel, np.copy(initial_state), 4 ) @@ -114,8 +114,8 @@ def test_unitary_channel_probability_tolerance(backend): prob_pauli = param / num_terms probs = [prob_identity] + [prob_pauli] * (num_terms - 1) probs = np.array(probs, dtype="float64") - matrices = len(probs) * [((0, 1), np.random.random((4, 4)))] - gate = gates.UnitaryChannel(probs, matrices) + matrices_ = len(probs) * [((0, 1), np.random.random((4, 4)))] + gate = gates.UnitaryChannel(probs, matrices_) def test_unitary_channel_errors(): @@ -123,16 +123,16 @@ def test_unitary_channel_errors(): a1 = matrices.X a2 = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]) probs = [0.4, 0.3] - matrices = [((0,), a1), ((2, 3), a2)] + matrices_ = [((0,), a1), ((2, 3), a2)] # Invalid probability length with pytest.raises(ValueError): - gate = gates.UnitaryChannel([0.1, 0.3, 0.2], matrices) + gate = gates.UnitaryChannel([0.1, 0.3, 0.2], matrices_) # Probability > 1 with pytest.raises(ValueError): - gate = gates.UnitaryChannel([1.1, 0.2], matrices) + gate = gates.UnitaryChannel([1.1, 0.2], matrices_) # Probability sum < 0 with pytest.raises(ValueError): - gate = gates.UnitaryChannel([0.0, 0.0], matrices) + gate = gates.UnitaryChannel([0.0, 0.0], matrices_) def test_pauli_noise_channel(backend): From f9144cf394b098f93cec2a0968ecaaaaf8a1c6eb Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Tue, 7 Feb 2023 20:13:51 +0400 Subject: [PATCH 20/36] fix coverage --- src/qibo/tests/test_gates_channels.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/qibo/tests/test_gates_channels.py b/src/qibo/tests/test_gates_channels.py index b7af17a862..c203dc7a5d 100644 --- a/src/qibo/tests/test_gates_channels.py +++ b/src/qibo/tests/test_gates_channels.py @@ -103,6 +103,7 @@ def test_unitary_channel(backend): ) backend.assert_allclose(final_state, target_state) + channel.to_choi(backend=backend) def test_unitary_channel_probability_tolerance(backend): """Create ``UnitaryChannel`` with probability sum within tolerance (see #562).""" From 96bb0b588078194df793ed349e6d75f334ae0728 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 16:14:11 +0000 Subject: [PATCH 21/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/tests/test_gates_channels.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/qibo/tests/test_gates_channels.py b/src/qibo/tests/test_gates_channels.py index c203dc7a5d..a587fde3c7 100644 --- a/src/qibo/tests/test_gates_channels.py +++ b/src/qibo/tests/test_gates_channels.py @@ -105,6 +105,7 @@ def test_unitary_channel(backend): channel.to_choi(backend=backend) + def test_unitary_channel_probability_tolerance(backend): """Create ``UnitaryChannel`` with probability sum within tolerance (see #562).""" nqubits = 2 From 76cfa649dd09e3a0f0341225fe65abafb2edc7ee Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Wed, 8 Feb 2023 10:39:33 +0400 Subject: [PATCH 22/36] adding note about Kraus ops --- doc/source/api-reference/qibo.rst | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/doc/source/api-reference/qibo.rst b/doc/source/api-reference/qibo.rst index 6902c0b55e..68845932c5 100644 --- a/doc/source/api-reference/qibo.rst +++ b/doc/source/api-reference/qibo.rst @@ -1161,6 +1161,7 @@ Superoperator Transformations Functions used to convert superoperators among their possible representations. + Vectorization """"""""""""" @@ -1188,7 +1189,17 @@ Choi to Liouville Choi to Kraus """"""""""""" -.. autofunction:: qibo.quantum_info.choi_to_kraus +.. autofunction:: qibo.quantum_info.superoperator_transformations.choi_to_kraus + +.. note:: + Due to the spectral decomposition subroutine in this function, the resulting Kraus + operators :math:`\{K_{\alpha}\}_{\alpha}` might contain global phases. That + implies these operators are not exactly equal to the "true" Kraus operators + :math:`\{K_{\alpha}^{(\text{ideal})}\}_{\alpha}`. However, since these are + global phases, the operators' actions are the same, i.e. + + .. math:: + K_{\alpha} \, \rho \, K_{\alpha}^{\dagger} = K_{\alpha}^{\text{(ideal)}} \, \rho \,\, (K_{\alpha}^{\text{(ideal)}})^{\dagger} \,\,\,\,\, , \,\, \forall \, \alpha Kraus to Choi @@ -1208,6 +1219,16 @@ Liouville to Kraus .. autofunction:: qibo.quantum_info.liouville_to_kraus +.. note:: + Due to the spectral decomposition subroutine in this function, the resulting Kraus + operators :math:`\{K_{\alpha}\}_{\alpha}` might contain global phases. That + implies these operators are not exactly equal to the "true" Kraus operators + :math:`\{K_{\alpha}^{(\text{ideal})}\}_{\alpha}`. However, since these are + global phases, the operators' actions are the same, i.e. + + .. math:: + K_{\alpha} \, \rho \, K_{\alpha}^{\dagger} = K_{\alpha}^{\text{(ideal)}} \, \rho \,\, (K_{\alpha}^{\text{(ideal)}})^{\dagger} \,\,\,\,\, , \,\, \forall \, \alpha + Utility Functions ^^^^^^^^^^^^^^^^^ From 41b999a07be6a0d80e4e376311bd7a2ac99e92fd Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Wed, 8 Feb 2023 13:51:49 +0400 Subject: [PATCH 23/36] implementation for row- and column- vectorizations --- .../superoperator_transformations.py | 102 +++++++++++++++--- ...ntum_info_superoperator_transformations.py | 18 +++- 2 files changed, 98 insertions(+), 22 deletions(-) diff --git a/src/qibo/quantum_info/superoperator_transformations.py b/src/qibo/quantum_info/superoperator_transformations.py index da6fc58c86..d9e4723d42 100644 --- a/src/qibo/quantum_info/superoperator_transformations.py +++ b/src/qibo/quantum_info/superoperator_transformations.py @@ -114,35 +114,48 @@ def unvectorization(state, order: str = "row"): return state -def liouville_to_choi(super_op): +def liouville_to_choi(super_op, order: str = "row"): """Convert Liouville representation of quantum channel to its Choi representation. Args: super_op: Liouville representation of quanutm channel. - + order (str, optional): If ``"row"``, reshuffling is performed + with respect to row-wise vectorization. If ``"column"``, + reshuffling is performed with respect to column-wise + vectorization. If ``"system"``, operator is converted to + a representation based on row vectorization, reshuffled, + and then converted back to its representation with + respect to system-wise vectorization. Default is ``"row"``. Returns: ndarray: Choi representation of quantum channel. """ - return _reshuffling(super_op) + return _reshuffling(super_op, order=order) -def choi_to_liouville(choi_super_op): +def choi_to_liouville(choi_super_op, order: str = "row"): """Convert Choi representation of quantum channel to its Liouville representation. Args: choi_super_op: Choi representation of quanutm channel. + order (str, optional): If ``"row"``, reshuffling is performed + with respect to row-wise vectorization. If ``"column"``, + reshuffling is performed with respect to column-wise + vectorization. If ``"system"``, operator is converted to + a representation based on row vectorization, reshuffled, + and then converted back to its representation with + respect to system-wise vectorization. Default is ``"row"``. Returns: ndarray: Liouville representation of quantum channel. """ - return _reshuffling(choi_super_op) + return _reshuffling(choi_super_op, order=order) -def choi_to_kraus(choi_super_op, precision_tol: float = None): +def choi_to_kraus(choi_super_op, precision_tol: float = None, order: str = "row"): """Convert Choi representation of a quantum channel into Kraus operators. Args: @@ -152,6 +165,13 @@ def choi_to_kraus(choi_super_op, precision_tol: float = None): :math:`\\lambda < \\text{precision_tol}` is set to 0 (zero). If ``None``, ``precision_tol`` defaults to ``qibo.config.PRECISION_TOL=1e-8``. Defaults to ``None``. + order (str, optional): If ``"row"``, reshuffling is performed + with respect to row-wise vectorization. If ``"column"``, + reshuffling is performed with respect to column-wise + vectorization. If ``"system"``, operator is converted to + a representation based on row vectorization, reshuffled, + and then converted back to its representation with + respect to system-wise vectorization. Default is ``"row"``. Returns: (ndarray, ndarray): Kraus operators of quantum channel and their @@ -183,7 +203,7 @@ def choi_to_kraus(choi_super_op, precision_tol: float = None): kraus_ops, coefficients = list(), list() for eig, kraus in zip(eigenvalues, eigenvectors): if np.abs(eig) > precision_tol: - kraus_ops.append(unvectorization(kraus)) + kraus_ops.append(unvectorization(kraus, order=order)) coefficients.append(np.sqrt(eig)) kraus_ops = np.array(kraus_ops) @@ -192,7 +212,7 @@ def choi_to_kraus(choi_super_op, precision_tol: float = None): return kraus_ops, coefficients -def kraus_to_choi(kraus_ops): +def kraus_to_choi(kraus_ops, order: str = "row"): """Convert Kraus representation of quantum channel to its Choi representation. @@ -200,6 +220,13 @@ def kraus_to_choi(kraus_ops): kraus_ops (list): List of Kraus operators as pairs ``(qubits, Ak)`` where ``qubits`` refers the qubit ids that :math:`A_k` acts on and :math:`A_k` is the corresponding matrix as a ``np.ndarray``. + order (str, optional): If ``"row"``, reshuffling is performed + with respect to row-wise vectorization. If ``"column"``, + reshuffling is performed with respect to column-wise + vectorization. If ``"system"``, operator is converted to + a representation based on row vectorization, reshuffled, + and then converted back to its representation with + respect to system-wise vectorization. Default is ``"row"``. Returns: ndarray: Choi representation of the Kraus channel. @@ -218,31 +245,38 @@ def kraus_to_choi(kraus_ops): kraus_op = FusedGate(*range(nqubits)) kraus_op.append(gate) kraus_op = kraus_op.asmatrix(backend) - kraus_op = vectorization(kraus_op) + kraus_op = vectorization(kraus_op, order=order) super_op += np.outer(kraus_op, np.conj(kraus_op)) del kraus_op return super_op -def kraus_to_liouville(kraus_ops): +def kraus_to_liouville(kraus_ops, order: str = "row"): """Convert from Kraus representation to Liouville representation. Args: kraus_ops (list): List of Kraus operators as pairs ``(qubits, Ak)`` where ``qubits`` refers the qubit ids that :math:`A_k` acts on and :math:`A_k` is the corresponding matrix as a ``np.ndarray``. + order (str, optional): If ``"row"``, reshuffling is performed + with respect to row-wise vectorization. If ``"column"``, + reshuffling is performed with respect to column-wise + vectorization. If ``"system"``, operator is converted to + a representation based on row vectorization, reshuffled, + and then converted back to its representation with + respect to system-wise vectorization. Default is ``"row"``. Returns: ndarray: Liouville representation of quantum channel. """ - super_op = kraus_to_choi(kraus_ops) - super_op = choi_to_liouville(super_op) + super_op = kraus_to_choi(kraus_ops, order=order) + super_op = choi_to_liouville(super_op, order=order) return super_op -def liouville_to_kraus(super_op, precision_tol: float = None): +def liouville_to_kraus(super_op, precision_tol: float = None, order: str = "row"): """Convert Liouville representation of a quantum channel to its Kraus representation. It uses the Choi representation as an intermediate step. @@ -254,32 +288,66 @@ def liouville_to_kraus(super_op, precision_tol: float = None): :math:`\\lambda < \\text{precision_tol}` is set to 0 (zero). If ``None``, ``precision_tol`` defaults to ``qibo.config.PRECISION_TOL=1e-8``. Defaults to None. + order (str, optional): If ``"row"``, reshuffling is performed + with respect to row-wise vectorization. If ``"column"``, + reshuffling is performed with respect to column-wise + vectorization. If ``"system"``, operator is converted to + a representation based on row vectorization, reshuffled, + and then converted back to its representation with + respect to system-wise vectorization. Default is ``"row"``. Returns: (ndarray, ndarray): Kraus operators of quantum channel and their respective coefficients. """ - choi_super_op = liouville_to_choi(super_op) - kraus_ops, coefficients = choi_to_kraus(choi_super_op, precision_tol) + choi_super_op = liouville_to_choi(super_op, order=order) + kraus_ops, coefficients = choi_to_kraus(choi_super_op, precision_tol, order=order) return kraus_ops, coefficients -def _reshuffling(super_op): +def _reshuffling(super_op, order: str = "row"): """Reshuffling operation used to convert Lioville representation of quantum channels to their Choi representation (and vice-versa). Args: super_op (ndarray): Liouville (Choi) representation of a quantum channel. + order (str, optional): If ``"row"``, reshuffling is performed + with respect to row-wise vectorization. If ``"column"``, + reshuffling is performed with respect to column-wise + vectorization. If ``"system"``, operator is converted to + a representation based on row vectorization, reshuffled, + and then converted back to its representation with + respect to system-wise vectorization. Default is ``"row"``. Returns: ndarray: Choi (Liouville) representation of the quantum channel. """ + + if not isinstance(order, str): + raise_error(TypeError, f"order must be type str, but it is type {type(order)}.") + + orders = ["row", "column", "system"] + if order not in orders: + raise_error( + ValueError, + f"order must be either 'row' or 'column' or 'system', but it is {order}.", + ) + del orders + + if order == "system": + raise_error( + NotImplementedError, "reshuffling not implemented for system vectorization." + ) + d = int(np.sqrt(super_op.shape[0])) super_op = np.reshape(super_op, [d] * 4) - super_op = np.swapaxes(super_op, 0, 3) + + axes = [1, 2] if order == "row" else [0, 3] + super_op = np.swapaxes(super_op, *axes) + super_op = np.reshape(super_op, [d**2, d**2]) return super_op diff --git a/src/qibo/tests/test_quantum_info_superoperator_transformations.py b/src/qibo/tests/test_quantum_info_superoperator_transformations.py index 056d15169b..2f13f6c823 100644 --- a/src/qibo/tests/test_quantum_info_superoperator_transformations.py +++ b/src/qibo/tests/test_quantum_info_superoperator_transformations.py @@ -219,15 +219,23 @@ def test_liouville_to_kraus(): assert np.linalg.norm(evolution_a1 - test_evolution_a1) < PRECISION_TOL, True -def test_reshuffling(): +@pytest.mark.parametrize("order", ["row", "column"]) +def test_reshuffling(order): from qibo.quantum_info.superoperator_transformations import _reshuffling - reshuffled = _reshuffling(test_superop) - reshuffled = _reshuffling(reshuffled) + with pytest.raises(TypeError): + _reshuffling(test_superop, True) + with pytest.raises(ValueError): + _reshuffling(test_superop, "sustem") + with pytest.raises(NotImplementedError): + _reshuffling(test_superop, "system") + + reshuffled = _reshuffling(test_superop, order) + reshuffled = _reshuffling(reshuffled, order) assert np.linalg.norm(reshuffled - test_superop) < PRECISION_TOL, True - reshuffled = _reshuffling(test_choi) - reshuffled = _reshuffling(reshuffled) + reshuffled = _reshuffling(test_choi, order) + reshuffled = _reshuffling(reshuffled, order) assert np.linalg.norm(reshuffled - test_choi) < PRECISION_TOL, True From 1af9bdde211a202c329f593d42d1c8d6f44ae503 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Wed, 8 Feb 2023 14:24:34 +0400 Subject: [PATCH 24/36] add note to `vec` and `unvec` --- doc/source/api-reference/qibo.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/source/api-reference/qibo.rst b/doc/source/api-reference/qibo.rst index 68845932c5..e6661e7741 100644 --- a/doc/source/api-reference/qibo.rst +++ b/doc/source/api-reference/qibo.rst @@ -1167,12 +1167,22 @@ Vectorization .. autofunction:: qibo.quantum_info.vectorization +.. note:: + Due to `numpy` limitations on handling transposition of tensors, + this function will not work when the number of qubits :math:`n` + is such that :math:`n > 16`. + Unvectorization """"""""""""""" .. autofunction:: qibo.quantum_info.unvectorization +.. note:: + Due to `numpy` limitations on handling transposition of tensors, + this function will not work when the number of qubits :math:`n` + is such that :math:`n > 16`. + Liouville to Choi """"""""""""""""" From 86a8d8273a03cdae9f05e99dc15406cd31d2d9fa Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Wed, 8 Feb 2023 14:59:17 +0400 Subject: [PATCH 25/36] raises error if Choi operator is not Hermitian --- .../superoperator_transformations.py | 7 ++++ ...ntum_info_superoperator_transformations.py | 35 ++++++++++++------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/qibo/quantum_info/superoperator_transformations.py b/src/qibo/quantum_info/superoperator_transformations.py index d9e4723d42..805f0c9102 100644 --- a/src/qibo/quantum_info/superoperator_transformations.py +++ b/src/qibo/quantum_info/superoperator_transformations.py @@ -195,6 +195,13 @@ def choi_to_kraus(choi_super_op, precision_tol: float = None, order: str = "row" precision_tol = PRECISION_TOL + norm = np.linalg.norm(choi_super_op - choi_super_op.T.conj()) + if norm > PRECISION_TOL: + raise_error( + ValueError, + "Input operator is not Hermitian." + ) + # using eigh because Choi representation is, # in theory, always Hermitian eigenvalues, eigenvectors = np.linalg.eigh(choi_super_op) diff --git a/src/qibo/tests/test_quantum_info_superoperator_transformations.py b/src/qibo/tests/test_quantum_info_superoperator_transformations.py index 2f13f6c823..c28f9e337a 100644 --- a/src/qibo/tests/test_quantum_info_superoperator_transformations.py +++ b/src/qibo/tests/test_quantum_info_superoperator_transformations.py @@ -154,25 +154,33 @@ def test_unvectorization(nqubits, order): test_choi = np.reshape(test_superop, [2] * 4).swapaxes(0, 3).reshape([4, 4]) -def test_liouville_to_choi(): - choi = liouville_to_choi(test_superop) +@pytest.mark.parametrize("order", ["row", "column"]) +def test_liouville_to_choi(order): + choi = liouville_to_choi(test_superop, order) assert np.linalg.norm(choi - test_choi) < PRECISION_TOL, True -def test_choi_to_liouville(): - liouville = choi_to_liouville(test_choi) +@pytest.mark.parametrize("order", ["row", "column"]) +def test_choi_to_liouville(order): + liouville = choi_to_liouville(test_choi, order=order) assert np.linalg.norm(liouville - test_superop) < PRECISION_TOL, True -def test_choi_to_kraus(): +@pytest.mark.parametrize("order", ["row", "column"]) +def test_choi_to_kraus(order): with pytest.raises(TypeError): choi_to_kraus(test_choi, "1e-8") with pytest.raises(ValueError): choi_to_kraus(test_choi, -1.0 * 1e-8) + with pytest.raises(ValueError): + # a random unitary is extremely unlikely to be Hermitian + # so this test should be fine + d = 2 ** 3 + choi_to_kraus(random_unitary(d)) - kraus_ops, coefficients = choi_to_kraus(test_choi) + kraus_ops, coefficients = choi_to_kraus(test_choi, order=order) a0 = coefficients[0] * kraus_ops[0] a1 = coefficients[1] * kraus_ops[1] @@ -189,20 +197,23 @@ def test_choi_to_kraus(): assert np.linalg.norm(evolution_a1 - test_evolution_a1) < PRECISION_TOL, True -def test_kraus_to_choi(): - choi = kraus_to_choi(test_kraus) +@pytest.mark.parametrize("order", ["row", "column"]) +def test_kraus_to_choi(order): + choi = kraus_to_choi(test_kraus, order=order) assert np.linalg.norm(choi - test_choi) < PRECISION_TOL, True -def test_kraus_to_liouville(): - liouville = kraus_to_liouville(test_kraus) +@pytest.mark.parametrize("order", ["row", "column"]) +def test_kraus_to_liouville(order): + liouville = kraus_to_liouville(test_kraus, order=order) assert np.linalg.norm(liouville - test_superop) < PRECISION_TOL, True -def test_liouville_to_kraus(): - kraus_ops, coefficients = liouville_to_kraus(test_superop) +@pytest.mark.parametrize("order", ["row", "column"]) +def test_liouville_to_kraus(order): + kraus_ops, coefficients = liouville_to_kraus(test_superop, order=order) a0 = coefficients[0] * kraus_ops[0] a1 = coefficients[1] * kraus_ops[1] From 34d9b6e15bad7bbb48e3eb19639e425b7d94ad8f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 8 Feb 2023 10:59:33 +0000 Subject: [PATCH 26/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/quantum_info/superoperator_transformations.py | 5 +---- .../tests/test_quantum_info_superoperator_transformations.py | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/qibo/quantum_info/superoperator_transformations.py b/src/qibo/quantum_info/superoperator_transformations.py index 805f0c9102..7eca25e143 100644 --- a/src/qibo/quantum_info/superoperator_transformations.py +++ b/src/qibo/quantum_info/superoperator_transformations.py @@ -197,10 +197,7 @@ def choi_to_kraus(choi_super_op, precision_tol: float = None, order: str = "row" norm = np.linalg.norm(choi_super_op - choi_super_op.T.conj()) if norm > PRECISION_TOL: - raise_error( - ValueError, - "Input operator is not Hermitian." - ) + raise_error(ValueError, "Input operator is not Hermitian.") # using eigh because Choi representation is, # in theory, always Hermitian diff --git a/src/qibo/tests/test_quantum_info_superoperator_transformations.py b/src/qibo/tests/test_quantum_info_superoperator_transformations.py index c28f9e337a..844631cb1f 100644 --- a/src/qibo/tests/test_quantum_info_superoperator_transformations.py +++ b/src/qibo/tests/test_quantum_info_superoperator_transformations.py @@ -177,7 +177,7 @@ def test_choi_to_kraus(order): with pytest.raises(ValueError): # a random unitary is extremely unlikely to be Hermitian # so this test should be fine - d = 2 ** 3 + d = 2**3 choi_to_kraus(random_unitary(d)) kraus_ops, coefficients = choi_to_kraus(test_choi, order=order) From 43ea4d9bcd40cbf76d263e59cd56f20e0a76dc5f Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 9 Feb 2023 13:51:01 +0400 Subject: [PATCH 27/36] inclusion of non-CP maps in `choi_to_kraus()` --- .../superoperator_transformations.py | 85 +++++++++++++++---- ...ntum_info_superoperator_transformations.py | 69 +++++++++++++-- 2 files changed, 129 insertions(+), 25 deletions(-) diff --git a/src/qibo/quantum_info/superoperator_transformations.py b/src/qibo/quantum_info/superoperator_transformations.py index 7eca25e143..09dbe85743 100644 --- a/src/qibo/quantum_info/superoperator_transformations.py +++ b/src/qibo/quantum_info/superoperator_transformations.py @@ -155,7 +155,12 @@ def choi_to_liouville(choi_super_op, order: str = "row"): return _reshuffling(choi_super_op, order=order) -def choi_to_kraus(choi_super_op, precision_tol: float = None, order: str = "row"): +def choi_to_kraus( + choi_super_op, + precision_tol: float = None, + order: str = "row", + validate_CP: bool = True, +): """Convert Choi representation of a quantum channel into Kraus operators. Args: @@ -172,10 +177,16 @@ def choi_to_kraus(choi_super_op, precision_tol: float = None, order: str = "row" a representation based on row vectorization, reshuffled, and then converted back to its representation with respect to system-wise vectorization. Default is ``"row"``. + validate_CP (bool, optional): If ``True``, checks if ``choi_super_op`` + is a completely positive map. If ``False``, it assumes that + ``choi_super_op`` is completely positive (and Hermitian). + Defaults to ``True``. Returns: - (ndarray, ndarray): Kraus operators of quantum channel and their - respective coefficients. + (ndarray, ndarray) or (ndarray, ndarray, ndarray): Kraus operators of + quantum channel and their respective coefficients. If map is non-CP, + then function returns left- and right-generalized Kraus operators + as well as the square root of their corresponding singular values. """ if precision_tol is not None and not isinstance(precision_tol, float): @@ -195,25 +206,63 @@ def choi_to_kraus(choi_super_op, precision_tol: float = None, order: str = "row" precision_tol = PRECISION_TOL - norm = np.linalg.norm(choi_super_op - choi_super_op.T.conj()) - if norm > PRECISION_TOL: - raise_error(ValueError, "Input operator is not Hermitian.") + if not isinstance(validate_CP, bool): + raise_error( + TypeError, + f"validate_CP must be type bool, but it is type {type(validate_CP)}.", + ) + + non_CP = False - # using eigh because Choi representation is, - # in theory, always Hermitian - eigenvalues, eigenvectors = np.linalg.eigh(choi_super_op) - eigenvectors = np.transpose(eigenvectors) + if validate_CP: + norm = np.linalg.norm(choi_super_op - np.transpose(np.conj(choi_super_op))) + if norm > PRECISION_TOL: + non_CP = True + else: + # using eigh because, in this case, choi_super_op is + # *already confirmed* to be Hermitian + eigenvalues, eigenvectors = np.linalg.eigh(choi_super_op) + eigenvectors = np.transpose(eigenvectors) - kraus_ops, coefficients = list(), list() - for eig, kraus in zip(eigenvalues, eigenvectors): - if np.abs(eig) > precision_tol: - kraus_ops.append(unvectorization(kraus, order=order)) - coefficients.append(np.sqrt(eig)) + non_CP = True if any(eigenvalues < -PRECISION_TOL) else False + else: + # using eigh because, in this case, choi_super_op is + # *assumed* to be Hermitian + eigenvalues, eigenvectors = np.linalg.eigh(choi_super_op) + eigenvectors = np.transpose(eigenvectors) - kraus_ops = np.array(kraus_ops) - coefficients = np.array(coefficients) + if non_CP: + from warnings import warn - return kraus_ops, coefficients + warn("Input choi_super_op is a non-completely positive map.") + + # using singular value decomposition because choi_super_op is non-CP + U, S, V = np.linalg.svd(choi_super_op) + U = np.transpose(U) + S = np.sqrt(S) + V = np.conj(V) + + kraus_left, kraus_right = list(), list() + for eigenvector_left, eigenvector_right in zip(U, V): + kraus_left.append(unvectorization(eigenvector_left, order=order)) + kraus_right.append(unvectorization(eigenvector_right, order=order)) + + kraus_left = np.array(kraus_left) + kraus_right = np.array(kraus_right) + + return kraus_left, kraus_right, S + else: + # when choi_super_op is CP + kraus_ops, coefficients = list(), list() + for eig, kraus in zip(eigenvalues, eigenvectors): + if np.abs(eig) > precision_tol: + kraus_ops.append(unvectorization(kraus, order=order)) + coefficients.append(np.sqrt(eig)) + + kraus_ops = np.array(kraus_ops) + coefficients = np.array(coefficients) + + return kraus_ops, coefficients def kraus_to_choi(kraus_ops, order: str = "row"): diff --git a/src/qibo/tests/test_quantum_info_superoperator_transformations.py b/src/qibo/tests/test_quantum_info_superoperator_transformations.py index 844631cb1f..b9216d640f 100644 --- a/src/qibo/tests/test_quantum_info_superoperator_transformations.py +++ b/src/qibo/tests/test_quantum_info_superoperator_transformations.py @@ -152,6 +152,42 @@ def test_unvectorization(nqubits, order): ] ) test_choi = np.reshape(test_superop, [2] * 4).swapaxes(0, 3).reshape([4, 4]) +test_unitary = random_unitary(4, seed=1) +test_kraus_left = np.array( + [ + [ + [-0.38068877 + 0.11296525j, -0.39782478 + 0.08989654j], + [-0.65281073 + 0.02893486j, -0.49884838 - 0.01096303j], + ], + [ + [0.35475606 - 0.43422396j, 0.13898893 + 0.04923652j], + [0.19211425 + 0.29348959j, -0.70049065 - 0.22388468j], + ], + [ + [-0.65483846 + 0.11750935j, 0.1586421 + 0.39708145j], + [0.41066392 + 0.09820736j, -0.05065834 - 0.44009243j], + ], + [ + [-0.15692373 - 0.24630405j, 0.79248345 + 0.03226286j], + [-0.51702235 + 0.0659943j, 0.118193 + 0.00118186j], + ], + ] +) +test_kraus_right = np.array( + [ + [[-0.0 - 0.0j, 0.34874292 - 0.0j], [-0.92998111 - 0.0j, 0.11624764 - 0.0j]], + [ + [0.0 - 0.0j, 0.32088598 + 0.09012539j], + [0.02665682 - 0.03748461j, -0.74940337 - 0.57025303j], + ], + [[-1.0 - 0.0j, -0.0 - 0.0j], [0.0 - 0.0j, -0.0 - 0.0j]], + [ + [-0.0 - 0.0j, 0.85328705 + 0.1979626j], + [0.35119646 + 0.09838952j, 0.24971055 + 0.19322839j], + ], + ] +) +test_coefficients = np.ones(4) @pytest.mark.parametrize("order", ["row", "column"]) @@ -168,19 +204,19 @@ def test_choi_to_liouville(order): assert np.linalg.norm(liouville - test_superop) < PRECISION_TOL, True +@pytest.mark.parametrize("validate_CP", [False, True]) @pytest.mark.parametrize("order", ["row", "column"]) -def test_choi_to_kraus(order): +def test_choi_to_kraus(order, validate_CP): with pytest.raises(TypeError): choi_to_kraus(test_choi, "1e-8") with pytest.raises(ValueError): choi_to_kraus(test_choi, -1.0 * 1e-8) - with pytest.raises(ValueError): - # a random unitary is extremely unlikely to be Hermitian - # so this test should be fine - d = 2**3 - choi_to_kraus(random_unitary(d)) + with pytest.raises(TypeError): + choi_to_kraus(test_choi, validate_CP="True") - kraus_ops, coefficients = choi_to_kraus(test_choi, order=order) + kraus_ops, coefficients = choi_to_kraus( + test_choi, order=order, validate_CP=validate_CP + ) a0 = coefficients[0] * kraus_ops[0] a1 = coefficients[1] * kraus_ops[1] @@ -196,6 +232,25 @@ def test_choi_to_kraus(order): assert np.linalg.norm(evolution_a0 - test_evolution_a0) < PRECISION_TOL, True assert np.linalg.norm(evolution_a1 - test_evolution_a1) < PRECISION_TOL, True + if validate_CP: + kraus_left, kraus_right, coefficients = choi_to_kraus( + test_unitary, order="row", validate_CP=validate_CP + ) + + for test_left, left, test_right, right, test_coeff, coeff in zip( + test_kraus_left, + kraus_left, + test_kraus_right, + kraus_right, + test_coefficients, + coefficients, + ): + state = random_density_matrix(2) + evolution = coeff**2 * left @ state @ right.T.conj() + test_evolution = test_coeff**2 * test_left @ state @ test_right.T.conj() + + assert np.linalg.norm(evolution - test_evolution) < PRECISION_TOL, True + @pytest.mark.parametrize("order", ["row", "column"]) def test_kraus_to_choi(order): From 58880c3d7050612df74ab04cd87d490dd517f64e Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 9 Feb 2023 14:05:16 +0400 Subject: [PATCH 28/36] include note about `validate_CP` flag --- doc/source/api-reference/qibo.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/source/api-reference/qibo.rst b/doc/source/api-reference/qibo.rst index e6661e7741..34c14a1236 100644 --- a/doc/source/api-reference/qibo.rst +++ b/doc/source/api-reference/qibo.rst @@ -1211,6 +1211,12 @@ Choi to Kraus .. math:: K_{\alpha} \, \rho \, K_{\alpha}^{\dagger} = K_{\alpha}^{\text{(ideal)}} \, \rho \,\, (K_{\alpha}^{\text{(ideal)}})^{\dagger} \,\,\,\,\, , \,\, \forall \, \alpha +.. note:: + User can set `validate_CP=False` in order to speed up execution by not checking if + input map `choi_super_op` is completely positive (CP) and Hermitian. However, that may + lead to erroneous outputs if `choi_super_op` is not guaranteed to be CP. We advise users + to either set this flag carefully or leave it in its default setting (`validate_CP=True`). + Kraus to Choi """"""""""""" From 0f0fc19cdc88f161a77214341b2c1a79b8815d67 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 9 Feb 2023 14:58:15 +0400 Subject: [PATCH 29/36] fix test bug --- .../superoperator_transformations.py | 3 +- ...ntum_info_superoperator_transformations.py | 52 ++++++++----------- 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/src/qibo/quantum_info/superoperator_transformations.py b/src/qibo/quantum_info/superoperator_transformations.py index 09dbe85743..5a208ee9ac 100644 --- a/src/qibo/quantum_info/superoperator_transformations.py +++ b/src/qibo/quantum_info/superoperator_transformations.py @@ -212,8 +212,6 @@ def choi_to_kraus( f"validate_CP must be type bool, but it is type {type(validate_CP)}.", ) - non_CP = False - if validate_CP: norm = np.linalg.norm(choi_super_op - np.transpose(np.conj(choi_super_op))) if norm > PRECISION_TOL: @@ -226,6 +224,7 @@ def choi_to_kraus( non_CP = True if any(eigenvalues < -PRECISION_TOL) else False else: + non_CP = False # using eigh because, in this case, choi_super_op is # *assumed* to be Hermitian eigenvalues, eigenvectors = np.linalg.eigh(choi_super_op) diff --git a/src/qibo/tests/test_quantum_info_superoperator_transformations.py b/src/qibo/tests/test_quantum_info_superoperator_transformations.py index b9216d640f..1b21876db4 100644 --- a/src/qibo/tests/test_quantum_info_superoperator_transformations.py +++ b/src/qibo/tests/test_quantum_info_superoperator_transformations.py @@ -152,42 +152,31 @@ def test_unvectorization(nqubits, order): ] ) test_choi = np.reshape(test_superop, [2] * 4).swapaxes(0, 3).reshape([4, 4]) -test_unitary = random_unitary(4, seed=1) +test_non_CP = np.array( + [ + [0.20031418, 0.37198771, 0.05642046, 0.37127765], + [0.15812476, 0.21466209, 0.41971479, 0.20749836], + [0.29408764, 0.01474688, 0.40320494, 0.28796054], + [0.17587069, 0.42052825, 0.16171658, 0.24188449], + ] +) test_kraus_left = np.array( [ - [ - [-0.38068877 + 0.11296525j, -0.39782478 + 0.08989654j], - [-0.65281073 + 0.02893486j, -0.49884838 - 0.01096303j], - ], - [ - [0.35475606 - 0.43422396j, 0.13898893 + 0.04923652j], - [0.19211425 + 0.29348959j, -0.70049065 - 0.22388468j], - ], - [ - [-0.65483846 + 0.11750935j, 0.1586421 + 0.39708145j], - [0.41066392 + 0.09820736j, -0.05065834 - 0.44009243j], - ], - [ - [-0.15692373 - 0.24630405j, 0.79248345 + 0.03226286j], - [-0.51702235 + 0.0659943j, 0.118193 + 0.00118186j], - ], + [[-0.50326669, -0.50293148], [-0.49268667, -0.50104133]], + [[-0.54148474, 0.32931326], [0.64742923, -0.42329947]], + [[0.49035364, -0.62330138], [0.495577, -0.35419223]], + [[-0.46159531, -0.50010808], [0.30413594, 0.66657558]], ] ) test_kraus_right = np.array( [ - [[-0.0 - 0.0j, 0.34874292 - 0.0j], [-0.92998111 - 0.0j, 0.11624764 - 0.0j]], - [ - [0.0 - 0.0j, 0.32088598 + 0.09012539j], - [0.02665682 - 0.03748461j, -0.74940337 - 0.57025303j], - ], - [[-1.0 - 0.0j, -0.0 - 0.0j], [0.0 - 0.0j, -0.0 - 0.0j]], - [ - [-0.0 - 0.0j, 0.85328705 + 0.1979626j], - [0.35119646 + 0.09838952j, 0.24971055 + 0.19322839j], - ], + [[-0.41111026, -0.51035787], [-0.51635098, -0.55127567]], + [[0.13825514, -0.69451163], [0.69697805, -0.11296331]], + [[0.43827885, -0.49057101], [-0.48197173, 0.57875296]], + [[0.78726458, 0.12856331], [-0.12371948, -0.59023677]], ] ) -test_coefficients = np.ones(4) +test_coefficients = np.array([1.002719 , 0.65635444, 0.43548 , 0.21124177]) @pytest.mark.parametrize("order", ["row", "column"]) @@ -232,9 +221,9 @@ def test_choi_to_kraus(order, validate_CP): assert np.linalg.norm(evolution_a0 - test_evolution_a0) < PRECISION_TOL, True assert np.linalg.norm(evolution_a1 - test_evolution_a1) < PRECISION_TOL, True - if validate_CP: + if validate_CP and order == "row": kraus_left, kraus_right, coefficients = choi_to_kraus( - test_unitary, order="row", validate_CP=validate_CP + test_non_CP, order=order, validate_CP=validate_CP ) for test_left, left, test_right, right, test_coeff, coeff in zip( @@ -245,6 +234,9 @@ def test_choi_to_kraus(order, validate_CP): test_coefficients, coefficients, ): + + print(test_left) + print(left) state = random_density_matrix(2) evolution = coeff**2 * left @ state @ right.T.conj() test_evolution = test_coeff**2 * test_left @ state @ test_right.T.conj() From d4b0512b7ca54e0e3e119777942011428151c997 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 9 Feb 2023 10:58:33 +0000 Subject: [PATCH 30/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../tests/test_quantum_info_superoperator_transformations.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/qibo/tests/test_quantum_info_superoperator_transformations.py b/src/qibo/tests/test_quantum_info_superoperator_transformations.py index 1b21876db4..1c62eeb722 100644 --- a/src/qibo/tests/test_quantum_info_superoperator_transformations.py +++ b/src/qibo/tests/test_quantum_info_superoperator_transformations.py @@ -176,7 +176,7 @@ def test_unvectorization(nqubits, order): [[0.78726458, 0.12856331], [-0.12371948, -0.59023677]], ] ) -test_coefficients = np.array([1.002719 , 0.65635444, 0.43548 , 0.21124177]) +test_coefficients = np.array([1.002719, 0.65635444, 0.43548, 0.21124177]) @pytest.mark.parametrize("order", ["row", "column"]) @@ -234,7 +234,6 @@ def test_choi_to_kraus(order, validate_CP): test_coefficients, coefficients, ): - print(test_left) print(left) state = random_density_matrix(2) From f5ee267cc02317d10b26950eb482d0b1c12e0d8c Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 9 Feb 2023 15:12:44 +0400 Subject: [PATCH 31/36] remove print from test --- .../tests/test_quantum_info_superoperator_transformations.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/qibo/tests/test_quantum_info_superoperator_transformations.py b/src/qibo/tests/test_quantum_info_superoperator_transformations.py index 1c62eeb722..3b51550164 100644 --- a/src/qibo/tests/test_quantum_info_superoperator_transformations.py +++ b/src/qibo/tests/test_quantum_info_superoperator_transformations.py @@ -234,8 +234,6 @@ def test_choi_to_kraus(order, validate_CP): test_coefficients, coefficients, ): - print(test_left) - print(left) state = random_density_matrix(2) evolution = coeff**2 * left @ state @ right.T.conj() test_evolution = test_coeff**2 * test_left @ state @ test_right.T.conj() From 2c6bb48a452ba01fa582db769c4c4c9760c8d319 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 9 Feb 2023 16:07:50 +0400 Subject: [PATCH 32/36] standardizing output format in `choi_to_kraus()` --- .../superoperator_transformations.py | 16 ++++++++-------- ...quantum_info_superoperator_transformations.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/qibo/quantum_info/superoperator_transformations.py b/src/qibo/quantum_info/superoperator_transformations.py index 5a208ee9ac..2ba7f7c7d7 100644 --- a/src/qibo/quantum_info/superoperator_transformations.py +++ b/src/qibo/quantum_info/superoperator_transformations.py @@ -183,10 +183,10 @@ def choi_to_kraus( Defaults to ``True``. Returns: - (ndarray, ndarray) or (ndarray, ndarray, ndarray): Kraus operators of - quantum channel and their respective coefficients. If map is non-CP, - then function returns left- and right-generalized Kraus operators - as well as the square root of their corresponding singular values. + (ndarray, ndarray): Kraus operators of quantum channel and their respective + coefficients. If map is non-CP, then function returns left- and + right-generalized Kraus operators as well as the square root of their + corresponding singular values. """ if precision_tol is not None and not isinstance(precision_tol, float): @@ -236,9 +236,9 @@ def choi_to_kraus( warn("Input choi_super_op is a non-completely positive map.") # using singular value decomposition because choi_super_op is non-CP - U, S, V = np.linalg.svd(choi_super_op) + U, coefficients, V = np.linalg.svd(choi_super_op) U = np.transpose(U) - S = np.sqrt(S) + coefficients = np.sqrt(coefficients) V = np.conj(V) kraus_left, kraus_right = list(), list() @@ -249,7 +249,7 @@ def choi_to_kraus( kraus_left = np.array(kraus_left) kraus_right = np.array(kraus_right) - return kraus_left, kraus_right, S + kraus_ops = np.array([kraus_left, kraus_right]) else: # when choi_super_op is CP kraus_ops, coefficients = list(), list() @@ -261,7 +261,7 @@ def choi_to_kraus( kraus_ops = np.array(kraus_ops) coefficients = np.array(coefficients) - return kraus_ops, coefficients + return kraus_ops, coefficients def kraus_to_choi(kraus_ops, order: str = "row"): diff --git a/src/qibo/tests/test_quantum_info_superoperator_transformations.py b/src/qibo/tests/test_quantum_info_superoperator_transformations.py index 3b51550164..7da548754c 100644 --- a/src/qibo/tests/test_quantum_info_superoperator_transformations.py +++ b/src/qibo/tests/test_quantum_info_superoperator_transformations.py @@ -222,7 +222,7 @@ def test_choi_to_kraus(order, validate_CP): assert np.linalg.norm(evolution_a1 - test_evolution_a1) < PRECISION_TOL, True if validate_CP and order == "row": - kraus_left, kraus_right, coefficients = choi_to_kraus( + (kraus_left, kraus_right), coefficients = choi_to_kraus( test_non_CP, order=order, validate_CP=validate_CP ) From 706b14cb9430d96961e5dfa4e65f82ec4dd5cebf Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Wed, 15 Feb 2023 15:05:45 +0400 Subject: [PATCH 33/36] updated doc --- .../superoperator_transformations.py | 146 +++++++++++++++--- ...ntum_info_superoperator_transformations.py | 11 +- 2 files changed, 127 insertions(+), 30 deletions(-) diff --git a/src/qibo/quantum_info/superoperator_transformations.py b/src/qibo/quantum_info/superoperator_transformations.py index 2ba7f7c7d7..0b26025b8b 100644 --- a/src/qibo/quantum_info/superoperator_transformations.py +++ b/src/qibo/quantum_info/superoperator_transformations.py @@ -8,7 +8,17 @@ def vectorization(state, order: str = "row"): """Returns state :math:`\\rho` in its Liouville - representation :math:`\\ket{\\rho}`. + representation :math:`|\\rho\\rangle\\rangle`. + + If ``order="row"``, then: + + .. math:: + |\\rho\\rangle\\rangle = \\sum_{k, l} \\, \\rho_{kl} \\, \\ket{k} \\otimes \\ket{l} + + If ``order="column"``, then: + + .. math:: + |\\rho\\rangle\\rangle = \\sum_{k, l} \\, \\rho_{kl} \\, \\ket{l} \\otimes \\ket{k} Args: state: state vector or density matrix. @@ -65,10 +75,17 @@ def vectorization(state, order: str = "row"): def unvectorization(state, order: str = "row"): """Returns state :math:`\\rho` from its Liouville - representation :math:`\\ket{\\rho}`. + representation :math:`|\\rho\\rangle\\rangle`. This operation is + the inverse function of :func:`vectorization`, i.e. + + .. math:: + \\begin{align} + \\rho &= \\text{unvectorization}(|\\rho\\rangle\\rangle) \\nonumber \\\\ + &= \\text{unvectorization}(\\text{vectorization}(\\rho)) \\nonumber + \\end{align} Args: - state: :func:`vectorization` of a quantum state. + state: quantum state in Liouville representation. order (str, optional): If ``"row"``, unvectorization is performed row-wise. If ``"column"``, unvectorization is performed column-wise. If ``"system"``, system-wise vectorization is @@ -115,8 +132,19 @@ def unvectorization(state, order: str = "row"): def liouville_to_choi(super_op, order: str = "row"): - """Convert Liouville representation of quantum channel - to its Choi representation. + """Convert Liouville representation of quantum channel :math:`\\mathcal{E}` + to its Choi representation :math:`\\Lambda`. Indexing :math:`\\mathcal{E}` as + :math:`\\mathcal{E}_{\\alpha\\beta, \\, \\gamma\\delta} \\,\\,`, then + + If ``order="row"``: + + .. math:: + \\Lambda = \\sum_{k, l} \\, \\ketbra{k}{l} \\otimes \\mathcal{E}(\\ketbra{k}{l}) \\equiv \\mathcal{E}_{\\alpha\\gamma, \\, \\beta\\delta} + + If ``order="column"``, then: + + .. math:: + \\Lambda = \\sum_{k, l} \\, \\mathcal{E}(\\ketbra{k}{l}) \\otimes \\ketbra{k}{l} \\equiv \\mathcal{E}_{\\delta\\beta, \\, \\gamma\\alpha} Args: super_op: Liouville representation of quanutm channel. @@ -135,8 +163,20 @@ def liouville_to_choi(super_op, order: str = "row"): def choi_to_liouville(choi_super_op, order: str = "row"): - """Convert Choi representation of quantum channel - to its Liouville representation. + """Convert Choi representation :math:`\\Lambda` of quantum channel + to its Liouville representation :math:`\\mathcal{E}`. + + + If ``order="row"``, then: + + .. math:: + \\Lambda_{\\alpha\\beta, \\, \\gamma\\delta} \\mapsto \\Lambda_{\\alpha\\gamma, \\, \\beta\\delta} \\equiv \\mathcal{E} + + If ``order="column"``, then: + + .. math:: + \\Lambda_{\\alpha\\beta, \\, \\gamma\\delta} \\mapsto \\Lambda_{\\delta\\beta, \\, \\gamma\\alpha} \\equiv \\mathcal{E} + Args: choi_super_op: Choi representation of quanutm channel. @@ -161,13 +201,39 @@ def choi_to_kraus( order: str = "row", validate_CP: bool = True, ): - """Convert Choi representation of a quantum channel into Kraus operators. + """Convert Choi representation :math:`\\Lambda` of a quantum channel :math:`\\mathcal{E}` + into Kraus operators :math:`\\{ K_{\\alpha} \\}_{\\alpha}`. + + If :math:`\\mathcal{E}` is a completely positive (CP) map, then + + .. math:: + \\Lambda = \\sum_{\\alpha} \\, \\lambda_{\\alpha}^{2} \\, |\\tilde{K}_{\\alpha}\\rangle\\rangle \\langle\\langle \\tilde{K}_{\\alpha}| \\, . + + This is the spectral decomposition of :math:`\\Lambda`, Hence, the set :math:`\\{\\lambda_{\\alpha}, \\, \\tilde{K}_{\\alpha}\\}_{\\alpha}` + is found by diagonalization of :math:`\\Lambda`. The Kraus operators :math:`\\{K_{\\alpha}\\}_{\\alpha}` + are defined as + + .. math:: + K_{\\alpha} = \\lambda_{\\alpha} \\, \\text{unvectorization}(|\\tilde{K}_{\\alpha}\\rangle\\rangle) \\, . + + If :math:`\\mathcal{E}` is not CP, then spectral composition is replaced by + a singular value decomposition (SVD), i.e. + + .. math:: + \\Lambda = U \\, S \\, V^{\\dagger} \\, , + + where :math:`U` is a :math:`d^{2} \\times d^{2}` unitary matrix, :math:`S` is a + :math:`d^{2} \\times d^{2}` positive diagonal matrix containing the singular values + of :math:`\\Lambda`, and :math:`V` is a :math:`d^{2} \\times d^{2}` unitary matrix. + The Kraus coefficients are replaced by the square root of the singular values, and + :math:`U` (:math:`V`) determine the left-generalized (right-generalized) Kraus + operators. Args: choi_super_op: Choi representation of a quantum channel. precision_tol (float, optional): Precision tolerance for eigenvalues found in the spectral decomposition problem. Any eigenvalue - :math:`\\lambda < \\text{precision_tol}` is set to 0 (zero). + :math:`\\lambda <` ``precision_tol`` is set to 0 (zero). If ``None``, ``precision_tol`` defaults to ``qibo.config.PRECISION_TOL=1e-8``. Defaults to ``None``. order (str, optional): If ``"row"``, reshuffling is performed @@ -183,10 +249,12 @@ def choi_to_kraus( Defaults to ``True``. Returns: - (ndarray, ndarray): Kraus operators of quantum channel and their respective - coefficients. If map is non-CP, then function returns left- and - right-generalized Kraus operators as well as the square root of their - corresponding singular values. + (ndarray, ndarray): The set :math:`\\{K_{\\alpha}, \\, \\lambda_{\\alpha} \\}_{\\alpha}` + of Kraus operators representing the quantum channel and their respective coefficients. + If map is non-CP, then function returns the set + :math:`\\{ \\{K_{L}, \\, K_{R}\\}_{\\alpha}, \\, \\lambda_{\\alpha} \\}_{\\alpha}`, + with the left- and right-generalized Kraus operators as well as the square root of + their corresponding singular values. """ if precision_tol is not None and not isinstance(precision_tol, float): @@ -242,9 +310,9 @@ def choi_to_kraus( V = np.conj(V) kraus_left, kraus_right = list(), list() - for eigenvector_left, eigenvector_right in zip(U, V): - kraus_left.append(unvectorization(eigenvector_left, order=order)) - kraus_right.append(unvectorization(eigenvector_right, order=order)) + for coeff, eigenvector_left, eigenvector_right in zip(coefficients, U, V): + kraus_left.append(coeff * unvectorization(eigenvector_left, order=order)) + kraus_right.append(coeff * unvectorization(eigenvector_right, order=order)) kraus_left = np.array(kraus_left) kraus_right = np.array(kraus_right) @@ -255,8 +323,9 @@ def choi_to_kraus( kraus_ops, coefficients = list(), list() for eig, kraus in zip(eigenvalues, eigenvectors): if np.abs(eig) > precision_tol: - kraus_ops.append(unvectorization(kraus, order=order)) - coefficients.append(np.sqrt(eig)) + eig = np.sqrt(eig) + kraus_ops.append(eig * unvectorization(kraus, order=order)) + coefficients.append(eig) kraus_ops = np.array(kraus_ops) coefficients = np.array(coefficients) @@ -265,8 +334,11 @@ def choi_to_kraus( def kraus_to_choi(kraus_ops, order: str = "row"): - """Convert Kraus representation of quantum channel - to its Choi representation. + """Convert Kraus representation :math:`\\{K_{\\alpha}\\}_{\\alpha}` + of quantum channel to its Choi representation :math:`\\Lambda`. + + .. math:: + \\Lambda = \\sum_{\\alpha} \\, |K_{\\alpha}\\rangle\\rangle \\langle\\langle K_{\\alpha} | Args: kraus_ops (list): List of Kraus operators as pairs ``(qubits, Ak)`` @@ -305,7 +377,15 @@ def kraus_to_choi(kraus_ops, order: str = "row"): def kraus_to_liouville(kraus_ops, order: str = "row"): - """Convert from Kraus representation to Liouville representation. + """Convert from Kraus representation :math:`\\{K_{\\alpha}\\}_{\\alpha}` + of quantum channel to its Liouville representation :math:`\\mathcal{E}`. + It uses the Choi representation as an intermediate step. + + .. math:: + \\begin{align} + \\mathcal{E} &= \\sum_{\\alpha} \\, K_{\\alpha}^{*} \\otimes K_{\\alpha} \\\\ + &\\equiv \\text{choi_to_liouville}(\\text{kraus_to_choi}(\\{K_{\\alpha}\\}_{\\alpha})) + \\end{align} Args: kraus_ops (list): List of Kraus operators as pairs ``(qubits, Ak)`` @@ -329,9 +409,12 @@ def kraus_to_liouville(kraus_ops, order: str = "row"): def liouville_to_kraus(super_op, precision_tol: float = None, order: str = "row"): - """Convert Liouville representation of a quantum channel to - its Kraus representation. It uses the Choi representation as - an intermediate step. + """Convert Liouville representation :math:`\\mathcal{E}` of a quantum + channel to its Kraus representation :math:`\\{K_{\\alpha}\\}_{\\alpha}`. + It uses the Choi representation as an intermediate step. + + .. math:: + \\{K_{\\alpha}, \\, \\lambda_{\\alpha}\\}_{\\alpha} = \\text{choi_to_kraus}(\\text{liouville_to_choi}(\\mathcal{E})) Args: super_op (ndarray): Liouville representation of quantum channel. @@ -362,6 +445,21 @@ def _reshuffling(super_op, order: str = "row"): """Reshuffling operation used to convert Lioville representation of quantum channels to their Choi representation (and vice-versa). + For an operator :math:`A` with dimensions :math:`d^{2} \times d^{2}`, + the reshuffling operation consists of reshaping :math:`A` as a + 4-dimensional tensor, swapping two axes, and reshaping back to a + :math:`d^{2} \times d^{2}` matrix. + + If ``order="row"``, then: + + .. math:: + A_{\\alpha\\beta, \\, \\gamma\\delta} \\mapsto A_{\\alpha, \\, \\beta, \\, \\gamma, \\, \\delta} \\mapsto A_{\\alpha, \\, \\gamma, \\, \\beta, \\, \\delta} \\mapsto A_{\\alpha\\gamma, \\, \\beta\\delta} + + If ``order="column"``, then: + + .. math:: + A_{\\alpha\\beta, \\, \\gamma\\delta} \\mapsto A_{\\alpha, \\, \\beta, \\, \\gamma, \\, \\delta} \\mapsto A_{\\delta, \\, \\beta, \\, \\gamma, \\, \\alpha} \\mapsto A_{\\delta\\beta, \\, \\gamma\\alpha} + Args: super_op (ndarray): Liouville (Choi) representation of a quantum channel. diff --git a/src/qibo/tests/test_quantum_info_superoperator_transformations.py b/src/qibo/tests/test_quantum_info_superoperator_transformations.py index 7da548754c..8426f8d7f4 100644 --- a/src/qibo/tests/test_quantum_info_superoperator_transformations.py +++ b/src/qibo/tests/test_quantum_info_superoperator_transformations.py @@ -207,8 +207,8 @@ def test_choi_to_kraus(order, validate_CP): test_choi, order=order, validate_CP=validate_CP ) - a0 = coefficients[0] * kraus_ops[0] - a1 = coefficients[1] * kraus_ops[1] + a0 = kraus_ops[0] + a1 = kraus_ops[1] state = random_density_matrix(2) @@ -222,20 +222,19 @@ def test_choi_to_kraus(order, validate_CP): assert np.linalg.norm(evolution_a1 - test_evolution_a1) < PRECISION_TOL, True if validate_CP and order == "row": - (kraus_left, kraus_right), coefficients = choi_to_kraus( + (kraus_left, kraus_right), _ = choi_to_kraus( test_non_CP, order=order, validate_CP=validate_CP ) - for test_left, left, test_right, right, test_coeff, coeff in zip( + for test_left, left, test_right, right, test_coeff in zip( test_kraus_left, kraus_left, test_kraus_right, kraus_right, test_coefficients, - coefficients, ): state = random_density_matrix(2) - evolution = coeff**2 * left @ state @ right.T.conj() + evolution = left @ state @ right.T.conj() test_evolution = test_coeff**2 * test_left @ state @ test_right.T.conj() assert np.linalg.norm(evolution - test_evolution) < PRECISION_TOL, True From 582dbfd044fe54a328f50248ef534c3e1e9b49f4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 15 Feb 2023 11:06:33 +0000 Subject: [PATCH 34/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../superoperator_transformations.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/qibo/quantum_info/superoperator_transformations.py b/src/qibo/quantum_info/superoperator_transformations.py index 0b26025b8b..f64f2babd2 100644 --- a/src/qibo/quantum_info/superoperator_transformations.py +++ b/src/qibo/quantum_info/superoperator_transformations.py @@ -215,17 +215,17 @@ def choi_to_kraus( .. math:: K_{\\alpha} = \\lambda_{\\alpha} \\, \\text{unvectorization}(|\\tilde{K}_{\\alpha}\\rangle\\rangle) \\, . - + If :math:`\\mathcal{E}` is not CP, then spectral composition is replaced by a singular value decomposition (SVD), i.e. - + .. math:: \\Lambda = U \\, S \\, V^{\\dagger} \\, , - - where :math:`U` is a :math:`d^{2} \\times d^{2}` unitary matrix, :math:`S` is a + + where :math:`U` is a :math:`d^{2} \\times d^{2}` unitary matrix, :math:`S` is a :math:`d^{2} \\times d^{2}` positive diagonal matrix containing the singular values - of :math:`\\Lambda`, and :math:`V` is a :math:`d^{2} \\times d^{2}` unitary matrix. - The Kraus coefficients are replaced by the square root of the singular values, and + of :math:`\\Lambda`, and :math:`V` is a :math:`d^{2} \\times d^{2}` unitary matrix. + The Kraus coefficients are replaced by the square root of the singular values, and :math:`U` (:math:`V`) determine the left-generalized (right-generalized) Kraus operators. @@ -251,9 +251,9 @@ def choi_to_kraus( Returns: (ndarray, ndarray): The set :math:`\\{K_{\\alpha}, \\, \\lambda_{\\alpha} \\}_{\\alpha}` of Kraus operators representing the quantum channel and their respective coefficients. - If map is non-CP, then function returns the set + If map is non-CP, then function returns the set :math:`\\{ \\{K_{L}, \\, K_{R}\\}_{\\alpha}, \\, \\lambda_{\\alpha} \\}_{\\alpha}`, - with the left- and right-generalized Kraus operators as well as the square root of + with the left- and right-generalized Kraus operators as well as the square root of their corresponding singular values. """ @@ -377,7 +377,7 @@ def kraus_to_choi(kraus_ops, order: str = "row"): def kraus_to_liouville(kraus_ops, order: str = "row"): - """Convert from Kraus representation :math:`\\{K_{\\alpha}\\}_{\\alpha}` + """Convert from Kraus representation :math:`\\{K_{\\alpha}\\}_{\\alpha}` of quantum channel to its Liouville representation :math:`\\mathcal{E}`. It uses the Choi representation as an intermediate step. From 971742c7ce62aa72e9839103903eb008ecd4af38 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Wed, 15 Feb 2023 15:20:37 +0400 Subject: [PATCH 35/36] add reference --- doc/source/api-reference/qibo.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/source/api-reference/qibo.rst b/doc/source/api-reference/qibo.rst index 34c14a1236..bc64d5a615 100644 --- a/doc/source/api-reference/qibo.rst +++ b/doc/source/api-reference/qibo.rst @@ -1160,6 +1160,8 @@ Superoperator Transformations ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Functions used to convert superoperators among their possible representations. +For more in-depth theoretical description of the representations and transformations, +we direct the reader to `Wood, Biamonte, and Cory, Quant. Inf. Comp. 15, 0579-0811 (2015) `_. Vectorization @@ -1212,10 +1214,10 @@ Choi to Kraus K_{\alpha} \, \rho \, K_{\alpha}^{\dagger} = K_{\alpha}^{\text{(ideal)}} \, \rho \,\, (K_{\alpha}^{\text{(ideal)}})^{\dagger} \,\,\,\,\, , \,\, \forall \, \alpha .. note:: - User can set `validate_CP=False` in order to speed up execution by not checking if - input map `choi_super_op` is completely positive (CP) and Hermitian. However, that may - lead to erroneous outputs if `choi_super_op` is not guaranteed to be CP. We advise users - to either set this flag carefully or leave it in its default setting (`validate_CP=True`). + User can set ``validate_CP=False`` in order to speed up execution by not checking if + input map ``choi_super_op`` is completely positive (CP) and Hermitian. However, that may + lead to erroneous outputs if ``choi_super_op`` is not guaranteed to be CP. We advise users + to either set this flag carefully or leave it in its default setting (``validate_CP=True``). Kraus to Choi From d105f5bd8f49352022066481e439468618b99fa0 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Wed, 15 Feb 2023 15:41:26 +0400 Subject: [PATCH 36/36] fix test --- .../tests/test_quantum_info_superoperator_transformations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qibo/tests/test_quantum_info_superoperator_transformations.py b/src/qibo/tests/test_quantum_info_superoperator_transformations.py index 8426f8d7f4..9269666c46 100644 --- a/src/qibo/tests/test_quantum_info_superoperator_transformations.py +++ b/src/qibo/tests/test_quantum_info_superoperator_transformations.py @@ -258,8 +258,8 @@ def test_kraus_to_liouville(order): def test_liouville_to_kraus(order): kraus_ops, coefficients = liouville_to_kraus(test_superop, order=order) - a0 = coefficients[0] * kraus_ops[0] - a1 = coefficients[1] * kraus_ops[1] + a0 = kraus_ops[0] + a1 = kraus_ops[1] state = random_density_matrix(2)