Example #1
0
def test_group_experiments_greedy():
    ungrouped_tomo_expt = Experiment(
        [
            [
                ExperimentSetting(
                    _pauli_to_product_state(
                        PauliTerm.from_compact_str("(1+0j)*Z7Y8Z1Y4Z2Y5Y0X6")),
                    PauliTerm.from_compact_str("(1+0j)*Z4X8Y5X3Y7Y1"),
                )
            ],
            [ExperimentSetting(plusZ(7), sY(1))],
        ],
        program=Program(H(0), H(1), H(2)),
    )
    grouped_tomo_expt = group_settings(ungrouped_tomo_expt, method="greedy")
    expected_grouped_tomo_expt = Experiment(
        [[
            ExperimentSetting(
                TensorProductState.from_str(
                    "Z0_7 * Y0_8 * Z0_1 * Y0_4 * Z0_2 * Y0_5 * Y0_0 * X0_6"),
                PauliTerm.from_compact_str("(1+0j)*Z4X8Y5X3Y7Y1"),
            ),
            ExperimentSetting(plusZ(7), sY(1)),
        ]],
        program=Program(H(0), H(1), H(2)),
    )
    assert grouped_tomo_expt == expected_grouped_tomo_expt
Example #2
0
def test_results_by_qubit_groups():
    er1 = ExperimentResult(
        setting=ExperimentSetting(plusX(0), sZ(0)),
        expectation=0.0,
        std_err=0.0,
        total_counts=1,
    )

    er2 = ExperimentResult(
        setting=ExperimentSetting(plusX(0), sZ(1)),
        expectation=0.0,
        std_err=0.0,
        total_counts=1,
    )

    er3 = ExperimentResult(
        setting=ExperimentSetting(plusX(0),
                                  sX(0) * sZ(1)),
        expectation=0.0,
        std_err=0.0,
        total_counts=1,
    )

    er4 = ExperimentResult(
        setting=ExperimentSetting(plusX(0),
                                  sX(0) * sZ(2)),
        expectation=0.0,
        std_err=0.0,
        total_counts=1,
    )
    groups = [(0, ), (1, ), (2, 0)]
    res_by_group = get_results_by_qubit_groups([er1, er2, er3, er4], groups)

    assert res_by_group == {(0, ): [er1], (1, ): [er2], (0, 2): [er1, er4]}
Example #3
0
def test_max_tpb_overlap_3():
    # add another ExperimentSetting to the above
    expt_setting = ExperimentSetting(
        _pauli_to_product_state(
            PauliTerm.from_compact_str("(1+0j)*Z7Y8Z1Y4Z2Y5Y0X6")),
        PauliTerm.from_compact_str("(1+0j)*Z4X8Y5X3Y7Y1"),
    )
    expt_setting2 = ExperimentSetting(plusZ(7), sY(1))
    p = Program(H(0), H(1), H(2))
    tomo_expt2 = Experiment([expt_setting, expt_setting2], p)
    expected_dict2 = {expt_setting: [expt_setting, expt_setting2]}
    assert expected_dict2 == _max_tpb_overlap(tomo_expt2)
Example #4
0
def test_merge_disjoint_experiments():
    sett1 = ExperimentSetting(TensorProductState(), sX(0) * sY(1))
    sett2 = ExperimentSetting(plusZ(1), sY(1))
    sett3 = ExperimentSetting(plusZ(0), sX(0))
    sett4 = ExperimentSetting(minusX(1), sY(1))
    sett5 = ExperimentSetting(TensorProductState(), sZ(2))

    expt1 = Experiment(settings=[sett1, sett2], program=Program(X(1)))
    expt2 = Experiment(settings=[sett3, sett4], program=Program(Z(0)))
    expt3 = Experiment(settings=[sett5], program=Program())

    merged_expt = merge_disjoint_experiments([expt1, expt2, expt3])
    assert len(merged_expt) == 2
Example #5
0
def test_group_experiments(grouping_method):
    expts = [  # cf above, I removed the inner nesting. Still grouped visually
        ExperimentSetting(TensorProductState(),
                          sX(0) * sI(1)),
        ExperimentSetting(TensorProductState(),
                          sI(0) * sX(1)),
        ExperimentSetting(TensorProductState(),
                          sZ(0) * sI(1)),
        ExperimentSetting(TensorProductState(),
                          sI(0) * sZ(1)),
    ]
    suite = Experiment(expts, Program())
    grouped_suite = group_settings(suite, method=grouping_method)
    assert len(suite) == 4
    assert len(grouped_suite) == 2
