def _gate_matrix(self, gate): """Gets a PTM representation of the gate""" if isinstance(gate, Gate): return PTM(gate).data if callable(gate): c = QuantumCircuit(1) gate(c, c.qubits[0]) return PTM(c).data return None
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 linear_inversion(self) -> Dict[str, PTM]: """ Reconstruct a gate set from measurement data using linear inversion. Returns: For each gate in the gateset: its approximation found using the linear inversion process. Additional Information: Given a gate set (G1,...,Gm) and SPAM circuits (F1,...,Fn) constructed from those gates the data should contain the probabilities of the following types: p_ijk = E*F_i*G_k*F_j*rho p_ij = E*F_i*F_j*rho We have p_ijk = self.probs[(Fj, Gk, Fi)] since in self.probs (Fj, Gk, Fi) indicates first applying Fj, then Gk, then Fi. One constructs the Gram matrix g = (p_ij)_ij which can be described as a product g=AB where A = sum (i> <E F_i) and B=sum (F_j rho><j) For each gate Gk one can also construct the matrix Mk=(pijk)_ij which can be described as Mk=A*Gk*B Inverting g we obtain g^-1 = B^-1A^-1 and so g^1 * Mk = B^-1 * Gk * B This gives us a matrix similiar to Gk's representing matrix. However, it will not be the same as Gk, since the observable results cannot distinguish between (G1,...,Gm) and (B^-1*G1*B,...,B^-1*Gm*B) a further step of *Gauge optimization* is required on the results of the linear inversion stage. One can also use the linear inversion results as a starting point for a MLE optimization for finding a physical gateset, since unless the probabilities are accurate, the resulting gateset need not be physical. """ n = len(self.gateset_basis.spam_labels) m = len(self.gateset_basis.gate_labels) gram_matrix = np.zeros((n, n)) gate_matrices = [] for i in range(m): gate_matrices.append(np.zeros((n, n))) for i in range(n): # row for j in range(n): # column F_i = self.gateset_basis.spam_labels[i] F_j = self.gateset_basis.spam_labels[j] gram_matrix[i][j] = self.probs[(F_j, F_i)] for k in range(m): # gate G_k = self.gateset_basis.gate_labels[k] gate_matrices[k][i][j] = self.probs[(F_j, G_k, F_i)] gram_inverse = np.linalg.inv(gram_matrix) gates = [ PTM(gram_inverse @ gate_matrix) for gate_matrix in gate_matrices ] return dict(zip(self.gateset_basis.gate_labels, gates))
def test_depolarization_standard_basis(self): p = 0.05 noise_ptm = PTM( np.array([[1, 0, 0, 0], [0, 1 - p, 0, 0], [0, 0, 1 - p, 0], [0, 0, 0, 1 - p]])) noise_model = NoiseModel() noise_model.add_all_qubit_quantum_error(noise_ptm, ['u1', 'u2', 'u3']) self.run_test_on_basis_and_noise(noise_model=noise_model, noise_ptm=np.real(noise_ptm.data))
def test_amplitude_damping_standard_basis(self): gamma = 0.05 noise_ptm = PTM( np.array([[1, 0, 0, 0], [0, np.sqrt(1 - gamma), 0, 0], [0, 0, np.sqrt(1 - gamma), 0], [gamma, 0, 0, 1 - gamma]])) noise_model = NoiseModel() noise_model.add_all_qubit_quantum_error(noise_ptm, ['u1', 'u2', 'u3']) self.run_test_on_basis_and_noise(noise_model=noise_model, noise_ptm=np.real(noise_ptm.data))
def _process_result(self, x: np.array) -> Dict: """Transforms the optimization result to a friendly format Args: x: the optimization result vector Returns: The final GST data, as dictionary. """ E, rho, G_matrices = self._split_input_vector(x) result = {} result['E'] = Operator(self._convert_from_ptm(E)) result['rho'] = DensityMatrix(self._convert_from_ptm(rho)) for i in range(len(self.Gs)): result[self.Gs[i]] = PTM(G_matrices[i]) return result
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)
def _x_to_gateset(self, x: np.array) -> Dict[str, PTM]: """Converts the gauge to the gateset defined by it Args: x: An array representation of the B matrix Returns: The gateset obtained from B Additional information: Given a vector representation of B, this functions produces the list [B*G1*B^-1,...,B*Gn*B^-1] of gates correpsonding to the gauge B """ B = np.array(x).reshape((self.d, self.d)) try: BB = np.linalg.inv(B) except np.linalg.LinAlgError: return None gateset = {label: PTM(BB @ self.initial_gateset[label].data @ B) for label in self.gateset_basis.gate_labels} gateset['E'] = self.initial_gateset['E'] @ B gateset['rho'] = BB @ self.initial_gateset['rho'] return gateset
def _ideal_gateset(self, size): ideal_gateset = {label: PTM(self.gateset_basis.gate_matrices[label]) for label in self.gateset_basis.gate_labels} ideal_gateset['E'] = self._default_measurement_op(size) ideal_gateset['rho'] = self._default_init_state(size) return ideal_gateset