def test_qc():
    from pyquil.api import ForestConnection, QuantumComputer
    from pyquil.api._compiler import _extract_attribute_dictionary_from_program
    from pyquil.api._qac import AbstractCompiler
    from pyquil.device import NxDevice
    from pyquil.gates import I

    class BasicQVMCompiler(AbstractCompiler):
        def quil_to_native_quil(self, program: Program):
            return basic_compile(program)

        def native_quil_to_executable(self, nq_program: Program):
            return PyQuilExecutableResponse(
                program=nq_program.out(),
                attributes=_extract_attribute_dictionary_from_program(nq_program))
    try:
        qc = QuantumComputer(
            name='testing-qc',
            qam=QVM(connection=ForestConnection(), random_seed=52),
            device=NxDevice(nx.complete_graph(2)),
            compiler=BasicQVMCompiler(),
        )
        qc.run_and_measure(Program(I(0)), trials=1)
        return qc
    except (RequestException, TimeoutError) as e:
        return pytest.skip("This test requires a running local QVM: {}".format(e))
Example #2
0
def test_qpu_run():
    config = PyquilConfig()
    if config.qpu_url and config.qpu_compiler_url:
        g = nx.Graph()
        g.add_node(0)
        device = NxDevice(g)

        qc = QuantumComputer(
            name="pyQuil test QC",
            qam=QPU(endpoint=config.qpu_url, user="******"),
            device=device,
            compiler=QPUCompiler(
                quilc_endpoint=config.quilc_url,
                qpu_compiler_endpoint=config.qpu_compiler_url,
                device=device,
            ),
        )
        bitstrings = qc.run_and_measure(program=Program(X(0)), trials=1000)
        assert bitstrings[0].shape == (1000, )
        assert np.mean(bitstrings[0]) > 0.8
        bitstrings = qc.run(qc.compile(Program(X(0))))
        assert bitstrings.shape == (0, 0)
    else:
        pytest.skip(
            "QPU or compiler-server not available; skipping QPU run test.")
def test_set_initial_state():
    # That is test the assigned state matrix in ReferenceDensitySimulator is persistent between
    # rounds of run.
    rho1 = np.array([[0.0, 0.0], [0.0, 1.0]])

    # run prog
    prog = Program(I(0))
    ro = prog.declare("ro", "BIT", 1)
    prog += MEASURE(0, ro[0])

    # make a quantum computer object
    device = NxDevice(nx.complete_graph(1))
    qc_density = QuantumComputer(
        name="testy!",
        qam=PyQVM(n_qubits=1,
                  quantum_simulator_type=ReferenceDensitySimulator),
        device=device,
        compiler=DummyCompiler(),
    )

    qc_density.qam.wf_simulator.set_initial_state(rho1).reset()

    out = [qc_density.run(prog) for _ in range(0, 4)]
    ans = [np.array([[1]]), np.array([[1]]), np.array([[1]]), np.array([[1]])]
    assert all([np.allclose(x, y) for x, y in zip(out, ans)])

    # Run and measure style
    progRAM = Program(I(0))

    results = qc_density.run_and_measure(progRAM, trials=10)
    ans = {0: np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])}
    assert np.allclose(results[0], ans[0])

    # test reverting ReferenceDensitySimulator to the default state
    rho0 = np.array([[1.0, 0.0], [0.0, 0.0]])
    qc_density.qam.wf_simulator.set_initial_state(rho0).reset()
    assert np.allclose(qc_density.qam.wf_simulator.density, rho0)
    assert np.allclose(qc_density.qam.wf_simulator.initial_density, rho0)
Example #4
0
topology = nx.from_edgelist([
    (10, 2),
    (10, 4),
    (10, 6),
    (10, 8),
])

device = NxDevice(topology)


class MyLazyCompiler(AbstractCompiler):
    def quil_to_native_quil(self, program, *, protoquil=None):
        return program

    def native_quil_to_executable(self, nq_program):
        return nq_program


