def test_commuting_sets():
    q = QubitPlaceholder.register(8)
    term1 = PauliTerm("X", q[0]) * PauliTerm("X", q[1])
    term2 = PauliTerm("Y", q[0]) * PauliTerm("Y", q[1])
    term3 = PauliTerm("Y", q[0]) * PauliTerm("Z", q[2])
    pauli_sum = term1 + term2 + term3
    commuting_sets(pauli_sum)
Esempio n. 2
0
def uccsd_ansatz_circuit_parametric(n_orbitals, n_electrons, cq=None):
    """
    This function returns a UCCSD variational ansatz with hard-coded gate angles. The number of orbitals specifies the
    number of qubits, the number of electrons specifies the initial HF reference state which is assumed was prepared.
    The list cq is an optional list which specifies the qubit label ordering which is to be assumed.


    :param int n_orbitals: number of spatial orbitals in the molecule (for building UCCSD singlet generator)
    :param int n_electrons: number of electrons in the molecule
    :param list() cq: custom qubits
    
    :return: program which prepares the UCCSD :math:`T_1 + T_2` propagator with a spin-singlet assumption.
    :rtype: Program
    """
    # determine number of required parameters
    trotter_steps = 1
    m = (2 * n_orbitals - n_electrons) * n_electrons / 4
    n_parameters = int(trotter_steps * (m * (m + 3)) / 2)

    prog = Program()
    memref = prog.declare('theta',
                          memory_type='REAL',
                          memory_size=n_parameters)

    fermionic_list, indices = uccsd_singlet_generator_with_indices(
        2 * n_orbitals, n_electrons)

    pauli_list = []
    index_dict = {}
    for i, fermionic_term in enumerate(fermionic_list):
        pauli_sum = qubitop_to_pyquilpauli(
            jordan_wigner(normal_ordered(fermionic_term)))
        for term in pauli_sum:
            new_term = term
            # if the QPU has a custom lattice labeling, supplied by user through a list cq, reorder the Pauli labels.
            if cq is not None:
                new_term = term.coefficient
                for pauli in term:
                    new_index = cq[pauli[0]]
                    op = pauli[1]
                    new_term = new_term * PauliTerm(op=op, index=new_index)

            pauli_list.append(new_term)
            # keep track of the indices in a dictionary
            index_dict[new_term.operations_as_set()] = indices[i]

    # add each term as successive exponentials (1 trotter step, and not taking into account commutation relations!)
    term_sets = commuting_sets(simplify_pauli_sum(PauliSum(pauli_list)))

    for j, terms in enumerate(term_sets):
        prog += exponentiate_commuting_pauli_sum_parametric(
            terms, index_dict, memref)

    return prog
Esempio n. 3
0
def uccsd_ansatz_circuit(packed_amplitudes, n_orbitals, n_electrons, cq=None):
    # TODO apply logical re-ordering to Fermionic non-parametric way too!
    """
    This function returns a UCCSD variational ansatz with hard-coded gate angles. The number of orbitals specifies the
    number of qubits, the number of electrons specifies the initial HF reference state which is assumed was prepared.
    The packed_amplitudes input defines which gate angles to apply for each CC operation. The list cq is an optional
    list which specifies the qubit label ordering which is to be assumed.

    :param list() packed_amplitudes: amplitudes t_ij and t_ijkl of the T_1 and T_2 operators of the UCCSD ansatz
    :param int n_orbitals: number of *spatial* orbitals
    :param int n_electrons: number of electrons considered
    :param list() cq: list of qubit label order

    :return: circuit which prepares the UCCSD variational ansatz
    :rtype: Program
    """

    # Fermionic UCCSD
    uccsd_propagator = normal_ordered(
        uccsd_singlet_generator(packed_amplitudes, 2 * n_orbitals,
                                n_electrons))
    qubit_operator = jordan_wigner(uccsd_propagator)

    # convert the fermionic propagator to a Pauli spin basis via JW, then convert to a Pyquil compatible PauliSum
    pyquilpauli = qubitop_to_pyquilpauli(qubit_operator)

    # re-order logical stuff if necessary
    if cq is not None:
        pauli_list = [PauliTerm("I", 0, 0.0)]
        for term in pyquilpauli.terms:
            new_term = term
            # if the QPU has a custom lattice labeling, supplied by user through a list cq, reorder the Pauli labels.
            if cq is not None:
                new_term = term.coefficient
                for pauli in term:
                    new_index = cq[pauli[0]]
                    op = pauli[1]
                    new_term = new_term * PauliTerm(op=op, index=new_index)

            pauli_list.append(new_term)
        pyquilpauli = PauliSum(pauli_list)

    # create a pyquil program which performs the ansatz state preparation with angles unpacked from packed_amplitudes
    ansatz_prog = Program()

    # add each term as successive exponentials (1 single Trotter step, not taking into account commutation relations!)
    for commuting_set in commuting_sets(simplify_pauli_sum(pyquilpauli)):
        ansatz_prog += exponentiate_commuting_pauli_sum(
            -1j * PauliSum(commuting_set))(-1.0)

    return ansatz_prog
