Example #1
0
    def test_ideal_tensored_meas_cal(self):
        """Test ideal execution, without noise."""

        mit_pattern = [[1, 2], [3, 4, 5], [6]]
        meas_layout = [1, 2, 3, 4, 5, 6]

        # Generate the calibration circuits
        meas_calibs, _ = tensored_meas_cal(mit_pattern=mit_pattern)

        # Perform an ideal execution on the generated circuits
        backend = Aer.get_backend("qasm_simulator")
        cal_results = qiskit.execute(meas_calibs,
                                     backend=backend,
                                     shots=self.shots).result()

        # Make calibration matrices
        meas_cal = TensoredMeasFitter(cal_results, mit_pattern=mit_pattern)

        # Assert that the calibration matrices are equal to identity
        cal_matrices = meas_cal.cal_matrices
        self.assertEqual(len(mit_pattern), len(cal_matrices),
                         "Wrong number of calibration matrices")
        for qubit_list, cal_mat in zip(mit_pattern, cal_matrices):
            IdentityMatrix = np.identity(2**len(qubit_list))
            self.assertListEqual(
                cal_mat.tolist(),
                IdentityMatrix.tolist(),
                "Error: the calibration matrix is not equal to identity",
            )

        # Assert that the readout fidelity is equal to 1
        self.assertEqual(
            meas_cal.readout_fidelity(),
            1.0,
            "Error: the average fidelity is not equal to 1",
        )

        # Generate ideal (equally distributed) results
        results_dict, _ = self.generate_ideal_results(count_keys(6), 6)

        # Output the filter
        meas_filter = meas_cal.filter

        # Apply the calibration matrix to results
        # in list and dict forms using different methods
        results_dict_1 = meas_filter.apply(results_dict,
                                           method="least_squares",
                                           meas_layout=meas_layout)
        results_dict_0 = meas_filter.apply(results_dict,
                                           method="pseudo_inverse",
                                           meas_layout=meas_layout)

        # Assert that the results are equally distributed
        self.assertDictEqual(results_dict, results_dict_0)
        round_results = {}
        for key, val in results_dict_1.items():
            round_results[key] = np.round(val)
        self.assertDictEqual(results_dict, round_results)
Example #2
0
    def __init__(
        self,
        results,
        mit_pattern: List[List[int]],
        substate_labels_list: List[List[str]] = None,
        circlabel: str = "",
    ):
        """
        Initialize a measurement calibration matrix from the results of running
        the circuits returned by `measurement_calibration_circuits`.

        .. warning::

            This class is not a public API. The internals are not stable and will
            likely change. It is used solely for the
            ``measurement_error_mitigation_cls`` kwarg of the
            :class:`~qiskit.utils.QuantumInstance` class's constructor (as
            a class not an instance). Anything outside of that usage does
            not have the normal user-facing API stability.

        Args:
            results: the results of running the measurement calibration
                circuits. If this is `None`, the user will set calibration
                matrices later.

            mit_pattern: qubits to perform the
                measurement correction on, divided to groups according to
                tensors

            substate_labels_list: for each
                calibration matrix, the labels of its rows and columns.
                If `None`, the labels are ordered lexicographically

            circlabel: if the qubits were labeled

        Raises:
            ValueError: if the mit_pattern doesn't match the
                substate_labels_list
        """

        self._result_list = []
        self._cal_matrices = None
        self._circlabel = circlabel
        self._mit_pattern = mit_pattern

        self._qubit_list_sizes = [
            len(qubit_list) for qubit_list in mit_pattern
        ]

        self._indices_list = []
        if substate_labels_list is None:
            self._substate_labels_list = []
            for list_size in self._qubit_list_sizes:
                self._substate_labels_list.append(count_keys(list_size))
        else:
            self._substate_labels_list = substate_labels_list
            if len(self._qubit_list_sizes) != len(substate_labels_list):
                raise ValueError(
                    "mit_pattern does not match substate_labels_list")

        self._indices_list = []
        for _, sub_labels in enumerate(self._substate_labels_list):
            self._indices_list.append(
                {lab: ind
                 for ind, lab in enumerate(sub_labels)})

        self.add_data(results)
