def test_amplitude_damping_representation_with_choi( gate: Gate, noise: float, circuit_type: str, ): """Tests the representation by comparing exact Choi matrices.""" q = LineQubit(0) ideal_circuit = convert_from_mitiq(Circuit(gate.on(q)), circuit_type) ideal_choi = _circuit_to_choi(Circuit(gate.on(q))) op_rep = _represent_operation_with_amplitude_damping_noise( ideal_circuit, noise, ) choi_components = [] for noisy_op, coeff in op_rep.basis_expansion.items(): implementable_circ = noisy_op.circuit() depolarizing_op = AmplitudeDampingChannel(noise).on(q) # Apply noise after each sequence. # NOTE: noise is not applied after each operation. implementable_circ.append(depolarizing_op) sequence_choi = _operation_to_choi(implementable_circ) choi_components.append(coeff * sequence_choi) combination_choi = np.sum(choi_components, axis=0) assert np.allclose(ideal_choi, combination_choi, atol=10**-8)
def test_represent_operations_in_circuit_with_measurements( circuit_type: str, rep_function, ): """Tests measurements in circuit are ignored (not represented).""" q0, q1 = LineQubit.range(2) circ_mitiq = Circuit( X(q1), MeasurementGate(num_qubits=1)(q0), X(q1), MeasurementGate(num_qubits=1)(q0), ) circ = convert_from_mitiq(circ_mitiq, circuit_type) reps = rep_function(ideal_circuit=circ, noise_level=0.1) for op in convert_to_mitiq(circ)[0].all_operations(): found = False for rep in reps: if _equal(rep.ideal, Circuit(op), require_qubit_equality=True): found = True if isinstance(op.gate, MeasurementGate): assert not found else: assert found # Number of unique gates excluding measurement gates assert len(reps) == 1
def _run( self, circuits: Sequence[QPROGRAM], force_run_all: bool = False, **kwargs: Any, ) -> Sequence[QuantumResult]: """Runs all input circuits using the least number of possible calls to the executor. Args: circuits: Sequence of circuits to execute using the executor. force_run_all: If True, force every circuit in the input sequence to be executed (if some are identical). Else, detects identical circuits and runs a minimal set. """ start_result_index = len(self._quantum_results) if force_run_all: to_run = circuits else: # Make circuits hashable. # Note: Assumes all circuits are the same type. # TODO: Bug! These conversions to/from Mitiq are not safe in that, # e.g., they do not preserve classical register structure in # Qiskit circuits, potentially causing executed results to be # incorrect. Safe conversions should follow the logic in # mitiq.interface.noise_scaling_converter. _, conversion_type = convert_to_mitiq(circuits[0]) hashable_circuits = [ convert_to_mitiq(circ)[0].freeze() for circ in circuits ] # Get the unique circuits and counts collection = Counter(hashable_circuits) to_run = [ convert_from_mitiq(circ.unfreeze(), conversion_type) for circ in collection.keys() ] if not self.can_batch: for circuit in to_run: self._call_executor(circuit, **kwargs) else: stop = len(to_run) step = self._max_batch_size for i in range(int(np.ceil(stop / step))): batch = to_run[i * step : (i + 1) * step] self._call_executor(batch, **kwargs) these_results = self._quantum_results[start_result_index:] if force_run_all: return these_results # Expand computed results to all results using counts. results_dict = dict(zip(collection.keys(), these_results)) results = [results_dict[key] for key in hashable_circuits] return results
def test_execute_with_pec_mitigates_noise(circuit, executor, circuit_type): """Tests that execute_with_pec mitigates the error of a noisy expectation value. """ circuit = convert_from_mitiq(circuit, circuit_type) true_noiseless_value = 1.0 unmitigated = serial_executor(circuit) if circuit_type == "qiskit": # Note this is an important subtlety necessary because of conversions. reps = get_pauli_and_cnot_representations( base_noise=BASE_NOISE, qubits=[cirq.NamedQubit(name) for name in ("q_0", "q_1")], ) # TODO: PEC with Qiskit is slow. # See https://github.com/unitaryfund/mitiq/issues/507. circuit, _ = convert_to_mitiq(circuit) else: reps = pauli_representations mitigated = execute_with_pec( circuit, executor, representations=reps, num_samples=100, force_run_all=False, random_state=101, ) error_unmitigated = abs(unmitigated - true_noiseless_value) error_mitigated = abs(mitigated - true_noiseless_value) assert error_mitigated < error_unmitigated assert np.isclose(mitigated, true_noiseless_value, atol=0.1)
def generate_ghz_circuit( n_qubits: int, return_type: Optional[str] = None, ) -> QPROGRAM: """Returns a GHZ circuit ie a circuit that prepares an ``n_qubits`` GHZ state. Args: n_qubits: The number of qubits in the circuit. return_type: String which specifies the type of the returned circuits. See the keys of ``mitiq.SUPPORTED_PROGRAM_TYPES`` for options. If ``None``, the returned circuits have type ``cirq.Circuit``. Returns: A GHZ circuit acting on ``n_qubits`` qubits. """ if n_qubits <= 0: raise ValueError("Cannot prepare a GHZ circuit with {} qubits", n_qubits) qubits = cirq.LineQubit.range(n_qubits) circuit = cirq.Circuit() circuit.append(cirq.H(qubits[0])) for i in range(0, n_qubits - 1): circuit.append(cirq.CNOT(qubits[i], qubits[i + 1])) return_type = "cirq" if not return_type else return_type return convert_from_mitiq(circuit, return_type)
def test_mitigated_execute_with_cdr(circuit_type, fit_function, kwargs, random_state): circuit = random_x_z_cnot_circuit( LineQubit.range(2), n_moments=5, random_state=random_state, ) circuit = convert_from_mitiq(circuit, circuit_type) obs = Observable(PauliString("XZ"), PauliString("YY")) true_value = obs.expectation(circuit, simulate) noisy_value = obs.expectation(circuit, execute) cdr_executor = mitigate_executor( executor=execute, observable=obs, simulator=simulate, num_training_circuits=20, fraction_non_clifford=0.5, fit_function=fit_function, random_state=random_state, **kwargs, ) cdr_mitigated = cdr_executor(circuit) assert abs(cdr_mitigated - true_value) <= abs(noisy_value - true_value)
def test_find_optimal_representation_depolarizing_two_qubit_gates(circ_type): """Test optimal representation agrees with a known analytic result.""" for ideal_gate, noise_level in product([CNOT, CZ], [0.1, 0.5]): q = LineQubit.range(2) ideal_op = Circuit(ideal_gate(*q)) implementable_circuits = [Circuit(ideal_op)] # Append two-qubit-gate with Paulis on one qubit for gate in [X, Y, Z]: implementable_circuits.append(Circuit([ideal_op, gate(q[0])])) implementable_circuits.append(Circuit([ideal_op, gate(q[1])])) # Append two-qubit gate with Paulis on both qubits for gate_a, gate_b in product([X, Y, Z], repeat=2): implementable_circuits.append( Circuit([ideal_op, gate_a(q[0]), gate_b(q[1])]) ) noisy_circuits = [ circ + Circuit(DepolarizingChannel(noise_level).on_each(*q)) for circ in implementable_circuits ] super_operators = [ choi_to_super(_circuit_to_choi(circ)) for circ in noisy_circuits ] # Define circuits with native types implementable_native = [ convert_from_mitiq(c, circ_type) for c in implementable_circuits ] ideal_op_native = convert_from_mitiq(ideal_op, circ_type) noisy_operations = [ NoisyOperation(ideal, real) for ideal, real in zip(implementable_native, super_operators) ] # Find optimal representation noisy_basis = NoisyBasis(*noisy_operations) rep = find_optimal_representation( ideal_op_native, noisy_basis, tol=1.0e-8 ) # Expected analytical result expected_rep = represent_operation_with_local_depolarizing_noise( ideal_op_native, noise_level, ) assert np.allclose(np.sort(rep.coeffs), np.sort(expected_rep.coeffs)) assert rep == expected_rep
def generate_rb_circuits( n_qubits: int, num_cliffords: int, trials: int = 1, return_type: Optional[str] = None, ) -> List[QPROGRAM]: """Returns a list of randomized benchmarking circuits, i.e. circuits that are equivalent to the identity. Args: n_qubits: The number of qubits. Can be either 1 or 2. num_cliffords: The number of Clifford group elements in the random circuits. This is proportional to the depth per circuit. trials: The number of random circuits at each num_cfd. return_type: String which specifies the type of the returned circuits. See the keys of ``mitiq.SUPPORTED_PROGRAM_TYPES`` for options. If ``None``, the returned circuits have type ``cirq.Circuit``. Returns: A list of randomized benchmarking circuits. """ if n_qubits not in (1, 2): raise ValueError( "Only generates RB circuits on one or two " f"qubits not {n_qubits}." ) qubits = LineQubit.range(n_qubits) cliffords = _single_qubit_cliffords() if n_qubits == 1: c1 = cliffords.c1_in_xy cfd_mat_1q = cast( np.ndarray, [_gate_seq_to_mats(gates) for gates in c1] ) circuits = [ _random_single_q_clifford(qubits[0], num_cliffords, c1, cfd_mat_1q) for _ in range(trials) ] else: cfd_matrices = _two_qubit_clifford_matrices( qubits[0], qubits[1], cliffords, ) circuits = [ _random_two_q_clifford( qubits[0], qubits[1], num_cliffords, cfd_matrices, cliffords, ) for _ in range(trials) ] return_type = "cirq" if not return_type else return_type return [convert_from_mitiq(circuit, return_type) for circuit in circuits]
def test_atomic_one_to_many_converter(to_type): circuit = convert_from_mitiq(cirq_circuit, to_type) circuits = returns_several_circuits(circuit) for circuit in circuits: assert isinstance(circuit, circuit_types[to_type]) circuits = returns_several_circuits(circuit, return_mitiq=True) for circuit in circuits: assert isinstance(circuit, cirq.Circuit)
def test_generate_training_circuits_any_qprogram(circuit_type): circuit = random_x_z_cnot_circuit(cirq.LineQubit.range(3), n_moments=5, random_state=1) circuit = convert_from_mitiq(circuit, circuit_type) (clifford_circuit, ) = generate_training_circuits( circuit, num_training_circuits=1, fraction_non_clifford=0.0) assert is_clifford(clifford_circuit)
def sample_circuit( ideal_circuit: QPROGRAM, representations: List[OperationRepresentation], random_state: Optional[Union[int, np.random.RandomState]] = None, ) -> Tuple[QPROGRAM, int, float]: """Samples an implementable circuit from the PEC representation of the input ideal circuit & returns this circuit as well as its sign and norm. This function iterates through each operation in the circuit and samples an implementable sequence. The returned sign (norm) is the product of signs (norms) sampled for each operation. Args: ideal_circuit: The ideal circuit from which an implementable circuit is sampled. representations: List of representations of every operation in the input circuit. If a representation cannot be found for an operation in the circuit, a ValueError is raised. random_state: Seed for sampling. Returns: imp_circuit: The sampled implementable circuit. sign: The sign associated to sampled_circuit. norm: The one norm of the PEC coefficients of the circuit. Raises: ValueError: If a representation is not found for an operation in the circuit. """ if isinstance(random_state, int): random_state = np.random.RandomState(random_state) # TODO: Likely to cause issues - conversions may introduce gates which are # not included in `decompositions`. ideal, rtype = convert_to_mitiq(ideal_circuit) # copy and remove all moments sampled_circuit = deepcopy(ideal)[0:0] # Iterate over all operations sign = 1 norm = 1.0 for op in ideal.all_operations(): # Ignore all measurements. if cirq.is_measurement(op): continue imp_seq, loc_sign, loc_norm = sample_sequence(cirq.Circuit(op), representations, random_state) cirq_seq, _ = convert_to_mitiq(imp_seq) sign *= loc_sign norm *= loc_norm sampled_circuit.append(cirq_seq.all_operations()) return convert_from_mitiq(sampled_circuit, rtype), sign, norm
def test_execute_with_cdr(circuit_type): circuit = random_x_z_circuit(LineQubit.range(2), n_moments=2, random_state=1) circuit = convert_from_mitiq(circuit, circuit_type) # Define observables for testing. sigma_z = np.diag([1, -1]) obs = np.kron(np.identity(2), sigma_z) obs2 = np.kron(sigma_z, sigma_z) obs_list = [np.diag(obs), np.diag(obs2)] exact_solution = [ calculate_observable(simulator_statevector(circuit), observable=obs) for obs in obs_list ] kwargs = { "method_select": "gaussian", "method_replace": "gaussian", "sigma_select": 0.5, "sigma_replace": 0.5, "random_state": 1, } num_circuits = 4 frac_non_cliff = 0.5 noisy_executor = partial(executor, noise_level=0.5) results0 = execute_with_cdr( circuit, noisy_executor, simulator_statevector, obs_list, num_circuits, frac_non_cliff, full_output=True, ) results1 = execute_with_cdr( circuit, noisy_executor, simulator_statevector, obs_list, num_circuits, frac_non_cliff, ansatz=linear_fit_function_no_intercept, num_fit_parameters=1, scale_noise=fold_gates_from_left, scale_factors=[3], full_output=True, **kwargs, ) for results in [results0, results1]: for i in range(len(results[1])): assert abs(results[1][i][0] - exact_solution[i]) >= abs(results[0][i] - exact_solution[i])
def test_find_optimal_representation_single_qubit_amp_damping(circ_type): """Test optimal representation of agrees with a known analytic result.""" for ideal_gate, noise_level in product([X, Y, H], [0.1, 0.3]): q = LineQubit(0) ideal_op = Circuit(ideal_gate(q)) implementable_circuits = [Circuit(ideal_op)] # Add ideal gate followed by Paulis and reset for gate in [Z, reset]: implementable_circuits.append(Circuit([ideal_op, gate(q)])) noisy_circuits = [ circ + Circuit(AmplitudeDampingChannel(noise_level).on_each(q)) for circ in implementable_circuits ] super_operators = [ choi_to_super(_circuit_to_choi(circ)) for circ in noisy_circuits ] # Define circuits with native types implementable_native = [ convert_from_mitiq(c, circ_type) for c in implementable_circuits ] ideal_op_native = convert_from_mitiq(ideal_op, circ_type) noisy_operations = [ NoisyOperation(ideal, real) for ideal, real in zip(implementable_native, super_operators) ] # Find optimal representation noisy_basis = NoisyBasis(*noisy_operations) rep = find_optimal_representation( ideal_op_native, noisy_basis, tol=1.0e-7, initial_guess=[0, 0, 0] ) # Expected analytical result expected_rep = _represent_operation_with_amplitude_damping_noise( ideal_op_native, noise_level, ) assert np.allclose(np.sort(rep.coeffs), np.sort(expected_rep.coeffs)) assert rep == expected_rep
def ideal_circuit(self, return_type: Optional[str] = None) -> cirq.Circuit: """Returns the ideal circuit of the NoisyOperation. Args: return_type: Type of the circuit to return. If not specified, the returned type is the same type as the circuit used to initialize the NoisyOperation. """ if not return_type: return self._native_ideal return convert_from_mitiq(self._ideal, return_type)
def test_count_non_cliffords(circuit_type): a, b = cirq.LineQubit.range(2) circuit = Circuit( cirq.rz(0.0).on(a), # Clifford. cirq.rx(0.1 * np.pi).on(b), # Non-Clifford. cirq.rx(0.5 * np.pi).on(b), # Clifford cirq.rz(0.4 * np.pi).on(b), # Non-Clifford. cirq.rz(0.5 * np.pi).on(b), # Clifford. cirq.CNOT.on(a, b), # Clifford. ) circuit = convert_from_mitiq(circuit, circuit_type) assert count_non_cliffords(circuit) == 2
def decorated_execute_with_cdr(circuit_type): circuit = random_x_z_cnot_circuit( LineQubit.range(2), n_moments=5, random_state=random_state, ) circuit = convert_from_mitiq(circuit, circuit_type) true_value = obs.expectation(circuit, simulate) noisy_value = obs.expectation(circuit, execute) cdr_value = decorated_execute(circuit, ) assert abs(cdr_value - true_value) <= abs(noisy_value - true_value)
def run( self, circuits: Sequence[QPROGRAM], force_run_all: bool = False, **kwargs, ) -> List[float]: """Runs all input circuits using the least number of possible calls to the executor. Args: circuits: Sequence of circuits to execute using the executor. force_run_all: If True, force every circuit in the input sequence to be executed (if some are identical). Else, detects identical circuits and runs a minimal set. """ if force_run_all: to_run = circuits else: # Make circuits hashable. # Note: Assumes all circuits are the same type. _, conversion_type = convert_to_mitiq(circuits[0]) hashable_circuits = [ convert_to_mitiq(circ)[0].freeze() for circ in circuits ] # Get the unique circuits and counts collection = Counter(hashable_circuits) to_run = [ convert_from_mitiq(circ.unfreeze(), conversion_type) for circ in collection.keys() ] if not self._can_batch: for circuit in to_run: self._call_executor(circuit, **kwargs) else: stop = len(to_run) step = self._max_batch_size for i in range(int(np.ceil(stop / step))): batch = to_run[i * step:(i + 1) * step] self._call_executor(batch, **kwargs) # Expand computed results to all results using counts. if force_run_all: return self._computed_results expval_dict = dict(zip(collection.keys(), self._computed_results)) results = [expval_dict[key] for key in hashable_circuits] return results
def test_execute_with_ddd_without_noise(circuit_type, circuit, rule): """Tests that execute_with_ddd preserves expected results in the absence of noise. """ circuit = convert_from_mitiq(circuit, circuit_type) true_noiseless_value = 1.0 unmitigated = noiseless_serial_executor(circuit) mitigated = execute_with_ddd( circuit, executor=noiseless_serial_executor, rule=rule, ) error_unmitigated = abs(unmitigated - true_noiseless_value) error_mitigated = abs(mitigated - true_noiseless_value) assert np.isclose(error_unmitigated, error_mitigated)
def test_execute_with_ddd_and_damping_noise(circuit_type, rule): """Tests that with execute_with_ddd the error of a noisy expectation value is unchanged with depolarizing noise. """ circuit = convert_from_mitiq(circuit_cirq_a, circuit_type) true_noiseless_value = 1.0 unmitigated = amp_damp_executor(circuit) mitigated = execute_with_ddd( circuit, amp_damp_executor, rule=rule, ) error_unmitigated = abs(unmitigated - true_noiseless_value) error_mitigated = abs(mitigated - true_noiseless_value) assert error_mitigated < error_unmitigated
def test_executed_circuits_have_the_expected_type(circuit_type): circuit = convert_from_mitiq(oneq_circ, circuit_type) circuit_type = type(circuit) # Fake executor just for testing types def type_detecting_executor(circuit: QPROGRAM): assert type(circuit) is circuit_type return 0.0 mitigated = execute_with_pec( circuit, executor=type_detecting_executor, representations=pauli_representations, num_samples=1, ) assert np.isclose(mitigated, 0.0)
def test_execute_with_zne_with_supported_circuits(circuit_type): # Define a circuit equivalent to the identity qreg = cirq.LineQubit.range(2) cirq_circuit = cirq.Circuit( cirq.H.on_each(qreg), cirq.CNOT(*qreg), cirq.CNOT(*qreg), cirq.H.on_each(qreg), ) # Convert to one of the supported program types circuit = convert_from_mitiq(cirq_circuit, circuit_type) expected = generic_executor(circuit, noise_level=0.0) unmitigated = generic_executor(circuit) # Use odd scale factors for deterministic results fac = RichardsonFactory([1.0, 3.0, 5.0]) zne_value = execute_with_zne(circuit, generic_executor, factory=fac) # Test zero noise limit is better than unmitigated expectation value assert abs(unmitigated - expected) > abs(zne_value - expected)
def test_represent_operations_in_circuit_local(circuit_type: str): """Tests all operation representations are created.""" qreg = LineQubit.range(2) circ_mitiq = Circuit([CNOT(*qreg), H(qreg[0]), Y(qreg[1]), CNOT(*qreg)]) circ = convert_from_mitiq(circ_mitiq, circuit_type) reps = represent_operations_in_circuit_with_local_depolarizing_noise( ideal_circuit=circ, noise_level=0.1, ) for op in convert_to_mitiq(circ)[0].all_operations(): found = False for rep in reps: if _equal(rep.ideal, Circuit(op), require_qubit_equality=True): found = True assert found # The number of reps. should match the number of unique operations assert len(reps) == 3
def test_execute_with_ddd_and_depolarizing_noise(circuit_type, circuit, executor, rule): """Tests that with execute_with_ddd the error of a noisy expectation value is unchanged with depolarizing noise. """ circuit = convert_from_mitiq(circuit, circuit_type) true_noiseless_value = 1.0 unmitigated = serial_executor(circuit) mitigated = execute_with_ddd( circuit, executor, rule=rule, ) error_unmitigated = abs(unmitigated - true_noiseless_value) error_mitigated = abs(mitigated - true_noiseless_value) # For moment-based depolarizing noise DDD should # have no effect (since noise commutes with DDD gates). assert np.isclose(error_mitigated, error_unmitigated)
def test_execute_with_variable_noise_cdr(circuit_type): circuit = random_x_z_cnot_circuit(LineQubit.range(2), n_moments=5, random_state=1) circuit = convert_from_mitiq(circuit, circuit_type) obs = Observable(PauliString("IZ"), PauliString("ZZ")) true_value = obs.expectation(circuit, simulate) noisy_value = obs.expectation(circuit, execute) vncdr_value = execute_with_cdr( circuit, execute, obs, simulator=simulate, num_training_circuits=10, fraction_non_clifford=0.5, scale_factors=[1, 3], random_state=1, ) assert abs(vncdr_value - true_value) <= abs(noisy_value - true_value)
def test_accept_any_qprogram_as_input(circuit_and_expected, to_type): circuit, expected = circuit_and_expected wavefunction = get_wavefunction(convert_from_mitiq(circuit, to_type)) assert np.allclose(wavefunction, expected)
def execute_with_pec( circuit: QPROGRAM, executor: Union[Executor, Callable[[QPROGRAM], QuantumResult]], observable: Optional[Observable] = None, *, representations: Sequence[OperationRepresentation], precision: float = 0.03, num_samples: Optional[int] = None, force_run_all: bool = True, random_state: Optional[Union[int, np.random.RandomState]] = None, full_output: bool = False, ) -> Union[float, Tuple[float, Dict[str, Any]]]: r"""Estimates the error-mitigated expectation value associated to the input circuit, via the application of probabilistic error cancellation (PEC). [Temme2017]_ [Endo2018]_. This function implements PEC by: 1. Sampling different implementable circuits from the quasi-probability representation of the input circuit; 2. Evaluating the noisy expectation values associated to the sampled circuits (through the "executor" function provided by the user); 3. Estimating the ideal expectation value from a suitable linear combination of the noisy ones. Args: circuit: The input circuit to execute with error-mitigation. executor: A Mitiq executor that executes a circuit and returns the unmitigated ``QuantumResult`` (e.g. an expectation value). observable: Observable to compute the expectation value of. If None, the `executor` must return an expectation value. Otherwise, the `QuantumResult` returned by `executor` is used to compute the expectation of the observable. representations: Representations (basis expansions) of each operation in the input circuit. precision: The desired estimation precision (assuming the observable is bounded by 1). The number of samples is deduced according to the formula (one_norm / precision) ** 2, where 'one_norm' is related to the negativity of the quasi-probability representation [Temme2017]_. If 'num_samples' is explicitly set by the user, 'precision' is ignored and has no effect. num_samples: The number of noisy circuits to be sampled for PEC. If not given, this is deduced from the argument 'precision'. force_run_all: If True, all sampled circuits are executed regardless of uniqueness, else a minimal unique set is executed. random_state: Seed for sampling circuits. full_output: If False only the average PEC value is returned. If True a dictionary containing all PEC data is returned too. Returns: The tuple ``(pec_value, pec_data)`` where ``pec_value`` is the expectation value estimated with PEC and ``pec_data`` is a dictionary which contains all the raw data involved in the PEC process (including the PEC estimation error). The error is estimated as ``pec_std / sqrt(num_samples)``, where ``pec_std`` is the standard deviation of the PEC samples, i.e., the square root of the mean squared deviation of the sampled values from ``pec_value``. If ``full_output`` is ``True``, only ``pec_value`` is returned. .. [Endo2018] : Suguru Endo, Simon C. Benjamin, Ying Li, "Practical Quantum Error Mitigation for Near-Future Applications" *Phys. Rev. **X 8**, 031027 (2018), (https://arxiv.org/abs/1712.09271). .. [Takagi2020] : Ryuji Takagi, "Optimal resource cost for error mitigation," (https://arxiv.org/abs/2006.12509). """ if isinstance(random_state, int): random_state = np.random.RandomState(random_state) if not (0 < precision <= 1): raise ValueError( "The value of 'precision' should be within the interval (0, 1]," f" but precision is {precision}.") converted_circuit, input_type = convert_to_mitiq(circuit) # Get the 1-norm of the circuit quasi-probability representation _, _, norm = sample_circuit( converted_circuit, representations, num_samples=1, ) # Deduce the number of samples (if not given by the user) if not isinstance(num_samples, int): num_samples = int((norm / precision)**2) # Issue warning for very large sample size if num_samples > 10**5: warnings.warn(_LARGE_SAMPLE_WARN, LargeSampleWarning) # Sample all the circuits sampled_circuits, signs, _ = sample_circuit( converted_circuit, representations, random_state=random_state, num_samples=num_samples, ) # Convert back to the original type sampled_circuits = [ convert_from_mitiq(cast(CirqCircuit, c), input_type) for c in sampled_circuits ] # Execute all sampled circuits if not isinstance(executor, Executor): executor = Executor(executor) results = executor.evaluate(sampled_circuits, observable, force_run_all) # Evaluate unbiased estimators [Temme2017] [Endo2018] [Takagi2020] unbiased_estimators = [ norm * s * val # type: ignore[operator] for s, val in zip(signs, results) ] pec_value = np.average(unbiased_estimators) if not full_output: return pec_value # Build dictionary with additional results and data pec_data: Dict[str, Any] = { "num_samples": num_samples, "precision": precision, "pec_value": pec_value, "pec_error": np.std(unbiased_estimators) / np.sqrt(num_samples), "unbiased_estimators": unbiased_estimators, "measured_expectation_values": results, "sampled_circuits": sampled_circuits, } return pec_value, pec_data
def represent_operation_with_global_depolarizing_noise( ideal_operation: QPROGRAM, noise_level: float) -> OperationRepresentation: r"""As described in [Temme2017]_, this function maps an ``ideal_operation`` :math:`\mathcal{U}` into its quasi-probability representation, which is a linear combination of noisy implementable operations :math:`\sum_\alpha \eta_{\alpha} \mathcal{O}_{\alpha}`. This function assumes a depolarizing noise model and, more precicely, that the following noisy operations are implementable :math:`\mathcal{O}_{\alpha} = \mathcal{D} \circ \mathcal P_\alpha \circ \mathcal{U}`, where :math:`\mathcal{U}` is the unitary associated to the input ``ideal_operation`` acting on :math:`k` qubits, :math:`\mathcal{P}_\alpha` is a Pauli operation and :math:`\mathcal{D}(\rho) = (1 - \epsilon) \rho + \epsilon I/2^k` is a depolarizing channel (:math:`\epsilon` is a simple function of ``noise_level``). For a single-qubit ``ideal_operation``, the representation is as follows: .. math:: \mathcal{U}_{\beta} = \eta_1 \mathcal{O}_1 + \eta_2 \mathcal{O}_2 + \eta_3 \mathcal{O}_3 + \eta_4 \mathcal{O}_4 .. math:: \eta_1 =1 + \frac{3}{4} \frac{\epsilon}{1- \epsilon}, \qquad \mathcal{O}_1 = \mathcal{D} \circ \mathcal{I} \circ \mathcal{U} \eta_2 =- \frac{1}{4}\frac{\epsilon}{1- \epsilon} , \qquad \mathcal{O}_2 = \mathcal{D} \circ \mathcal{X} \circ \mathcal{U} \eta_3 =- \frac{1}{4}\frac{\epsilon}{1- \epsilon} , \qquad \mathcal{O}_3 = \mathcal{D} \circ \mathcal{Y} \circ \mathcal{U} \eta_4 =- \frac{1}{4}\frac{\epsilon}{1- \epsilon} , \qquad \mathcal{O}_4 = \mathcal{D} \circ \mathcal{Z} \circ \mathcal{U} It was proven in [Takagi2020]_ that, under suitable assumptions, this representation is optimal (minimum 1-norm). Args: ideal_operation: The ideal operation (as a QPROGRAM) to represent. noise_level: The noise level (as a float) of the depolarizing channel. Returns: The quasi-probability representation of the ``ideal_operation``. .. note:: This representation is based on the ideal assumption that one can append Pauli gates to a noisy operation without introducing additional noise. For a beckend which violates this assumption, it remains a good approximation for small values of ``noise_level``. .. note:: The input ``ideal_operation`` is typically a QPROGRAM with a single gate but could also correspond to a sequence of more gates. This is possible as long as the unitary associated to the input QPROGRAM, followed by a single final depolarizing channel, is physically implementable. """ circ, in_type = convert_to_mitiq(ideal_operation) post_ops: List[List[Operation]] qubits = circ.all_qubits() # The single-qubit case: linear combination of 1Q Paulis if len(qubits) == 1: q = tuple(qubits)[0] epsilon = 4 / 3 * noise_level alpha_pos = 1 + ((3 / 4) * epsilon / (1 - epsilon)) alpha_neg = -(1 / 4) * epsilon / (1 - epsilon) alphas = [alpha_pos] + 3 * [alpha_neg] post_ops = [[]] # for alpha_pos, we do nothing, rather than I post_ops += [[P(q)] for P in [X, Y, Z]] # 1Q Paulis # The two-qubit case: linear combination of 2Q Paulis elif len(qubits) == 2: q0, q1 = qubits epsilon = 16 / 15 * noise_level alpha_pos = 1 + ((15 / 16) * epsilon / (1 - epsilon)) alpha_neg = -(1 / 16) * epsilon / (1 - epsilon) alphas = [alpha_pos] + 15 * [alpha_neg] post_ops = [[]] # for alpha_pos, we do nothing, rather than I x I post_ops += [[P(q0)] for P in [X, Y, Z]] # 1Q Paulis for q0 post_ops += [[P(q1)] for P in [X, Y, Z]] # 1Q Paulis for q1 post_ops += [[Pi(q0), Pj(q1)] for Pi in [X, Y, Z] for Pj in [X, Y, Z]] # 2Q Paulis else: raise ValueError("Can only represent single- and two-qubit gates." "Consider pre-compiling your circuit.") # Basis of implementable operations as circuits imp_op_circuits = [circ + Circuit(op) for op in post_ops] # Convert back to input type imp_op_circuits = [convert_from_mitiq(c, in_type) for c in imp_op_circuits] # Build basis_expantion expansion = {NoisyOperation(c): a for c, a in zip(imp_op_circuits, alphas)} return OperationRepresentation(ideal_operation, expansion)
def represent_operation_with_local_depolarizing_noise( ideal_operation: QPROGRAM, noise_level: float) -> OperationRepresentation: r"""As described in [Temme2017]_, this function maps an ``ideal_operation`` :math:`\mathcal{U}` into its quasi-probability representation, which is a linear combination of noisy implementable operations :math:`\sum_\alpha \eta_{\alpha} \mathcal{O}_{\alpha}`. This function assumes a (local) single-qubit depolarizing noise model even for multi-qubit operations. More precicely, it assumes that the following noisy operations are implementable :math:`\mathcal{O}_{\alpha} = \mathcal{D}^{\otimes k} \circ \mathcal P_\alpha \circ \mathcal{U}`, where :math:`\mathcal{U}` is the unitary associated to the input ``ideal_operation`` acting on :math:`k` qubits, :math:`\mathcal{P}_\alpha` is a Pauli operation and :math:`\mathcal{D}(\rho) = (1 - \epsilon) \rho + \epsilon I/2` is a single-qubit depolarizing channel (:math:`\epsilon` is a simple function of ``noise_level``). More information about the quasi-probability representation for a depolarizing noise channel can be found in: :func:`represent_operation_with_global_depolarizing_noise`. Args: ideal_operation: The ideal operation (as a QPROGRAM) to represent. noise_level: The noise level of each depolarizing channel. Returns: The quasi-probability representation of the ``ideal_operation``. .. note:: The input ``ideal_operation`` is typically a QPROGRAM with a single gate but could also correspond to a sequence of more gates. This is possible as long as the unitary associated to the input QPROGRAM, followed by a single final depolarizing channel, is physically implementable. .. [Temme2017] : Kristan Temme, Sergey Bravyi, Jay M. Gambetta, "Error mitigation for short-depth quantum circuits," *Phys. Rev. Lett.* **119**, 180509 (2017), (https://arxiv.org/abs/1612.02058). """ circ, in_type = convert_to_mitiq(ideal_operation) qubits = circ.all_qubits() if len(qubits) == 1: return represent_operation_with_global_depolarizing_noise( ideal_operation, noise_level, ) # The two-qubit case: tensor product of two depolarizing channels. elif len(qubits) == 2: q0, q1 = qubits # Single-qubit representation coefficients. epsilon = noise_level * 4 / 3 c_neg = -(1 / 4) * epsilon / (1 - epsilon) c_pos = 1 - 3 * c_neg imp_op_circuits = [] alphas = [] # The zero-pauli term in the linear combination imp_op_circuits.append(circ) alphas.append(c_pos * c_pos) # The single-pauli terms in the linear combination for qubit in qubits: for pauli in [X, Y, Z]: imp_op_circuits.append(circ + Circuit(pauli(qubit))) alphas.append(c_neg * c_pos) # The two-pauli terms in the linear combination for pauli_0, pauli_1 in product([X, Y, Z], repeat=2): imp_op_circuits.append(circ + Circuit(pauli_0(q0), pauli_1(q1))) alphas.append(c_neg * c_neg) else: raise ValueError("Can only represent single- and two-qubit gates." "Consider pre-compiling your circuit.") # Convert back to input type imp_op_circuits = [convert_from_mitiq(c, in_type) for c in imp_op_circuits] # Build basis_expantion expansion = {NoisyOperation(c): a for c, a in zip(imp_op_circuits, alphas)} return OperationRepresentation(ideal_operation, expansion)
def test_is_clifford_with_nonclifford(circuit_type): circuit = convert_from_mitiq(cirq.Circuit(cirq.T.on(cirq.LineQubit(0))), circuit_type) assert not is_clifford(circuit)
def test_from_mitiq(to_type): converted_circuit = convert_from_mitiq(cirq_circuit, to_type) circuit, input_type = convert_to_mitiq(converted_circuit) assert _equal(circuit, cirq_circuit) assert input_type == to_type