Example #1
0
def estimate_pauli_sum(pauli_terms,
                       basis_transform_dict,
                       program,
                       variance_bound,
                       quantum_resource,
                       commutation_check=True,
                       symmetrize=True,
                       rand_samples=16):
    r"""
    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])
Example #2
0
def estimate_joint_reset_confusion(qc: QuantumComputer, qubits: Sequence[int] = None,
                                   num_trials: int = 10, joint_group_size: int = 1,
                                   use_active_reset: bool = True, show_progress_bar: bool = False) \
                                    -> Dict[Tuple[int, ...], np.ndarray]:
    """
    Measures a reset 'confusion matrix' for all groups of size joint_group_size among the qubits.

    Specifically, for each possible joint_group_sized group among the qubits we perform a
    measurement for each bitstring on that group. The measurement proceeds as follows:
        -Repeatedly try to prepare the bitstring state on the qubits until success.
        -If use_active_reset is true (default) actively reset qubits to ground state; otherwise,
            wait the preset amount of time for qubits to decay to ground state.
        -Measure the state after the chosen reset method.
    Since reset should result in the all zeros state this 'confusion matrix' should ideally have
    all ones down the left-most column of the matrix. The entry at (row_idx, 0) thus represents
    the success probability of reset given that the pre-reset state is the binary representation
    of the number row_idx. WARNING: this method can be very slow

    :param qc: a quantum computer whose reset error you wish to characterize
    :param qubits: a list of accessible qubits on the qc you wish to characterize. Defaults to
        all qubits in qc.
    :param num_trials: number of repeated trials of reset after preparation of each bitstring on
        each joint group of qubits. Note: num_trials does not correspond to num_shots; a new
        program must be run in each trial, so running a group of n trials takes longer than would
        collecting the same number of shots output from a single program.
    :param joint_group_size: the size of each group; a square matrix with 2^joint_group_size
        number of rows/columns will be estimated for each group of qubits of the given size
        among the provided qubits.
    :param use_active_reset: dictates whether to actively reset qubits or else wait the
        pre-allotted amount of time for the qubits to decay to the ground state. Using active
        reset will allow for faster data collection.
    :param show_progress_bar: displays a progress bar via tqdm if true.
    :return: a dictionary whose keys are all possible joint_group_sized tuples that can be
        formed from the qubits. Each value is an estimated 2^group_size square matrix
        for the corresponding tuple of qubits. Each key is listed in order of increasing qubit
        number. The corresponding matrix has rows and columns indexed in increasing bitstring
        order, with most significant (leftmost) bit labeling the smallest qubit number. Each
        matrix row corresponds to the bitstring that was prepared before the reset, and each
        column corresponds to the bitstring measured after the reset.
    """
    # establish default as all operational qubits in qc
    if qubits is None:
        qubits = qc.qubits()

    qubits = sorted(qubits)

    groups = list(itertools.combinations(qubits, joint_group_size))
    confusion_matrices = {}

    total_num_rounds = len(groups) * 2**joint_group_size
    with tqdm(total=total_num_rounds, disable=not show_progress_bar) as pbar:
        for group in groups:
            reg_name = 'bitstr'
            # program prepares a bit string (specified by parameterization) and immediately measures
            prep_and_meas = parameterized_bitstring_prep(group,
                                                         reg_name,
                                                         append_measure=True)
            prep_executable = qc.compiler.native_quil_to_executable(
                prep_and_meas)

            matrix = np.zeros((2**joint_group_size, 2**joint_group_size))
            for row, bitstring in enumerate(
                    itertools.product([0, 1], repeat=joint_group_size)):
                for _ in range(num_trials):

                    # try preparation at most 10 times.
                    for _ in range(10):
                        # prepare the given bitstring and measure
                        result = qc.run(prep_executable,
                                        memory_map={reg_name: bitstring})

                        # if the preparation is successful, move on to reset.
                        if np.array_equal(result[0], bitstring):
                            break

                    # execute program that measures the post-reset state
                    if use_active_reset:
                        # program runs immediately after prep program and actively resets qubits
                        reset_measure_program = Program(RESET())
                    else:
                        # this program automatically waits pre-allotted time for qubits to decay
                        reset_measure_program = Program()
                    ro = reset_measure_program.declare('ro',
                                                       memory_type='BIT',
                                                       memory_size=len(qubits))
                    for idx, qubit in enumerate(group):
                        reset_measure_program += MEASURE(qubit, ro[idx])
                    executable = qc.compiler.native_quil_to_executable(
                        reset_measure_program)
                    results = qc.run(executable)

                    # update confusion matrix
                    for result in results:
                        base = np.array(
                            [2**i for i in reversed(range(joint_group_size))])
                        observed = np.sum(base * result)
                        matrix[row, observed] += 1 / num_trials

                # update the progress bar
                pbar.update(1)

            # store completed confusion matrix in dictionary
            confusion_matrices[group] = matrix

    return confusion_matrices
Example #3
0
def test_unitary_measure():
    prog = Program(Declare("ro", "BIT"), H(0), H(1),
                   MEASURE(0, MemoryReference("ro", 0)))
    with pytest.raises(ValueError):
        program_unitary(prog, n_qubits=2)
Example #4
0
def test_get_qubit_placeholders():
    qs = QubitPlaceholder.register(8)
    pq = Program(X(qs[0]), CNOT(qs[0], qs[4]), MEASURE(qs[5], [5]))
    assert pq.get_qubits() == {qs[i] for i in [0, 4, 5]}
Example #5
0
def test_get_classical_addresses_from_program():
    p = Program([H(i) for i in range(4)])
    assert get_classical_addresses_from_program(p) == []

    p += [MEASURE(i, i) for i in [0, 3, 1]]
    assert get_classical_addresses_from_program(p) == [0, 1, 3]
Example #6
0
def test_get_qubit_placeholders():
    qs = QubitPlaceholder.register(8)
    pq = Program(Declare("ro", "BIT"), X(qs[0]), CNOT(qs[0], qs[4]),
                 MEASURE(qs[5], MemoryReference("ro", 0)))
    assert pq.get_qubits() == {qs[i] for i in [0, 4, 5]}
Example #7
0
    # program_reset.inst(H(qubits[0]))

    if gate == 0:
        print('CNOT')
        program_reset.inst(multi_CNOT(qubits, int(n / 2)))
    elif gate == 1:
        print('H')
        program_reset.inst(multi_H(qubits, n))

    else:
        raise ValueError('invalid input args')

    # Measurements
    ro = program_reset.declare('ro', 'BIT', len(qubits))
    program_reset.inst(
        [MEASURE(qubit, ro[idx]) for idx, qubit in enumerate(qubits)])

    program_reset.wrap_in_numshots_loop(N)
    binary_reset = qpu.compile(program_reset)

    start = time.time()
    results = qpu.run(binary_reset)
    total = time.time() - start
    print(f'\nExecution time with active reset: {total:.3f} s')

    # magic (there should be a less messy way to reformat those, but cant be bothered

    # reformat results
    results = list(results)
    for i, result in enumerate(results):
        results[i] = str(result).replace(',', '').replace(',', '').replace(
Example #8
0
def test_implicit_declare():
    program = Program(MEASURE(0, MemoryReference("ro", 0)))
    assert program.out() == ('DECLARE ro BIT[1]\n'
                             'MEASURE 0 ro[0]\n')
Example #9
0
            "quil-instructions": "H 0\nCNOT 0 1\n"
        }
        return '[[0,0],[1,1]]'

    with requests_mock.Mocker() as m:
        m.post('https://api.rigetti.com/qvm', text=mock_response)
        assert qvm.run_and_measure(BELL_STATE, [0, 1], trials=2) == [[0, 0],
                                                                     [1, 1]]


WAVEFUNCTION_BINARY = (
    b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xe6'
    b'\xa0\x9ef\x7f;\xcc\x00\x00\x00\x00\x00\x00\x00\x00\xbf\xe6\xa0\x9ef'
    b'\x7f;\xcc\x00\x00\x00\x00\x00\x00\x00\x00')
WAVEFUNCTION_PROGRAM = Program(H(0), CNOT(0, 1), MEASURE(0, 0), H(0))


def test_sync_wavefunction():
    def mock_response(request, context):
        assert json.loads(request.text) == {
            "type": "wavefunction",
            "quil-instructions": "H 0\nCNOT 0 1\nMEASURE 0 [0]\nH 0\n",
            "addresses": [0, 1]
        }
        return WAVEFUNCTION_BINARY

    with requests_mock.Mocker() as m:
        m.post('https://api.rigetti.com/qvm', content=mock_response)
        result = qvm.wavefunction(WAVEFUNCTION_PROGRAM, [0, 1])
        wf_expected = np.array(
Example #10
0
def test_quantum_gate_shift():
    prog = Program(X(0), CNOT(0, 4), MEASURE(5, [5]))
    assert shift_quantum_gates(prog, 5) == Program(X(5), CNOT(5, 9),
                                                   MEASURE(5, [5]))
Example #11
0
def test_construction_syntax():
    p = Program().inst(Declare('ro', 'BIT', 2), X(0), Y(1), Z(0)).measure(0, MemoryReference('ro', 1))
    assert p.out() == ('DECLARE ro BIT[2]\n'
                       'X 0\n'
                       'Y 1\n'
                       'Z 0\n'
                       'MEASURE 0 ro[1]\n')
    p = Program().inst(Declare('ro', 'BIT', 3), X(0)).inst(Y(1)).measure(0, MemoryReference('ro', 1)).inst(MEASURE(1, MemoryReference('ro', 2)))
    assert p.out() == ('DECLARE ro BIT[3]\n'
                       'X 0\n'
                       'Y 1\n'
                       'MEASURE 0 ro[1]\n'
                       'MEASURE 1 ro[2]\n')
    p = Program().inst(Declare('ro', 'BIT', 2), X(0)).measure(0, MemoryReference('ro', 1)).inst(Y(1), X(0)).measure(0, MemoryReference('ro', 0))
    assert p.out() == ('DECLARE ro BIT[2]\n'
                       'X 0\n'
                       'MEASURE 0 ro[1]\n'
                       'Y 1\n'
                       'X 0\n'
                       'MEASURE 0 ro[0]\n')
Example #12
0
 def measure(self, qubit, clbit):
     self.program += MEASURE(qubit, clbit)
Example #13
0
from pyquil.api import QVMConnection, QPUCompiler, get_qc, QVMCompiler
from pyquil.api._base_connection import (validate_noise_probabilities,
                                         validate_qubit_list,
                                         prepare_register_list)
from pyquil.device import ISA, NxDevice
from pyquil.gates import CNOT, H, MEASURE, PHASE, Z, RZ, RX, CZ
from pyquil.paulis import PauliTerm
from pyquil.quil import Program
from pyquil.quilbase import Halt, Declare
from pyquil.quilatom import MemoryReference

EMPTY_PROGRAM = Program()
BELL_STATE = Program(H(0), CNOT(0, 1))
BELL_STATE_MEASURE = Program(Declare('ro', 'BIT', 2), H(0), CNOT(0, 1),
                             MEASURE(0, MemoryReference('ro', 0)),
                             MEASURE(1, MemoryReference('ro', 1)))
COMPILED_BELL_STATE = Program([
    RZ(pi / 2, 0),
    RX(pi / 2, 0),
    RZ(-pi / 2, 1),
    RX(pi / 2, 1),
    CZ(1, 0),
    RZ(-pi / 2, 0),
    RX(-pi / 2, 1),
    RZ(pi / 2, 1),
    Halt()
])
DUMMY_ISA_DICT = {"1Q": {"0": {}, "1": {}}, "2Q": {"0-1": {}}}
DUMMY_ISA = ISA.from_dict(DUMMY_ISA_DICT)
Example #14
0
from pyquil import Program, get_qc
from pyquil.gates import H, CNOT, MEASURE

qvm = get_qc('2q-qvm')  # connect to QVM with 2 quibts
prg = Program()  # build the program
out = prg.declare('ro', 'BIT', 2)  # declare classical memeory

# construct the code
prg += H(0)
prg += CNOT(0, 1)
prg += MEASURE(0, out[0])
print(prg)

# construct the intermediate representation
exe = qvm.compile(prg)
print(exe.program)

# run the code
res = qvm.run(exe)
print(res)
Example #15
0
def test_validate_supported_quil_with_pragma():
    prog = Program(RESET(), H(1), Pragma("DELAY"), MEASURE(1, None))
    assert prog.is_supported_on_qpu()
Example #16
0
from pyquil.quil import Program
from pyquil.gates import H, X, CNOT, MEASURE
from pyquil.api import SyncConnection

p = Program()
p.inst("""DEFGATE CH(%theta):
    1, 0, 0, 0
    0, 1, 0, 0
    0, 0, cos(2*%theta), sin(2*%theta)
    0, 0, sin(2*%theta), cos(-2*%theta)""")
p.inst(H(3))
p.inst(CNOT(3, 2))
p.inst(CNOT(2, 1))
p.inst("CH(pi/8) 1 0")
p.inst("CH(pi/16) 2 0")
p.inst(H(1))
p.inst(H(2))
p.inst(X(0))
p.inst(MEASURE(0))
p.inst(MEASURE(1))
p.inst(MEASURE(2))

# run the program on a QVM
qvm = SyncConnection()

wvf, _ = qvm.wavefunction(p)
print(wvf)
Example #17
0
def test_validate_supported_quil_multiple_measures():
    prog = Program(RESET(), H(1), Pragma("DELAY"), MEASURE(1, None),
                   MEASURE(1, None))
    with pytest.raises(ValueError):
        validate_supported_quil(prog)
Example #18
0
    def compute_loss(self, parameters, history_list, indices=None):
        """Helper routine to compute loss.

        Args:
        =====
        parameters : list or numpy.ndarray
            Vector of circuit parameters
        history_list : list, required
            List to store losses
        indices : list[int]
            List of indices pointing to state preparation circuits (for training or testing set)

        Returns:
        ========
        loss_values : list or numpy.ndarray
            List of average loss values for given set (training or test)
        """
        total_qubits = self.n_qubits_in + (self.n_qubits_in -
                                           self.n_qubits_latent_space)

        losses = []

        for index in indices:

            # Apply state preparation then training circuit
            qae_circuit = self.construct_compression_program(parameters, index)

            # Apply daggered training circuit (with adjusted indices)
            new_range = range(total_qubits - self.n_qubits_in, total_qubits)
            new_range = new_range[::-1]
            qae_circuit += self.training_circuit(parameters, None,
                                                 new_range).dagger()

            # Apply daggered state preparation circuit (with adjusted indices)
            qae_circuit += self.state_preparation_circuits_dag[index]

            # Measure data qubits
            for q, i in enumerate(new_range):
                qae_circuit += MEASURE(i, q)

            # Run circuit
            result = self.connection.run(quil_program=qae_circuit,
                                         classical_addresses=range(
                                             self.n_qubits_in),
                                         trials=self.n_samples)

            # Count measurements of all 0's on data qubits
            n = self.n_qubits_in
            result_count = result.count([0] * self.n_qubits_in)
            losses.append(result_count / self.n_samples)

        mean_loss = -1. * numpy.mean(losses)

        self._prepare_loss_history(history_list, mean_loss)

        if self.verbose:
            if (len(history_list) - 1) % self.print_interval == 0:
                # losses_str = ["{0:.4f}".format(loss_val) for loss_val in losses]
                # print("Loss values: {}".format(losses_str))
                print("Iter {0:4d} Mean Loss: {1:.7f}".format(
                    len(history_list) - 1, mean_loss))

        return mean_loss
Example #19
0
def test_get_qubits_not_as_indices():
    pq = Program(Declare("ro", "BIT"), X(0), CNOT(0, 4),
                 MEASURE(5, MemoryReference("ro", 0)))
    assert pq.get_qubits(indices=False) == {Qubit(i) for i in [0, 4, 5]}
Example #20
0
def test_get_classical_addresses_from_program():
    p = Program(Declare("ro", "BIT", 4), [H(i) for i in range(4)])
    assert get_classical_addresses_from_program(p) == {}

    p += [MEASURE(i, MemoryReference("ro", i)) for i in [0, 3, 1]]
    assert get_classical_addresses_from_program(p) == {"ro": [0, 1, 3]}
Example #21
0
def test_measurement_calls():
    p = Program()
    p.inst(MEASURE(0, 1), MEASURE(0, Addr(1)))
    assert p.out() == 'MEASURE 0 [1]\n' * 2
Example #22
0
def test_implicit_declare():
    with pytest.warns(UserWarning):
        program = Program(MEASURE(0, MemoryReference("ro", 0)))
        assert program.out() == ("DECLARE ro BIT[1]\nMEASURE 0 ro[0]\n")
Example #23
0
def test_get_qubits_not_as_indices():
    pq = Program(X(0), CNOT(0, 4), MEASURE(5, [5]))
    assert pq.get_qubits(indices=False) == {Qubit(i) for i in [0, 4, 5]}
Example #24
0
def test_no_implicit_declare():
    program = Program(Declare("read_out", "BIT", 5),
                      MEASURE(0, MemoryReference("read_out", 4)))
    assert program.out() == (
        "DECLARE read_out BIT[5]\nMEASURE 0 read_out[4]\n")
Example #25
0
from rpcq.json_rpc.server import Server

from pyquil.api import (QVMConnection, QPUCompiler, BenchmarkConnection,
                        get_qc, LocalQVMCompiler, QVMCompiler, LocalBenchmarkConnection)
from pyquil.api._base_connection import validate_noise_probabilities, validate_qubit_list, \
    prepare_register_list
from pyquil.api._config import PyquilConfig
from pyquil.device import ISA, NxDevice
from pyquil.gates import CNOT, H, MEASURE, PHASE, Z, RZ, RX, CZ
from pyquil.paulis import PauliTerm
from pyquil.quil import Program
from pyquil.quilbase import Pragma, Declare

EMPTY_PROGRAM = Program()
BELL_STATE = Program(H(0), CNOT(0, 1))
BELL_STATE_MEASURE = Program(H(0), CNOT(0, 1), MEASURE(0, 0), MEASURE(1, 1))
COMPILED_BELL_STATE = Program([
    Declare("ro", "BIT", 2),
    Pragma("EXPECTED_REWIRING", ('"#(0 1 2 3)"',)),
    RZ(pi / 2, 0),
    RX(pi / 2, 0),
    RZ(-pi / 2, 1),
    RX(pi / 2, 1),
    CZ(1, 0),
    RZ(-pi / 2, 0),
    RX(-pi / 2, 1),
    RZ(pi / 2, 1),
    Pragma("CURRENT_REWIRING", ('"#(0 1 2 3)"',)),
    Pragma("EXPECTED_REWIRING", ('"#(0 1 2 3)"',)),
    Pragma("CURRENT_REWIRING", ('"#(0 1 2 3)"',)),
])
Example #26
0
def test_no_implicit_declare_2():
    program = Program(MEASURE(0, MemoryReference("asdf", 4)))
    assert program.out() == "MEASURE 0 asdf[4]\n"
Example #27
0
def test_measure():
    parse_equals("MEASURE 0", MEASURE(0, None))
    parse_equals("MEASURE 0 ro[1]", MEASURE(0, MemoryReference("ro", 1)))
Example #28
0
def test_validate_supported_quil_measure_last():
    prog = Program(MEASURE(0, None), H(0))
    with pytest.raises(ValueError):
        validate_supported_quil(prog)
    assert not prog.is_supported_on_qpu()
Example #29
0
def adder(num_a: Sequence[int],
          num_b: Sequence[int],
          register_a: Sequence[int],
          register_b: Sequence[int],
          carry_ancilla: int,
          z_ancilla: int,
          in_x_basis: bool = False,
          use_param_program: bool = False) -> Program:
    """
    Produces a program implementing reversible adding on a quantum computer to compute a + b.

    This implementation is based on [CDKM96], which is easy to implement, if not the most
    efficient. Each register of qubit labels should be provided such that the first qubit in
    each register is expected to carry the least significant bit of the respective number. This
    method also requires two extra ancilla, one initialized to 0 that acts as a dummy initial
    carry bit and another (which also probably ought be initialized to 0) that stores the most
    significant bit of the addition (should there be a final carry). The most straightforward
    ordering of the registers and two ancilla for adding n-bit numbers follows the pattern

        carry_ancilla
        b_0
        a_0
        ...
        b_j
        a_j
        ...
        b_n
        a_n
        z_ancilla

    With this layout, all gates in the circuit act on sets of three adjacent qubits. Such a
    layout is provided by calling get_qubit_registers_for_adder on the quantum resource. Note
    that even with this layout some of the gates used to implement the circuit may not be native.
    In particular there are CCNOT gates which must be decomposed and CNOT(q1, q3) gates acting on
    potentially non-adjacenct qubits (the layout only ensures q2 is adjacent to both q1 and q3).

    The output of the circuit falls on the qubits initially labeled by the b bits (and z_ancilla).

    The default option is to compute the addition in the computational (aka Z) basis. By setting
    in_x_basis true, the gates CNOT_X_basis and CCNOT_X_basis (defined above) will replace CNOT
    and CCNOT so that the computation happens in the X basis.

        [CDKM96]
        "A new quantum ripple-carry addition circuit"
        S. Cuccaro, T. Draper, s. Kutin, D. Moulton
        https://arxiv.org/abs/quant-ph/0410184

    :param num_a: the bitstring representation of the number a with least significant bit last
    :param num_b: the bitstring representation of the number b with least significant bit last
    :param register_a: list of qubit labels for register a, with least significant bit labeled first
    :param register_b: list of qubit labels for register b, with least significant bit labeled first
    :param carry_ancilla: qubit labeling a zero-initialized qubit, ideally adjacent to b_0
    :param z_ancilla: qubit label, a zero-initialized qubit, ideally adjacent to register_a[-1]
    :param in_x_basis: if true, prepare the bitstring-representation of the numbers in the x basis
        and subsequently performs all addition logic in the x basis.
    :param use_param_program: if true, the input num_a and num_b should be arrays of the proper
        length, but their contents will be disregarded. Instead, the program returned will be
        parameterized and the input bitstrings to add must be specified at run time.
    :return: pyQuil program that implements the addition a+b, with output falling on the qubits
        formerly storing the input b. The output of a measurement will list the lsb as the last bit.
    """
    if len(num_a) != len(num_b):
        raise ValueError("Numbers being added must be equal length bitstrings")

    # First, generate a set preparation program in the desired basis.
    prog = Program(Pragma('PRESERVE_BLOCK'))
    if use_param_program:
        # do_measure set to False makes the returned program a parameterized prep program
        input_register = register_a + register_b
        prog += _readout_group_parameterized_bitstring(input_register[::-1],
                                                       do_measure=False)
        if in_x_basis:
            prog += [H(qubit) for qubit in input_register]
    else:
        prog += prepare_bitstring(num_a[::-1], register_a, in_x_basis)
        prog += prepare_bitstring(num_b[::-1], register_b, in_x_basis)

    if in_x_basis:
        prog += [H(carry_ancilla), H(z_ancilla)]

    # preparation complete; end the preserve block
    prog += Pragma("END_PRESERVE_BLOCK")

    prog_to_rev = Program()
    current_carry_label = carry_ancilla
    for (a, b) in zip(register_a, register_b):
        prog += majority_gate(a, b, current_carry_label, in_x_basis)
        prog_to_rev += unmajority_add_gate(a, b, current_carry_label,
                                           in_x_basis).dagger()
        current_carry_label = a

    undo_and_add_prog = prog_to_rev.dagger()
    if in_x_basis:
        prog += CNOT_X_basis(register_a[-1], z_ancilla)
        # need to switch back to computational (z) basis before measuring
        for qubit in register_b:  # answer lays on the b qubit register
            undo_and_add_prog.inst(H(qubit))
        undo_and_add_prog.inst(H(z_ancilla))
    else:
        prog += CNOT(register_a[-1], z_ancilla)
    prog += undo_and_add_prog

    ro = prog.declare('ro', memory_type='BIT', memory_size=len(register_b) + 1)
    for idx, qubit in enumerate(register_b):
        prog += MEASURE(qubit, ro[len(register_b) - idx])
    prog += MEASURE(z_ancilla, ro[0])

    return prog
Example #30
0
def test_append_measures_to_program():
    gate_program = Program()
    meas_program = Program(MEASURE(0, 0), MEASURE(1, 1))
    assert gate_program + meas_program == append_measures_to_program(
        gate_program, [0, 1])