def _evolve(self, state, qargs=None): """Evolve a quantum state by the quantum channel. Args: state (DensityMatrix or Statevector): The input state. qargs (list): a list of quantum state subsystem positions to apply the quantum channel on. Returns: DensityMatrix: the output quantum state as a density matrix. Raises: QiskitError: if the quantum channel dimension does not match the specified quantum state subsystem dimensions. """ # Prevent cyclic imports by importing DensityMatrix here # pylint: disable=cyclic-import from qiskit.quantum_info.states.densitymatrix import DensityMatrix if not isinstance(state, DensityMatrix): state = DensityMatrix(state) if qargs is None: # Evolution on full matrix if state._op_shape.shape[0] != self._op_shape.shape[1]: raise QiskitError( "Operator input dimension is not equal to density matrix dimension." ) # We reshape in column-major vectorization (Fortran order in Numpy) # since that is how the SuperOp is defined vec = np.ravel(state.data, order="F") mat = np.reshape( np.dot(self.data, vec), (self._output_dim, self._output_dim), order="F" ) return DensityMatrix(mat, dims=self.output_dims()) # Otherwise we are applying an operator only to subsystems # Check dimensions of subsystems match the operator if state.dims(qargs) != self.input_dims(): raise QiskitError( "Operator input dimensions are not equal to statevector subsystem dimensions." ) # Reshape statevector and operator tensor = np.reshape(state.data, state._op_shape.tensor_shape) mat = np.reshape(self.data, self._tensor_shape) # Construct list of tensor indices of statevector to be contracted num_indices = len(state.dims()) indices = [num_indices - 1 - qubit for qubit in qargs] + [ 2 * num_indices - 1 - qubit for qubit in qargs ] tensor = Operator._einsum_matmul(tensor, mat, indices) # Replace evolved dimensions new_dims = list(state.dims()) output_dims = self.output_dims() for i, qubit in enumerate(qargs): new_dims[qubit] = output_dims[i] new_dim = np.product(new_dims) # reshape tensor to density matrix tensor = np.reshape(tensor, (new_dim, new_dim)) return DensityMatrix(tensor, dims=new_dims)
def _kraus_to_other_single(self, rep, qubits_test_cases, repetitions): """Test single Kraus to Other evolution.""" for nq in qubits_test_cases: dim = 2**nq for _ in range(repetitions): rho = self.rand_rho(dim) kraus = self.rand_kraus(dim, dim, dim**2) chan = Kraus(kraus) rho1 = DensityMatrix(rho).evolve(chan).data rho2 = DensityMatrix(rho).evolve(rep(chan)).data assert_allclose(rho1, rho2)
def _superop_to_other(self, rep, qubits_test_cases, repetitions): """Test SuperOp to Other evolution.""" for nq in qubits_test_cases: dim = 2**nq for _ in range(repetitions): rho = self.rand_rho(dim) mat = self.rand_matrix(dim**2, dim**2) chan = SuperOp(mat) rho1 = DensityMatrix(rho).evolve(chan).data rho2 = DensityMatrix(rho).evolve(rep(chan)).data assert_allclose(rho1, rho2)
def _other_to_operator(self, rep, qubits_test_cases, repetitions): """Test Other to Operator evolution.""" for nq in qubits_test_cases: dim = 2**nq for _ in range(repetitions): rho = self.rand_rho(dim) mat = self.rand_matrix(dim, dim) chan = rep(Operator(mat)) rho1 = DensityMatrix(rho).evolve(chan).data rho2 = DensityMatrix(rho).evolve(Operator(chan)).data assert_allclose(rho1, rho2)
def _unitary_to_other(self, rep, qubits_test_cases, repetitions): """Test Operator to Other evolution.""" for nq in qubits_test_cases: dim = 2**nq for _ in range(repetitions): rho = self.rand_rho(dim) mat = self.rand_matrix(dim, dim) chan = Operator(mat) rho1 = DensityMatrix(rho).evolve(chan).data rho2 = DensityMatrix(rho).evolve(rep(chan)).data self.assertAllClose(rho1, rho2)
def _ptm_to_other(self, rep, qubits_test_cases, repetitions): """Test PTM to Other evolution.""" for nq in qubits_test_cases: dim = 2**nq for _ in range(repetitions): rho = self.rand_rho(dim) mat = self.rand_matrix(dim**2, dim**2, real=True) chan = PTM(mat) rho1 = DensityMatrix(rho).evolve(chan).data rho2 = DensityMatrix(rho).evolve(rep(chan)).data self.assertAllClose(rho1, rho2)
def _stinespring_to_other_single(self, rep, qubits_test_cases, repetitions): """Test single Stinespring to Other evolution.""" for nq in qubits_test_cases: dim = 2**nq for _ in range(repetitions): rho = self.rand_rho(dim) mat = self.rand_matrix(dim**2, dim) chan = Stinespring(mat) rho1 = DensityMatrix(rho).evolve(chan).data rho2 = DensityMatrix(rho).evolve(rep(chan)).data assert_allclose(rho1, rho2)
def _choi_to_other_cp(self, rep, qubits_test_cases, repetitions): """Test CP Choi to Other evolution.""" for nq in qubits_test_cases: dim = 2**nq for _ in range(repetitions): rho = self.rand_rho(dim) mat = dim * self.rand_rho(dim**2) chan = Choi(mat) rho1 = DensityMatrix(rho).evolve(chan).data rho2 = DensityMatrix(rho).evolve(rep(chan)).data assert_allclose(rho1, rho2)
def _kraus_to_other_double(self, rep, qubits_test_cases, repetitions): """Test double Kraus to Other evolution.""" for nq in qubits_test_cases: dim = 2**nq for _ in range(repetitions): rho = self.rand_rho(dim) kraus_l = self.rand_kraus(dim, dim, dim**2) kraus_r = self.rand_kraus(dim, dim, dim**2) chan = Kraus((kraus_l, kraus_r)) rho1 = DensityMatrix(rho).evolve(chan).data rho2 = DensityMatrix(rho).evolve(rep(chan)).data self.assertAllClose(rho1, rho2)
def _stinespring_to_other_double(self, rep, qubits_test_cases, repetitions): """Test double Stinespring to Other evolution.""" for nq in qubits_test_cases: dim = 2**nq for _ in range(repetitions): rho = self.rand_rho(dim) mat_l = self.rand_matrix(dim**2, dim) mat_r = self.rand_matrix(dim**2, dim) chan = Stinespring((mat_l, mat_r)) rho1 = DensityMatrix(rho).evolve(chan).data rho2 = DensityMatrix(rho).evolve(rep(chan)).data self.assertAllClose(rho1, rho2)
def _test_kraus_gate_noise_on_QFT(self, **options): shots = 10000 # Build noise model error1 = noise.amplitude_damping_error(0.2) error2 = error1.tensor(error1) noise_model = noise.NoiseModel() noise_model.add_all_qubit_quantum_error(error1, ['h']) noise_model.add_all_qubit_quantum_error(error2, ['cp', 'swap']) backend = self.backend(**options, noise_model=noise_model) ideal_circuit = transpile(QFT(3), backend) # manaully build noise circuit noise_circuit = QuantumCircuit(3) for inst, qargs, cargs in ideal_circuit.data: noise_circuit.append(inst, qargs, cargs) if inst.name == "h": noise_circuit.append(error1, qargs) elif inst.name in ["cp", "swap"]: noise_circuit.append(error2, qargs) # compute target counts noise_state = DensityMatrix(noise_circuit) ref_target = {i: shots * p for i, p in noise_state.probabilities_dict().items()} # Run sim ideal_circuit.measure_all() result = backend.run(ideal_circuit, shots=shots).result() self.assertSuccess(result) self.compare_counts(result, [ideal_circuit], [ref_target], hex_counts=False, delta=0.1 * shots)
def concurrence(state): r"""Calculate the concurrence of a quantum state. The concurrence of a bipartite :class:`~qiskit.quantum_info.Statevector` :math:`|\psi\rangle` is given by .. math:: C(|\psi\rangle) = \sqrt{2(1 - Tr[\rho_0^2])} where :math:`\rho_0 = Tr_1[|\psi\rangle\!\langle\psi|]` is the reduced state from by taking the :func:`~qiskit.quantum_info.partial_trace` of the input state. For density matrices the concurrence is only defined for 2-qubit states, it is given by: .. math:: C(\rho) = \max(0, \lambda_1 - \lambda_2 - \lambda_3 - \lambda_4) where :math:`\lambda _1 \ge \lambda _2 \ge \lambda _3 \ge \lambda _4` are the ordered eigenvalues of the matrix :math:`R=\sqrt{\sqrt{\rho }(Y\otimes Y)\overline{\rho}(Y\otimes Y)\sqrt{\rho}}`. Args: state (Statevector or DensityMatrix): a 2-qubit quantum state. Returns: float: The concurrence. Raises: QiskitError: if the input state is not a valid QuantumState. QiskitError: if input is not a bipartite QuantumState. QiskitError: if density matrix input is not a 2-qubit state. """ import scipy.linalg as la # Concurrence computation requires the state to be valid state = _format_state(state, validate=True) if isinstance(state, Statevector): # Pure state concurrence dims = state.dims() if len(dims) != 2: raise QiskitError("Input is not a bipartite quantum state.") qargs = [0] if dims[0] > dims[1] else [1] rho = partial_trace(state, qargs) return float(np.sqrt(2 * (1 - np.real(purity(rho))))) # If input is a density matrix it must be a 2-qubit state if state.dim != 4: raise QiskitError("Input density matrix must be a 2-qubit state.") rho = DensityMatrix(state).data yy_mat = np.fliplr(np.diag([-1, 1, 1, -1])) sigma = rho.dot(yy_mat).dot(rho.conj()).dot(yy_mat) w = np.sort(np.real(la.eigvals(sigma))) w = np.sqrt(np.maximum(w, 0.0)) return max(0.0, w[-1] - np.sum(w[0:-1]))
def partial_trace(state, qargs): """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 partial_trace(state, qargs): """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) # If we are tracing over all subsystems we return the trace if sorted(qargs) == list(range(len(state.dims()))): # Should this raise an exception instead? # Or return a 1x1 density matrix? return state.trace() # Statevector case if isinstance(state, Statevector): trace_systems = len(state._dims) - 1 - np.array(qargs) new_dims = tuple(np.delete(np.array(state._dims), qargs)) new_dim = np.product(new_dims) arr = state._data.reshape(state._shape) rho = np.tensordot(arr, arr.conj(), axes=(trace_systems, trace_systems)) rho = np.reshape(rho, (new_dim, new_dim)) return DensityMatrix(rho, dims=new_dims) # Density matrix case # 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._reshape(tuple(np.delete(np.array(ret._dims), qargs))) return ret
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 process_fidelity(channel, target=None, require_cp=True, require_tp=False): r"""Return the process fidelity of a noisy quantum channel. The process fidelity :math:`F_{\text{pro}}(\mathcal{E}, \methcal{F})` between two quantum channels :math:`\mathcal{E}, \mathcal{F}` is given by .. math: F_{\text{pro}}(\mathcal{E}, \mathcal{F}) = F(\rho_{\mathcal{E}}, \rho_{\mathcal{F}}) where :math:`F` is the :func:`~qiskit.quantum_info.state_fidelity`, :math:`\rho_{\mathcal{E}} = \Lambda_{\mathcal{E}} / d` is the normalized :class:`~qiskit.quantum_info.Choi` matrix for the channel :math:`\mathcal{E}`, and :math:`d` is the input dimension of :math:`\mathcal{E}`. When the target channel is unitary this is equivalent to .. 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:`\mathcal{E}` and *target* unitary :math:`U` respectively, and :math:`d` is the input dimension of the channel. Args: channel (Operator or QuantumChannel): input quantum channel. target (Operator or QuantumChannel or None): target quantum channel. 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 process fidelity :math:`F_{\text{pro}}`. Raises: QiskitError: if the channel and target do not have the same dimensions. QiskitError: if the channel and target are not completely-positive (with ``require_cp=True``) or not trace-preserving (with ``require_tp=True``). """ # Format inputs channel = _input_formatter(channel, SuperOp, 'process_fidelity', 'channel') target = _input_formatter(target, Operator, 'process_fidelity', 'target') if target: # Validate dimensions if channel.dim != target.dim: raise QiskitError( 'Input quantum channel and target unitary must have the same ' 'dimensions ({} != {}).'.format(channel.dim, target.dim)) # Validate complete-positivity and trace-preserving for label, chan in [('Input', channel), ('Target', target)]: if isinstance(chan, Operator) and (require_cp or require_tp): is_unitary = chan.is_unitary() # Validate as unitary if require_cp and not is_unitary: raise QiskitError( '{} channel is not completely-positive'.format(label)) if require_tp and not is_unitary: raise QiskitError( '{} channel is not trace-preserving'.format(label)) elif chan is not None: # Validate as QuantumChannel if require_cp and not chan.is_cp(): raise QiskitError( '{} channel is not completely-positive'.format(label)) if require_tp and not chan.is_tp(): raise QiskitError( '{} channel is not trace-preserving'.format(label)) if isinstance(target, Operator): # Compute fidelity with unitary target by applying the inverse # to channel and computing fidelity with the identity channel = channel @ target.adjoint() target = None input_dim, _ = channel.dim if target is None: # 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(SuperOp(channel).data) / (input_dim**2) return float(np.real(fid)) # For comparing two non-unitary channels we compute the state fidelity of # the normalized Choi-matrices. This is equivalent to the previous definition # when the target is a unitary channel. state1 = DensityMatrix(Choi(channel).data / input_dim) state2 = DensityMatrix(Choi(target).data / input_dim) return state_fidelity(state1, state2, validate=False)
def plot_state_qsphere( state, figsize=None, ax=None, show_state_labels=True, show_state_phases=False, use_degrees=False, *, rho=None, filename=None, ): """Plot the qsphere representation of a quantum state. Here, the size of the points is proportional to the probability of the corresponding term in the state and the color represents the phase. Args: state (Statevector or DensityMatrix or ndarray): an N-qubit quantum state. figsize (tuple): Figure size in inches. ax (matplotlib.axes.Axes): An optional Axes object to be used for the visualization output. If none is specified a new matplotlib Figure will be created and used. Additionally, if specified there will be no returned Figure since it is redundant. show_state_labels (bool): An optional boolean indicating whether to show labels for each basis state. show_state_phases (bool): An optional boolean indicating whether to show the phase for each basis state. use_degrees (bool): An optional boolean indicating whether to use radians or degrees for the phase values in the plot. Returns: Figure: A matplotlib figure instance if the ``ax`` kwarg is not set Raises: MissingOptionalLibraryError: Requires matplotlib. VisualizationError: if input is not a valid N-qubit state. QiskitError: Input statevector does not have valid dimensions. Example: .. jupyter-execute:: from qiskit import QuantumCircuit from qiskit.quantum_info import Statevector from qiskit.visualization import plot_state_qsphere %matplotlib inline qc = QuantumCircuit(2) qc.h(0) qc.cx(0, 1) state = Statevector.from_instruction(qc) plot_state_qsphere(state) """ if not HAS_MATPLOTLIB: raise MissingOptionalLibraryError( libname="Matplotlib", name="plot_state_qsphere", pip_install="pip install matplotlib", ) import matplotlib.gridspec as gridspec from matplotlib import pyplot as plt from matplotlib.patches import Circle from qiskit.visualization.bloch import Arrow3D try: import seaborn as sns except ImportError as ex: raise MissingOptionalLibraryError( libname="seaborn", name="plot_state_qsphere", pip_install="pip install seaborn", ) from ex rho = DensityMatrix(state) num = rho.num_qubits if num is None: raise VisualizationError("Input is not a multi-qubit quantum state.") # get the eigenvectors and eigenvalues eigvals, eigvecs = linalg.eigh(rho.data) if figsize is None: figsize = (7, 7) if ax is None: return_fig = True fig = plt.figure(figsize=figsize) else: return_fig = False fig = ax.get_figure() gs = gridspec.GridSpec(nrows=3, ncols=3) ax = fig.add_subplot(gs[0:3, 0:3], projection="3d") ax.axes.set_xlim3d(-1.0, 1.0) ax.axes.set_ylim3d(-1.0, 1.0) ax.axes.set_zlim3d(-1.0, 1.0) ax.axes.grid(False) ax.view_init(elev=5, azim=275) # Force aspect ratio # MPL 3.2 or previous do not have set_box_aspect if hasattr(ax.axes, "set_box_aspect"): ax.axes.set_box_aspect((1, 1, 1)) # start the plotting # Plot semi-transparent sphere u = np.linspace(0, 2 * np.pi, 25) v = np.linspace(0, np.pi, 25) x = np.outer(np.cos(u), np.sin(v)) y = np.outer(np.sin(u), np.sin(v)) z = np.outer(np.ones(np.size(u)), np.cos(v)) ax.plot_surface( x, y, z, rstride=1, cstride=1, color=plt.rcParams["grid.color"], alpha=0.2, linewidth=0 ) # Get rid of the panes ax.w_xaxis.set_pane_color((1.0, 1.0, 1.0, 0.0)) ax.w_yaxis.set_pane_color((1.0, 1.0, 1.0, 0.0)) ax.w_zaxis.set_pane_color((1.0, 1.0, 1.0, 0.0)) # Get rid of the spines ax.w_xaxis.line.set_color((1.0, 1.0, 1.0, 0.0)) ax.w_yaxis.line.set_color((1.0, 1.0, 1.0, 0.0)) ax.w_zaxis.line.set_color((1.0, 1.0, 1.0, 0.0)) # Get rid of the ticks ax.set_xticks([]) ax.set_yticks([]) ax.set_zticks([]) # traversing the eigvals/vecs backward as sorted low->high for idx in range(eigvals.shape[0] - 1, -1, -1): if eigvals[idx] > 0.001: # get the max eigenvalue state = eigvecs[:, idx] loc = np.absolute(state).argmax() # remove the global phase from max element angles = (np.angle(state[loc]) + 2 * np.pi) % (2 * np.pi) angleset = np.exp(-1j * angles) state = angleset * state d = num for i in range(2 ** num): # get x,y,z points element = bin(i)[2:].zfill(num) weight = element.count("1") zvalue = -2 * weight / d + 1 number_of_divisions = n_choose_k(d, weight) weight_order = bit_string_index(element) angle = (float(weight) / d) * (np.pi * 2) + ( weight_order * 2 * (np.pi / number_of_divisions) ) if (weight > d / 2) or ( (weight == d / 2) and (weight_order >= number_of_divisions / 2) ): angle = np.pi - angle - (2 * np.pi / number_of_divisions) xvalue = np.sqrt(1 - zvalue ** 2) * np.cos(angle) yvalue = np.sqrt(1 - zvalue ** 2) * np.sin(angle) # get prob and angle - prob will be shade and angle color prob = np.real(np.dot(state[i], state[i].conj())) prob = min(prob, 1) # See https://github.com/Qiskit/qiskit-terra/issues/4666 colorstate = phase_to_rgb(state[i]) alfa = 1 if yvalue >= 0.1: alfa = 1.0 - yvalue if not np.isclose(prob, 0) and show_state_labels: rprime = 1.3 angle_theta = np.arctan2(np.sqrt(1 - zvalue ** 2), zvalue) xvalue_text = rprime * np.sin(angle_theta) * np.cos(angle) yvalue_text = rprime * np.sin(angle_theta) * np.sin(angle) zvalue_text = rprime * np.cos(angle_theta) element_text = "$\\vert" + element + "\\rangle$" if show_state_phases: element_angle = (np.angle(state[i]) + (np.pi * 4)) % (np.pi * 2) if use_degrees: element_text += "\n$%.1f^\\circ$" % (element_angle * 180 / np.pi) else: element_angle = pi_check(element_angle, ndigits=3).replace("pi", "\\pi") element_text += "\n$%s$" % (element_angle) ax.text( xvalue_text, yvalue_text, zvalue_text, element_text, ha="center", va="center", size=12, ) ax.plot( [xvalue], [yvalue], [zvalue], markerfacecolor=colorstate, markeredgecolor=colorstate, marker="o", markersize=np.sqrt(prob) * 30, alpha=alfa, ) a = Arrow3D( [0, xvalue], [0, yvalue], [0, zvalue], mutation_scale=20, alpha=prob, arrowstyle="-", color=colorstate, lw=2, ) ax.add_artist(a) # add weight lines for weight in range(d + 1): theta = np.linspace(-2 * np.pi, 2 * np.pi, 100) z = -2 * weight / d + 1 r = np.sqrt(1 - z ** 2) x = r * np.cos(theta) y = r * np.sin(theta) ax.plot(x, y, z, color=(0.5, 0.5, 0.5), lw=1, ls=":", alpha=0.5) # add center point ax.plot( [0], [0], [0], markerfacecolor=(0.5, 0.5, 0.5), markeredgecolor=(0.5, 0.5, 0.5), marker="o", markersize=3, alpha=1, ) else: break n = 64 theta = np.ones(n) colors = sns.hls_palette(n) ax2 = fig.add_subplot(gs[2:, 2:]) ax2.pie(theta, colors=colors[5 * n // 8 :] + colors[: 5 * n // 8], radius=0.75) ax2.add_artist(Circle((0, 0), 0.5, color="white", zorder=1)) offset = 0.95 # since radius of sphere is one. if use_degrees: labels = ["Phase\n(Deg)", "0", "90", "180 ", "270"] else: labels = ["Phase", "$0$", "$\\pi/2$", "$\\pi$", "$3\\pi/2$"] ax2.text(0, 0, labels[0], horizontalalignment="center", verticalalignment="center", fontsize=14) ax2.text( offset, 0, labels[1], horizontalalignment="center", verticalalignment="center", fontsize=14 ) ax2.text( 0, offset, labels[2], horizontalalignment="center", verticalalignment="center", fontsize=14 ) ax2.text( -offset, 0, labels[3], horizontalalignment="center", verticalalignment="center", fontsize=14 ) ax2.text( 0, -offset, labels[4], horizontalalignment="center", verticalalignment="center", fontsize=14 ) if return_fig: matplotlib_close_if_inline(fig) if filename is None: return fig else: return fig.savefig(filename)
def plot_state_hinton( state, title="", figsize=None, ax_real=None, ax_imag=None, *, rho=None, filename=None ): """Plot a hinton diagram for the density matrix of a quantum state. Args: state (Statevector or DensityMatrix or ndarray): An N-qubit quantum state. title (str): a string that represents the plot title figsize (tuple): Figure size in inches. filename (str): file path to save image to. ax_real (matplotlib.axes.Axes): An optional Axes object to be used for the visualization output. If none is specified a new matplotlib Figure will be created and used. If this is specified without an ax_imag only the real component plot will be generated. Additionally, if specified there will be no returned Figure since it is redundant. ax_imag (matplotlib.axes.Axes): An optional Axes object to be used for the visualization output. If none is specified a new matplotlib Figure will be created and used. If this is specified without an ax_imag only the real component plot will be generated. Additionally, if specified there will be no returned Figure since it is redundant. Returns: matplotlib.Figure: The matplotlib.Figure of the visualization if neither ax_real or ax_imag is set. Raises: MissingOptionalLibraryError: Requires matplotlib. VisualizationError: if input is not a valid N-qubit state. Example: .. jupyter-execute:: from qiskit import QuantumCircuit from qiskit.quantum_info import DensityMatrix from qiskit.visualization import plot_state_hinton %matplotlib inline qc = QuantumCircuit(2) qc.h(0) qc.cx(0, 1) state = DensityMatrix.from_instruction(qc) plot_state_hinton(state, title="New Hinton Plot") """ if not HAS_MATPLOTLIB: raise MissingOptionalLibraryError( libname="Matplotlib", name="plot_state_hinton", pip_install="pip install matplotlib", ) from matplotlib import pyplot as plt # Figure data rho = DensityMatrix(state) num = rho.num_qubits if num is None: raise VisualizationError("Input is not a multi-qubit quantum state.") max_weight = 2 ** np.ceil(np.log(np.abs(rho.data).max()) / np.log(2)) datareal = np.real(rho.data) dataimag = np.imag(rho.data) if figsize is None: figsize = (8, 5) if not ax_real and not ax_imag: fig, (ax1, ax2) = plt.subplots(1, 2, figsize=figsize) else: if ax_real: fig = ax_real.get_figure() else: fig = ax_imag.get_figure() ax1 = ax_real ax2 = ax_imag column_names = [bin(i)[2:].zfill(num) for i in range(2 ** num)] row_names = [bin(i)[2:].zfill(num) for i in range(2 ** num)] ly, lx = datareal.shape # Real if ax1: ax1.patch.set_facecolor("gray") ax1.set_aspect("equal", "box") ax1.xaxis.set_major_locator(plt.NullLocator()) ax1.yaxis.set_major_locator(plt.NullLocator()) for (x, y), w in np.ndenumerate(datareal): color = "white" if w > 0 else "black" size = np.sqrt(np.abs(w) / max_weight) rect = plt.Rectangle( [0.5 + x - size / 2, 0.5 + y - size / 2], size, size, facecolor=color, edgecolor=color, ) ax1.add_patch(rect) ax1.set_xticks(0.5 + np.arange(lx)) ax1.set_yticks(0.5 + np.arange(ly)) ax1.set_xlim([0, lx]) ax1.set_ylim([ly, 0]) ax1.set_yticklabels(row_names, fontsize=14) ax1.set_xticklabels(column_names, fontsize=14, rotation=90) ax1.invert_yaxis() ax1.set_title("Re[$\\rho$]", fontsize=14) # Imaginary if ax2: ax2.patch.set_facecolor("gray") ax2.set_aspect("equal", "box") ax2.xaxis.set_major_locator(plt.NullLocator()) ax2.yaxis.set_major_locator(plt.NullLocator()) for (x, y), w in np.ndenumerate(dataimag): color = "white" if w > 0 else "black" size = np.sqrt(np.abs(w) / max_weight) rect = plt.Rectangle( [0.5 + x - size / 2, 0.5 + y - size / 2], size, size, facecolor=color, edgecolor=color, ) ax2.add_patch(rect) ax2.set_xticks(0.5 + np.arange(lx)) ax2.set_yticks(0.5 + np.arange(ly)) ax1.set_xlim([0, lx]) ax1.set_ylim([ly, 0]) ax2.set_yticklabels(row_names, fontsize=14) ax2.set_xticklabels(column_names, fontsize=14, rotation=90) ax2.invert_yaxis() ax2.set_title("Im[$\\rho$]", fontsize=14) if title: fig.suptitle(title, fontsize=16) if ax_real is None and ax_imag is None: matplotlib_close_if_inline(fig) if filename is None: return fig else: return fig.savefig(filename)
def plot_state_city( state, title="", figsize=None, color=None, alpha=1, ax_real=None, ax_imag=None, *, rho=None, filename=None, ): """Plot the cityscape of quantum state. Plot two 3d bar graphs (two dimensional) of the real and imaginary part of the density matrix rho. Args: state (Statevector or DensityMatrix or ndarray): an N-qubit quantum state. title (str): a string that represents the plot title figsize (tuple): Figure size in inches. color (list): A list of len=2 giving colors for real and imaginary components of matrix elements. alpha (float): Transparency value for bars ax_real (matplotlib.axes.Axes): An optional Axes object to be used for the visualization output. If none is specified a new matplotlib Figure will be created and used. If this is specified without an ax_imag only the real component plot will be generated. Additionally, if specified there will be no returned Figure since it is redundant. ax_imag (matplotlib.axes.Axes): An optional Axes object to be used for the visualization output. If none is specified a new matplotlib Figure will be created and used. If this is specified without an ax_real only the imaginary component plot will be generated. Additionally, if specified there will be no returned Figure since it is redundant. Returns: matplotlib.Figure: The matplotlib.Figure of the visualization if the ``ax_real`` and ``ax_imag`` kwargs are not set Raises: MissingOptionalLibraryError: Requires matplotlib. ValueError: When 'color' is not a list of len=2. VisualizationError: if input is not a valid N-qubit state. Example: .. jupyter-execute:: from qiskit import QuantumCircuit from qiskit.quantum_info import DensityMatrix from qiskit.visualization import plot_state_city %matplotlib inline qc = QuantumCircuit(2) qc.h(0) qc.cx(0, 1) state = DensityMatrix.from_instruction(qc) plot_state_city(state, color=['midnightblue', 'midnightblue'], title="New State City") """ if not HAS_MATPLOTLIB: raise MissingOptionalLibraryError( libname="Matplotlib", name="plot_state_city", pip_install="pip install matplotlib", ) from matplotlib import pyplot as plt from mpl_toolkits.mplot3d.art3d import Poly3DCollection rho = DensityMatrix(state) num = rho.num_qubits if num is None: raise VisualizationError("Input is not a multi-qubit quantum state.") # get the real and imag parts of rho datareal = np.real(rho.data) dataimag = np.imag(rho.data) # get the labels column_names = [bin(i)[2:].zfill(num) for i in range(2 ** num)] row_names = [bin(i)[2:].zfill(num) for i in range(2 ** num)] lx = len(datareal[0]) # Work out matrix dimensions ly = len(datareal[:, 0]) xpos = np.arange(0, lx, 1) # Set up a mesh of positions ypos = np.arange(0, ly, 1) xpos, ypos = np.meshgrid(xpos + 0.25, ypos + 0.25) xpos = xpos.flatten() ypos = ypos.flatten() zpos = np.zeros(lx * ly) dx = 0.5 * np.ones_like(zpos) # width of bars dy = dx.copy() dzr = datareal.flatten() dzi = dataimag.flatten() if color is None: color = ["#648fff", "#648fff"] else: if len(color) != 2: raise ValueError("'color' must be a list of len=2.") if color[0] is None: color[0] = "#648fff" if color[1] is None: color[1] = "#648fff" if ax_real is None and ax_imag is None: # set default figure size if figsize is None: figsize = (15, 5) fig = plt.figure(figsize=figsize) ax1 = fig.add_subplot(1, 2, 1, projection="3d") ax2 = fig.add_subplot(1, 2, 2, projection="3d") elif ax_real is not None: fig = ax_real.get_figure() ax1 = ax_real ax2 = ax_imag else: fig = ax_imag.get_figure() ax1 = None ax2 = ax_imag max_dzr = max(dzr) min_dzr = min(dzr) min_dzi = np.min(dzi) max_dzi = np.max(dzi) if ax1 is not None: fc1 = generate_facecolors(xpos, ypos, zpos, dx, dy, dzr, color[0]) for idx, cur_zpos in enumerate(zpos): if dzr[idx] > 0: zorder = 2 else: zorder = 0 b1 = ax1.bar3d( xpos[idx], ypos[idx], cur_zpos, dx[idx], dy[idx], dzr[idx], alpha=alpha, zorder=zorder, ) b1.set_facecolors(fc1[6 * idx : 6 * idx + 6]) xlim, ylim = ax1.get_xlim(), ax1.get_ylim() x = [xlim[0], xlim[1], xlim[1], xlim[0]] y = [ylim[0], ylim[0], ylim[1], ylim[1]] z = [0, 0, 0, 0] verts = [list(zip(x, y, z))] pc1 = Poly3DCollection(verts, alpha=0.15, facecolor="k", linewidths=1, zorder=1) if min(dzr) < 0 < max(dzr): ax1.add_collection3d(pc1) ax1.set_xticks(np.arange(0.5, lx + 0.5, 1)) ax1.set_yticks(np.arange(0.5, ly + 0.5, 1)) if max_dzr != min_dzr: ax1.axes.set_zlim3d(np.min(dzr), max(np.max(dzr) + 1e-9, max_dzi)) else: if min_dzr == 0: ax1.axes.set_zlim3d(np.min(dzr), max(np.max(dzr) + 1e-9, np.max(dzi))) else: ax1.axes.set_zlim3d(auto=True) ax1.get_autoscalez_on() ax1.w_xaxis.set_ticklabels(row_names, fontsize=14, rotation=45, ha="right", va="top") ax1.w_yaxis.set_ticklabels( column_names, fontsize=14, rotation=-22.5, ha="left", va="center" ) ax1.set_zlabel("Re[$\\rho$]", fontsize=14) for tick in ax1.zaxis.get_major_ticks(): tick.label.set_fontsize(14) if ax2 is not None: fc2 = generate_facecolors(xpos, ypos, zpos, dx, dy, dzi, color[1]) for idx, cur_zpos in enumerate(zpos): if dzi[idx] > 0: zorder = 2 else: zorder = 0 b2 = ax2.bar3d( xpos[idx], ypos[idx], cur_zpos, dx[idx], dy[idx], dzi[idx], alpha=alpha, zorder=zorder, ) b2.set_facecolors(fc2[6 * idx : 6 * idx + 6]) xlim, ylim = ax2.get_xlim(), ax2.get_ylim() x = [xlim[0], xlim[1], xlim[1], xlim[0]] y = [ylim[0], ylim[0], ylim[1], ylim[1]] z = [0, 0, 0, 0] verts = [list(zip(x, y, z))] pc2 = Poly3DCollection(verts, alpha=0.2, facecolor="k", linewidths=1, zorder=1) if min(dzi) < 0 < max(dzi): ax2.add_collection3d(pc2) ax2.set_xticks(np.arange(0.5, lx + 0.5, 1)) ax2.set_yticks(np.arange(0.5, ly + 0.5, 1)) if min_dzi != max_dzi: eps = 0 ax2.axes.set_zlim3d(np.min(dzi), max(np.max(dzr) + 1e-9, np.max(dzi) + eps)) else: if min_dzi == 0: ax2.set_zticks([0]) eps = 1e-9 ax2.axes.set_zlim3d(np.min(dzi), max(np.max(dzr) + 1e-9, np.max(dzi) + eps)) else: ax2.axes.set_zlim3d(auto=True) ax2.w_xaxis.set_ticklabels(row_names, fontsize=14, rotation=45, ha="right", va="top") ax2.w_yaxis.set_ticklabels( column_names, fontsize=14, rotation=-22.5, ha="left", va="center" ) ax2.set_zlabel("Im[$\\rho$]", fontsize=14) for tick in ax2.zaxis.get_major_ticks(): tick.label.set_fontsize(14) ax2.get_autoscalez_on() fig.suptitle(title, fontsize=16) if ax_real is None and ax_imag is None: matplotlib_close_if_inline(fig) if filename is None: return fig else: return fig.savefig(filename)
def plot_state_qsphere(state, figsize=None, ax=None, show_state_labels=True, show_state_phases=False, use_degrees=False, *, rho=None): """Plot the qsphere representation of a quantum state. Here, the size of the points is proportional to the probability of the corresponding term in the state and the color represents the phase. Args: state (Statevector or DensityMatrix or ndarray): an N-qubit quantum state. figsize (tuple): Figure size in inches. ax (matplotlib.axes.Axes): An optional Axes object to be used for the visualization output. If none is specified a new matplotlib Figure will be created and used. Additionally, if specified there will be no returned Figure since it is redundant. show_state_labels (bool): An optional boolean indicating whether to show labels for each basis state. show_state_phases (bool): An optional boolean indicating whether to show the phase for each basis state. use_degrees (bool): An optional boolean indicating whether to use radians or degrees for the phase values in the plot. Returns: Figure: A matplotlib figure instance if the ``ax`` kwarg is not set Raises: ImportError: Requires matplotlib. VisualizationError: if input is not a valid N-qubit state. QiskitError: Input statevector does not have valid dimensions. Example: .. jupyter-execute:: from qiskit import QuantumCircuit from qiskit.quantum_info import Statevector from qiskit.visualization import plot_state_qsphere %matplotlib inline qc = QuantumCircuit(2) qc.h(0) qc.cx(0, 1) state = Statevector.from_instruction(qc) plot_state_qsphere(state) """ if not HAS_MATPLOTLIB: raise ImportError('Must have Matplotlib installed. To install, run ' '"pip install matplotlib".') from mpl_toolkits.mplot3d import proj3d from matplotlib.patches import FancyArrowPatch import matplotlib.gridspec as gridspec from matplotlib import pyplot as plt from matplotlib.patches import Circle from matplotlib import get_backend class Arrow3D(FancyArrowPatch): """Standard 3D arrow.""" def __init__(self, xs, ys, zs, *args, **kwargs): """Create arrow.""" FancyArrowPatch.__init__(self, (0, 0), (0, 0), *args, **kwargs) self._verts3d = xs, ys, zs def draw(self, renderer): """Draw the arrow.""" xs3d, ys3d, zs3d = self._verts3d xs, ys, _ = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M) self.set_positions((xs[0], ys[0]), (xs[1], ys[1])) FancyArrowPatch.draw(self, renderer) try: import seaborn as sns except ImportError as ex: raise ImportError( 'Must have seaborn installed to use ' 'plot_state_qsphere. To install, run "pip install seaborn".' ) from ex rho = DensityMatrix(state) num = rho.num_qubits if num is None: raise VisualizationError("Input is not a multi-qubit quantum state.") # get the eigenvectors and eigenvalues eigvals, eigvecs = linalg.eigh(rho.data) if figsize is None: figsize = (7, 7) if ax is None: return_fig = True fig = plt.figure(figsize=figsize) else: return_fig = False fig = ax.get_figure() gs = gridspec.GridSpec(nrows=3, ncols=3) ax = fig.add_subplot(gs[0:3, 0:3], projection='3d') ax.axes.set_xlim3d(-1.0, 1.0) ax.axes.set_ylim3d(-1.0, 1.0) ax.axes.set_zlim3d(-1.0, 1.0) ax.axes.grid(False) ax.view_init(elev=5, azim=275) # Force aspect ratio # MPL 3.2 or previous do not have set_box_aspect if hasattr(ax.axes, 'set_box_aspect'): ax.axes.set_box_aspect((1, 1, 1)) # start the plotting # Plot semi-transparent sphere u = np.linspace(0, 2 * np.pi, 25) v = np.linspace(0, np.pi, 25) x = np.outer(np.cos(u), np.sin(v)) y = np.outer(np.sin(u), np.sin(v)) z = np.outer(np.ones(np.size(u)), np.cos(v)) ax.plot_surface(x, y, z, rstride=1, cstride=1, color=plt.rcParams['grid.color'], alpha=0.2, linewidth=0) # Get rid of the panes ax.w_xaxis.set_pane_color((1.0, 1.0, 1.0, 0.0)) ax.w_yaxis.set_pane_color((1.0, 1.0, 1.0, 0.0)) ax.w_zaxis.set_pane_color((1.0, 1.0, 1.0, 0.0)) # Get rid of the spines ax.w_xaxis.line.set_color((1.0, 1.0, 1.0, 0.0)) ax.w_yaxis.line.set_color((1.0, 1.0, 1.0, 0.0)) ax.w_zaxis.line.set_color((1.0, 1.0, 1.0, 0.0)) # Get rid of the ticks ax.set_xticks([]) ax.set_yticks([]) ax.set_zticks([]) # traversing the eigvals/vecs backward as sorted low->high for idx in range(eigvals.shape[0] - 1, -1, -1): if eigvals[idx] > 0.001: # get the max eigenvalue state = eigvecs[:, idx] loc = np.absolute(state).argmax() # remove the global phase from max element angles = (np.angle(state[loc]) + 2 * np.pi) % (2 * np.pi) angleset = np.exp(-1j * angles) state = angleset * state d = num for i in range(2**num): # get x,y,z points element = bin(i)[2:].zfill(num) weight = element.count("1") zvalue = -2 * weight / d + 1 number_of_divisions = n_choose_k(d, weight) weight_order = bit_string_index(element) angle = (float(weight) / d) * (np.pi * 2) + \ (weight_order * 2 * (np.pi / number_of_divisions)) if (weight > d / 2) or ( (weight == d / 2) and (weight_order >= number_of_divisions / 2)): angle = np.pi - angle - (2 * np.pi / number_of_divisions) xvalue = np.sqrt(1 - zvalue**2) * np.cos(angle) yvalue = np.sqrt(1 - zvalue**2) * np.sin(angle) # get prob and angle - prob will be shade and angle color prob = np.real(np.dot(state[i], state[i].conj())) if prob > 1: # See https://github.com/Qiskit/qiskit-terra/issues/4666 prob = 1 colorstate = phase_to_rgb(state[i]) alfa = 1 if yvalue >= 0.1: alfa = 1.0 - yvalue if not np.isclose(prob, 0) and show_state_labels: rprime = 1.3 angle_theta = np.arctan2(np.sqrt(1 - zvalue**2), zvalue) xvalue_text = rprime * np.sin(angle_theta) * np.cos(angle) yvalue_text = rprime * np.sin(angle_theta) * np.sin(angle) zvalue_text = rprime * np.cos(angle_theta) element_text = '$\\vert' + element + '\\rangle$' if show_state_phases: element_angle = (np.angle(state[i]) + (np.pi * 4)) % (np.pi * 2) if use_degrees: element_text += '\n$%.1f^\\circ$' % ( element_angle * 180 / np.pi) else: element_angle = pi_check(element_angle, ndigits=3).replace( 'pi', '\\pi') element_text += '\n$%s$' % (element_angle) ax.text(xvalue_text, yvalue_text, zvalue_text, element_text, ha='center', va='center', size=12) ax.plot([xvalue], [yvalue], [zvalue], markerfacecolor=colorstate, markeredgecolor=colorstate, marker='o', markersize=np.sqrt(prob) * 30, alpha=alfa) a = Arrow3D([0, xvalue], [0, yvalue], [0, zvalue], mutation_scale=20, alpha=prob, arrowstyle="-", color=colorstate, lw=2) ax.add_artist(a) # add weight lines for weight in range(d + 1): theta = np.linspace(-2 * np.pi, 2 * np.pi, 100) z = -2 * weight / d + 1 r = np.sqrt(1 - z**2) x = r * np.cos(theta) y = r * np.sin(theta) ax.plot(x, y, z, color=(.5, .5, .5), lw=1, ls=':', alpha=.5) # add center point ax.plot([0], [0], [0], markerfacecolor=(.5, .5, .5), markeredgecolor=(.5, .5, .5), marker='o', markersize=3, alpha=1) else: break n = 64 theta = np.ones(n) ax2 = fig.add_subplot(gs[2:, 2:]) ax2.pie(theta, colors=sns.color_palette("hls", n), radius=0.75) ax2.add_artist( Circle((0, 0), 0.5, color=plt.rcParams['figure.facecolor'], zorder=1)) offset = 0.95 # since radius of sphere is one. if use_degrees: labels = ['Phase\n(Deg)', '0', '90', '180 ', '270'] else: labels = ['Phase', '$0$', '$\\pi/2$', '$\\pi$', '$3\\pi/2$'] ax2.text(0, 0, labels[0], horizontalalignment='center', verticalalignment='center', fontsize=14) ax2.text(offset, 0, labels[1], horizontalalignment='center', verticalalignment='center', fontsize=14) ax2.text(0, offset, labels[2], horizontalalignment='center', verticalalignment='center', fontsize=14) ax2.text(-offset, 0, labels[3], horizontalalignment='center', verticalalignment='center', fontsize=14) ax2.text(0, -offset, labels[4], horizontalalignment='center', verticalalignment='center', fontsize=14) if return_fig: if get_backend() in [ 'module://ipykernel.pylab.backend_inline', 'nbAgg' ]: plt.close(fig) return fig
def process_fidelity(channel, target=None, require_cp=True, require_tp=True): r"""Return the process fidelity of a noisy quantum channel. The process fidelity :math:`F_{\text{pro}}(\mathcal{E}, \methcal{F})` between two quantum channels :math:`\mathcal{E}, \mathcal{F}` is given by .. math: F_{\text{pro}}(\mathcal{E}, \mathcal{F}) = F(\rho_{\mathcal{E}}, \rho_{\mathcal{F}}) where :math:`F` is the :func:`~qiskit.quantum_info.state_fidelity`, :math:`\rho_{\mathcal{E}} = \Lambda_{\mathcal{E}} / d` is the normalized :class:`~qiskit.quantum_info.Choi` matrix for the channel :math:`\mathcal{E}`, and :math:`d` is the input dimension of :math:`\mathcal{E}`. When the target channel is unitary this is equivalent to .. 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:`\mathcal{E}` and *target* unitary :math:`U` respectively, and :math:`d` is the input dimension of the channel. Args: channel (Operator or QuantumChannel): input quantum channel. target (Operator or QuantumChannel or None): target quantum channel. If `None` target is the identity operator [Default: None]. require_cp (bool): check if input and target channels are completely-positive and if non-CP log warning containing negative eigenvalues of Choi-matrix [Default: True]. require_tp (bool): check if input and target channels are trace-preserving and if non-TP log warning containing negative eigenvalues of partial Choi-matrix :math:`Tr_{\mbox{out}}[\mathcal{E}] - I` [Default: True]. Returns: float: The process fidelity :math:`F_{\text{pro}}`. Raises: QiskitError: if the channel and target do not have the same dimensions. """ # Format inputs channel = _input_formatter(channel, SuperOp, 'process_fidelity', 'channel') target = _input_formatter(target, Operator, 'process_fidelity', 'target') if target: # Validate dimensions if channel.dim != target.dim: raise QiskitError( 'Input quantum channel and target unitary must have the same ' 'dimensions ({} != {}).'.format(channel.dim, target.dim)) # Validate complete-positivity and trace-preserving for label, chan in [('Input', channel), ('Target', target)]: if chan is not None and require_cp: cp_cond = _cp_condition(chan) neg = cp_cond < -1 * chan.atol if np.any(neg): logger.warning( '%s channel is not CP. Choi-matrix has negative eigenvalues: %s', label, cp_cond[neg]) if chan is not None and require_tp: tp_cond = _tp_condition(chan) non_zero = np.logical_not( np.isclose(tp_cond, 0, atol=chan.atol, rtol=chan.rtol)) if np.any(non_zero): logger.warning( '%s channel is not TP. Tr_2[Choi] - I has non-zero eigenvalues: %s', label, tp_cond[non_zero]) if isinstance(target, Operator): # Compute fidelity with unitary target by applying the inverse # to channel and computing fidelity with the identity channel = channel @ target.adjoint() target = None input_dim, _ = channel.dim if target is None: # 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(SuperOp(channel).data) / (input_dim**2) return float(np.real(fid)) # For comparing two non-unitary channels we compute the state fidelity of # the normalized Choi-matrices. This is equivalent to the previous definition # when the target is a unitary channel. state1 = DensityMatrix(Choi(channel).data / input_dim) state2 = DensityMatrix(Choi(target).data / input_dim) return state_fidelity(state1, state2, validate=False)