Example #6
0
def test_setting_no_in():
    out_ops = _generate_random_paulis(n_qubits=4, n_terms=7)
    for oop in out_ops:
        expt = ExperimentSetting(zeros_state(oop.get_qubits()), oop)
        expt2 = ExperimentSetting.from_str(str(expt))
        assert expt == expt2
        assert expt2.in_operator == functools.reduce(mul, [sZ(q) for q in oop.get_qubits()], sI())
        assert expt2.out_operator == oop
Example #7
0
def test_setting_no_in_back_compat():
    out_ops = _generate_random_paulis(n_qubits=4, n_terms=7)
    for oop in out_ops:
        expt = ExperimentSetting(TensorProductState(), oop)
        expt2 = ExperimentSetting.from_str(str(expt))
        assert expt == expt2
        assert expt2.in_operator == sI()
        assert expt2.out_operator == oop
Example #8
0
def _max_tpb_overlap(
    tomo_expt: Experiment,
) -> Dict[ExperimentSetting, List[ExperimentSetting]]:
    """
    Given an input Experiment, provide a dictionary indicating which ExperimentSettings
    share a tensor product basis

    :param tomo_expt: Experiment, from which to group ExperimentSettings that share a tpb
        and can be run together
    :return: dictionary keyed with ExperimentSetting (specifying a tpb), and with each value being a
            list of ExperimentSettings (diagonal in that tpb)
    """
    # initialize empty dictionary
    diagonal_sets: Dict[ExperimentSetting, List[ExperimentSetting]] = {}
    # loop through ExperimentSettings of the Experiment
    for expt_setting in tomo_expt:
        # no need to group already grouped Experiment
        assert len(expt_setting) == 1, "already grouped?"
        unpacked_expt_setting = expt_setting[0]
        # calculate max overlap of expt_setting with keys of diagonal_sets
        # keep track of whether a shared tpb was found
        found_tpb = False
        # loop through dict items
        for es, es_list in diagonal_sets.items():
            trial_es_list = es_list + [unpacked_expt_setting]
            diag_in_term = _max_weight_state(expst.in_state
                                             for expst in trial_es_list)
            diag_out_term = _max_weight_operator(expst.out_operator
                                                 for expst in trial_es_list)
            # max_weight_xxx returns None if the set of xxx's don't share a TPB, so the following
            # conditional is True if expt_setting can be inserted into the current es_list.
            if diag_in_term is not None and diag_out_term is not None:
                found_tpb = True
                assert len(diag_in_term) >= len(
                    es.in_state
                ), "Highest weight in-state can't be smaller than the given in-state"
                assert len(diag_out_term) >= len(
                    es.out_operator
                ), "Highest weight out-PauliTerm can't be smaller than the given out-PauliTerm"

                # update the diagonalizing basis (key of dict) if necessary
                if len(diag_in_term) > len(
                        es.in_state) or len(diag_out_term) > len(
                            es.out_operator):
                    del diagonal_sets[es]
                    new_es = ExperimentSetting(diag_in_term, diag_out_term)
                    diagonal_sets[new_es] = trial_es_list
                else:
                    diagonal_sets[es] = trial_es_list
                break

        if not found_tpb:
            # made it through entire dict without finding any ExperimentSetting with shared tpb,
            # so need to make a new item
            diagonal_sets[unpacked_expt_setting] = [unpacked_expt_setting]

    return diagonal_sets
Example #9
0
def test_experiment_setting():
    in_states = _generate_random_states(n_qubits=4, n_terms=7)
    out_ops = _generate_random_paulis(n_qubits=4, n_terms=7)
    for ist, oop in zip(in_states, out_ops):
        expt = ExperimentSetting(ist, oop)
        assert str(expt) == expt.serializable()
        expt2 = ExperimentSetting.from_str(str(expt))
        assert expt == expt2
        assert expt2.in_state == ist
        assert expt2.out_operator == oop
Example #10
0
def test_max_tpb_overlap_2():
    expt_setting = ExperimentSetting(
        _pauli_to_product_state(
            PauliTerm.from_compact_str("(1+0j)*Z7Y8Z1Y4Z2Y5Y0X6")),
        PauliTerm.from_compact_str("(1+0j)*Z4X8Y5X3Y7Y1"),
    )
    p = Program(H(0), H(1), H(2))
    tomo_expt = Experiment([expt_setting], p)
    expected_dict = {expt_setting: [expt_setting]}
    assert expected_dict == _max_tpb_overlap(tomo_expt)