Example #3
0
    def subset_fitter(self, qubit_sublist):
        """
        Return a fitter object that is a subset of the qubits in the original
        list.

        Args:
            qubit_sublist (list): must be a subset of qubit_list

        Returns:
            CompleteMeasFitter: A new fitter that has the calibration for a
                subset of qubits

        Raises:
            QiskitError: If the calibration matrix is not initialized
        """

        if self._tens_fitt.cal_matrices is None:
            raise QiskitError("Calibration matrix is not initialized")

        if qubit_sublist is None:
            raise QiskitError("Qubit sublist must be specified")

        for qubit in qubit_sublist:
            if qubit not in self._qubit_list:
                raise QiskitError("Qubit not in the original set of qubits")

        # build state labels
        new_state_labels = count_keys(len(qubit_sublist))

        # mapping between indices in the state_labels and the qubits in
        # the sublist
        qubit_sublist_ind = []
        for sqb in qubit_sublist:
            for qbind, qubit in enumerate(self._qubit_list):
                if qubit == sqb:
                    qubit_sublist_ind.append(qbind)

        # states in the full calibration which correspond
        # to the reduced labels
        q_q_mapping = []
        state_labels_reduced = []
        for label in self.state_labels:
            tmplabel = [label[index] for index in qubit_sublist_ind]
            state_labels_reduced.append("".join(tmplabel))

        for sub_lab_ind, _ in enumerate(new_state_labels):
            q_q_mapping.append([])
            for labelind, label in enumerate(state_labels_reduced):
                if label == new_state_labels[sub_lab_ind]:
                    q_q_mapping[-1].append(labelind)

        new_fitter = CompleteMeasFitter(results=None,
                                        state_labels=new_state_labels,
                                        qubit_list=qubit_sublist)

        new_cal_matrix = np.zeros(
            [len(new_state_labels),
             len(new_state_labels)])

        # do a partial trace
        for i in range(len(new_state_labels)):
            for j in range(len(new_state_labels)):

                for q_q_i_map in q_q_mapping[i]:
                    for q_q_j_map in q_q_mapping[j]:
                        new_cal_matrix[i, j] += self.cal_matrix[q_q_i_map,
                                                                q_q_j_map]

                new_cal_matrix[i, j] /= len(q_q_mapping[i])

        new_fitter.cal_matrix = new_cal_matrix

        return new_fitter
Example #4
0
    def test_tensored_meas_fitter_with_noise(self):
        """Test the TensoredFitter with noise."""
        cal_results, mit_pattern, circuit_results, meas_layout = tensored_calib_circ_execution(
            1000, SEED
        )

        meas_cal = TensoredMeasFitter(cal_results, mit_pattern=mit_pattern)
        meas_filter = meas_cal.filter

        # Calculate the results after mitigation
        results_pseudo_inverse = meas_filter.apply(
            circuit_results.get_counts(), method="pseudo_inverse", meas_layout=meas_layout
        )
        results_least_square = meas_filter.apply(
            circuit_results.get_counts(), method="least_squares", meas_layout=meas_layout
        )
        saved_info = {
            "cal_results": cal_results.to_dict(),
            "results": circuit_results.to_dict(),
            "mit_pattern": mit_pattern,
            "meas_layout": meas_layout,
            "fidelity": meas_cal.readout_fidelity(),
            "results_pseudo_inverse": results_pseudo_inverse,
            "results_least_square": results_least_square,
        }

        saved_info["cal_results"] = Result.from_dict(saved_info["cal_results"])
        saved_info["results"] = Result.from_dict(saved_info["results"])

        meas_cal = TensoredMeasFitter(
            saved_info["cal_results"], mit_pattern=saved_info["mit_pattern"]
        )

        # Calculate the fidelity
        fidelity = meas_cal.readout_fidelity(0) * meas_cal.readout_fidelity(1)
        # Compare with expected fidelity and expected results
        self.assertAlmostEqual(fidelity, saved_info["fidelity"], places=0)

        meas_filter = meas_cal.filter

        # Calculate the results after mitigation
        output_results_pseudo_inverse = meas_filter.apply(
            saved_info["results"].get_counts(0),
            method="pseudo_inverse",
            meas_layout=saved_info["meas_layout"],
        )
        output_results_least_square = meas_filter.apply(
            saved_info["results"], method="least_squares", meas_layout=saved_info["meas_layout"]
        )

        self.assertAlmostEqual(
            output_results_pseudo_inverse["000"],
            saved_info["results_pseudo_inverse"]["000"],
            places=0,
        )

        self.assertAlmostEqual(
            output_results_least_square.get_counts(0)["000"],
            saved_info["results_least_square"]["000"],
            places=0,
        )

        self.assertAlmostEqual(
            output_results_pseudo_inverse["111"],
            saved_info["results_pseudo_inverse"]["111"],
            places=0,
        )

        self.assertAlmostEqual(
            output_results_least_square.get_counts(0)["111"],
            saved_info["results_least_square"]["111"],
            places=0,
        )

        substates_list = []
        for qubit_list in saved_info["mit_pattern"]:
            substates_list.append(count_keys(len(qubit_list))[::-1])

        fitter_other_order = TensoredMeasFitter(
            saved_info["cal_results"],
            substate_labels_list=substates_list,
            mit_pattern=saved_info["mit_pattern"],
        )

        fidelity = fitter_other_order.readout_fidelity(0) * meas_cal.readout_fidelity(1)

        self.assertAlmostEqual(fidelity, saved_info["fidelity"], places=0)

        meas_filter = fitter_other_order.filter

        # Calculate the results after mitigation
        output_results_pseudo_inverse = meas_filter.apply(
            saved_info["results"].get_counts(0),
            method="pseudo_inverse",
            meas_layout=saved_info["meas_layout"],
        )
        output_results_least_square = meas_filter.apply(
            saved_info["results"], method="least_squares", meas_layout=saved_info["meas_layout"]
        )

        self.assertAlmostEqual(
            output_results_pseudo_inverse["000"],
            saved_info["results_pseudo_inverse"]["000"],
            places=0,
        )

        self.assertAlmostEqual(
            output_results_least_square.get_counts(0)["000"],
            saved_info["results_least_square"]["000"],
            places=0,
        )

        self.assertAlmostEqual(
            output_results_pseudo_inverse["111"],
            saved_info["results_pseudo_inverse"]["111"],
            places=0,
        )

        self.assertAlmostEqual(
            output_results_least_square.get_counts(0)["111"],
            saved_info["results_least_square"]["111"],
            places=0,
        )
