def test_precision_option_in_execute_with_pec(precision: float): """Tests that the 'precision' argument is used to deduce num_samples.""" # For a noiseless circuit we expect num_samples = 1/precision^2: _, pec_data = execute_with_pec( oneq_circ, partial(fake_executor, random_state=np.random.RandomState(0)), representations=pauli_representations, precision=precision, force_run_all=True, full_output=True, random_state=1, ) # The error should scale as precision print(pec_data["pec_error"] / precision) assert np.isclose(pec_data["pec_error"] / precision, 1.0, atol=0.15) # Check precision is ignored when num_samples is given. num_samples = 1 _, pec_data = execute_with_pec( oneq_circ, partial(fake_executor, random_state=np.random.RandomState(0)), representations=pauli_representations, precision=precision, num_samples=num_samples, full_output=True, ) assert pec_data["num_samples"] == num_samples
def test_execute_with_pec_with_different_samples(circuit, seed): """Tests that, on average, the error decreases as the number of samples is increased. """ errors_few_samples = [] errors_more_samples = [] for _ in range(10): mitigated = execute_with_pec( circuit, serial_executor, representations=pauli_representations, num_samples=10, force_run_all=True, random_state=seed, ) errors_few_samples.append(abs(mitigated - 1.0)) mitigated = execute_with_pec( circuit, serial_executor, representations=pauli_representations, num_samples=100, force_run_all=True, random_state=seed, ) errors_more_samples.append(abs(mitigated - 1.0)) assert np.average(errors_more_samples) < np.average(errors_few_samples)
def test_bad_precision_argument(bad_value: float): """Tests that if 'precision' is not within (0, 1] an error is raised.""" with pytest.raises(ValueError, match="The value of 'precision' should"): execute_with_pec(oneq_circ, serial_executor, pauli_representations, precision=bad_value)
def test_precision_option_in_execute_with_pec(precision: float): """Tests that the 'precision' argument is used to deduce num_samples.""" # For a noiseless circuit we expect num_samples = 1/precision^2: _, pec_data = execute_with_pec( oneq_circ, partial(fake_executor, random_state=np.random.RandomState(0)), representations=pauli_representations, precision=precision, force_run_all=True, full_output=True, ) # The error should scale as precision assert np.isclose(pec_data["pec_error"] / precision, 1.0, atol=0.1) # If num_samples is given, precision is ignored. nsamples = 1000 _, pec_data = execute_with_pec( oneq_circ, partial(fake_executor, random_state=np.random.RandomState(0)), representations=pauli_representations, precision=precision, num_samples=nsamples, full_output=True, ) # The error should scale as 1/sqrt(num_samples) assert not np.isclose(pec_data["pec_error"] / precision, 1.0, atol=0.1) assert np.isclose(pec_data["pec_error"] * np.sqrt(nsamples), 1.0, atol=0.1)
def test_large_sample_size_warning(): """Tests whether a warning is raised when PEC sample size is greater than 10 ** 5. """ with pytest.warns( LargeSampleWarning, match=r"The number of PEC samples is very large.", ): execute_with_pec(oneq_circ, partial(fake_executor, random_state=np.random.RandomState(0)), pauli_representations, num_samples=100001)
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_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, 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 test_qiskit_noiseless_decomposition_multiqubit(nqubits): qreg = [qiskit.QuantumRegister(1) for _ in range(nqubits)] circuit = qiskit.QuantumCircuit(*qreg) for q in qreg: circuit.h(q) # Decompose H(q) for each qubit q into Paulis. representations = [] for q in qreg: opcircuit = qiskit.QuantumCircuit(q) opcircuit.h(q) xcircuit = qiskit.QuantumCircuit(q) xcircuit.x(q) zcircuit = qiskit.QuantumCircuit(q) zcircuit.z(q) representation = OperationRepresentation( ideal=opcircuit, basis_expansion={ NoisyOperation(ideal=xcircuit): 0.5, NoisyOperation(ideal=zcircuit): 0.5, }) representations.append(representation) exact = noiseless_serial_executor(circuit) pec_value = execute_with_pec( circuit, noiseless_serial_executor, representations=representations, num_samples=500, random_state=1, ) assert np.isclose(pec_value, exact, atol=0.1)
def test_pec_data_with_full_output(): """Tests that execute_with_pec mitigates the error of a noisy expectation value. """ precision = 0.1 pec_value, pec_data = execute_with_pec( twoq_circ, serial_executor, precision=precision, representations=pauli_representations, full_output=True, random_state=102, ) # Get num samples from precision norm = 1.0 for op in twoq_circ.all_operations(): for rep in pauli_representations: if rep.ideal == cirq.Circuit(op): norm *= rep.norm num_samples = int((norm / precision)**2) # Manually get raw expectation values exp_values = [serial_executor(c) for c in pec_data["sampled_circuits"]] assert pec_data["num_samples"] == num_samples assert pec_data["precision"] == precision assert np.isclose(pec_data["pec_value"], pec_value) assert np.isclose( pec_data["pec_error"], np.std(pec_data["unbiased_estimators"]) / np.sqrt(num_samples), ) assert np.isclose(np.average(pec_data["unbiased_estimators"]), pec_value) assert np.allclose(pec_data["measured_expectation_values"], exp_values)
def test_execute_with_pec_cirq_noiseless_decomposition(circuit): unmitigated = noiseless_serial_executor(circuit) mitigated = execute_with_pec( circuit, noiseless_serial_executor, representations=noiseless_pauli_representations, num_samples=10, random_state=1, ) assert np.isclose(unmitigated, mitigated)
def test_execute_with_pec_pyquil_trivial_decomposition(): circuit = pyquil.Program(pyquil.gates.H(0)) rep = OperationRepresentation( circuit, basis_expansion={NoisyOperation(circuit): 1.0}) unmitigated = serial_executor(circuit) mitigated = execute_with_pec( circuit, serial_executor, representations=[rep], num_samples=10, random_state=1, ) assert np.isclose(unmitigated, mitigated)
def test_execute_with_pec_cirq_trivial_decomposition(): circuit = cirq.Circuit(cirq.H.on(cirq.LineQubit(0))) rep = OperationRepresentation( circuit, basis_expansion={NoisyOperation(circuit): 1.0}) unmitigated = serial_executor(circuit) mitigated = execute_with_pec( circuit, serial_executor, representations=[rep], num_samples=10, random_state=1, ) assert np.isclose(unmitigated, mitigated)
def test_execute_with_pec_error_scaling(num_samples: int): """Tests that the error associated to the PEC value scales as 1/sqrt(num_samples). """ _, pec_data = execute_with_pec( oneq_circ, partial(fake_executor, random_state=np.random.RandomState(0)), representations=pauli_representations, num_samples=num_samples, force_run_all=True, full_output=True, ) # The error should scale as 1/sqrt(num_samples) normalized_error = pec_data["pec_error"] * np.sqrt(num_samples) assert np.isclose(normalized_error, 1.0, atol=0.1)
def test_execute_with_pec_qiskit_trivial_decomposition(): qreg = qiskit.QuantumRegister(1) circuit = qiskit.QuantumCircuit(qreg) _ = circuit.x(qreg) rep = OperationRepresentation( circuit, basis_expansion={NoisyOperation(circuit): 1.0}) unmitigated = serial_executor(circuit) mitigated = execute_with_pec( circuit, serial_executor, representations=[rep], num_samples=10, random_state=1, ) assert np.isclose(unmitigated, mitigated)
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_pec_partial_representations(): # Only use the CNOT representation. reps = [pauli_representations[-1]] pec_value = execute_with_pec( twoq_circ, executor=partial( mitiq_cirq.compute_density_matrix, noise_model=cirq.depolarize, noise_level=(BASE_NOISE, ), ), observable=Observable(PauliString("ZZ")), representations=reps, num_samples=100, force_run_all=False, random_state=101, ) assert isinstance(pec_value, complex)
def track_pec( circuit_type: str, nqubits: int, depth: int, observable: Observable, num_samples: int, ) -> float: """Returns the PEC error mitigation factor, i.e., the ratio (error without PEC) / (error with PEC). Args: circuit_type: Type of benchmark circuit. nqubits: Number of qubits in the benchmark circuit. depth: Some proxy of depth in the benchmark circuit. observable: Observable to compute the expectation value of. num_samples: Number of circuits to sample/run. """ circuit = get_benchmark_circuit(circuit_type, nqubits, depth) noise_level = 0.01 reps = pec.represent_operations_in_circuit_with_local_depolarizing_noise( circuit, noise_level ) compute_density_matrix = functools.partial( mitiq_cirq.compute_density_matrix, noise_model=cirq.depolarize, noise_level=(noise_level,), ) true_value = raw.execute( circuit, compute_density_matrix_noiseless, observable ) raw_value = raw.execute(circuit, compute_density_matrix, observable) pec_value = pec.execute_with_pec( circuit, compute_density_matrix, observable, representations=reps, num_samples=num_samples, ) return np.real(abs(true_value - raw_value) / abs(true_value - pec_value))
def test_execute_with_pec_with_different_samples(circuit, seed): """Tests that, on average, the error decreases as the number of samples is increased. """ small_sample_number = 10 large_sample_number = 100 errors = [] for num_samples in (small_sample_number, large_sample_number): mitigated = execute_with_pec( circuit, serial_executor, representations=pauli_representations, num_samples=num_samples, force_run_all=True, random_state=seed, ) errors.append(abs(mitigated - 1.0)) assert np.average(errors[1]) < np.average(errors[0])
def test_execute_with_pec_with_observable(): circuit = twoq_circ obs = Observable(PauliString("ZZ")) executor = partial( mitiq_cirq.compute_density_matrix, noise_model=cirq.depolarize, noise_level=(BASE_NOISE, ), ) true_value = 1.0 noisy_value = obs.expectation(circuit, mitiq_cirq.compute_density_matrix) pec_value = execute_with_pec( circuit, executor, observable=obs, representations=pauli_representations, num_samples=100, force_run_all=False, random_state=101, ) assert abs(pec_value - true_value) < abs(noisy_value - true_value) assert np.isclose(pec_value, true_value, atol=0.1)
def test_pyquil_noiseless_decomposition_multiqubit(nqubits): circuit = pyquil.Program(pyquil.gates.H(q) for q in range(nqubits)) # Decompose H(q) for each qubit q into Paulis. representations = [] for q in range(nqubits): representation = OperationRepresentation( ideal=pyquil.Program(pyquil.gates.H(q)), basis_expansion={ NoisyOperation(ideal=pyquil.Program(pyquil.gates.X(q))): 0.5, NoisyOperation(ideal=pyquil.Program(pyquil.gates.Z(q))): 0.5, }) representations.append(representation) exact = noiseless_serial_executor(circuit) pec_value = execute_with_pec( circuit, noiseless_serial_executor, representations=representations, num_samples=500, random_state=1, ) assert np.isclose(pec_value, exact, atol=0.1)