Example #11
0
def test_expt_settings_share_ntpb():
    expts = [
        [
            ExperimentSetting(zeros_state([0, 1]),
                              sX(0) * sI(1)),
            ExperimentSetting(zeros_state([0, 1]),
                              sI(0) * sX(1)),
        ],
        [
            ExperimentSetting(zeros_state([0, 1]),
                              sZ(0) * sI(1)),
            ExperimentSetting(zeros_state([0, 1]),
                              sI(0) * sZ(1)),
        ],
    ]
    for group in expts:
        for e1, e2 in itertools.combinations(group, 2):
            assert _max_weight_state([e1.in_state, e2.in_state]) is not None
            assert _max_weight_operator([e1.out_operator,
                                         e2.out_operator]) is not None
Example #12
0
def test_expt_settings_diagonal_in_tpb():
    def _expt_settings_diagonal_in_tpb(es1: ExperimentSetting,
                                       es2: ExperimentSetting):
        """
        Extends the concept of being diagonal in the same tpb to ExperimentSettings, by
        determining if the pairs of in_states and out_operators are separately diagonal in the same
        tpb
        """
        max_weight_in = _max_weight_state([es1.in_state, es2.in_state])
        max_weight_out = _max_weight_operator(
            [es1.out_operator, es2.out_operator])
        return max_weight_in is not None and max_weight_out is not None

    expt_setting1 = ExperimentSetting(plusZ(1) * plusX(0), sY(1) * sZ(0))
    expt_setting2 = ExperimentSetting(plusY(2) * plusZ(1), sZ(2) * sY(1))
    assert _expt_settings_diagonal_in_tpb(expt_setting1, expt_setting2)
    expt_setting3 = ExperimentSetting(plusX(2) * plusZ(1), sZ(2) * sY(1))
    expt_setting4 = ExperimentSetting(plusY(2) * plusZ(1), sX(2) * sY(1))
    assert not _expt_settings_diagonal_in_tpb(expt_setting2, expt_setting3)
    assert not _expt_settings_diagonal_in_tpb(expt_setting2, expt_setting4)
Example #13
0
def test_max_tpb_overlap_1():
    tomo_expt_settings = [
        ExperimentSetting(plusZ(1) * plusX(0),
                          sY(2) * sY(1)),
        ExperimentSetting(plusX(2) * plusZ(1),
                          sY(2) * sZ(0)),
    ]
    tomo_expt_program = Program(H(0), H(1), H(2))
    tomo_expt = Experiment(tomo_expt_settings, tomo_expt_program)
    expected_dict = {
        ExperimentSetting(
            plusX(0) * plusZ(1) * plusX(2),
            sZ(0) * sY(1) * sY(2)): [
            ExperimentSetting(plusZ(1) * plusX(0),
                              sY(2) * sY(1)),
            ExperimentSetting(plusX(2) * plusZ(1),
                              sY(2) * sZ(0)),
        ]
    }
    assert expected_dict == _max_tpb_overlap(tomo_expt)
Example #14
0
    def generate_calibration_experiment(self) -> "Experiment":
        """
        Generate another ``Experiment`` object that can be used to calibrate the various multi-qubit
        observables involved in this ``Experiment``. This is achieved by preparing the plus-one
        (minus-one) eigenstate of each ``out_operator``, and measuring the resulting expectation
        value of the same ``out_operator``. Ideally, this would always give +1 (-1), but when
        symmetric readout error is present the effect is to scale the resultant expectations by some
        constant factor. Determining this scale factor is what we call *readout calibration*, and
        then the readout error in subsequent measurements can then be mitigated by simply dividing
        by the scale factor.

        :return: A new ``Experiment`` that can calibrate the readout error of all the
            observables involved in this experiment.
        """
        if self.calibration != CalibrationMethod.PLUS_EIGENSTATE:
            raise ValueError(
                'We currently only support the "plus eigenstate" calibration method.'
            )

        calibration_settings = []
        for settings in self:
            assert len(settings) == 1
            calibration_settings.append(
                ExperimentSetting(
                    in_state=settings[0].out_operator,
                    out_operator=settings[0].out_operator,
                    additional_expectations=settings[0].
                    additional_expectations,
                ))

        calibration_program = Program()
        if self.reset:
            calibration_program += RESET()
        calibration_program.wrap_in_numshots_loop(self.shots)

        if self.symmetrization != SymmetrizationLevel.EXHAUSTIVE:
            raise ValueError(
                "We currently only support calibration for exhaustive symmetrization"
            )

        return Experiment(
            settings=calibration_settings,
            program=calibration_program,
            symmetrization=SymmetrizationLevel.EXHAUSTIVE,
            calibration=CalibrationMethod.NONE,
        )
