def test_sample_circuit_with_seed(): decomp = _simple_pauli_deco_dict(0.7, simplify_paulis=True) circ = Circuit([X.on(LineQubit(0)) for _ in range(10)]) expected = sample_circuit(circ, decomp, random_state=4)[0] # Check we're not sampling the same operation every call to sample_sequence assert len(set(expected.all_operations())) > 1 for _ in range(10): sampled = sample_circuit(circ, decomp, random_state=4)[0] assert _equal(sampled, expected)
def test_sample_circuit_random_state(seed, seed_type): decomposition = _simple_pauli_deco_dict(0.5) if isinstance(seed_type, np.random.RandomState): seed = np.random.RandomState(seed) circuit, sign, norm = sample_circuit(twoq_circ, decomposition, random_state=seed) for _ in range(20): if isinstance(seed_type, np.random.RandomState): seed = np.random.RandomState(seed) new_circuit, new_sign, new_norm = sample_circuit(twoq_circ, decomposition, random_state=seed) assert _equal(new_circuit, circuit) assert new_sign == sign assert np.isclose(new_norm, norm)
def test_sample_circuit_choi(decomposition_dict: DecompositionDict): """Tests the sample_circuit by comparing the exact Choi matrices.""" ideal_choi = _circuit_to_choi(twoq_circ) noisy_circuit = twoq_circ.with_noise(depolarize(BASE_NOISE)) noisy_choi = _circuit_to_choi(noisy_circuit) choi_unbiased_estimates = [] for _ in range(500): imp_circuit, sign, norm = sample_circuit(twoq_circ, decomposition_dict) noisy_imp_circuit = imp_circuit.with_noise(depolarize(BASE_NOISE)) imp_circuit_choi = _circuit_to_choi(noisy_imp_circuit) choi_unbiased_estimates.append(norm * sign * imp_circuit_choi) choi_pec_estimate = np.average(choi_unbiased_estimates, axis=0) noise_error = np.linalg.norm(ideal_choi - noisy_choi) pec_error = np.linalg.norm(ideal_choi - choi_pec_estimate) assert pec_error < noise_error assert np.allclose(ideal_choi, choi_pec_estimate, atol=0.05)
def test_sample_circuit_types(): imp_circuit, sign, norm = sample_circuit(twoq_circ, DECO_DICT) assert isinstance(imp_circuit, Circuit) assert sign in {1, -1} assert norm > 1
def test_sample_circuit_types_trivial(): imp_circuit, sign, norm = sample_circuit(twoq_circ, NOISELESS_DECO_DICT) assert imp_circuit == twoq_circ assert sign == 1 assert np.isclose(norm, 1)
def execute_with_pec( circuit: QPROGRAM, executor: Callable[[QPROGRAM], float], decomposition_dict: DecompositionDict, precision: float = 0.03, num_samples: Optional[int] = None, random_state: Optional[Union[int, np.random.RandomState]] = None, full_output: bool = False, ) -> Union[float, Tuple[float, float]]: """Evaluates the expectation value associated to the input circuit using 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 function which executes a circuit and returns an expectation value. decomposition_dict: The decomposition dictionary containing the quasi-probability representation of the ideal operations (those which are part of the input circuit). num_samples: The number of noisy circuits to be sampled for PEC. If not given, this is deduced from the argument 'precision'. 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. random_state: Seed for sampling circuits. full_output: If False only the average PEC value is returned. If True an estimate of the associated error is returned too. Returns: pec_value: The PEC estimate of the ideal expectation value associated to the input circuit. pec_error: The estimated error between the mitigated 'pec_value' and the actual ideal expectation value. This is estimated as the ratio 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'. This is returned only if 'full_output' is True. .. [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). .. [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) # Get the 1-norm of the circuit quasi-probability representation _, _, norm = sample_circuit(circuit, decomposition_dict) if not (0 < precision <= 1): raise ValueError( "The value of 'precision' should be within the interval (0, 1]," f" but precision is {precision}." ) # 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) sampled_circuits = [] signs = [] for _ in range(num_samples): sampled_circuit, sign, _ = sample_circuit( circuit, decomposition_dict, random_state ) sampled_circuits.append(sampled_circuit) signs.append(sign) # TODO gh-412: Add support for batched executors in the PEC module # Execute all the circuits exp_values = [executor(circ) for circ in sampled_circuits] # Evaluate unbiased estimators [Temme2017] [Endo2018] [Takagi2020] unbiased_estimators = [norm * s * val for s, val in zip(signs, exp_values)] pec_value = np.average(unbiased_estimators) if full_output: pec_error = np.std(unbiased_estimators) / np.sqrt(num_samples) return pec_value, pec_error return pec_value
def execute_with_pec( circuit: QPROGRAM, executor: Callable[[QPROGRAM], float], decomposition_dict: DecompositionDict, num_samples: Optional[int] = None, random_state: Optional[Union[int, np.random.RandomState]] = None, full_output: bool = False, ) -> Union[float, Tuple[float, float]]: """Evaluates the expectation value associated to the input circuit using probabilistic error cancellation (PEC) [Temme2017]_. 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 function which executes a circuit and returns an expectation value. decomposition_dict: The decomposition dictionary containing the quasi-probability representation of the ideal operations (those which are part of the input circuit). num_samples: The number of noisy circuits to be sampled for PEC. If equal to None, it is deduced from the amount of "negativity" of the quasi-probability representation of the input circuit. Note: the latter feature is not yet implemented and num_samples is just set to 1000 if not specified. random_state: Seed for sampling circuits. full_output: If False only the average PEC value is returned. If True an estimate of the associated error is returned too. Returns: pec_value: The PEC estimate of the ideal expectation value associated to the input circuit. pec_error: The estimated error between the mitigated 'pec_value' and the actual ideal expectation value. This is estimated as the ratio 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'. This is returned only if 'full_output' is True. .. [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). .. [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). """ # TODO gh-413: Add option to automatically deduce the number of PEC samples if not num_samples: num_samples = 1000 sampled_circuits = [] signs = [] for _ in range(num_samples): # Note: the norm is the same for each sample. sampled_circuit, sign, norm = sample_circuit( circuit, decomposition_dict, random_state ) sampled_circuits.append(sampled_circuit) signs.append(sign) # TODO gh-412: Add support for batched executors in the PEC module # Execute all the circuits exp_values = [executor(circ) for circ in sampled_circuits] # Evaluate unbiased estimators [Temme2017], [Endo2018], [Takagi2020] unbiased_estimators = [norm * s * val for s, val in zip(signs, exp_values)] pec_value = np.average(unbiased_estimators) if full_output: pec_error = np.std(unbiased_estimators) / np.sqrt(num_samples) return pec_value, pec_error return pec_value