Esempio n. 4
0
def test_commuting_sets():
    term1 = PauliTerm("X", 0) * PauliTerm("X", 1)
    term2 = PauliTerm("Y", 0) * PauliTerm("Y", 1)
    term3 = PauliTerm("Y", 0) * PauliTerm("Z", 2)
    pauli_sum = term1 + term2 + term3
    commuting_sets(pauli_sum, 3)
Esempio n. 5
0
def estimate_pauli_sum(pauli_terms,
                       basis_transform_dict,
                       program,
                       variance_bound,
                       quantum_resource,
                       commutation_check=True):
    """
    Estimate the mean of a sum of pauli terms to set variance

    The sample variance is calculated by

    .. math::
        \begin{align}
        \mathrm{Var}[\hat{\langle H \rangle}] = \sum_{i, j}h_{i}h_{j}
        \mathrm{Cov}(\hat{\langle P_{i} \rangle}, \hat{\langle P_{j} \rangle})
        \end{align}

    :param pauli_terms: list of pauli terms to measure simultaneously or a
                        PauliSum object
    :param basis_transform_dict: basis transform dictionary where the key is
                                 the qubit index and the value is the basis to
                                 rotate into. Valid basis is [I, X, Y, Z].
    :param program: program generating a state to sample from
    :param variance_bound:  Bound on the variance of the estimator for the
                            PauliSum. Remember this is the SQUARE of the
                            standard error!
    :param quantum_resource: quantum abstract machine object
    :param Bool commutation_check: Optional flag toggling a safety check
                                   ensuring all terms in `pauli_terms`
                                   commute with each other
    :return: estimated expected value, covariance matrix, variance of the
             estimator, and the number of shots taken
    """
    if not isinstance(pauli_terms, (list, PauliSum)):
        raise TypeError("pauli_terms needs to be a list or a PauliSum")

    if isinstance(pauli_terms, PauliSum):
        pauli_terms = PauliSum.terms

    # check if each term commutes with everything
    if commutation_check:
        if len(commuting_sets(sum(pauli_terms))) != 1:
            raise CommutationError("Not all terms commute in the expected way")

    pauli_for_rotations = PauliTerm.from_list([
        (value, key) for key, value in basis_transform_dict.items()
    ])

    post_rotations = get_rotation_program(pauli_for_rotations)

    coeff_vec = np.array(list(map(lambda x: x.coefficient,
                                  pauli_terms))).reshape((-1, 1))

    # upper bound on samples given by IV of arXiv:1801.03524
    num_sample_ubound = int(
        np.ceil(np.sum(np.abs(coeff_vec))**2 / variance_bound))
    results = None
    sample_variance = np.infty
    number_of_samples = 0
    while (sample_variance > variance_bound
           and number_of_samples < num_sample_ubound):
        # note: bit string values are returned according to their listed order
        # in run_and_measure.  Therefore, sort beforehand to keep alpha numeric
        tresults = quantum_resource.run_and_measure(
            program + post_rotations,
            sorted(list(basis_transform_dict.keys())),
            trials=min(10000, num_sample_ubound))
        number_of_samples += len(tresults)

        parity_results = get_parity(pauli_terms, tresults)

        # Note: easy improvement would be to update mean and variance on the fly
        # instead of storing all these results.
        if results is None:
            results = parity_results
        else:
            results = np.hstack((results, parity_results))

        # calculate the expected values....
        covariance_mat = np.cov(results)
        sample_variance = coeff_vec.T.dot(covariance_mat).dot(coeff_vec) / \
                          results.shape[1]

    return coeff_vec.T.dot(np.mean(results, axis=1)), covariance_mat, \
           sample_variance, results.shape[1]