Example #15
0
    def experiment(
        self,
        experiment: Experiment,
        memory_map: Optional[Mapping[str, Sequence[Union[int, float]]]] = None,
    ) -> List[ExperimentResult]:
        """
        Run an ``Experiment`` on a QVM or QPU backend. An ``Experiment`` is composed of:

            - A main ``Program`` body (or ansatz).
            - A collection of ``ExperimentSetting`` objects, each of which encodes a particular
              state preparation and measurement.
            - A ``SymmetrizationLevel`` for enacting different readout symmetrization strategies.
            - A number of shots to collect for each (unsymmetrized) ``ExperimentSetting``.

        Because the main ``Program`` is static from run to run of an ``Experiment``, we can leverage
        our platform's Parametric Compilation feature. This means that the ``Program`` can be
        compiled only once, and the various alterations due to state preparation, measurement,
        and symmetrization can all be realized at runtime by providing a ``memory_map``. Thus, the
        steps in the ``experiment`` method are as follows:

            1. Generate a parameterized program corresponding to the ``Experiment``
                (see the ``Experiment.generate_experiment_program()`` method for more
                details on how it changes the main body program to support state preparation,
                measurement, and symmetrization).
            2. Compile the parameterized program into a parametric (binary) executable, which
                   contains declared variables that can be assigned at runtime.

            3. For each ``ExperimentSetting`` in the ``Experiment``, we repeat the following:

                a. Build a collection of memory maps that correspond to the various state
                   preparation, measurement, and symmetrization specifications.
                b. Run the parametric executable on the QVM or QPU backend, providing the memory map
                   to assign variables at runtime.
                c. Extract the desired statistics from the classified bitstrings that are produced
                   by the QVM or QPU backend, and package them in an ``ExperimentResult`` object.

            4. Return the list of ``ExperimentResult`` objects.

        This method is extremely useful shorthand for running near-term applications and algorithms,
        which often have this ansatz + settings structure.

        :param experiment: The ``Experiment`` to run.
        :param memory_map: A dictionary mapping declared variables / parameters to their values.
            The values are a list of floats or integers. Each float or integer corresponds to
            a particular classical memory register. The memory map provided to the ``experiment``
            method corresponds to variables in the main body program that we would like to change
            at runtime (e.g. the variational parameters provided to the ansatz of the variational
            quantum eigensolver).
        :return: A list of ``ExperimentResult`` objects containing the statistics gathered
            according to the specifications of the ``Experiment``.
        """
        experiment_program = experiment.generate_experiment_program()
        executable = self.compile(experiment_program)

        if memory_map is None:
            memory_map = {}

        results = []
        for settings in experiment:
            if len(settings) > 1:
                raise ValueError("settings must be of length 1")
            setting = settings[0]

            qubits = cast(List[int], setting.out_operator.get_qubits())
            experiment_setting_memory_map = experiment.build_setting_memory_map(
                setting)
            symmetrization_memory_maps = experiment.build_symmetrization_memory_maps(
                qubits)
            merged_memory_maps = merge_memory_map_lists(
                [experiment_setting_memory_map], symmetrization_memory_maps)

            all_bitstrings = []
            for merged_memory_map in merged_memory_maps:
                final_memory_map = {**memory_map, **merged_memory_map}
                self.qam.reset()
                bitstrings = self.run(executable, memory_map=final_memory_map)

                if "symmetrization" in final_memory_map:
                    bitmask = np.array(
                        np.array(final_memory_map["symmetrization"]) / np.pi,
                        dtype=int)
                    bitstrings = np.bitwise_xor(bitstrings, bitmask)
                all_bitstrings.append(bitstrings)
            symmetrized_bitstrings = np.concatenate(all_bitstrings)

            joint_expectations = [experiment.get_meas_registers(qubits)]
            if setting.additional_expectations:
                joint_expectations += setting.additional_expectations
            expectations = bitstrings_to_expectations(
                symmetrized_bitstrings, joint_expectations=joint_expectations)

            means = cast(np.ndarray, np.mean(expectations, axis=0))
            std_errs = np.std(expectations, axis=0, ddof=1) / np.sqrt(
                len(expectations))

            joint_results = []
            for qubit_subset, mean, std_err in zip(joint_expectations, means,
                                                   std_errs):
                out_operator = PauliTerm.from_list([
                    (setting.out_operator[i], i) for i in qubit_subset
                ])
                s = ExperimentSetting(
                    in_state=setting.in_state,
                    out_operator=out_operator,
                    additional_expectations=None,
                )
                r = ExperimentResult(setting=s,
                                     expectation=mean,
                                     std_err=std_err,
                                     total_counts=len(expectations))
                joint_results.append(r)

            result = ExperimentResult(
                setting=setting,
                expectation=joint_results[0].expectation,
                std_err=joint_results[0].std_err,
                total_counts=joint_results[0].total_counts,
                additional_results=joint_results[1:],
            )
            results.append(result)

        return results