コード例 #1
0
def test_readout_symmetrization(forest):
    device = NxDevice(nx.complete_graph(3))
    noise_model = decoherence_noise_with_asymmetric_ro(gates=gates_in_isa(device.get_isa()))
    qc = QuantumComputer(
        name='testy!',
        qam=QVM(connection=forest, noise_model=noise_model),
        device=device,
        compiler=DummyCompiler()
    )

    prog = Program(I(0), X(1),
                   MEASURE(0, 0),
                   MEASURE(1, 1))
    prog.wrap_in_numshots_loop(1000)

    bs1 = qc.run(prog)
    avg0_us = np.mean(bs1[:, 0])
    avg1_us = 1 - np.mean(bs1[:, 1])
    diff_us = avg1_us - avg0_us
    assert diff_us > 0.03

    bs2 = qc.run_symmetrized_readout(prog, 1000)
    avg0_s = np.mean(bs2[:, 0])
    avg1_s = 1 - np.mean(bs2[:, 1])
    diff_s = avg1_s - avg0_s
    assert diff_s < 0.05
コード例 #2
0
def measure_observables(
    qc: QuantumComputer,
    tomo_experiment: Experiment,
    n_shots: Optional[int] = None,
    progress_callback: Optional[Callable[[int, int], None]] = None,
    active_reset: Optional[bool] = None,
    symmetrize_readout: Optional[Union[int, str]] = "None",
    calibrate_readout: Optional[str] = "plus-eig",
    readout_symmetrize: Optional[str] = None,
) -> Generator[ExperimentResult, None, 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 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 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' (default value).
        The preceding symmetrization and this step together yield a more accurate estimation of
        the observable. Set to `None` if no calibration is desired.
    """
    shots = tomo_experiment.shots
    symmetrization = tomo_experiment.symmetrization
    reset = tomo_experiment.reset

    if n_shots is not None:
        warnings.warn(
            "'n_shots' has been deprecated; if you want to set the number of shots "
            "for this run of measure_observables please provide the number to "
            "Program.wrap_in_numshots_loop() for the Quil program that you provide "
            "when creating your TomographyExperiment object. For now, this value will "
            "override that in the TomographyExperiment, but eventually this keyword "
            "argument will be removed.",
            FutureWarning,
        )
        shots = n_shots
    else:
        if shots == 1:
            warnings.warn(
                "'n_shots' has been deprecated; if you want to set the number of shots "
                "for this run of measure_observables please provide the number to "
                "Program.wrap_in_numshots_loop() for the Quil program that you provide "
                "when creating your TomographyExperiment object. It looks like your "
                "TomographyExperiment object has shots = 1, so for now we will change "
                "that to 10000, which was the previous default value.",
                FutureWarning,
            )
            shots = 10000

    if active_reset is not None:
        warnings.warn(
            "'active_reset' has been deprecated; if you want to enable active qubit "
            "reset please provide a Quil program that has a RESET instruction in it when "
            "creating your TomographyExperiment object. For now, this value will "
            "override that in the TomographyExperiment, but eventually this keyword "
            "argument will be removed.",
            FutureWarning,
        )
        reset = active_reset

    if readout_symmetrize is not None and symmetrize_readout != "None":
        raise ValueError(
            "'readout_symmetrize' and 'symmetrize_readout' are conflicting keyword "
            "arguments -- please provide only one.")

    if readout_symmetrize is not None:
        warnings.warn(
            "'readout_symmetrize' has been deprecated; please provide the symmetrization "
            "level when creating your TomographyExperiment object. For now, this value "
            "will override that in the TomographyExperiment, but eventually this keyword "
            "argument will be removed.",
            FutureWarning,
        )
        symmetrization = SymmetrizationLevel(readout_symmetrize)

    if symmetrize_readout != "None":
        warnings.warn(
            "'symmetrize_readout' has been deprecated; please provide the symmetrization "
            "level when creating your TomographyExperiment object. For now, this value "
            "will override that in the TomographyExperiment, but eventually this keyword "
            "argument will be removed.",
            FutureWarning,
        )
        if symmetrize_readout is None:
            symmetrize_readout = SymmetrizationLevel.NONE
        elif symmetrize_readout == "exhaustive":
            symmetrize_readout = SymmetrizationLevel.EXHAUSTIVE
        symmetrization = SymmetrizationLevel(symmetrize_readout)

    # calibration readout only works with symmetrization turned on
    if calibrate_readout is not None and symmetrization != SymmetrizationLevel.EXHAUSTIVE:
        raise ValueError(
            "Readout calibration only currently works with exhaustive readout "
            "symmetrization turned on.")

    # generate programs for each group of simultaneous settings.
    programs, meas_qubits = _generate_experiment_programs(
        tomo_experiment, reset)

    for i, (prog, qubits,
            settings) in enumerate(zip(programs, meas_qubits,
                                       tomo_experiment)):
        log.info(
            f"Collecting bitstrings for the {len(settings)} settings: {settings}"
        )

        # we don't need to do any actual measurement if the combined operator is simply the
        # identity, i.e. weight=0. We handle this specially below.
        if len(qubits) > 0:
            # obtain (optionally symmetrized) bitstring results for all of the qubits
            bitstrings = qc.run_symmetrized_readout(prog, shots,
                                                    symmetrization, qubits)

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

        # 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 (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:
            # Get the term's coefficient so we can multiply it in later.
            coeff = setting.out_operator.coefficient
            assert isinstance(coeff, Complex)
            if not np.isclose(coeff.imag, 0):
                raise ValueError(
                    f"{setting}'s out_operator has a complex coefficient.")
            coeff = coeff.real

            # 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,
                                       std_err=0.0,
                                       total_counts=shots)
                continue

            # Obtain statistics from result of experiment
            obs_mean, obs_var = _stats_from_measurements(
                bitstrings, {q: idx
                             for idx, q in enumerate(qubits)}, setting, shots,
                coeff)

            if calibrate_readout == "plus-eig":
                # Readout calibration
                # Obtain calibration program
                calibr_prog = _calibration_program(qc, tomo_experiment,
                                                   setting)
                calibr_qubs = setting.out_operator.get_qubits()
                calibr_qub_dict = {
                    cast(int, q): idx
                    for idx, q in enumerate(calibr_qubs)
                }

                # Perform symmetrization on the calibration program
                calibr_results = qc.run_symmetrized_readout(
                    calibr_prog, shots, SymmetrizationLevel.EXHAUSTIVE,
                    calibr_qubs)

                # Obtain statistics from the measurement process
                obs_calibr_mean, obs_calibr_var = _stats_from_measurements(
                    calibr_results, calibr_qub_dict, setting, shots)
                # 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(),
                    std_err=np.sqrt(corrected_var).item(),
                    total_counts=len(bitstrings),
                    raw_expectation=obs_mean.item(),
                    raw_std_err=np.sqrt(obs_var).item(),
                    calibration_expectation=obs_calibr_mean.item(),
                    calibration_std_err=np.sqrt(obs_calibr_var).item(),
                    calibration_counts=len(calibr_results),
                )

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

            else:
                raise ValueError(
                    "Calibration readout method must be either 'plus-eig' or None"
                )
コード例 #3
0
def calibrate_observable_estimates(qc: QuantumComputer, expt_results: List[ExperimentResult],
                                   num_shots: int = 500, symm_type: int = -1,
                                   noisy_program: Program = None, active_reset: bool = False,
                                   show_progress_bar: bool = False) \
        -> Iterable[ExperimentResult]:
    """
    Calibrates the expectation and std_err of the input expt_results and updates those estimates.

    The input expt_results should be estimated with symmetrized readout error for this to work
    properly. Calibration is done by measuring expectation values of eigenstates of the
    observable, which ideally should yield either +/- 1 but in practice will have magnitude less
    than 1. For default exhaustive_symmetrization the calibration expectation magnitude
    averaged over all eigenvectors is recorded as calibration_expectation. The original
    expectation is moved to raw_expectation and replaced with the old value scaled by the inverse
    calibration expectation.

    :param qc: a quantum computer object on which to run the programs necessary to calibrate each
        result.
    :param expt_results: a list of results, each of which will be separately calibrated.
    :param num_shots: the number of shots to run for each eigenvector
    :param symm_type: the type of symmetrization

        * -1 -- exhaustive symmetrization uses every possible combination of flips; this option
            is the default since it ensures proper calibration and is only exponential in the
            weight of each observable (rather than the total number of qubits in a program).
        * 0 -- no symmetrization
        * 1 -- symmetrization using an OA with strength 1
        * 2 -- symmetrization using an OA with strength 2
        * 3 -- symmetrization using an OA with strength 3

        TODO: accomodate calibration for weight > symmetrization strength (symm_type)
        Currently, the symmetrization type must be at least the maximum weight of any observable
        estimated and also match the symmetrization type used to estimate the observables.

    :param noisy_program: an optional program from which to inherit a noise model; only relevant
        for running on a QVM
    :param active_reset: whether or not to begin the program by actively resetting. If true,
        execution of each of the returned programs in a loop on the QPU will generally be faster.
    :param show_progress_bar: displays a progress bar via tqdm if true.
    :return: a copy of the input results with updated estimates and calibration results.
    """
    observables = [copy(res.setting.observable) for res in expt_results]
    for obs in observables:
        obs.coefficient = complex(1.)
    observables = list(set(observables))  # get unique observables that will need to be calibrated

    programs = [get_calibration_program(obs, noisy_program, active_reset) for obs in observables]
    meas_qubits = [obs.get_qubits() for obs in observables]

    calibrations = {}
    for prog, meas_qs, obs in zip(tqdm(programs, disable=not show_progress_bar), meas_qubits,
                                  observables):
        results = qc.run_symmetrized_readout(prog, num_shots, symm_type, meas_qs)

        # Obtain statistics from result of experiment
        obs_mean, obs_var = shots_to_obs_moments(results, meas_qs, obs)
        calibrations[obs.operations_as_set()] = (obs_mean, obs_var, len(results))

    for expt_result in expt_results:
        # TODO: allow weight > symm_type
        if -1 < symm_type < len(expt_result.setting.observable.get_qubits()):
            warnings.warn(f'Calibration of observable {expt_result.setting.observable} '
                          f'currently not supported since it acts on more qubits than the '
                          f'symm_type {symm_type}.')

        # get the calibration data for this observable
        cal_data = calibrations[expt_result.setting.observable.operations_as_set()]
        obs_mean, obs_var, counts = cal_data

        # Use the calibration to correct the mean and var
        result_mean = expt_result.expectation
        result_var = expt_result.std_err**2
        corrected_mean = result_mean / obs_mean
        corrected_var = ratio_variance(result_mean, result_var, obs_mean, obs_var)

        yield ExperimentResult(
            setting=expt_result.setting,
            expectation=corrected_mean,
            std_err=np.sqrt(corrected_var),
            total_counts=expt_result.total_counts,
            raw_expectation=result_mean,
            raw_std_err=expt_result.std_err,
            calibration_expectation=obs_mean,
            calibration_std_err=np.sqrt(obs_var),
            calibration_counts=counts
        )
コード例 #4
0
def estimate_observables(qc: QuantumComputer, obs_expt: ObservablesExperiment,
                         num_shots: int = 500, symm_type: int = 0,
                         active_reset: bool = False, show_progress_bar: bool = False,
                         use_basic_compile: bool = True)\
        -> Iterable[ExperimentResult]:
    """
    Standard wrapper for estimating the observables in an `ObservablesExperiment`.

    An expectation and standard error will be estimated for each observable in each setting of
    the ObservablesExperiment. Settings which are grouped together will be run in parallel under
    the same call to the QuantumComputer.

    .. CAUTION::
        Note that the call to `qc.run_symmetrized_readout` adds MEASURE instructions to the
        programs output by :func:`generate_experiment_programs` and also uses the qc.compiler to
        compile each program. The default compiler (e.g. for qc returned by `get_qc`) may make
        optimizations that remove gates necessary for benchmarking. If this is not desired,
        consider setting the use_basic_compile flag to True for a basic compilation pass that
        simply replaces gates with non-native gates.

    :param qc: a quantum computer object on which to run the programs necessary to estimate each
        observable of obs_expt.
    :param obs_expt: a single ObservablesExperiment with settings pre-grouped as desired.
    :param num_shots: the number of shots to run each program or each symmetrized program.
    :param symm_type: the type of symmetrization

        * -1 -- exhaustive symmetrization uses every possible combination of flips
        * 0 -- no symmetrization
        * 1 -- symmetrization using an OA with strength 1
        * 2 -- symmetrization using an OA with strength 2
        * 3 -- symmetrization using an OA with strength 3

    :param active_reset: whether or not to begin the program by actively resetting. If true,
        execution of each of the returned programs in a loop on the QPU will generally be faster.
    :param show_progress_bar: displays a progress bar via tqdm if true.
    :param use_basic_compile: instead of using the qc.compiler standard quil_to_native_quil
        compilation step, which may optimize gates away, instead use only basic_compile which
        makes as few manual gate substitutions as possible.
    :return: all of the ExperimentResults which hold an estimate of each observable of obs_expt
    """
    if use_basic_compile:
        old_method = qc.compiler.quil_to_native_quil
        # temporarily replace compiler.quil_to_native_quil with basic_compile
        qc.compiler.quil_to_native_quil = basic_compile

    programs, meas_qubits = generate_experiment_programs(obs_expt, active_reset)
    for prog, meas_qs, settings in zip(tqdm(programs, disable=not show_progress_bar), meas_qubits,
                                       obs_expt):
        results = qc.run_symmetrized_readout(prog, num_shots, symm_type, meas_qs)

        for setting in settings:
            observable = setting.observable

            # Obtain statistics from result of experiment
            obs_mean, obs_var = shots_to_obs_moments(results, meas_qs, observable)

            yield ExperimentResult(
                setting=setting,
                expectation=obs_mean,
                std_err=np.sqrt(obs_var),
                total_counts=len(results),
            )

    if use_basic_compile:
        # revert to original
        qc.compiler.quil_to_native_quil = old_method