def stacked_QAE_fidelity(QAE, L, k): # QAE : QAE to use # L : list of test states # k : stacking maximum backend = Aer.get_backend('statevector_simulator') QAE_circ, out_qubits = QAE.subroutine_2() circ_0 = QuantumCircuit(QAE.W) circ_0.append(L[0][0], range(QAE.M[0])) for i in range(k): circ_0.append(QAE_circ, range(QAE.W)) circ_0.reset(list(set(range(QAE.W)) - set(out_qubits))) fid = [] for pair in L: circ_0.data[0] = (pair[0], circ_0.data[0][1], circ_0.data[0][2]) state_0 = partial_trace(execute(circ_0, backend).result().get_statevector(), list(set(range(QAE.W)) - set(out_qubits))) state_1 = execute(pair[1], backend).result().get_statevector() fid.append(state_fidelity(state_0, state_1)) return fid
def stacked_QAE_fidelity_range(QAE, L, k, filename = None): # QAE : QAE to use # L : list of test states # k : stacking maximum # filename : whether to save figure and figure name backend = Aer.get_backend('statevector_simulator') QAE_circ, out_qubits = QAE.subroutine_2() circ_0 = QuantumCircuit(QAE.W) circ_0.append(L[0][0], range(QAE.M[0])) all_fid_mean = [] all_fid_std = [] for i in range(k): circ_0.append(QAE_circ, range(QAE.W)) circ_0.reset(list(set(range(QAE.W)) - set(out_qubits))) fid = [] for pair in L: circ_0.data[0] = (pair[0], circ_0.data[0][1], circ_0.data[0][2]) state_0 = partial_trace(execute(circ_0, backend).result().get_statevector(), list(set(range(QAE.W)) - set(out_qubits))) state_1 = execute(pair[1], backend).result().get_statevector() fid.append(state_fidelity(state_0, state_1)) all_fid_mean.append(np.mean(fid)) all_fid_std.append(np.std(fid)) # Plot plt.errorbar(range(1,k+1), all_fid_mean, all_fid_std, marker = 'D', ms = 4, color = '#0000CC', capsize = 5) plt.grid(lw = 0.5, ls = 'dotted') plt.xlabel('Number of QAEs stacked') plt.ylabel('Fidelity with GHZ') if filename != None: plt.savefig(filename + '.pdf', bbox_inches = 'tight')
def collisional_model(self, channel='phase_damping', target_qubit=2, collision_number=5, theta=np.pi / 4, initial_statevector=np.array([1, 1] / np.sqrt(2)), print_results=True, **kwargs): # collisional model function def collision(channel, **kwargs): # introduces one collision into the circuit if channel == 'phase_damping': phase_damping(**kwargs) def phase_damping(qc, q, a, k, theta): # sets up phase damping collision qc.h(a[k - 1]) qc.cx(a[k - 1], q[-1]) qc.rz(theta, q[-1]) qc.cx(a[k - 1], q[-1]) # obtains unitary function for the phase damping operator def phase_damping_operator(n, theta): if n: # unitary function when measuring entangled state _qc = QuantumCircuit(n) _qc.h(-1) _qc.cx(-1, -2) _qc.rz(theta, -2) _qc.cx(-1, -2) else: # unitary function when measuring teleported state _qc = QuantumCircuit(2) _qc.h(1) _qc.cx(1, 0) _qc.rz(theta, 0) _qc.cx(1, 0) job = execute(_qc, Aer.get_backend('unitary_simulator')) result = job.result() return result.get_unitary() def evolve_theoretical_rho( rho, U, traced_out_qubit ): # evolution function of state after every collision evolving_rho = np.kron(np.array([[1, 0], [0, 0]]), rho) evolved_rho = np.matmul(np.matmul(U, evolving_rho), U.conj().T) return partial_trace(evolved_rho, [traced_out_qubit]).data def apply_protocol(qc, state): # apply teleportation protocol qc.barrier() # barrier to separate components qc.cx(0, 1) qc.h(0) if state == 'GHZ': # protocol for secret sharing qc.h(2) qc.cz(2, 3) qc.cx(1, 3) qc.cz(0, 3) elif state == 'Bell': # protocol for standard teleportation qc.cx(1, 2) qc.cz(0, 2) def revert(qc, d): # Function to revert back before protocol was applied for _ in range(d): qc.data.pop(-1) print('Collisional model for protocol {} on {}:'.format( self.protocol, self.device)) print('Channel: {}, number of collisions: {}\n'.format( channel, collision_number)) if self.protocol == 'GHZ_teleport': # Settings for secret sharing protocol state = 'GHZ' n = 3 d = 6 theoretical_rho_S = self.general_GHZ_matrix( n) # Create theoretical GHZ state elif self.protocol == 'Bell_teleport': # Settings for standard teleportation protocol state = 'Bell' n = 2 d = 4 theoretical_rho_S = self.general_GHZ_matrix( n) # Create theoretical Bell state # Create quantum circuit q = QuantumRegister(n + 1, 'q') qc = QuantumCircuit(q) # Initialise random qubit state to be teleported qc.initialize(initial_statevector, q[0]) theoretical_rho_T = np.outer(initial_statevector, np.conj(initial_statevector)) self.general_GHZ(qc, q, n, 1) # Create general GHZ state in circuit # Generate ancilla states if collision_number > 0: a = QuantumRegister(collision_number, 'a') qc.add_register(a) if channel == 'phase_damping': # Create phase damping operators U_S = phase_damping_operator(n + 1, theta) U_T = phase_damping_operator(None, theta) # create lists used to store data for simulation theoretical_rho_S_list = [] rho_S_list = [] theoretical_rho_T_list = [] rho_T_list = [] numerical_tangle_list = [] concurrence_list = [] fidelity_S_list = [] fidelity_T_list = [] rho_S_list_sim = [] rho_T_list_sim = [] numerical_tangle_list_sim = [] concurrence_list_sim = [] fidelity_S_list_sim = [] fidelity_T_list_sim = [] job_ids_list = [] # Start simulation and collision process for k in range(collision_number + 1): if k > 0: # Apply collision to circuit collision(channel, qc=qc, q=q, a=a, k=k, theta=theta) theoretical_rho_S = evolve_theoretical_rho( theoretical_rho_S, U_S, n) theoretical_rho_T = evolve_theoretical_rho( theoretical_rho_T, U_T, 1) if self.protocol == 'GHZ_teleport': # State tomography for GHZ state rho_S, S_id = self.tomography(qc, self.backend, True, [q[1], q[2], q[3]]) elif self.protocol == 'Bell_teleport': # State tomography for Bell state rho_S, S_id = self.tomography(qc, self.backend, True, [q[1], q[2]]) apply_protocol(qc, state) # applies teleportation protocol in circuit # State tomography for teleported state rho_T, T_id = self.tomography(qc, self.backend, True, [q[target_qubit]]) revert(qc, d) # Revert back before protocol was applied # Append results to respective lists, calculated concurrence/three-tangle and fidelity rho_S_list.append(rho_S) theoretical_rho_S_list.append(theoretical_rho_S) rho_T_list.append(rho_T) theoretical_rho_T_list.append(theoretical_rho_T) job_ids_list.append([S_id, T_id]) if self.protocol == 'GHZ_teleport': NT = self.three_tangle(rho_S, **kwargs) numerical_tangle_list.append(NT) if self.protocol == 'Bell_teleport': C = self.concurrence(rho_S) concurrence_list.append(C) F_S = state_fidelity(theoretical_rho_S_list[0], rho_S) fidelity_S_list.append(F_S) F_T = state_fidelity(theoretical_rho_T_list[0], rho_T) fidelity_T_list.append(F_T) # simulate artificial quantum computer if self.qasm_sim: if self.protocol == 'GHZ_teleport': # State tomography for GHZ state rho_S_sim, _ = self.tomography( qc, Aer.get_backend('qasm_simulator'), False, [q[1], q[2], q[3]]) elif self.protocol == 'Bell_teleport': # State tomography for Bell state rho_S_sim, _ = self.tomography( qc, Aer.get_backend('qasm_simulator'), False, [q[1], q[2]]) apply_protocol( qc, state) # applies teleportation protocol in circuit rho_T_sim, _ = self.tomography( qc, Aer.get_backend('qasm_simulator'), False, [q[target_qubit]]) revert(qc, d) # Revert back before protocol was applied # Append results to respective lists, calculated concurrence/three-tangle and fidelity rho_S_list_sim.append(rho_S_sim) rho_T_list_sim.append(rho_T_sim) if self.protocol == 'GHZ_teleport': NT_sim = self.three_tangle(rho_S_sim, **kwargs) numerical_tangle_list_sim.append(NT_sim) if self.protocol == 'Bell_teleport': C_sim = self.concurrence(rho_S_sim) concurrence_list_sim.append(C_sim) F_S_sim = state_fidelity(theoretical_rho_S_list[0], rho_S_sim) fidelity_S_list_sim.append(F_S_sim) F_T_sim = state_fidelity(theoretical_rho_T_list[0], rho_T_sim) fidelity_T_list_sim.append(F_T_sim) # Print results and other quantities if print_results: print("Collision Number:", k) print("Original {} Density Matrix:".format(state)) print(theoretical_rho_S_list[0]) print("Theoretical {} Density Matrix:".format(state)) print(theoretical_rho_S) print("Measured {} Density Matrix:".format(state)) print(rho_S) print("Original Teleported Density Matrix:") print(theoretical_rho_T_list[0]) print("Theoretical Teleported Density Matrix:") print(theoretical_rho_T) print("Measured Teleported Density Matrix:") print(rho_T) print("Trace:", np.real(np.trace(rho_S))) if self.protocol == 'GHZ_teleport': print("Numerical Three-Tangle:", NT) if self.protocol == 'Bell_teleport': print("Concurrence:", C) print("{} State Fidelity:".format(state), F_S) print("Teleported state Fidelity:", F_T) print("{} state Eigenvalues:".format(state), np.sort(np.real(np.linalg.eigvals(rho_S)))[::-1]) print("Teleported state Eigenvalues:", np.sort(np.real(np.linalg.eigvals(rho_T)))[::-1]) print('\n') if self.save_results: # save results self.save_data(self.directory, collision_number=collision_number, theoretical_rho_S_list=theoretical_rho_S_list, rho_S_list=rho_S_list, theoretical_rho_T_list=theoretical_rho_T_list, rho_T_list=rho_T_list, numerical_tangle_list=numerical_tangle_list, concurrence_list=concurrence_list, fidelity_S_list=fidelity_S_list, fidelity_T_list=fidelity_T_list, rho_S_list_sim=rho_S_list_sim, rho_T_list_sim=rho_T_list_sim, numerical_tangle_list_sim=numerical_tangle_list_sim, concurrence_list_sim=concurrence_list_sim, fidelity_S_list_sim=fidelity_S_list_sim, fidelity_T_list_sim=fidelity_T_list_sim, theta=theta, initial_statevector=initial_statevector, job_ids_list=job_ids_list, **kwargs)
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)
def collisional_model(self, backend='qasm_simulator', live=False, noise_model=None, channel='amplitude_damping', shots=1024, measured_qubits=(), max_collisions=5, theta=np.pi / 2, concurrence=False, tangle=False, witness=False, tangle_witness=True, markov=True, print_results=True, save_results=False, directory=None, full=True, initial_statevector=np.array([0.5, 0.5])): self.channel = channel self.markov = markov if self.state == 'h': self.qc.h(0) elif self.state == 'GHZ_teleport': initial_state = initial_statevector initial_state /= np.linalg.norm(initial_state) self.qc.initialize(initial_state, [0]) self.theoretical_rho = np.outer(initial_state, initial_state) self.GHZ([1, 2, 3]) elif self.state == 'D': self.D() if full: self.theoretical_rho = self.full_theoretical_D(self.n, self.k) else: self.theoretical_rho = self.theoretical_D(self.n, self.k) elif self.state == 'W': self.W() if full: self.theoretical_rho = self.full_theoretical_D(self.n, self.k) else: self.theoretical_rho = self.theoretical_D(self.n, self.k) elif self.state == 'GHZ': self.GHZ(range(self.n)) if full: self.theoretical_rho = self.full_theoretical_GHZ(self.n) else: self.theoretical_rho = self.theoretical_GHZ(self.n) self.histogram_data = [] self.directory = directory if backend == 'qasm_simulator': self.backend = Aer.get_backend('qasm_simulator') self.coupling_map = None if noise_model: self.noise_model = noise_model self.basis_gates = self.noise_model.basis_gates else: self.noise_model = None self.basis_gates = None else: provider = IBMQ.get_provider(group='open') self.device = provider.get_backend(backend) self.properties = self.device.properties() self.coupling_map = self.device.configuration().coupling_map self.noise_model = noise.device.basic_device_noise_model( self.properties) self.basis_gates = self.noise_model.basis_gates if save_results: visual.plot_error_map(self.device).savefig( '{}/error_map.png'.format(self.directory)) if live: check = input( "Are you sure you want to run the circuit live? [y/n]: ") if check == 'y' or check == 'yes': self.backend = self.device else: self.backend = Aer.get_backend('qasm_simulator') else: self.backend = Aer.get_backend('qasm_simulator') self.unitary_backend = Aer.get_backend('unitary_simulator') self.shots = shots if self.state == 'GHZ_teleport': self.a = 3 self.b = 3 elif measured_qubits == (): self.a = 0 self.b = self.n - 1 else: self.a = measured_qubits[0] self.b = measured_qubits[1] self.max_collisions = max_collisions self.theta = theta if self.max_collisions > 0: if self.channel == 'phase_damping_one_qubit': self.ancilla = QuantumRegister(1, 'a') self.qc.add_register(self.ancilla) elif not markov: self.ancilla = QuantumRegister(3, 'a') self.qc.add_register(self.ancilla) self.qc.h(self.ancilla[2]) self.qc.cx(self.ancilla[2], self.ancilla[1]) self.qc.cx(self.ancilla[1], self.ancilla[0]) else: self.ancilla = QuantumRegister(self.max_collisions, 'a') self.qc.add_register(self.ancilla) if self.channel == 'amplitude_damping': self.U = self.amplitude_damping_operator(full) elif self.channel == 'phase_damping' or 'phase_damping_one_qubit': self.U = self.phase_damping_operator(full) elif self.channel == 'heisenberg': print('Not yet programmed') quit() self.U = self.heisenberg_operator() if tangle_witness: self.tangle_witness_GHZ = 3 / 4 * np.kron(np.kron( I, I), I) - self.full_theoretical_GHZ(3) self.tangle_witness_tri = 1 / 2 * np.kron(np.kron( I, I), I) - self.full_theoretical_GHZ(3) counts_list = [] rho_list = [] theoretical_rho_list = [] concurrence_list = [] theoretical_concurrence_list = [] tangle_ub_list = [] tangle_lb_list = [] theoretical_tangle_list = [] fidelity_list = [] witness_list = [] tangle_witness_GHZ_list = [] tangle_witness_tri_list = [] trace_squared_list = [] for k in range(self.max_collisions + 1): if k > 0: self.collision(k) if witness: counts = self.measure(witness) else: rho = self.tomography(full) self.evolve_theoretical_rho(full) else: if witness: counts = 1 / 2 else: rho = self.tomography(full) if save_results: visual.plot_state_city(rho).savefig( '{}/state_city_{}.png'.format(self.directory, k)) visual.plot_state_paulivec(rho).savefig( '{}/paulivec_{}.png'.format(self.directory, k)) if witness: counts_list.append(counts) else: rho_list.append(rho) theoretical_rho_list.append(self.theoretical_rho) if not full and self.state != 'GHZ_teleport': C = self.concurrence(rho) concurrence_list.append(C) TC = self.concurrence(self.theoretical_rho) theoretical_concurrence_list.append(TC) if tangle: T_ub = self.tangle_ub(rho) tangle_ub_list.append(T_ub) #T_lb = self.tangle_lb(rho) #tangle_lb_list.append(T_lb) if self.state == 'GHZ': TT = np.exp(np.log(np.cos(self.theta)) * k) theoretical_tangle_list.append(TT) F = state_fidelity(theoretical_rho_list[0], rho) fidelity_list.append(F) if witness: W = np.real( np.trace( np.matmul(self.full_theoretical_GHZ(self.n), rho))) witness_list.append(W) if tangle_witness and full: TWGHZ = np.real( np.trace(np.matmul(self.tangle_witness_GHZ, rho))) tangle_witness_GHZ_list.append(TWGHZ) TWtri = np.real( np.trace(np.matmul(self.tangle_witness_tri, rho))) tangle_witness_tri_list.append(TWtri) Tr2 = np.real(np.trace(np.matmul(rho, rho))) trace_squared_list.append(Tr2) if print_results: print("Collision Number:", k) print("Original Density Matrix:") print(theoretical_rho_list[0]) print("Theoretical Density Matrix:") print(self.theoretical_rho) print("Measured Density Matrix:") print(rho) print("Trace:", np.real(np.trace(rho))) print("Trace Squared:", Tr2) if concurrence: print("Concurrence:", C) print("Theoretical Concurrence:", TC) if tangle: print("Tangle Upper Bound:", T_ub) #print("Tangle Lower Bound:", T_lb) print("Theoretical Tangle:", TT) print("Fidelity:", F) if witness: print("Witness:", W) if tangle_witness and full: print("GHZ Tangle Witness:", TWGHZ) print("Tripartite Tangle Witness:", TWtri) print("Eigenvalues:", np.sort(np.real(np.linalg.eigvals(rho)))[::-1]) print( "\n-----------------------------------------------------------------------------------\n" ) if print_results: if save_results: visual.circuit_drawer( self.qc, filename='{}/constructed_circuit.png'.format( self.directory), output='mpl') else: print(self.qc) print("Constructed Circuit Depth: ", self.qc.depth()) try: transpiled_qc = transpile(self.qc, self.device, optimization_level=3) if save_results: visual.circuit_drawer( transpiled_qc, filename='{}/transpiled_circuit.png'.format( self.directory), output='mpl') print("Transpiled Circuit Depth: ", self.qc.depth()) except: pass print() if save_results: visual.plot_histogram( self.histogram_data, title=self.state).savefig( '{}/histogram.png'.format(self.directory)) return counts_list, theoretical_rho_list, rho_list, theoretical_concurrence_list, concurrence_list, \ theoretical_tangle_list, tangle_ub_list, tangle_lb_list, fidelity_list, witness_list, \ tangle_witness_GHZ_list, tangle_witness_tri_list, trace_squared_list
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)