Esempio n. 6
0
def estimate_pauli_sum(pauli_terms, basis_transform_dict, program,
                       variance_bound, quantum_resource,
                       commutation_check=True):
    """
    Estimate the mean of a sum of pauli terms to set variance

    The sample variance is calculated by

    .. math::
        \begin{align}
        \mathrm{Var}[\hat{\langle H \rangle}] = \sum_{i, j}h_{i}h_{j}
        \mathrm{Cov}(\hat{\langle P_{i} \rangle}, \hat{\langle P_{j} \rangle})
        \end{align}

    The expectation value of each Pauli operator (term and coefficient) is
    also returned.  It can be accessed through the named-tuple field
    `pauli_expectations'.

    :param pauli_terms: list of pauli terms to measure simultaneously or a
                        PauliSum object
    :param basis_transform_dict: basis transform dictionary where the key is
                                 the qubit index and the value is the basis to
                                 rotate into. Valid basis is [I, X, Y, Z].
    :param program: program generating a state to sample from
    :param variance_bound:  Bound on the variance of the estimator for the
                            PauliSum. Remember this is the SQUARE of the
                            standard error!
    :param quantum_resource: quantum abstract machine object
    :param Bool commutation_check: Optional flag toggling a safety check
                                   ensuring all terms in `pauli_terms`
                                   commute with each other
    :return: estimated expected value, expected value of each Pauli term in
             the sum, covariance matrix, variance of the estimator, and the
             number of shots taken.  The objected returned is a named tuple with
             field names as follows: expected_value, pauli_expectations,
             covariance, variance, n_shots.
             `expected_value' == coef_vec.dot(pauli_expectations)
    :rtype: EstimationResult
    """
    if not isinstance(pauli_terms, (list, PauliSum)):
        raise TypeError("pauli_terms needs to be a list or a PauliSum")

    if isinstance(pauli_terms, PauliSum):
        pauli_terms = pauli_terms.terms

    # check if each term commutes with everything
    if commutation_check:
        if len(commuting_sets(sum(pauli_terms))) != 1:
            raise CommutationError("Not all terms commute in the expected way")

    pauli_for_rotations = PauliTerm.from_list(
        [(value, key) for key, value in basis_transform_dict.items()])

    program += get_rotation_program(pauli_for_rotations)

    qubits = sorted(list(basis_transform_dict.keys()))
    for qubit in qubits:
        program.inst(MEASURE(qubit, qubit))

    coeff_vec = np.array(
        list(map(lambda x: x.coefficient, pauli_terms))).reshape((-1, 1))

    # upper bound on samples given by IV of arXiv:1801.03524
    num_sample_ubound = int(np.ceil(np.sum(np.abs(coeff_vec))**2 / variance_bound))
    results = None
    sample_variance = np.infty
    number_of_samples = 0
    while (sample_variance > variance_bound and
           number_of_samples < num_sample_ubound):
        tresults = quantum_resource.run(program, qubits, trials=min(10000, num_sample_ubound))
        number_of_samples += len(tresults)

        parity_results = get_parity(pauli_terms, tresults)

        # Note: easy improvement would be to update mean and variance on the fly
        # instead of storing all these results.
        if results is None:
            results = parity_results
        else:
            results = np.hstack((results, parity_results))

        # calculate the expected values....
        covariance_mat = np.cov(results)
        sample_variance = coeff_vec.T.dot(covariance_mat).dot(coeff_vec) / results.shape[1]

    return EstimationResult(expected_value=coeff_vec.T.dot(np.mean(results, axis=1)),
                            pauli_expectations=np.multiply(coeff_vec.flatten(), np.mean(results, axis=1).flatten()),
                            covariance=covariance_mat,
                            variance=sample_variance,
                            n_shots=results.shape[1])