Example #5
0
    def apply(
        self,
        raw_data,
        method="least_squares",
        meas_layout=None,
    ):
        """
        Apply the calibration matrices to results.

        Args:
            raw_data (dict or Result): The data to be corrected. Can be in one of two forms:

                * A counts dictionary from results.get_counts

                * A Qiskit Result

            method (str): fitting method. The following methods are supported:

                * 'pseudo_inverse': direct inversion of the cal matrices.
                    Mitigated counts can contain negative values
                    and the sum of counts would not equal to the shots.
                    Mitigation is conducted qubit wise:
                    For each qubit, mitigate the whole counts using the calibration matrices
                    which affect the corresponding qubit.
                    For example, assume we are mitigating the 3rd bit of the 4-bit counts
                    using '2\times 2' calibration matrix `A_3`.
                    When mitigating the count of '0110' in this step,
                    the following formula is applied:
                    `count['0110'] = A_3^{-1}[1, 0]*count['0100'] + A_3^{-1}[1, 1]*count['0110']`.

                    The total time complexity of this method is `O(m2^{n + t})`,
                    where `n` is the size of calibrated qubits,
                    `m` is the number of sets in `mit_pattern`,
                    and `t` is the size of largest set of mit_pattern.
                    If the `mit_pattern` is shaped like `[[0], [1], [2], ..., [n-1]]`,
                    which corresponds to the tensor product noise model without cross-talk,
                    then the time complexity would be `O(n2^n)`.
                    If the `mit_pattern` is shaped like `[[0, 1, 2, ..., n-1]]`,
                    which exactly corresponds to the complete error mitigation,
                    then the time complexity would be `O(2^(n+n)) = O(4^n)`.


                * 'least_squares': constrained to have physical probabilities.
                    Instead of directly applying inverse calibration matrices,
                    this method solve a constrained optimization problem to find
                    the closest probability vector to the result from 'pseudo_inverse' method.
                    Sequential least square quadratic programming (SLSQP) is used
                    in the internal process.
                    Every updating step in SLSQP takes `O(m2^{n+t})` time.
                    Since this method is using the SLSQP optimization over
                    the vector with lenght `2^n`, the mitigation for 8 bit counts
                    with the `mit_pattern = [[0], [1], [2], ..., [n-1]]` would
                    take 10 seconds or more.

                * If `None`, 'least_squares' is used.

            meas_layout (list of int): the mapping from classical registers to qubits

                * If you measure qubit `2` to clbit `0`, `0` to `1`, and `1` to `2`,
                    the list becomes `[2, 0, 1]`

                * If `None`, flatten(mit_pattern) is used.

        Returns:
            dict or Result: The corrected data in the same form as raw_data

        Raises:
            QiskitError: if raw_data is not in a one of the defined forms.
        """

        all_states = count_keys(self.nqubits)
        num_of_states = 2 ** self.nqubits

        if meas_layout is None:
            meas_layout = []
            for qubits in self._mit_pattern:
                meas_layout += qubits

        # check forms of raw_data
        if isinstance(raw_data, dict):
            # counts dictionary
            # convert to list
            raw_data2 = [np.zeros(num_of_states, dtype=float)]
            for state, count in raw_data.items():
                stateidx = int(state, 2)
                raw_data2[0][stateidx] = count

        elif isinstance(raw_data, qiskit.result.result.Result):

            # extract out all the counts, re-call the function with the
            # counts and push back into the new result
            new_result = deepcopy(raw_data)

            new_counts_list = parallel_map(
                self._apply_correction,
                [resultidx for resultidx, _ in enumerate(raw_data.results)],
                task_args=(raw_data, method, meas_layout),
            )

            for resultidx, new_counts in new_counts_list:
                new_result.results[resultidx].data.counts = new_counts

            return new_result

        else:
            raise QiskitError("Unrecognized type for raw_data.")

        if method == "pseudo_inverse":
            pinv_cal_matrices = []
            for cal_mat in self._cal_matrices:
                pinv_cal_matrices.append(la.pinv(cal_mat))

        meas_layout = meas_layout[::-1]  # reverse endian
        qubits_to_clbits = [-1 for _ in range(max(meas_layout) + 1)]
        for i, qubit in enumerate(meas_layout):
            qubits_to_clbits[qubit] = i

        # Apply the correction
        for data_idx, _ in enumerate(raw_data2):

            if method == "pseudo_inverse":
                for pinv_cal_mat, pos_qubits, indices in zip(
                    pinv_cal_matrices, self._mit_pattern, self._indices_list
                ):
                    inv_mat_dot_x = np.zeros([num_of_states], dtype=float)
                    pos_clbits = [qubits_to_clbits[qubit] for qubit in pos_qubits]
                    for state_idx, state in enumerate(all_states):
                        first_index = self.compute_index_of_cal_mat(state, pos_clbits, indices)
                        for i in range(len(pinv_cal_mat)):  # i is index of pinv_cal_mat
                            source_state = self.flip_state(state, i, pos_clbits)
                            second_index = self.compute_index_of_cal_mat(
                                source_state, pos_clbits, indices
                            )
                            inv_mat_dot_x[state_idx] += (
                                pinv_cal_mat[first_index, second_index]
                                * raw_data2[data_idx][int(source_state, 2)]
                            )
                    raw_data2[data_idx] = inv_mat_dot_x

            elif method == "least_squares":

                def fun(x):
                    mat_dot_x = deepcopy(x)
                    for cal_mat, pos_qubits, indices in zip(
                        self._cal_matrices, self._mit_pattern, self._indices_list
                    ):
                        res_mat_dot_x = np.zeros([num_of_states], dtype=float)
                        pos_clbits = [qubits_to_clbits[qubit] for qubit in pos_qubits]
                        for state_idx, state in enumerate(all_states):
                            second_index = self.compute_index_of_cal_mat(state, pos_clbits, indices)
                            for i in range(len(cal_mat)):
                                target_state = self.flip_state(state, i, pos_clbits)
                                first_index = self.compute_index_of_cal_mat(
                                    target_state, pos_clbits, indices
                                )
                                res_mat_dot_x[int(target_state, 2)] += (
                                    cal_mat[first_index, second_index] * mat_dot_x[state_idx]
                                )
                        mat_dot_x = res_mat_dot_x
                    return sum((raw_data2[data_idx] - mat_dot_x) ** 2)

                x0 = np.random.rand(num_of_states)
                x0 = x0 / sum(x0)
                nshots = sum(raw_data2[data_idx])
                cons = {"type": "eq", "fun": lambda x: nshots - sum(x)}
                bnds = tuple((0, nshots) for x in x0)
                res = minimize(fun, x0, method="SLSQP", constraints=cons, bounds=bnds, tol=1e-6)
                raw_data2[data_idx] = res.x

            else:
                raise QiskitError("Unrecognized method.")

        # convert back into a counts dictionary
        new_count_dict = {}
        for state_idx, state in enumerate(all_states):
            if raw_data2[0][state_idx] != 0:
                new_count_dict[state] = raw_data2[0][state_idx]

        return new_count_dict