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_sequence_qiskit(): qreg = qiskit.QuantumRegister(1) circuit = qiskit.QuantumCircuit(qreg) _ = circuit.h(qreg) xcircuit = qiskit.QuantumCircuit(qreg) _ = xcircuit.x(qreg) zcircuit = qiskit.QuantumCircuit(qreg) _ = zcircuit.z(qreg) noisy_xop = NoisyOperation(xcircuit) noisy_zop = NoisyOperation(zcircuit) rep = OperationRepresentation( ideal=circuit, basis_expansion={ noisy_xop: 0.5, noisy_zop: -0.5 }, ) for _ in range(5): seqs, signs, norm = sample_sequence(circuit, representations=[rep]) assert isinstance(seqs[0], qiskit.QuantumCircuit) assert signs[0] in {1, -1} assert norm == 1.0
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_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_sample_sequence_pyquil(): circuit = pyquil.Program(pyquil.gates.H(0)) noisy_xop = NoisyOperation(pyquil.Program(pyquil.gates.X(0))) noisy_zop = NoisyOperation(pyquil.Program(pyquil.gates.Z(0))) rep = OperationRepresentation( ideal=circuit, basis_expansion={noisy_xop: 0.5, noisy_zop: -0.5}, ) for _ in range(50): seq, sign, norm = sample_sequence(circuit, representations=[rep]) assert isinstance(seq, pyquil.Program) assert sign in {1, -1} assert norm == 1.0
def test_sample_sequence_cirq(): circuit = cirq.Circuit(cirq.H(cirq.LineQubit(0))) circuit.append(cirq.measure(cirq.LineQubit(0))) rep = OperationRepresentation( ideal=circuit, basis_expansion={ NoisyOperation.from_cirq(circuit=cirq.X): 0.5, NoisyOperation.from_cirq(circuit=cirq.Z): -0.5, }, ) for _ in range(5): seqs, signs, norm = sample_sequence(circuit, representations=[rep]) assert isinstance(seqs[0], cirq.Circuit) assert signs[0] in {1, -1} assert norm == 1.0
def test_sample_sequence_cirq_random_state(seed): circuit = cirq.Circuit(cirq.H.on(cirq.LineQubit(0))) rep = OperationRepresentation( ideal=circuit, basis_expansion={ NoisyOperation.from_cirq(circuit=cirq.X): 0.5, NoisyOperation.from_cirq(circuit=cirq.Z): -0.5, }, ) sequences, signs, norm = sample_sequence( circuit, [rep], random_state=np.random.RandomState(seed)) for _ in range(20): new_sequences, new_signs, new_norm = sample_sequence( circuit, [rep], random_state=np.random.RandomState(seed)) assert _equal(new_sequences[0], sequences[0]) assert new_signs[0] == signs[0] assert np.isclose(new_norm, norm)
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_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 decorated_serial_executor(circuit: QPROGRAM) -> float: """Returns a decorated serial executor for use with other tests. The serial executor is decorated with the same representations as those that are used in the tests for trivial decomposition. """ rep = OperationRepresentation( circuit, basis_expansion={NoisyOperation(circuit): 1.0}) @pec_decorator(representations=[rep]) def decorated_executor(qp): return serial_executor(qp) return decorated_executor(circuit)
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_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)
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_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_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_mitigate_executor_pyquil(): """Performs the same test as test_execute_with_pec_pyquil_trivial_decomposition(), but using mitigate_executor() instead of execute_with_pec(). """ circuit = pyquil.Program(pyquil.gates.H(0)) rep = OperationRepresentation( circuit, basis_expansion={NoisyOperation(circuit): 1.0} ) unmitigated = serial_executor(circuit) mitigated_executor = mitigate_executor( serial_executor, representations=[rep], num_samples=10, random_state=1, ) mitigated = mitigated_executor(circuit) assert np.isclose(unmitigated, mitigated)
def test_mitigate_executor_qiskit(): """Performs the same test as test_execute_with_pec_qiskit_trivial_decomposition(), but using mitigate_executor() instead of execute_with_pec(). """ 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_executor = mitigate_executor( serial_executor, representations=[rep], num_samples=10, random_state=1, ) mitigated = mitigated_executor(circuit) assert np.isclose(unmitigated, mitigated)
def represent_operation_with_local_biased_noise( ideal_operation: QPROGRAM, epsilon: float, eta: float, ) -> OperationRepresentation: r"""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 combined depolarizing and dephasing noise model with a bias factor :math:`\eta` (see :cite:`Strikis_2021_PRXQuantum`) and that the following noisy operations are implementable :math:`\mathcal{O}_{\alpha} = \mathcal{D} \circ \mathcal P_\alpha` where :math:`\mathcal{U}` is the unitary associated to the input ``ideal_operation``, :math:`\mathcal{P}_\alpha` is a Pauli operation and .. math:: \mathcal{D}(\epsilon) = (1 - \epsilon)[\mathbb{1}] + \epsilon(\frac{\eta}{\eta + 1} \mathcal{Z} + \frac{1}{3}\frac{1}{\eta + 1}(\mathcal{X} + \mathcal{Y} + \mathcal{Z})) is the combined (biased) dephasing and depolarizing channel acting on a single qubit. For multi-qubit operations, we use a noise channel that is the tensor product of the local single-qubit channels. Args: ideal_operation: The ideal operation (as a QPROGRAM) to represent. epsilon: The local noise severity (as a float) of the combined channel. eta: The noise bias between combined dephasing and depolarizing channels with :math:`\eta = 0` describing a fully depolarizing channel and :math:`\eta = \inf` describing a fully dephasing 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 backend which violates this assumption, it remains a good approximation for small values of ``epsilon``. .. 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 biased noise channel, is physically implementable. """ circuit_copy = copy.deepcopy(ideal_operation) converted_circ, _ = convert_to_mitiq(circuit_copy) post_ops: List[List[Operation]] qubits = converted_circ.all_qubits() # Calculate coefficients in basis expansion in terms of eta and epsilon a = 1 - epsilon b = epsilon * (3 * eta + 1) / (3 * (eta + 1)) c = epsilon / (3 * (eta + 1)) alpha = (a**2 + a * b - 2 * c**2) / (a**3 + a**2 * b - a * b**2 - 4 * a * c**2 - b**3 + 4 * b * c**2) beta = (-a * b - b**2 + 2 * c**2) / (a**3 + a**2 * b - a * b**2 - 4 * a * c**2 - b**3 + 4 * b * c**2) gamma = -c / (a**2 + 2 * a * b + b**2 - 4 * c**2) if len(qubits) == 1: q = tuple(qubits)[0] alphas = [alpha] + 2 * [gamma] + [beta] post_ops = [[]] # for eta_1, 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 alphas = ([alpha**2] + 2 * [alpha * gamma] + [alpha * beta] + 2 * [alpha * gamma] + [alpha * beta] + 2 * [gamma**2] + [beta * gamma] + 2 * [gamma**2] + 3 * [beta * gamma] + [beta**2]) post_ops = [[]] # for eta_1, 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 = [ append_cirq_circuit_to_qprogram(ideal_operation, Circuit(op)) for op in post_ops ] # Build basis expansion. expansion = {NoisyOperation(c): a for c, a in zip(imp_op_circuits, alphas)} return OperationRepresentation(ideal_operation, expansion)