my_qc = QuantumComputer(
    name='my-qvm',
    qam=QVM(connection=ForestConnection()),
    device=device,
    compiler=MyLazyCompiler(),
)

nx.draw(my_qc.qubit_topology())
plt.title('5qcm', fontsize=18)
plt.show()

my_qc.run_and_measure(Program(X(10)), trials=5)
def measure_observables(qc: QuantumComputer,
                        tomo_experiment: TomographyExperiment,
                        n_shots: int = 1000,
                        progress_callback=None,
                        active_reset=False,
                        readout_symmetrize: str = None,
                        calibrate_readout: str = None):
    """
    Measure all the observables in a TomographyExperiment.

    :param qc: A QuantumComputer which can run quantum programs
    :param tomo_experiment: A suite of tomographic observables to measure
    :param n_shots: The number of shots to take per ExperimentSetting
    :param progress_callback: If not None, this function is called each time a group of
        settings is run with arguments ``f(i, len(tomo_experiment)`` such that the progress
        is ``i / len(tomo_experiment)``.
    :param active_reset: Whether to actively reset qubits instead of waiting several
        times the coherence length for qubits to decay to |0> naturally. Setting this
        to True is much faster but there is a ~1% error per qubit in the reset operation.
        Thermal noise from "traditional" reset is not routinely characterized but is of the same
        order.
    :param readout_symmetrize: Method used to symmetrize the readout errors, i.e. set
        p(0|1) = p(1|0). For uncorrelated readout errors, this can be achieved by randomly
        selecting between the POVMs {X.D1.X, X.D0.X} and {D0, D1} (where both D0 and D1 are
        diagonal). However, here we currently support exhaustive symmetrization and loop through
        all possible 2^n POVMs {X/I . POVM . X/I}^n, and obtain symmetrization more generally,
        i.e. set p(00|00) = p(01|01) = .. = p(11|11), as well as p(00|01) = p(01|00) etc. If this
        is None, no symmetrization is performed. The exhaustive method can be specified by setting
        this variable to 'exhaustive'
    :param calibrate_readout: Method used to calibrate the readout results. Currently, the only
        method supported is normalizing against the operator's expectation value in its +1
        eigenstate, which can be specified by setting this variable to 'plus-eig'. The preceding
        symmetrization and this step together yield a more accurate estimation of the observable.
    """
    # calibration readout only works with symmetrization turned on
    if calibrate_readout is not None and readout_symmetrize is None:
        raise ValueError(
            "Readout calibration only works with readout symmetrization turned on"
        )

    # Outer loop over a collection of grouped settings for which we can simultaneously
    # estimate.
    for i, settings in enumerate(tomo_experiment):

        log.info(
            f"Collecting bitstrings for the {len(settings)} settings: {settings}"
        )

        # 1.1 Prepare a state according to the amalgam of all setting.in_state
        total_prog = Program()
        if active_reset:
            total_prog += RESET()
        max_weight_in_state = _max_weight_state(setting.in_state
                                                for setting in settings)
        for oneq_state in max_weight_in_state.states:
            total_prog += _one_q_state_prep(oneq_state)

        # 1.2 Add in the program
        total_prog += tomo_experiment.program

        # 1.3 Measure the state according to setting.out_operator
        max_weight_out_op = _max_weight_operator(setting.out_operator
                                                 for setting in settings)
        for qubit, op_str in max_weight_out_op:
            total_prog += _local_pauli_eig_meas(op_str, qubit)

        if readout_symmetrize == 'exhaustive':
            # 1.4 Symmetrize -- flip qubits pre-measurement
            qubits = max_weight_out_op.get_qubits()
            n_shots_symm = n_shots // 2**len(qubits)
            list_bitstrings_symm = []
            for ops_bool in itertools.product([0, 1], repeat=len(qubits)):
                total_prog_symm = total_prog.copy()
                prog_symm = _ops_bool_to_prog(ops_bool, qubits)
                total_prog_symm += prog_symm
                # 2. Run the experiment
                bitstrings_symm = qc.run_and_measure(total_prog_symm,
                                                     n_shots_symm)
                # 2.1 Flip the results post-measurement
                d_flips_symm = {
                    qubits[i]: op_bool
                    for i, op_bool in enumerate(ops_bool)
                }
                for qubit, bs_results in bitstrings_symm.items():
                    bitstrings_symm[qubit] = bs_results ^ d_flips_symm.get(
                        qubit, 0)
                # 2.2 Gather together the symmetrized results into list
                list_bitstrings_symm.append(bitstrings_symm)

            # 2.3 Gather together all the symmetrized results
            bitstrings = reduce(_stack_dicts, list_bitstrings_symm)

        elif readout_symmetrize is None:
            # 2. Run the experiment
            bitstrings = qc.run_and_measure(total_prog, n_shots)

        else:
            raise ValueError(
                "Readout symmetrization method must be either 'exhaustive' or None"
            )

        if progress_callback is not None:
            progress_callback(i, len(tomo_experiment))

        # 3. Post-process
        # Inner loop over the grouped settings. They only differ in which qubits' measurements
        # we include in the post-processing. For example, if `settings` is Z1, Z2, Z1Z2 and we
        # measure (n_shots, n_qubits=2) obs_strings then the full operator value involves selecting
        # either the first column, second column, or both and multiplying along the row.
        for setting in settings:
            # 3.1 Get the term's coefficient so we can multiply it in later.
            coeff = complex(setting.out_operator.coefficient)
            if not np.isclose(coeff.imag, 0):
                raise ValueError(
                    f"{setting}'s out_operator has a complex coefficient.")
            coeff = coeff.real

            # 3.2 Special case for measuring the "identity" operator, which doesn't make much
            #     sense but should happen perfectly.
            if is_identity(setting.out_operator):
                yield ExperimentResult(
                    setting=setting,
                    expectation=coeff,
                    stddev=0.0,
                    total_counts=n_shots,
                )
                continue

            # 3.3 Obtain statistics from result of experiment
            obs_mean, obs_var = _stats_from_measurements(
                bitstrings, setting, n_shots, coeff)

            if calibrate_readout == 'plus-eig':
                # 4 Readout calibration
                # 4.1 Prepare the +1 eigenstate for the out operator
                calibr_prog = Program()
                for q, op in setting.out_operator.operations_as_set():
                    calibr_prog += _one_q_pauli_prep(label=op,
                                                     index=0,
                                                     qubit=q)
                # 4.2 Measure the out operator in this state
                for q, op in setting.out_operator.operations_as_set():
                    calibr_prog += _local_pauli_eig_meas(op, q)
                calibr_shots = n_shots
                calibr_results = qc.run_and_measure(calibr_prog, calibr_shots)
                # 4.3 Obtain statistics from the measurement process
                obs_calibr_mean, obs_calibr_var = _stats_from_measurements(
                    calibr_results, setting, calibr_shots)
                # 4.4 Calibrate the readout results
                corrected_mean = obs_mean / obs_calibr_mean
                corrected_var = ratio_variance(obs_mean, obs_var,
                                               obs_calibr_mean, obs_calibr_var)

                yield ExperimentResult(
                    setting=setting,
                    expectation=corrected_mean.item(),
                    stddev=np.sqrt(corrected_var).item(),
                    total_counts=n_shots,
                    raw_expectation=obs_mean.item(),
                    raw_stddev=np.sqrt(obs_var).item(),
                    calibration_expectation=obs_calibr_mean.item(),
                    calibration_stddev=np.sqrt(obs_calibr_var).item(),
                    calibration_counts=calibr_shots,
                )

            elif calibrate_readout is None:
                # No calibration
                yield ExperimentResult(
                    setting=setting,
                    expectation=obs_mean.item(),
                    stddev=np.sqrt(obs_var).item(),
                    total_counts=n_shots,
                )

            else:
                raise ValueError(
                    "Calibration readout method must be either 'plus-eig' or None"
                )
