예제 #1
0
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
예제 #2
0
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)
예제 #3
0
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
예제 #4
0
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
예제 #5
0
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)
예제 #6
0
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
예제 #7
0
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
예제 #8
0
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
예제 #9
0
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