def estimate_pauli_sum(pauli_terms,
                       basis_transform_dict,
                       program,
                       variance_bound,
                       quantum_resource,
                       commutation_check=True,
                       symmetrize=True,
                       rand_samples=16):
    """
    Estimate the mean of a sum of pauli terms to set variance

    The sample variance is calculated by

    .. math::
        \begin{align}
        \mathrm{Var}[\hat{\langle H \rangle}] = \sum_{i, j}h_{i}h_{j}
        \mathrm{Cov}(\hat{\langle P_{i} \rangle}, \hat{\langle P_{j} \rangle})
        \end{align}

    The expectation value of each Pauli operator (term and coefficient) is
    also returned.  It can be accessed through the named-tuple field
    `pauli_expectations'.

    :param pauli_terms: list of pauli terms to measure simultaneously or a
                        PauliSum object
    :param basis_transform_dict: basis transform dictionary where the key is
                                 the qubit index and the value is the basis to
                                 rotate into. Valid basis is [I, X, Y, Z].
    :param program: program generating a state to sample from.  The program
                    is deep copied to ensure no mutation of gates or program
                    is perceived by the user.
    :param variance_bound:  Bound on the variance of the estimator for the
                            PauliSum. Remember this is the SQUARE of the
                            standard error!
    :param quantum_resource: quantum abstract machine object
    :param Bool commutation_check: Optional flag toggling a safety check
                                   ensuring all terms in `pauli_terms`
                                   commute with each other
    :param Bool symmetrize: Optional flag toggling symmetrization of readout
    :param Int rand_samples: number of random realizations for readout symmetrization
    :return: estimated expected value, expected value of each Pauli term in
             the sum, covariance matrix, variance of the estimator, and the
             number of shots taken.  The objected returned is a named tuple with
             field names as follows: expected_value, pauli_expectations,
             covariance, variance, n_shots.
             `expected_value' == coef_vec.dot(pauli_expectations)
    :rtype: EstimationResult
    """
    if not isinstance(pauli_terms, (list, PauliSum)):
        raise TypeError("pauli_terms needs to be a list or a PauliSum")

    if isinstance(pauli_terms, PauliSum):
        pauli_terms = pauli_terms.terms

    # check if each term commutes with everything
    if commutation_check:
        if len(commuting_sets(sum(pauli_terms))) != 1:
            raise CommutationError("Not all terms commute in the expected way")

    program = program.copy()
    pauli_for_rotations = PauliTerm.from_list([
        (value, key) for key, value in basis_transform_dict.items()
    ])

    program += get_rotation_program(pauli_for_rotations)

    qubits = sorted(list(basis_transform_dict.keys()))
    if symmetrize:
        theta = program.declare("ro_symmetrize", "REAL", len(qubits))
        for (idx, q) in enumerate(qubits):
            program += [RZ(np.pi / 2, q), RY(theta[idx], q), RZ(-np.pi / 2, q)]

    ro = program.declare("ro", "BIT", memory_size=len(qubits))
    for num, qubit in enumerate(qubits):
        program.inst(MEASURE(qubit, ro[num]))

    coeff_vec = np.array(list(map(lambda x: x.coefficient,
                                  pauli_terms))).reshape((-1, 1))

    # upper bound on samples given by IV of arXiv:1801.03524
    num_sample_ubound = 10 * int(
        np.ceil(np.sum(np.abs(coeff_vec))**2 / variance_bound))
    if num_sample_ubound <= 2:
        raise ValueError(
            "Something happened with our calculation of the max sample")

    if symmetrize:
        if min(STANDARD_NUMSHOTS, num_sample_ubound) // rand_samples == 0:
            raise ValueError(
                f"The number of shots must be larger than {rand_samples}.")

        program = program.wrap_in_numshots_loop(
            min(STANDARD_NUMSHOTS, num_sample_ubound) // rand_samples)
    else:
        program = program.wrap_in_numshots_loop(
            min(STANDARD_NUMSHOTS, num_sample_ubound))

    binary = quantum_resource.compiler.native_quil_to_executable(
        basic_compile(program))

    results = None
    sample_variance = np.infty
    number_of_samples = 0
    tresults = np.zeros((0, len(qubits)))
    while (sample_variance > variance_bound
           and number_of_samples < num_sample_ubound):
        if symmetrize:
            # for some number of times sample random bit string
            for r in range(rand_samples):
                rand_flips = np.random.randint(low=0, high=2, size=len(qubits))
                temp_results = quantum_resource.run(
                    binary, memory_map={'ro_symmetrize': np.pi * rand_flips})
                tresults = np.vstack((tresults, rand_flips ^ temp_results))
        else:
            tresults = quantum_resource.run(binary)

        number_of_samples += len(tresults)
        parity_results = get_parity(pauli_terms, tresults)

        # Note: easy improvement would be to update mean and variance on the fly
        # instead of storing all these results.
        if results is None:
            results = parity_results
        else:
            results = np.hstack((results, parity_results))

        # calculate the expected values....
        covariance_mat = np.cov(results, ddof=1)
        sample_variance = coeff_vec.T.dot(covariance_mat).dot(coeff_vec) / (
            results.shape[1] - 1)

    return EstimationResult(
        expected_value=coeff_vec.T.dot(np.mean(results, axis=1)),
        pauli_expectations=np.multiply(coeff_vec.flatten(),
                                       np.mean(results, axis=1).flatten()),
        covariance=covariance_mat,
        variance=sample_variance,
        n_shots=results.shape[1])