def measure_observables(qc: QuantumComputer,
                        tomo_experiment: TomographyExperiment,
                        n_shots=1000,
                        progress_callback=None,
                        active_reset=False):
    """
    Measure all the observables in a TomographyExperiment.

    :param qc: A QuantumComputer which can run quantum programs
    :param tomo_experiment: A suite of tomographic observables to measure
    :param n_shots: The number of shots to take per ExperimentSetting
    :param progress_callback: If not None, this function is called each time a group of
        settings is run with arguments ``f(i, len(tomo_experiment)`` such that the progress
        is ``i / len(tomo_experiment)``.
    :param active_reset: Whether to actively reset qubits instead of waiting several
        times the coherence length for qubits to decay to |0> naturally. Setting this
        to True is much faster but there is a ~1% error per qubit in the reset operation.
        Thermal noise from "traditional" reset is not routinely characterized but is of the same
        order.
    """
    for i, settings in enumerate(tomo_experiment):
        # Outer loop over a collection of grouped settings for which we can simultaneously
        # estimate.
        log.info(
            f"Collecting bitstrings for the {len(settings)} settings: {settings}"
        )

        # 1.1 Prepare a state according to the amalgam of all setting.in_state
        total_prog = Program()
        if active_reset:
            total_prog += RESET()
        max_weight_in_state = _max_weight_state(setting.in_state
                                                for setting in settings)
        for oneq_state in max_weight_in_state.states:
            total_prog += _one_q_state_prep(oneq_state)

        # 1.2 Add in the program
        total_prog += tomo_experiment.program

        # 1.3 Measure the state according to setting.out_operator
        max_weight_out_op = _max_weight_operator(setting.out_operator
                                                 for setting in settings)
        for qubit, op_str in max_weight_out_op:
            total_prog += _local_pauli_eig_meas(op_str, qubit)

        # 2. Run the experiment
        bitstrings = qc.run_and_measure(total_prog, n_shots)
        if progress_callback is not None:
            progress_callback(i, len(tomo_experiment))

        # 3. Post-process
        # 3.1 First transform bits to eigenvalues; ie (+1, -1)
        obs_strings = {q: 1 - 2 * bitstrings[q] for q in bitstrings}

        # Inner loop over the grouped settings. They only differ in which qubits' measurements
        # we include in the post-processing. For example, if `settings` is Z1, Z2, Z1Z2 and we
        # measure (n_shots, n_qubits=2) obs_strings then the full operator value involves selecting
        # either the first column, second column, or both and multiplying along the row.
        for setting in settings:
            # 3.2 Get the term's coefficient so we can multiply it in later.
            coeff = complex(setting.out_operator.coefficient)
            if not np.isclose(coeff.imag, 0):
                raise ValueError(
                    f"{setting}'s out_operator has a complex coefficient.")
            coeff = coeff.real

            # 3.3 Special case for measuring the "identity" operator, which doesn't make much
            #     sense but should happen perfectly.
            if is_identity(setting.out_operator):
                yield ExperimentResult(
                    setting=setting,
                    expectation=coeff,
                    stddev=0.0,
                    total_counts=n_shots,
                )
                continue

            # 3.4 Pick columns corresponding to qubits with a non-identity out_operation and stack
            #     into an array of shape (n_shots, n_measure_qubits)
            my_obs_strings = np.vstack(obs_strings[q]
                                       for q, op_str in setting.out_operator).T

            # 3.6 Multiply row-wise to get operator values. Do statistics. Yield result.
            obs_vals = coeff * np.prod(my_obs_strings, axis=1)
            obs_mean = np.mean(obs_vals)
            obs_var = np.var(obs_vals) / n_shots
            yield ExperimentResult(
                setting=setting,
                expectation=obs_mean.item(),
                stddev=np.sqrt(obs_var).item(),
                total_counts=n_shots,
            )