def test_sample_circuit_pyquil(): circuit = pyquil.Program(pyquil.gates.H(0), pyquil.gates.CNOT(0, 1)) h_rep = OperationRepresentation( ideal=circuit[:1], basis_expansion={ NoisyOperation(pyquil.Program(pyquil.gates.X(0))): 0.6, NoisyOperation(pyquil.Program(pyquil.gates.Z(0))): -0.6, }, ) cnot_rep = OperationRepresentation( ideal=circuit[1:], basis_expansion={ NoisyOperation(pyquil.Program(pyquil.gates.CNOT(0, 1))): 0.7, NoisyOperation(pyquil.Program(pyquil.gates.CZ(0, 1))): -0.7, }, ) for _ in range(50): sampled_circuits, signs, norm = sample_circuit( circuit, representations=[h_rep, cnot_rep]) assert isinstance(sampled_circuits[0], pyquil.Program) assert len(sampled_circuits[0]) == 2 assert signs[0] in (-1, 1) assert norm >= 1
def test_sample_circuit_cirq(measure): circuit = cirq.Circuit( cirq.ops.H.on(cirq.LineQubit(0)), cirq.ops.CNOT.on(*cirq.LineQubit.range(2)), ) if measure: circuit.append(cirq.measure_each(*cirq.LineQubit.range(2))) h_rep = OperationRepresentation( ideal=circuit[:1], basis_expansion={ NoisyOperation.from_cirq(circuit=cirq.X): 0.6, NoisyOperation.from_cirq(circuit=cirq.Z): -0.6, }, ) cnot_rep = OperationRepresentation( ideal=circuit[1:2], basis_expansion={ NoisyOperation.from_cirq(circuit=cirq.CNOT): 0.7, NoisyOperation.from_cirq(circuit=cirq.CZ): -0.7, }, ) for _ in range(50): sampled_circuits, signs, norm = sample_circuit( circuit, representations=[h_rep, cnot_rep]) assert isinstance(sampled_circuits[0], cirq.Circuit) assert len(sampled_circuits[0]) == 2 assert signs[0] in (-1, 1) assert norm >= 1
def test_sample_circuit_trivial_decomposition(): circuit = cirq.Circuit(cirq.ops.H.on(cirq.NamedQubit("Q"))) rep = OperationRepresentation( ideal=circuit, basis_expansion={NoisyOperation(circuit): 1.0}) sampled_circuits, signs, norm = sample_circuit(circuit, [rep], random_state=1) assert _equal(sampled_circuits[0], circuit) assert signs[0] == 1 assert np.isclose(norm, 1)
def test_sample_circuit_with_seed(): circ = cirq.Circuit([cirq.X.on(cirq.LineQubit(0)) for _ in range(10)]) rep = OperationRepresentation( ideal=cirq.Circuit(cirq.X.on(cirq.LineQubit(0))), basis_expansion={ NoisyOperation.from_cirq(cirq.Z): 1.0, NoisyOperation.from_cirq(cirq.X): -1.0, }, ) expected_circuits, expected_signs, expected_norm = sample_circuit( circ, [rep], random_state=4) # Check we're not sampling the same operation every call to sample_sequence assert len(set(expected_circuits[0].all_operations())) > 1 for _ in range(10): sampled_circuits, sampled_signs, sampled_norm = sample_circuit( circ, [rep], random_state=4) assert _equal(sampled_circuits[0], expected_circuits[0]) assert sampled_signs[0] == expected_signs[0] assert sampled_norm == expected_norm
def test_sample_circuit_partial_representations(): circuit = cirq.Circuit( cirq.ops.H.on(cirq.LineQubit(0)), cirq.ops.CNOT.on(*cirq.LineQubit.range(2)), ) cnot_rep = OperationRepresentation( ideal=circuit[1:2], basis_expansion={ NoisyOperation.from_cirq(circuit=cirq.CNOT): 0.7, NoisyOperation.from_cirq(circuit=cirq.CZ): -0.7, }, ) for _ in range(10): with pytest.warns(UserWarning, match="No representation found for"): sampled_circuits, signs, norm = sample_circuit( circuit, representations=[cnot_rep]) assert isinstance(sampled_circuits[0], cirq.Circuit) assert len(sampled_circuits[0]) == 2 assert signs[0] in (-1, 1) assert norm >= 1
def test_sample_circuit_choi(): """Tests the sample_circuit by comparing the exact Choi matrices.""" # A simple 2-qubit circuit qreg = cirq.LineQubit.range(2) ideal_circ = cirq.Circuit( cirq.X.on(qreg[0]), cirq.I.on(qreg[1]), cirq.CNOT.on(*qreg), ) noisy_circuit = ideal_circ.with_noise(cirq.depolarize(BASE_NOISE)) ideal_choi = _circuit_to_choi(ideal_circ) noisy_choi = _operation_to_choi(noisy_circuit) rep_list = [] for op in ideal_circ.all_operations(): rep_list.append( represent_operation_with_local_depolarizing_noise( cirq.Circuit(op), BASE_NOISE, )) choi_unbiased_estimates = [] rng = np.random.RandomState(1) for _ in range(500): imp_circ, sign, norm = sample_circuit(ideal_circ, rep_list, random_state=rng) noisy_imp_circ = imp_circ.with_noise(cirq.depolarize(BASE_NOISE)) sequence_choi = _circuit_to_choi(noisy_imp_circ) choi_unbiased_estimates.append(norm * sign * sequence_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 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 execute_with_pec( circuit: QPROGRAM, executor: Callable, representations: List[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"""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 (sequence of circuits) and returns an expectation value (sequence of expectation values). 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: pec_value: The PEC estimate of the ideal expectation value associated to the input circuit. pec_data: 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'. 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) if not (0 < precision <= 1): raise ValueError( "The value of 'precision' should be within the interval (0, 1]," f" but precision is {precision}.") # Get the 1-norm of the circuit quasi-probability representation _, _, norm = sample_circuit(circuit, representations) # 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, representations, random_state) sampled_circuits.append(sampled_circuit) signs.append(sign) # Execute all sampled circuits collected_executor = generate_collected_executor( executor, force_run_all=force_run_all) exp_values = collected_executor(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 not full_output: return pec_value # Build dictionary with additional results and data pec_data: Dict[str, Any] = {} pec_data = { "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": exp_values, "sampled_circuits": sampled_circuits, } return pec_value, pec_data