Source code for mimiqcircuits.operations.krauschannel

#
# Copyright © 2022-2023 University of Strasbourg. All Rights Reserved.
# Copyright © 2032-2024 QPerfect. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import sympy as sp
from mimiqcircuits.operations.operation import Operation
import symengine as se
import numpy as np


[docs] class krauschannel(Operation): r"""Supertype for all N-qubit Kraus channels. A Kraus channel is a quantum operation on a density matrix :math:`\\rho` of the form: .. math:: \mathcal{E}(\rho) = \sum_k E_k \rho E_k^\dagger, where :math:`E_k` are Kraus operators that need to fulfill :math:`\sum_k E_k^\dagger E_k \leq I`. **Special Properties:** - :func:`isCPTP`: A Kraus channel is a completely positive and trace-preserving (CPTP) operation when :math:`\\sum_k E_k^\dagger E_k = I`. Currently, all noise channels are CPTP. - :func:`ismixedunitary`: A Kraus channel is called a mixed unitary channel when the Kraus operators :math:`E_k` are each proportional to a unitary matrix :math:`U_k`, i.e., when :math:`\\mathcal{E}(\\rho) = \\sum_k p_k U_k \\rho U_k^\dagger` with some probabilities :math:`0 \leq p_k \leq 1` that add up to 1 and :math:`U_k^\dagger U_k = I`. See Also: :func:`krausmatrices` :func:`unitarymatrices` :func:`probabilities` """ _name = None _num_qubits = None _num_bits = 0 _num_cregs = 0 _cregsizes = () _num_zvars = 0
[docs] def krausmatrices(self): """ Returns the Kraus matrices associated with the given Kraus channel. A mixed unitary channel is written as: .. math:: \\mathcal{E}(\\rho) = \\sum_k p_k U_k \\rho U_k^\\dagger, where :math:`U_k` are the unitary matrices returned by this function. If the Kraus channel is parametric, the matrix elements are wrapped in a symengine or sympy object. Returns: list: A list of symengine matrices representing the Kraus operators. """ return [se.Matrix(kraus.matrix().tolist()) for kraus in self.krausoperators()]
[docs] def unwrappedkrausmatrices(self): """ Returns the unitary Kraus matrices associated to the mixed unitary Kraus channel without symbolic wrapper. See Also: :func:`unitarymatrices` Example: >>> from mimiqcircuits import * >>> op = PauliX(0.2) >>> op.unwrappedkrausmatrices() [[0.894427190999916, 0] [0, 0.894427190999916] , [0, 0.447213595499958] [0.447213595499958, 0] ] """ if self.numparams() == 0: return self.krausmatrices() params = [] for name in self.parnames: v = sp.simplify(self.getparam(name)) if isinstance(v, (int, float)): params.append(v) elif isinstance(v, sp.Basic) and v.is_constant(): params.append(float(v)) else: raise ValueError(f"Undefined parameter {name} in {self.name}") return self.krausmatrices()
[docs] @classmethod def isCPTP(self): r"""Determine whether the Kraus channel is Completely Positive and Trace Preserving (CPTP). A quantum operation is CPTP if the Kraus operators fulfill the condition: .. math:: \sum_k E_k^\dagger E_k = I If the condition is: .. math:: \sum_k E_k^\dagger E_k < I then the operation is not CPTP. Note: Currently, all noise channels are considered CPTP. Parameters: krauschannel: The Kraus channel to check. Returns: bool: `True` if the channel is CPTP, `False` otherwise. """ return issubclass(self, krauschannel) and False
[docs] @classmethod def ismixedunitary(self): r"""Determine whether the quantum operation is a mixed unitary channel. A channel is considered mixed unitary if all the Kraus operators :math:`E_k` are proportional to a unitary matrix :math:`U_k`, i.e., .. math:: \mathcal{E}(\rho) = \sum_k p_k U_k \rho U_k^\dagger, with some probabilities :math:`0 \leq p_k \leq 1` that add up to 1, and :math:`U_k^\dagger U_k = I`. Parameters: krauschannel: The Kraus channel to check. Returns: bool: `True` if the channel is a mixed unitary channel, `False` otherwise. Examples: >>> from mimiqcircuits import * >>> PauliX(0.1).ismixedunitary() True >>> AmplitudeDamping(0.1).ismixedunitary() False """ return issubclass(self, krauschannel) and False
[docs] def inverse(self): raise NotImplementedError("Cannot invert Kraus channels")
[docs] def power(op, n): raise NotImplementedError("Cannot take the power of Kraus channels")
[docs] def iswrapper(self): return False
[docs] def probabilities(self): """ Returns the probabilities for each Kraus operator in a mixed unitary channel. A mixed unitary channel is written as: .. math:: \\mathcal{E}(\\rho) = \\sum_k p_k U_k \\rho U_k^\\dagger, where :math:`p_k` are the probabilities. This method is valid only for mixed unitary channels. Returns: list: A list of probabilities for each Kraus operator. """ raise ValueError( "Probabilities can only be returned from a mixed unitary channel." )
[docs] def numparams(self): return len(self._parnames)
[docs] def cumprobabilities(self): """ Returns the cumulative sum of probabilities for each Kraus operator in a mixed unitary channel. A mixed unitary channel is written as: .. math:: \\mathcal{E}(\\rho) = \\sum_k p_k U_k \\rho U_k^\\dagger, where :math:`p_k` are the probabilities. This method is valid only for mixed unitary channels. Returns: list: Cumulative probabilities for the Kraus operators. Examples: >>> from mimiqcircuits import * >>> Depolarizing1(0.1).cumprobabilities() [0.9, 0.933333333333333, 0.966666666666667, 1.0] """ return self._cumprobabilities(self.ismixedunitary())
def _cumprobabilities(self, mixed_unitary): if not mixed_unitary: raise ValueError("Cumulative probabilities only available for mixed unitary channels.") return [se.RealDouble(cp) for cp in np.cumsum(self.probabilities())]
[docs] def unwrappedcumprobabilities(self): """ Returns the cumulative sum of probabilities for the mixed unitary channel without symbolic wrapping. See Also: :func:`cumprobabilities` Returns: list: A list of cumulative probabilities as float values. """ return [float(p) for p in self.cumprobabilities()]
[docs] def krausoperators(self): """ Returns the Kraus operators associated with the given Kraus channel. This should be implemented for each specific channel. Returns: list: A list of matrices representing the Kraus operators. """ raise NotImplementedError("This method should be implemented for the specific Kraus channel.")
[docs] def squaredkrausoperators(self): """ Returns the square of the Kraus operators (:math:`E_k^\\dagger E_k`). This computes the Hermitian conjugate (dagger) of each Kraus operator and multiplies it by the operator itself. Returns: list: A list of squared Kraus operators. """ return [k.opsquared() for k in self.krausoperators()]
[docs] def unitarymatrices(self): """ Unitary matrices associated with the given mixed unitary Kraus channel. A mixed unitary channel is written as: .. math:: \\mathcal{E}(\\rho) = \\sum_k p_k U_k \\rho U_k^\\dagger, where :math:`U_k` are the unitary matrices. An error is raised if the channel is not mixed unitary (i.e., `ismixedunitary(self)==False`). Note: If the Kraus channel is parametric, the matrix elements are wrapped in a symbolic object (e.g., from sympy or symengine). To manipulate expressions, use the appropriate symbolic manipulation libraries. See Also: :func:`ismixedunitary` :func:`probabilities` :func:`krausmatrices` Examples: >>> from mimiqcircuits import * >>> PauliX(0.2).unitarymatrices() [[1.0, 0] [0, 1.0] , [0, 1.0] [1.0, 0] ] """ if not self.ismixedunitary(): raise ValueError("unitarymatrices only available for mixed unitary Kraus channels.") return [se.Matrix(g.matrix().tolist()) for g in self.unitarygates()]
[docs] def unwrappedunitarymatrices(self): """ Returns the unitary matrices associated with the mixed unitary channel without symbolic wrapping. Returns: list: A list of unitary matrices as numerical values. """ matrices = [] for matrix in self.unitarymatrices(): # Prepare a list to hold the converted values flat_matrix = [] for i in range(matrix.rows): for j in range(matrix.cols): entry = matrix[i, j] # Convert symbolic expression to numerical form entry_numeric = sp.N(entry) # Handle real and complex parts separately if entry_numeric.is_real: flat_matrix.append(float(entry_numeric)) else: flat_matrix.append(complex(entry_numeric)) # Create a new symengine matrix from the flattened list new_matrix = se.Matrix(matrix.rows, matrix.cols, flat_matrix) matrices.append(new_matrix) return matrices
[docs] def unitarygates(self): """ Returns the unitary gates associated with the given mixed unitary Kraus channel. A mixed unitary channel is written as: .. math:: \\sum_k p_k U_k \\rho U_k^\\dagger, where :math:`U_k` are the unitary operators. This method is valid only for mixed unitary channels. """ if not self.ismixedunitary(): raise ValueError("unitarygates only available for mixed unitary channels.")
__all__ = ["Noise"]