def test_channel_process_fidelity(self): """Test the process_fidelity function for channel inputs""" depol = Choi(np.eye(4) / 2) iden = Choi(Operator.from_label('I')) # Completely depolarizing channel f_pro = process_fidelity(depol, require_cp=True, require_tp=True) self.assertAlmostEqual(f_pro, 0.25, places=7) # Identity f_pro = process_fidelity(iden, require_cp=True, require_tp=True) self.assertAlmostEqual(f_pro, 1.0, places=7) # Depolarizing channel prob = 0.3 chan = prob * depol + (1 - prob) * iden f_pro = process_fidelity(chan, require_cp=True, require_tp=True) f_target = prob * 0.25 + (1 - prob) self.assertAlmostEqual(f_pro, f_target, places=7) # Depolarizing channel prob = 0.5 op = Operator.from_label('Y') chan = (prob * depol + (1 - prob) * iden) @ op f_pro = process_fidelity(chan, op, require_cp=True, require_tp=True) target = prob * 0.25 + (1 - prob) self.assertAlmostEqual(f_pro, target, places=7)
def test_channel_average_gate_fidelity(self): """Test the average_gate_fidelity function for channel inputs""" depol = Choi(np.eye(4) / 2) iden = Choi(Operator.from_label('I')) # Completely depolarizing channel f_ave = average_gate_fidelity(depol, require_cp=True, require_tp=True) self.assertAlmostEqual(f_ave, 0.5, places=7) # Identity f_ave = average_gate_fidelity(iden, require_cp=True, require_tp=True) self.assertAlmostEqual(f_ave, 1.0, places=7) # Depolarizing channel prob = 0.11 chan = prob * depol + (1 - prob) * iden f_ave = average_gate_fidelity(chan, require_cp=True, require_tp=True) f_target = (2 * (prob * 0.25 + (1 - prob)) + 1) / 3 self.assertAlmostEqual(f_ave, f_target, places=7) # Depolarizing channel prob = 0.5 op = Operator.from_label('Y') chan = (prob * depol + (1 - prob) * iden).compose(op) f_ave = average_gate_fidelity(chan, op, require_cp=True, require_tp=True) target = (2 * (prob * 0.25 + (1 - prob)) + 1) / 3 self.assertAlmostEqual(f_ave, target, places=7)
def _fidelity_result( state_result: AnalysisResultData, target: Union[Choi, DensityMatrix], input_dim: int = 1, ): """Faster computation of fidelity from eigen decomposition""" evals = state_result.extra["eigvals"] evecs = state_result.extra["eigvecs"] # Format target to statevector or densitymatrix array name = "process_fidelity" if input_dim > 1 else "state_fidelity" if target is None: raise AnalysisError("No target state provided") if isinstance(target, QuantumChannel): target_state = Choi(target).data / input_dim elif isinstance(target, BaseOperator): target_state = np.ravel(Operator(target), order="F") / np.sqrt(input_dim) else: # Statevector or density matrix target_state = np.array(target) if target_state.ndim == 1: rho = evecs @ (evals / input_dim * evecs).T.conj() fidelity = np.real(target_state.conj() @ rho @ target_state) else: sqrt_rho = evecs @ (np.sqrt(evals / input_dim) * evecs).T.conj() eig = la.eigvalsh(sqrt_rho @ target_state @ sqrt_rho) fidelity = np.sum(np.sqrt(np.maximum(eig, 0)))**2 return AnalysisResultData(name, fidelity)
def test_noncp_process_fidelity(self): """Test process_fidelity for non-CP channel""" u1 = Operator.from_label('X') u2 = Operator.from_label('Z') chan = 1.01 * Choi(u1) - 0.01 * Choi(u2) fid = process_fidelity(chan) self.assertLogs('qiskit.quantum_info.operators.measures', level='WARNING') self.assertAlmostEqual(fid, 0, places=15)
def _target_quantum_channel(self) -> Union[Choi, Operator]: """Return the process tomography target""" # Check if circuit contains measure instructions # If so we cannot return target state circuit_ops = self._circuit.count_ops() if "measure" in circuit_ops: return None try: circuit = self._permute_circuit() if "reset" in circuit_ops or "kraus" in circuit_ops or "superop" in circuit_ops: channel = Choi(circuit) else: channel = Operator(circuit) except QiskitError: # Circuit couldn't be simulated return None total_qubits = self._circuit.num_qubits num_meas = total_qubits if self._meas_qubits is None else len(self._meas_qubits) num_prep = total_qubits if self._prep_qubits is None else len(self._prep_qubits) # If all qubits are prepared or measurement we are done if num_meas == total_qubits and num_prep == total_qubits: return channel # Convert channel to a state to project and trace out non-tomography # input and output qubits if isinstance(channel, Operator): chan_state = Statevector(np.ravel(channel, order="F")) else: chan_state = DensityMatrix(channel.data) # Get qargs for non measured and prepared subsystems non_meas_qargs = list(range(num_meas, total_qubits)) non_prep_qargs = list(range(total_qubits + num_prep, 2 * total_qubits)) # Project non-prepared subsystems on to the zero state if non_prep_qargs: proj0 = Operator([[1, 0], [0, 0]]) for qarg in non_prep_qargs: chan_state = chan_state.evolve(proj0, [qarg]) # Trace out indices to remove tr_qargs = non_meas_qargs + non_prep_qargs chan_state = partial_trace(chan_state, tr_qargs) channel = Choi(chan_state.data, input_dims=[2] * num_prep, output_dims=[2] * num_meas) return channel
def test_rank(self, rank): """Test random_quantum_channel with fixed rank {rank}""" choi = Choi(random_quantum_channel(2, rank=rank)) # Get number of non-zero eigenvalues evals = np.linalg.eigvals(choi.data).round(8) value = len(evals[evals > 0]) self.assertEqual(value, rank)
def _join_input_vector(self, E: np.array, rho: np.array, Gs: List[np.array] ) -> np.array: """Converts the GST data into a vector representation Args: E: The POVM measurement operator rho: The initial state Gs: The gates list Returns: The vector representation of (E, rho, Gs) Additional information: This function performs the inverse operation to split_input_vector; the notations are the same. """ d = (2 ** self.qubits) E_T = get_cholesky_like_decomposition(E.reshape((d, d))) rho_T = get_cholesky_like_decomposition(rho.reshape((d, d))) Gs_Choi = [Choi(PTM(G)).data for G in Gs] Gs_T = [get_cholesky_like_decomposition(G) for G in Gs_Choi] E_vec = self._complex_matrix_to_vec(E_T) rho_vec = self._complex_matrix_to_vec(rho_T) result = E_vec + rho_vec for G_T in Gs_T: result += self._complex_matrix_to_vec(G_T) return np.array(result)
def test_nontp_process_fidelity(self): """Test process_fidelity for non-TP channel""" chan = 0.99 * Choi(Operator.from_label('X')) fid = process_fidelity(chan) self.assertLogs('qiskit.quantum_info.operators.measures', level='WARNING') self.assertAlmostEqual(fid, 0, places=15)
def run_circuit_and_tomography(circuit, qubits, method): choi_ideal = Choi(circuit).data qst = tomo.process_tomography_circuits(circuit, qubits) job = qiskit.execute(qst, Aer.get_backend('qasm_simulator'), shots=5000) tomo_fit = tomo.ProcessTomographyFitter(job.result(), qst) choi = tomo_fit.fit(method=method).data return (choi, choi_ideal)
def test_channel_gate_error(self): """Test the gate_error function for channel inputs""" depol = Choi(np.eye(4) / 2) iden = Choi(Operator.from_label('I')) # Depolarizing channel prob = 0.11 chan = prob * depol + (1 - prob) * iden err = gate_error(chan, require_cp=True, require_tp=True) target = 1 - average_gate_fidelity(chan) self.assertAlmostEqual(err, target, places=7) # Depolarizing channel prob = 0.5 op = Operator.from_label('Y') chan = (prob * depol + (1 - prob) * iden) @ op err = gate_error(chan, op, require_cp=True, require_tp=True) target = 1 - average_gate_fidelity(chan, op) self.assertAlmostEqual(err, target, places=7)
def test_diamond_norm(self, num_qubits): """Test the diamond_norm for {num_qubits}-qubit pauli channel.""" try: import cvxpy except ImportError: # Skip test if CVXPY not installed self.skipTest("CVXPY not installed.") # Pauli channels have an analytic expression for the # diamond norm so we can easily test it op = Choi(np.zeros((4 ** num_qubits, 4 ** num_qubits))) labels = [num_qubits * i for i in ['I', 'X', 'Y', 'Z']] coeffs = [-1.0, 0.5, 2.5, -0.1] for coeff, label in zip(coeffs, labels): op = op + coeff * Choi(Operator.from_label(label)) target = np.sum(np.abs(coeffs)) try: value = diamond_norm(op) self.assertAlmostEqual(value, target, places=4) except cvxpy.SolverError: self.skipTest("CVXPY solver failed.")
def _fidelity_result(evals, evecs, target, qpt=False): """Faster computation of fidelity from eigen decomposition""" # Format target to statevector or densitymatrix array trace = np.sqrt(len(evals)) if qpt else 1 name = "process_fidelity" if qpt else "state_fidelity" if target is None: raise AnalysisError("No target state provided") if isinstance(target, QuantumChannel): target_state = Choi(target).data / trace elif isinstance(target, BaseOperator): target_state = np.ravel(Operator(target), order="F") / np.sqrt(trace) else: target_state = np.array(target) if target_state.ndim == 1: rho = evecs @ (evals / trace * evecs).T.conj() fidelity = np.real(target_state.conj() @ rho @ target_state) else: sqrt_rho = evecs @ (np.sqrt(evals / trace) * evecs).T.conj() eig = la.eigvalsh(sqrt_rho @ target_state @ sqrt_rho) fidelity = np.sum(np.sqrt(np.maximum(eig, 0))) ** 2 return AnalysisResultData(name, fidelity)
def _run_analysis(self, experiment_data, **options): # Extract tomography measurement data outcome_data, shot_data, measurement_data, preparation_data = self._fitter_data( experiment_data.data()) # Get tomography options measurement_basis = options.pop("measurement_basis") preparation_basis = options.pop("preparation_basis", None) rescale_positive = options.pop("rescale_positive") rescale_trace = options.pop("rescale_trace") target_state = options.pop("target") # Get target state from circuit metadata if target_state == "default": metadata = experiment_data.metadata target_state = metadata.get("target", None) # Get tomography fitter function fitter = self._get_fitter(options.pop("fitter", None)) try: t_fitter_start = time.time() state, fitter_metadata = fitter( outcome_data, shot_data, measurement_data, preparation_data, measurement_basis, preparation_basis, **options, ) t_fitter_stop = time.time() if fitter_metadata is None: fitter_metadata = {} state = Choi(state) if preparation_basis else DensityMatrix(state) fitter_metadata["fitter"] = fitter.__name__ fitter_metadata["fitter_time"] = t_fitter_stop - t_fitter_start analysis_results = self._postprocess_fit( state, metadata=fitter_metadata, target_state=target_state, rescale_positive=rescale_positive, rescale_trace=rescale_trace, qpt=bool(preparation_basis), ) except AnalysisError as ex: raise AnalysisError( f"Tomography fitter failed with error: {str(ex)}") from ex return analysis_results, []
def _state_result( cls, fit: np.ndarray, make_positive: bool = False, trace: Optional[float] = None, input_dims: Optional[Tuple[int, ...]] = None, output_dims: Optional[Tuple[int, ...]] = None, ) -> AnalysisResultData: """Convert fit data to state result data""" # Get eigensystem of state fit raw_eigvals, eigvecs = cls._state_eigensystem(fit) # Optionally rescale eigenvalues to be non-negative if make_positive and np.any(raw_eigvals < 0): eigvals = cls._make_positive(raw_eigvals) fit = eigvecs @ (eigvals * eigvecs).T.conj() rescaled_psd = True else: eigvals = raw_eigvals rescaled_psd = False # Optionally rescale fit trace fit_trace = np.sum(eigvals) if trace is not None and not np.isclose( fit_trace - trace, 0, atol=1e-12): scale = trace / fit_trace fit = fit * scale eigvals = eigvals * scale else: trace = fit_trace # Convert class of value if input_dims and np.prod(input_dims) > 1: value = Choi(fit, input_dims=input_dims, output_dims=output_dims) else: value = DensityMatrix(fit, dims=output_dims) # Construct state result extra metadata extra = { "trace": trace, "eigvals": eigvals, "raw_eigvals": raw_eigvals, "rescaled_psd": rescaled_psd, "eigvecs": eigvecs, } return AnalysisResultData("state", value, extra=extra)
def _run_analysis(self, experiment_data): # Extract tomography measurement data outcome_data, shot_data, measurement_data, preparation_data = self._fitter_data( experiment_data.data()) # Get tomography fitter function fitter = self._get_fitter(self.options.fitter) try: t_fitter_start = time.time() state, fitter_metadata = fitter( outcome_data, shot_data, measurement_data, preparation_data, self.options.measurement_basis, self.options.preparation_basis, **self.options.fitter_options, ) t_fitter_stop = time.time() if fitter_metadata is None: fitter_metadata = {} state = Choi( state) if self.options.preparation_basis else DensityMatrix( state) fitter_metadata["fitter"] = fitter.__name__ fitter_metadata["fitter_time"] = t_fitter_stop - t_fitter_start analysis_results = self._postprocess_fit( state, metadata=fitter_metadata, target_state=self.options.target, rescale_positive=self.options.rescale_positive, rescale_trace=self.options.rescale_trace, qpt=bool(self.options.preparation_basis), ) except AnalysisError as ex: raise AnalysisError( f"Tomography fitter failed with error: {str(ex)}") from ex return analysis_results, []
def _split_input_vector(self, x: np.array) -> Tuple: """Reconstruct the GST data from its vector representation Args: x: The vector representation of the GST data Returns: The GST data (E, rho, Gs) (see additional info) Additional information: The gate set tomography data is a tuple (E, rho, Gs) consisting of 1) A POVM measurement operator E 2) An initial quantum state rho 3) A list Gs = (G1, G2, ..., Gk) of gates, represented as matrices This function reconstructs (E, rho, Gs) from the vector x Since the MLE optimization procedure has PSD constraints on E, rho and the Choi represetnation of the PTM of the Gs, we rely on the following property: M is PSD iff there exists T such that M = T @ T^{dagger}. Hence, x stores those T matrices for E, rho and the Gs """ n = len(self.Gs) d = (2 ** self.qubits) ds = d ** 2 # d squared - the dimension of the density operator d_t = 2 * d ** 2 ds_t = 2 * ds ** 2 T_vars = self._split_list(x, [d_t, d_t] + [ds_t] * n) E_T = self._vec_to_complex_matrix(T_vars[0]) rho_T = self._vec_to_complex_matrix(T_vars[1]) Gs_T = [self._vec_to_complex_matrix(T_vars[2+i]) for i in range(n)] E = np.reshape(E_T @ np.conj(E_T.T), (1, ds)) rho = np.reshape(rho_T @ np.conj(rho_T.T), (ds, 1)) Gs = [PTM(Choi(G_T @ np.conj(G_T.T))).data for G_T in Gs_T] return (E, rho, Gs)