# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2020.
#
# 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.
"""
Quantum information utility functions for states.
"""
from __future__ import annotations
import numpy as np
from qiskit.exceptions import QiskitError
from qiskit.quantum_info.states.statevector import Statevector
from qiskit.quantum_info.states.densitymatrix import DensityMatrix
from qiskit.quantum_info.operators.channel import SuperOp
from qiskit.quantum_info.operators.predicates import ATOL_DEFAULT
[ドキュメント]def partial_trace(state: Statevector | DensityMatrix, qargs: list) -> DensityMatrix:
"""Return reduced density matrix by tracing out part of quantum state.
If all subsystems are traced over this returns the
:meth:`~qiskit.quantum_info.DensityMatrix.trace` of the
input state.
Args:
state (Statevector or DensityMatrix): the input state.
qargs (list): The subsystems to trace over.
Returns:
DensityMatrix: The reduced density matrix.
Raises:
QiskitError: if input state is invalid.
"""
state = _format_state(state, validate=False)
# Compute traced shape
traced_shape = state._op_shape.remove(qargs=qargs)
# Convert vector shape to matrix shape
traced_shape._dims_r = traced_shape._dims_l
traced_shape._num_qargs_r = traced_shape._num_qargs_l
# If we are tracing over all subsystems we return the trace
if traced_shape.size == 0:
return state.trace()
# Statevector case
if isinstance(state, Statevector):
trace_systems = len(state._op_shape.dims_l()) - 1 - np.array(qargs)
arr = state._data.reshape(state._op_shape.tensor_shape)
rho = np.tensordot(arr, arr.conj(), axes=(trace_systems, trace_systems))
rho = np.reshape(rho, traced_shape.shape)
return DensityMatrix(rho, dims=traced_shape._dims_l)
# Density matrix case
# Empty partial trace case.
if not qargs:
return state.copy()
# Trace first subsystem to avoid coping whole density matrix
dims = state.dims(qargs)
tr_op = SuperOp(np.eye(dims[0]).reshape(1, dims[0] ** 2), input_dims=[dims[0]], output_dims=[1])
ret = state.evolve(tr_op, [qargs[0]])
# Trace over remaining subsystems
for qarg, dim in zip(qargs[1:], dims[1:]):
tr_op = SuperOp(np.eye(dim).reshape(1, dim**2), input_dims=[dim], output_dims=[1])
ret = ret.evolve(tr_op, [qarg])
# Remove traced over subsystems which are listed as dimension 1
ret._op_shape = traced_shape
return ret
[ドキュメント]def shannon_entropy(pvec: list | np.ndarray, base: int = 2) -> float:
r"""Compute the Shannon entropy of a probability vector.
The shannon entropy of a probability vector
:math:`\vec{p} = [p_0, ..., p_{n-1}]` is defined as
.. math::
H(\vec{p}) = \sum_{i=0}^{n-1} p_i \log_b(p_i)
where :math:`b` is the log base and (default 2), and
:math:`0 \log_b(0) \equiv 0`.
Args:
pvec (array_like): a probability vector.
base (int): the base of the logarithm [Default: 2].
Returns:
float: The Shannon entropy H(pvec).
"""
if base == 2:
def logfn(x):
return -x * np.log2(x)
elif base == np.e:
def logfn(x):
return -x * np.log(x)
else:
def logfn(x):
return -x * np.log(x) / np.log(base)
h_val = 0.0
for x in pvec:
if 0 < x < 1:
h_val += logfn(x)
return h_val
[ドキュメント]def schmidt_decomposition(state, qargs):
r"""Return the Schmidt Decomposition of a pure quantum state.
For an arbitrary bipartite state:
.. math::
|\psi\rangle_{AB} = \sum_{i,j} c_{ij}
|x_i\rangle_A \otimes |y_j\rangle_B,
its Schmidt Decomposition is given by the single-index sum over k:
.. math::
|\psi\rangle_{AB} = \sum_{k} \lambda_{k}
|u_k\rangle_A \otimes |v_k\rangle_B
where :math:`|u_k\rangle_A` and :math:`|v_k\rangle_B` are an
orthonormal set of vectors in their respective spaces :math:`A` and :math:`B`,
and the Schmidt coefficients :math:`\lambda_k` are positive real values.
Args:
state (Statevector or DensityMatrix): the input state.
qargs (list): the list of Input state positions corresponding to subsystem :math:`B`.
Returns:
list: list of tuples ``(s, u, v)``, where ``s`` (float) are the Schmidt coefficients
:math:`\lambda_k`, and ``u`` (Statevector), ``v`` (Statevector) are the Schmidt vectors
:math:`|u_k\rangle_A`, :math:`|u_k\rangle_B`, respectively.
Raises:
QiskitError: if Input qargs is not a list of positions of the Input state.
QiskitError: if Input qargs is not a proper subset of Input state.
.. note::
In Qiskit, qubits are ordered using little-endian notation, with the least significant
qubits having smaller indices. For example, a four-qubit system is represented as
:math:`|q_3q_2q_1q_0\rangle`. Using this convention, setting ``qargs=[0]`` will partition the
state as :math:`|q_3q_2q_1\rangle_A\otimes|q_0\rangle_B`. Furthermore, qubits will be organized
in this notation regardless of the order they are passed. For instance, passing either
``qargs=[1,2]`` or ``qargs=[2,1]`` will result in partitioning the state as
:math:`|q_3q_0\rangle_A\otimes|q_2q_1\rangle_B`.
"""
state = _format_state(state, validate=False)
# convert to statevector if state is density matrix. Errors if state is mixed.
if isinstance(state, DensityMatrix):
state = state.to_statevector()
# reshape statevector into state tensor
dims = state.dims()
state_tens = state._data.reshape(dims[::-1])
ndim = state_tens.ndim
qudits = list(range(ndim))
# check if qargs are valid
if not isinstance(qargs, (list, np.ndarray)):
raise QiskitError("Input qargs is not a list of positions of the Input state")
qargs = set(qargs)
if qargs == set(qudits) or not qargs.issubset(qudits):
raise QiskitError("Input qargs is not a proper subset of Input state")
# define subsystem A and B qargs and dims
qargs_b = list(qargs)
qargs_a = [i for i in qudits if i not in qargs_b]
dims_b = state.dims(qargs_b)
dims_a = state.dims(qargs_a)
ndim_b = np.prod(dims_b)
ndim_a = np.prod(dims_a)
# permute state for desired qargs order
qargs_axes = [qudits[::-1].index(i) for i in qargs_b + qargs_a][::-1]
state_tens = state_tens.transpose(qargs_axes)
# convert state tensor to matrix of prob amplitudes and perform svd.
state_mat = state_tens.reshape([ndim_a, ndim_b])
u_mat, s_arr, vh_mat = np.linalg.svd(state_mat, full_matrices=False)
schmidt_components = [
(s, Statevector(u, dims=dims_a), Statevector(v, dims=dims_b))
for s, u, v in zip(s_arr, u_mat.T, vh_mat)
if s > ATOL_DEFAULT
]
return schmidt_components
def _format_state(state, validate=True):
"""Format input state into class object"""
if isinstance(state, list):
state = np.array(state, dtype=complex)
if isinstance(state, np.ndarray):
ndim = state.ndim
if ndim == 1:
state = Statevector(state)
elif ndim == 2:
dim1, dim2 = state.shape
if dim2 == 1:
state = Statevector(state)
elif dim1 == dim2:
state = DensityMatrix(state)
if not isinstance(state, (Statevector, DensityMatrix)):
raise QiskitError("Input is not a quantum state")
if validate and not state.is_valid():
raise QiskitError("Input quantum state is not a valid")
return state
def _funm_svd(matrix, func):
"""Apply real scalar function to singular values of a matrix.
Args:
matrix (array_like): (N, N) Matrix at which to evaluate the function.
func (callable): Callable object that evaluates a scalar function f.
Returns:
ndarray: funm (N, N) Value of the matrix function specified by func
evaluated at `A`.
"""
import scipy.linalg as la
unitary1, singular_values, unitary2 = la.svd(matrix)
diag_func_singular = np.diag(func(singular_values))
return unitary1.dot(diag_func_singular).dot(unitary2)