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: Union[Result, List[Result]], 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`. 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 test_tensored_meas_fitter_with_noise(self): """Test the TensoredFitter with noise.""" # pre-generated results with noise # load from json file with open( os.path.join(os.path.dirname(__file__), 'test_tensored_meas_results.json'), "r") as saved_file: saved_info = json.load(saved_file) 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') output_results_least_square = meas_filter.apply(saved_info['results'], method='least_squares') 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') output_results_least_square = meas_filter.apply(saved_info['results'], method='least_squares') 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'): """ 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. * 'least_squares': constrained to have physical probabilities. * If `None`, 'least_squares' 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 # 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)) 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)) # Apply the correction for data_idx, _ in enumerate(raw_data2): if method == 'pseudo_inverse': inv_mat_dot_raw = np.zeros([num_of_states], dtype=float) for state1_idx, state1 in enumerate(all_states): for state2_idx, state2 in enumerate(all_states): if raw_data2[data_idx][state2_idx] == 0: continue product = 1. end_index = self.nqubits for p_ind, pinv_mat in enumerate(pinv_cal_matrices): start_index = end_index - \ self._qubit_list_sizes[p_ind] state1_as_int = \ self._indices_list[p_ind][ state1[start_index:end_index]] state2_as_int = \ self._indices_list[p_ind][ state2[start_index:end_index]] end_index = start_index product *= \ pinv_mat[state1_as_int][state2_as_int] if product == 0: break inv_mat_dot_raw[state1_idx] += \ (product * raw_data2[data_idx][state2_idx]) raw_data2[data_idx] = inv_mat_dot_raw elif method == 'least_squares': def fun(x): mat_dot_x = np.zeros([num_of_states], dtype=float) for state1_idx, state1 in enumerate(all_states): mat_dot_x[state1_idx] = 0. for state2_idx, state2 in enumerate(all_states): if x[state2_idx] != 0: product = 1. end_index = self.nqubits for c_ind, cal_mat in \ enumerate(self._cal_matrices): start_index = end_index - \ self._qubit_list_sizes[c_ind] state1_as_int = \ self._indices_list[c_ind][ state1[start_index:end_index]] state2_as_int = \ self._indices_list[c_ind][ state2[start_index:end_index]] end_index = start_index product *= \ cal_mat[state1_as_int][state2_as_int] if product == 0: break mat_dot_x[state1_idx] += \ (product * x[state2_idx]) 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
def subset_fitter(self, qubit_sublist=None): """ 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 apply(self, raw_data: Union[qiskit.result.result.Result, dict], method: str = 'least_squares', meas_layout: List[int] = 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
def tensored_meas_cal(mit_pattern: List[List[int]] = None, qr: Union[int, List[QuantumRegister]] = None, cr: Union[int, List[ClassicalRegister]] = None, circlabel: str = '' ) -> Tuple[List[QuantumCircuit], List[List[int]] ]: """ Return a list of calibration circuits Args: mit_pattern: Qubits on which to perform the measurement correction, divided to groups according to tensors. If `None` and `qr` is given then assumed to be performed over the entire `qr` as one group (default `None`). qr: A quantum register (or its size). If `None`, one is created (default `None`). cr: A classical register (or its size). If `None`, one is created (default `None`). circlabel: A string to add to the front of circuit names for unique identification (default ' '). Returns: A list of two QuantumCircuit objects containing the calibration circuits mit_pattern Additional Information: The returned circuits are named circlabel+cal_XXX where XXX is the basis state, e.g., cal_000 and cal_111. Pass the results of these circuits to the TensoredMeasurementFitter constructor. Raises: QiskitError: if both `mit_pattern` and `qr` are None. QiskitError: if a qubit appears more than once in `mit_pattern`. """ if mit_pattern is None and qr is None: raise QiskitError("Must give one of mit_pattern or qr") if isinstance(qr, int): qr = QuantumRegister(qr) qubits_in_pattern = [] if mit_pattern is not None: for qubit_list in mit_pattern: for qubit in qubit_list: if qubit in qubits_in_pattern: raise QiskitError("mit_pattern cannot contain \ multiple instances of the same qubit") qubits_in_pattern.append(qubit) # Create the registers if not already done if qr is None: qr = QuantumRegister(max(qubits_in_pattern)+1) else: qubits_in_pattern = range(len(qr)) mit_pattern = [qubits_in_pattern] nqubits = len(qubits_in_pattern) # create classical bit registers if cr is None: cr = ClassicalRegister(nqubits) if isinstance(cr, int): cr = ClassicalRegister(cr) qubits_list_sizes = [len(qubit_list) for qubit_list in mit_pattern] nqubits = sum(qubits_list_sizes) size_of_largest_group = max(qubits_list_sizes) largest_labels = count_keys(size_of_largest_group) state_labels = [] for largest_state in largest_labels: basis_state = '' for list_size in qubits_list_sizes: basis_state = largest_state[:list_size] + basis_state state_labels.append(basis_state) cal_circuits = [] for basis_state in state_labels: qc_circuit = QuantumCircuit(qr, cr, name='%scal_%s' % (circlabel, basis_state)) end_index = nqubits for qubit_list, list_size in zip(mit_pattern, qubits_list_sizes): start_index = end_index - list_size substate = basis_state[start_index:end_index] for qind in range(list_size): if substate[list_size-qind-1] == '1': qc_circuit.x(qr[qubit_list[qind]]) end_index = start_index qc_circuit.barrier(qr) # add measurements end_index = nqubits for qubit_list, list_size in zip(mit_pattern, qubits_list_sizes): for qind in range(list_size): qc_circuit.measure(qr[qubit_list[qind]], cr[nqubits-(end_index-qind)]) end_index -= list_size cal_circuits.append(qc_circuit) return cal_circuits, mit_pattern
def complete_meas_cal(qubit_list: List[int] = None, qr: Union[int, List[QuantumRegister]] = None, cr: Union[int, List[ClassicalRegister]] = None, circlabel: str = '' ) -> Tuple[List[QuantumCircuit], List[str] ]: """ Return a list of measurement calibration circuits for the full Hilbert space. If the circuit contains :math:`n` qubits, then :math:`2^n` calibration circuits are created, each of which creates a basis state. Args: qubit_list: A list of qubits to perform the measurement correction on. If `None`, and qr is given then assumed to be performed over the entire qr. The calibration states will be labelled according to this ordering (default `None`). qr: Quantum registers (or their size). If `None`, one is created (default `None`). cr: Classical registers (or their size). If `None`, one is created(default `None`). circlabel: A string to add to the front of circuit names for unique identification(default ' '). Returns: A list of QuantumCircuit objects containing the calibration circuits. A list of calibration state labels. Additional Information: The returned circuits are named circlabel+cal_XXX where XXX is the basis state, e.g., cal_1001. Pass the results of these circuits to the CompleteMeasurementFitter constructor. Raises: QiskitError: if both `qubit_list` and `qr` are `None`. """ if qubit_list is None and qr is None: raise QiskitError("Must give one of a qubit_list or a qr") # Create the registers if not already done if qr is None: qr = QuantumRegister(max(qubit_list)+1) if isinstance(qr, int): qr = QuantumRegister(qr) if qubit_list is None: qubit_list = range(len(qr)) if isinstance(cr, int): cr = ClassicalRegister(cr) nqubits = len(qubit_list) # labels for 2**n qubit states state_labels = count_keys(nqubits) cal_circuits, _ = tensored_meas_cal([qubit_list], qr, cr, circlabel) return cal_circuits, state_labels