# -*- coding: utf-8 -*-
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
# pylint: disable=invalid-name
"""
A collection of useful quantum information functions for operators.
"""
import warnings
import numpy as np
from scipy import sparse
from qiskit.exceptions import QiskitError
from qiskit.quantum_info.operators.operator import Operator
from qiskit.quantum_info.operators.pauli import Pauli
from qiskit.quantum_info.operators.channel import SuperOp, Choi
try:
import cvxpy
_HAS_CVX = True
except ImportError:
_HAS_CVX = False
[docs]def process_fidelity(channel,
target=None,
require_cp=True,
require_tp=False,
require_cptp=False):
r"""Return the process fidelity of a noisy quantum channel.
This process fidelity :math:`F_{\text{pro}}` is given by
.. math::
F_{\text{pro}}(\mathcal{E}, U)
= \frac{Tr[S_U^\dagger S_{\mathcal{E}}]}{d^2}
where :math:`S_{\mathcal{E}}, S_{U}` are the
:class:`~qiskit.quantum_info.SuperOp` matrices for the input quantum
*channel* :math:`\cal{E}` and *target* unitary :math:`U` respectively,
and :math:`d` is the dimension of the *channel*.
Args:
channel (QuantumChannel): noisy quantum channel.
target (Operator or None): target unitary operator.
If `None` target is the identity operator [Default: None].
require_cp (bool): require channel to be completely-positive
[Default: True].
require_tp (bool): require channel to be trace-preserving
[Default: False].
require_cptp (bool): (DEPRECATED) require input channels to be
CPTP [Default: False].
Returns:
float: The process fidelity :math:`F_{\text{pro}}`.
Raises:
QiskitError: if the channel and target do not have the same dimensions,
or have different input and output dimensions.
QiskitError: if the channel and target or are not completely-positive
(with ``require_cp=True``) or not trace-preserving
(with ``require_tp=True``).
"""
# Format inputs
if isinstance(channel, (list, np.ndarray, Operator, Pauli)):
channel = Operator(channel)
else:
channel = SuperOp(channel)
input_dim, output_dim = channel.dim
if input_dim != output_dim:
raise QiskitError(
'Quantum channel must have equal input and output dimensions.')
if target is not None:
# Multiple channel by adjoint of target
target = Operator(target)
if (input_dim, output_dim) != target.dim:
raise QiskitError(
'Quantum channel and target must have the same dimensions.')
channel = channel @ target.adjoint()
# Validate complete-positivity and trace-preserving
if require_cptp:
# require_cptp kwarg is DEPRECATED
# Remove in future qiskit version
warnings.warn(
"Please use `require_cp=True, require_tp=True` "
"instead of `require_cptp=True`.", DeprecationWarning)
require_cp = True
require_tp = True
if isinstance(channel, Operator) and (require_cp or require_tp):
is_unitary = channel.is_unitary()
# Validate as unitary
if require_cp and not is_unitary:
raise QiskitError('channel is not completely-positive')
if require_tp and not is_unitary:
raise QiskitError('channel is not trace-preserving')
else:
# Validate as QuantumChannel
if require_cp and not channel.is_cp():
raise QiskitError('channel is not completely-positive')
if require_tp and not channel.is_tp():
raise QiskitError('channel is not trace-preserving')
# Compute process fidelity with identity channel
if isinstance(channel, Operator):
# |Tr[U]/dim| ** 2
fid = np.abs(np.trace(channel.data) / input_dim)**2
else:
# Tr[S] / (dim ** 2)
fid = np.trace(channel.data) / (input_dim**2)
return float(np.real(fid))
[docs]def average_gate_fidelity(channel,
target=None,
require_cp=True,
require_tp=False):
r"""Return the average gate fidelity of a noisy quantum channel.
The average gate fidelity :math:`F_{\text{ave}}` is given by
.. math::
F_{\text{ave}}(\mathcal{E}, U)
&= \int d\psi \langle\psi|U^\dagger
\mathcal{E}(|\psi\rangle\!\langle\psi|)U|\psi\rangle \\
&= \frac{d F_{\text{pro}}(\mathcal{E}, U) + 1}{d + 1}
where :math:`F_{\text{pro}}(\mathcal{E}, U)` is the
:meth:`~qiskit.quantum_info.process_fidelity` of the input quantum
*channel* :math:`\mathcal{E}` with a *target* unitary :math:`U`, and
:math:`d` is the dimension of the *channel*.
Args:
channel (QuantumChannel): noisy quantum channel.
target (Operator or None): target unitary operator.
If `None` target is the identity operator [Default: None].
require_cp (bool): require channel to be completely-positive
[Default: True].
require_tp (bool): require channel to be trace-preserving
[Default: False].
Returns:
float: The average gate fidelity :math:`F_{\text{ave}}`.
Raises:
QiskitError: if the channel and target do not have the same dimensions,
or have different input and output dimensions.
QiskitError: if the channel and target or are not completely-positive
(with ``require_cp=True``) or not trace-preserving
(with ``require_tp=True``).
"""
if isinstance(channel, (list, np.ndarray, Operator, Pauli)):
channel = Operator(channel)
else:
channel = SuperOp(channel)
dim, _ = channel.dim
f_pro = process_fidelity(channel,
target=target,
require_cp=require_cp,
require_tp=require_tp)
return (dim * f_pro + 1) / (dim + 1)
[docs]def gate_error(channel, target=None, require_cp=True, require_tp=False):
r"""Return the gate error of a noisy quantum channel.
The gate error :math:`E` is given by the average gate infidelity
.. math::
E(\mathcal{E}, U) = 1 - F_{\text{ave}}(\mathcal{E}, U)
where :math:`F_{\text{ave}}(\mathcal{E}, U)` is the
:meth:`~qiskit.quantum_info.average_gate_fidelity` of the input
quantum *channel* :math:`\mathcal{E}` with a *target* unitary
:math:`U`.
Args:
channel (QuantumChannel): noisy quantum channel.
target (Operator or None): target unitary operator.
If `None` target is the identity operator [Default: None].
require_cp (bool): require channel to be completely-positive
[Default: True].
require_tp (bool): require channel to be trace-preserving
[Default: False].
Returns:
float: The average gate error :math:`E`.
Raises:
QiskitError: if the channel and target do not have the same dimensions,
or have different input and output dimensions.
QiskitError: if the channel and target or are not completely-positive
(with ``require_cp=True``) or not trace-preserving
(with ``require_tp=True``).
"""
return 1 - average_gate_fidelity(
channel, target=target, require_cp=require_cp, require_tp=require_tp)
[docs]def diamond_norm(choi, **kwargs):
r"""Return the diamond norm of the input quantum channel object.
This function computes the completely-bounded trace-norm (often
referred to as the diamond-norm) of the input quantum channel object
using the semidefinite-program from reference [1].
Args:
choi(Choi or QuantumChannel): a quantum channel object or
Choi-matrix array.
kwargs: optional arguments to pass to CVXPY solver.
Returns:
float: The completely-bounded trace norm
:math:`\|\mathcal{E}\|_{\diamond}`.
Raises:
QiskitError: if CVXPY package cannot be found.
Additional Information:
The input to this function is typically *not* a CPTP quantum
channel, but rather the *difference* between two quantum channels
:math:`\|\Delta\mathcal{E}\|_\diamond` where
:math:`\Delta\mathcal{E} = \mathcal{E}_1 - \mathcal{E}_2`.
Reference:
J. Watrous. "Simpler semidefinite programs for completely bounded
norms", arXiv:1207.5726 [quant-ph] (2012).
.. note::
This function requires the optional CVXPY package to be installed.
Any additional kwargs will be passed to the ``cvxpy.solve``
function. See the CVXPY documentation for information on available
SDP solvers.
"""
_cvxpy_check('`diamond_norm`') # Check CVXPY is installed
if not isinstance(choi, Choi):
choi = Choi(choi)
def cvx_bmat(mat_r, mat_i):
"""Block matrix for embedding complex matrix in reals"""
return cvxpy.bmat([[mat_r, -mat_i], [mat_i, mat_r]])
# Dimension of input and output spaces
dim_in = choi._input_dim
dim_out = choi._output_dim
size = dim_in * dim_out
# SDP Variables to convert to real valued problem
r0_r = cvxpy.Variable((dim_in, dim_in))
r0_i = cvxpy.Variable((dim_in, dim_in))
r0 = cvx_bmat(r0_r, r0_i)
r1_r = cvxpy.Variable((dim_in, dim_in))
r1_i = cvxpy.Variable((dim_in, dim_in))
r1 = cvx_bmat(r1_r, r1_i)
x_r = cvxpy.Variable((size, size))
x_i = cvxpy.Variable((size, size))
iden = sparse.eye(dim_out)
# Watrous uses row-vec convention for his Choi matrix while we use
# col-vec. It turns out row-vec convention is requried for CVXPY too
# since the cvxpy.kron function must have a constant as its first argument.
c_r = cvxpy.bmat([[cvxpy.kron(iden, r0_r), x_r], [x_r.T, cvxpy.kron(iden, r1_r)]])
c_i = cvxpy.bmat([[cvxpy.kron(iden, r0_i), x_i], [-x_i.T, cvxpy.kron(iden, r1_i)]])
c = cvx_bmat(c_r, c_i)
# Transpose out Choi-matrix to row-vec convention and vectorize.
choi_vec = np.transpose(
np.reshape(choi.data, (dim_in, dim_out, dim_in, dim_out)),
(1, 0, 3, 2)).ravel(order='F')
choi_vec_r = choi_vec.real
choi_vec_i = choi_vec.imag
# Constraints
cons = [
r0 >> 0, r0_r == r0_r.T, r0_i == - r0_i.T, cvxpy.trace(r0_r) == 1,
r1 >> 0, r1_r == r1_r.T, r1_i == - r1_i.T, cvxpy.trace(r1_r) == 1,
c >> 0
]
# Objective function
obj = cvxpy.Maximize(choi_vec_r * cvxpy.vec(x_r) - choi_vec_i * cvxpy.vec(x_i))
prob = cvxpy.Problem(obj, cons)
sol = prob.solve(**kwargs)
return sol
def _cvxpy_check(name):
"""Check that a supported CVXPY version is installed"""
# Check if CVXPY package is installed
if not _HAS_CVX:
raise QiskitError(
'CVXPY backage is requried for {}. Install'
' with `pip install cvxpy` to use.'.format(name))
# Check CVXPY version
version = cvxpy.__version__
if version[0] != '1':
raise ImportError(
'Incompatible CVXPY version {} found.'
' Install version >=1.0.'.format(version))