def state_log_likelihood(state, results, qubits) -> float: """ The log Likelihood function used in the diluted MLE tomography routine. Equation 2 of [DIMLE1] :param state: The state (given as a density matrix) that we think we have. :param results: Measured results from a state tomography experiment :param qubits: Qubits that were tomographized. :return: The log likelihood that our state is the one we believe it is. """ ll = 0 for res in results: n = res.total_counts op_matrix = pauli2matrix(res.setting.out_operator, qubits) meas_exp = res.expectation pred_exp = np.real(np.trace(op_matrix @ state)) for sign in [1, -1]: f_j = n * (1 + sign * meas_exp) / 2 pr_j = (1 + sign * pred_exp) / 2 if pr_j <= 0: continue ll += f_j * np.log10(pr_j) return ll
def linear_inv_state_estimate(results: List[ExperimentResult], qubits: List[int]) -> np.ndarray: """ Estimate a quantum state using linear inversion. This is the simplest state tomography post processing. To use this function, collect state tomography data with :py:func:`generate_state_tomography_experiment` and :py:func:`~pyquil.operator_estimation.measure_observables`. For more details on this post-processing technique, see https://en.wikipedia.org/wiki/Quantum_tomography#Linear_inversion or see section 3.4 of [WOOD] Initialization and characterization of open quantum systems C. Wood, PhD thesis from University of Waterloo, (2015). http://hdl.handle.net/10012/9557 :param results: A tomographically complete list of results. :param qubits: All qubits that were tomographized. This specifies the order in which qubits will be kron'ed together. :return: A point estimate of the quantum state rho. """ measurement_matrix = np.vstack([ vec(pauli2matrix(result.setting.out_operator, qubits=qubits)).T.conj() for result in results ]) expectations = np.array([result.expectation for result in results]) rho = pinv(measurement_matrix) @ expectations return unvec(rho)
def linear_inv_process_estimate(results: List[ExperimentResult], qubits: List[int]) -> np.ndarray: """ Estimate a quantum process using linear inversion. This is the simplest process tomography post processing. To use this function, collect process tomography data with :py:func:`generate_process_tomography_experiment` and :py:func:`~forest.benchmarking.observable_estimation.estimate_observables`. For more details on this post-processing technique, see https://en.wikipedia.org/wiki/Quantum_tomography#Linear_inversion or see section 3.5 of [WOOD]_ :param results: A tomographically complete list of results. :param qubits: All qubits that were tomographized. This specifies the order in which qubits will be kron'ed together; the first qubit in the list is the left-most tensor factor. :return: A point estimate of the quantum process represented by a Choi matrix """ # state2matrix and pauli2matrix use pyquil tensor factor ordering where the least significant # qubit, e.g. qubit 0, is the right-most tensor factor. We stick with the standard convention # here that the first qubit in the list is the left-most tensor factor, so we have to reverse # the qubits before passing to state2matrix and pauli2matrix qs = qubits[::-1] measurement_matrix = np.vstack([ vec(np.kron(state2matrix(result.setting.in_state, qs).conj(), pauli2matrix(result.setting.observable, qs))).conj().T for result in results ]) expectations = np.array([result.expectation for result in results]) rho = pinv(measurement_matrix) @ expectations # add in identity term dim = 2 ** len(qubits) return unvec(rho) + np.eye(dim**2) / dim
def state_log_likelihood(state: np.ndarray, results: Iterator[ExperimentResult], qubits: Sequence[int]) -> float: """ The log Likelihood function used in the diluted MLE tomography routine. Equation 2 of [DIMLE1]_ :param state: The state (given as a density matrix) that we think we have. :param results: Measured results from a state tomography experiment :param qubits: All qubits that were tomographized. This specifies the order in which qubits will be kron'ed together; the first qubit in the list is the left-most tensor factor. This should agree with the provided state. :return: The log likelihood that our state is the one we believe it is. """ # state2matrix and pauli2matrix use pyquil tensor factor ordering where the least significant # qubit, e.g. qubit 0, is the right-most tensor factor. We stick with the standard convention # here that the first qubit in the list is the left-most tensor factor, so we have to reverse # the qubits before passing to state2matrix and pauli2matrix qs = qubits[::-1] ll = 0 for res in results: n = res.total_counts op_matrix = pauli2matrix(res.setting.observable, qs) meas_exp = res.expectation pred_exp = np.real(np.trace(op_matrix @ state)) for sign in [1, -1]: f_j = n * (1 + sign * meas_exp) / 2 pr_j = (1 + sign * pred_exp) / 2 if pr_j <= 0: continue ll += f_j * np.log10(pr_j) return ll
def _R(state, results, qubits): r""" This implements Eqn 4 in [DIMLE1] As stated in [DIMLE1] eqn 4 reads R(rho) = (1/N) \sum_j (f_j/Pr_j) Pi_j N = total number of measurements f_j = number of times j'th outcome was observed Pi_j = measurement operator or projector, with \sum_j Pi_j = Id and Pi_j \geq 0 Pr_j = Tr[Pi_j \rho] (up to some normalization of the Pi_j) We are working with results whose out_operators are elements of the un-normalized Pauli basis. Each Pauli P_j can be split into projectors onto the plus and minus eigenspaces P_k = Pi_k^+ - Pi_k^- ; Pi_k^+ = (I + P_k) / 2 ; Pi_k^- = (I - P_k) / 2 where each Pi \geq 0 as required above. Hence for each P_k we associate two Pi_k, and subsequently two f_k. We can express these in terms of Exp[P_k] := exp_k plus: f_k^+ / N = (1 + exp_k) / 2 minus: f_k^- / N = (1 - exp_k) / 2 We use these f_k and Pi_k to arrive at the code below. Finally, since our Pauli's are not normalized, i.e. Pi_k^+ + Pi_k^- = Id, in order to enforce the condition \sum_j Pi_j = Id stated above we need to divide our final answer by the number of Paulis. :param state: The state (given as a density matrix) that we think we have. :param results: Measured results from a state tomography experiment. (assumes Pauli basis) :param qubits: Qubits that were tomographized. :return: the operator of equation 4 in [DIMLE1] which fixes rho by left and right multiplication """ # this small number ~ 10^-304 is added so that we don't get divide by zero errors machine_eps = np.finfo(float).tiny update = np.zeros_like(state, dtype=complex) IdH = np.eye(update.shape[0]) for res in results: op_matrix = pauli2matrix(res.setting.out_operator, qubits) meas_exp = res.expectation pred_exp = np.trace(op_matrix @ state) for sign in [1, -1]: f_j_over_n = (1 + sign * meas_exp) / 2 pr_j = (1 + sign * pred_exp) / 2 pi_j = (IdH + sign * op_matrix) / 2 update += f_j_over_n / (pr_j + machine_eps) * pi_j return update / len(results)
def _extract_from_results(results: List[ExperimentResult], qubits: List[int]): """ Construct the matrix A such that the probabilities p_ij of outcomes n_ij given an estimate E can be cast in a vectorized form. Specifically:: p = vec(p_ij) = A x vec(E) This yields convenient vectorized calculations of the cost and its gradient, in terms of A, n, and E. """ A = [] n = [] grand_total_shots = 0 for result in results: # 'lift' the result's ExperimentSetting input TensorProductState to the corresponding # matrix. This is simply the density matrix of the state that was prepared. in_state_matrix = state2matrix(result.setting.in_state, qubits=qubits) # 'lift' the result's ExperimentSetting output PauliTerm to the corresponding matrix. operator = pauli2matrix(result.setting.out_operator, qubits=qubits) proj_plus = (np.eye(2 ** len(qubits)) + operator) / 2 proj_minus = (np.eye(2 ** len(qubits)) - operator) / 2 # Constructing A per eq. (A1) of [PGD] # TODO: figure out if we can avoid re-splitting into Pi+ and Pi- counts A += [ # vec() turns into a column vector; transpose to a row vector; index into the # 1 row to avoid an extra tensor dimension when we call np.asarray(A). vec(np.kron(in_state_matrix, proj_plus.T)).T[0], vec(np.kron(in_state_matrix, proj_minus.T)).T[0], ] expected_plus_ones = (1 + result.expectation) / 2 n += [ result.total_counts * expected_plus_ones, result.total_counts * (1 - expected_plus_ones) ] grand_total_shots += result.total_counts n_qubits = len(qubits) dimension = 2 ** n_qubits A = np.asarray(A) / dimension ** 2 n = np.asarray(n)[:, np.newaxis] / grand_total_shots return A, n
def lasso_state_estimate(results: List[ExperimentResult], qubits: List[int]) -> np.ndarray: """ Estimate a quantum state using compressed sensing [FLAMMIA] Quantum Tomography via Compressed Sensing: Error Bounds, Sample Complexity, and Efficient Estimators Steven T. Flammia et. al. (2012). https://arxiv.org/pdf/1205.2300.pdf :param results: A tomographically complete list of results. :param qubits: All qubits that were tomographized. This specifies the order in which qubits will be kron'ed together. :return: A point estimate of the quantum state rho. """ pauli_num = len(results) qubit_num = len(qubits) d = 2 ** qubit_num pauli_list = [] expectation_list = [] y = np.zeros((m,1)) mu = 4 * pauli_num / np.sqrt(1000 * pauli_num) for i in range(m): #Convert the Pauli term into a tensor r = results[i] p_tensor = pauli2matrix(r.setting.out_operator, qubits) e = r.expectation y[i] = e pauli_list.append(p_tensor) expectation_list.append(e) x = cp.Variable((d,d),complex = True) A = cp.vstack([cp.trace(cp.matmul(pauli_list[i], x)) * np.sqrt(d / m) for i in range(m)]) #Minimize trace norm obj = cp.Minimize(0.5 * cp.norm((A - y), 2) + mu * cp.norm(x, 'nuc')) constraints = [cp.trace(x) == 1] # Form and solve problem. prob = cp.Problem(obj, constraints) prob.solve() rho = x.value return rho
def compressed_sensing_state_estimate(results: List[ExperimentResult], qubits: List[int]) -> np.ndarray: """ Estimate a quantum state using compressed sensing [FLAMMIA] Quantum Tomography via Compressed Sensing: Error Bounds, Sample Complexity, and Efficient Estimators Steven T. Flammia et. al. (2012). https://arxiv.org/pdf/1205.2300.pdf :param results: A tomographically complete list of results. :param qubits: All qubits that were tomographized. This specifies the order in which qubits will be kron'ed together. :return: A point estimate of the quantum state rho. """ pauli_num = len(results) qubit_num = len(qubits) d = 2 ** qubit_num pauli_list = [] expectation_list = [] y = np.zeros((m,1)) for i in range(pauli_num): #Convert the Pauli term into a tensor r = results[i] p_tensor = pauli2matrix(r.setting.out_operator, qubits) e = r.expectation #A[i] = e * scale_factor pauli_list.append(p_tensor) expectation_list.append(e) s = cp.Variable((d,d),complex = True) obj = cp.Minimize(cp.norm(s, 'nuc')) constraints = [cp.trace(s) == 1] for i in range(len(pauli_list)): trace_bool = cp.trace(cp.matmul(pauli_list[i], s)) - expectation_list[i] == 0 constraints.append(trace_bool) # Form and solve problem. prob = cp.Problem(obj, constraints) prob.solve() rho = s.value return rho
def linear_inv_state_estimate(results: List[ExperimentResult], qubits: List[int]) -> np.ndarray: """ Estimate a quantum state using linear inversion. This is the simplest state tomography post processing. To use this function, collect state tomography data with :py:func:`generate_state_tomography_experiment` and :py:func:`~observable_estimation.estimate_observables`. For more details on this post-processing technique, see https://en.wikipedia.org/wiki/Quantum_tomography#Linear_inversion or see section 3.4 of [WOOD]_ .. [WOOD] Initialization and characterization of open quantum systems. C. Wood. PhD thesis from University of Waterloo, (2015). http://hdl.handle.net/10012/9557 :param results: A tomographically complete list of results. :param qubits: All qubits that were tomographized. This specifies the order in which qubits will be kron'ed together; the first qubit in the list is the left-most tensor factor. :return: A point estimate of the quantum state rho. """ # state2matrix and pauli2matrix use pyquil tensor factor ordering where the least significant # qubit, e.g. qubit 0, is the right-most tensor factor. We stick with the standard convention # here that the first qubit in the list is the left-most tensor factor, so we have to reverse # the qubits before passing to state2matrix and pauli2matrix qs = qubits[::-1] measurement_matrix = np.vstack([ vec(pauli2matrix(result.setting.observable, qubits=qs)).T.conj() for result in results]) expectations = np.array([result.expectation for result in results]) rho = pinv(measurement_matrix) @ expectations # add in the traceful identity term dim = 2**len(qubits) return unvec(rho) + np.eye(dim) / dim