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)
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)
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
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, )
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