from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram
import numpy as np
Quantum Error Correction
Method 1: Repetition Code
= QuantumRegister(3)
q = ClassicalRegister(3)
c = QuantumCircuit(q,c)
qc
0)
qc.h(
0,1)
qc.cx(0,2)
qc.cx(
range(3), range(3))
qc.measure(
# Construct a simulator using a noise model from a real backend.
= QiskitRuntimeService(channel="ibm_quantum", token="fbc77e68310da5c90990728b892ef5e327787a28a126ad837f8fb88280922284da2784a01e90dc9ccd3be7d48bbe76ca0b8375ac0d3f0f312b1e19da181a18ed")
provider = provider.get_backend("ibm_brisbane")
backend = AerSimulator.from_backend(backend)
aersim_backend # Perform noisy simulation
= aersim_backend.run(qc).result().get_counts()
results plot_histogram(results)
= QuantumRegister(5)
q = ClassicalRegister(2)
c = QuantumCircuit(q,c)
qc
0)
qc.h(
0,1)
qc.cx(0,2)
qc.cx(
0,3)
qc.cx(1,3)
qc.cx(
1,4)
qc.cx(2,4)
qc.cx(
3,4], range(2))
qc.measure([
"mpl",style="clifford")
qc.draw(
# Construct a simulator using a noise model from a real backend.
#provider = QiskitRuntimeService(channel="ibm_quantum", token="fbc77e68310da5c90990728b892ef5e327787a28a126ad837f8fb88280922284da2784a01e90dc9ccd3be7d48bbe76ca0b8375ac0d3f0f312b1e19da181a18ed")
#backend = provider.get_backend("ibm_brisbane")
#aersim_backend = AerSimulator.from_backend(backend)
# Perform noisy simulation
#results = aersim_backend.run(qc).result().get_counts()
#plot_histogram(results)
= QuantumRegister(5)
q = ClassicalRegister(2)
c = QuantumCircuit(q,c)
qc
0)
qc.h(
0,1)
qc.cx(0,2)
qc.cx(
0)
qc.x(
0,3)
qc.cx(1,3)
qc.cx(
1,4)
qc.cx(2,4)
qc.cx(
3,4], range(2))
qc.measure([
# Construct a simulator using a noise model from a real backend.
= QiskitRuntimeService(channel="ibm_quantum", token="fbc77e68310da5c90990728b892ef5e327787a28a126ad837f8fb88280922284da2784a01e90dc9ccd3be7d48bbe76ca0b8375ac0d3f0f312b1e19da181a18ed")
provider = provider.get_backend("ibm_brisbane")
backend = AerSimulator.from_backend(backend)
aersim_backend # Perform noisy simulation
= aersim_backend.run(qc).result().get_counts()
results plot_histogram(results)
= QuantumRegister(5)
q = ClassicalRegister(2)
c = QuantumCircuit(q,c)
qc
0)
qc.h(
0,1)
qc.cx(0,2)
qc.cx(range(3))
qc.h(
3,0)
qc.cx(3,1)
qc.cx(
4,1)
qc.cx(4,2)
qc.cx(
3,4])
qc.h([
3,4], range(2))
qc.measure([
"mpl", style="clifford") qc.draw(
Method 2: Error Mitigation Matrix
= QuantumRegister(2)
q = ClassicalRegister(2)
c = QuantumCircuit(q,c)
qc
range(2), range(2))
qc.measure(
# Construct a simulator using a noise model from a real backend.
= QiskitRuntimeService(channel="ibm_quantum", token="fbc77e68310da5c90990728b892ef5e327787a28a126ad837f8fb88280922284da2784a01e90dc9ccd3be7d48bbe76ca0b8375ac0d3f0f312b1e19da181a18ed")
provider = provider.get_backend("ibm_brisbane")
backend = AerSimulator.from_backend(backend)
aersim_backend # Perform noisy simulation
= aersim_backend.run(qc).result().get_counts()
results plot_histogram(results)
= QuantumRegister(2)
q = ClassicalRegister(2)
c = QuantumCircuit(q,c)
qc
0)
qc.x(
range(2), range(2))
qc.measure(
# Construct a simulator using a noise model from a real backend.
= QiskitRuntimeService(channel="ibm_quantum", token="fbc77e68310da5c90990728b892ef5e327787a28a126ad837f8fb88280922284da2784a01e90dc9ccd3be7d48bbe76ca0b8375ac0d3f0f312b1e19da181a18ed")
provider = provider.get_backend("ibm_brisbane")
backend = AerSimulator.from_backend(backend)
aersim_backend # Perform noisy simulation
= aersim_backend.run(qc).result().get_counts()
results plot_histogram(results)
= QuantumRegister(2)
q = ClassicalRegister(2)
c = QuantumCircuit(q,c)
qc
1)
qc.x(
range(2), range(2))
qc.measure(
# Construct a simulator using a noise model from a real backend.
= QiskitRuntimeService(channel="ibm_quantum", token="fbc77e68310da5c90990728b892ef5e327787a28a126ad837f8fb88280922284da2784a01e90dc9ccd3be7d48bbe76ca0b8375ac0d3f0f312b1e19da181a18ed")
provider = provider.get_backend("ibm_brisbane")
backend = AerSimulator.from_backend(backend)
aersim_backend # Perform noisy simulation
= aersim_backend.run(qc).result().get_counts()
results plot_histogram(results)
= QuantumRegister(2)
q = ClassicalRegister(2)
c = QuantumCircuit(q,c)
qc
0)
qc.x(1)
qc.x(
range(2), range(2))
qc.measure(
# Construct a simulator using a noise model from a real backend.
= QiskitRuntimeService(channel="ibm_quantum", token="fbc77e68310da5c90990728b892ef5e327787a28a126ad837f8fb88280922284da2784a01e90dc9ccd3be7d48bbe76ca0b8375ac0d3f0f312b1e19da181a18ed")
provider = provider.get_backend("ibm_brisbane")
backend = AerSimulator.from_backend(backend)
aersim_backend # Perform noisy simulation
= aersim_backend.run(qc).result().get_counts()
results plot_histogram(results)
Method 3: Zero Noise Extrapolation
# New installs, mitiq is an error mitigation library we will use for the zero noise extrapolation
# ! pip install ply,mitiq
##############################
## IMPORT ##
##############################
# Mitiq imports
from mitiq.benchmarks import generate_ghz_circuit # Creates an example circuit
from mitiq import zne # Zero noise extrapolation
# Qiskit imports
from qiskit import QuantumCircuit
from qiskit import transpile
from qiskit.visualization import plot_histogram
# Aer simulator
from qiskit_aer import AerSimulator
# Fake quantum computer for simulated noise
from qiskit_ibm_runtime.fake_provider import FakeJakartaV2 as FakeJakarta # Fake (simulated) QPUs
# Error based on an older quantum computer
# Number of qubits for the example circuit
= 2
n_qubits
# Generate an example circuit which should return either a string of zeros or a string of ones
# equal probability of each return type
#circuit = generate_ghz_circuit(n_qubits=n_qubits, return_type="qiskit")
= QuantumCircuit(2)
circuit range(2))
circuit.h(0,1)
circuit.cz(range(2))
circuit.h(range(2))
circuit.x(0,1)
circuit.cz(range(2))
circuit.x(range(2))
circuit.h(print("GHZ circuit:")
"mpl", style="clifford") circuit.draw(
GHZ circuit:
# Number of shots for the simulation
= 10 ** 5 shots
# The noiseless backend is the AerSimulator
= AerSimulator()
ideal_backend
# Create a copy of the circuit so it is not changed by the first simulation
= circuit.copy()
circuit_to_run # Measure all of the qubits
circuit_to_run.measure_all()
# Compile the circuit into the native gates of the backend
= transpile(circuit_to_run, ideal_backend)
compiled_circuit
# Simulate running the circuit on an ideal simulator
= ideal_backend.run(compiled_circuit,shots=shots).result().get_counts()
ideal_counts
plot_histogram(ideal_counts)
# Use a fake quantum computer modeled off the noise of an older real quantum computer
= FakeJakarta() # QPU emulator
noisy_backend
# Compile the circuit into the native gates of the backend
= transpile(circuit_to_run, noisy_backend)
compiled_circuit
# Simulate and get the counts
= noisy_backend.run(compiled_circuit, shots=shots).result().get_counts()
noisy_counts
# Plot the histogram but make it a bit wider
=(15, 5)) plot_histogram(noisy_counts,figsize
# Calculate the expectation value of an ideal simulation (should be 1)
= (ideal_counts[n_qubits * "1"] ) / shots
ideal_expectation_value print(f"The ideal expectation value is <A> = ", ideal_expectation_value)
# Calculate the expectation value of the noisy simulation
= (noisy_counts[n_qubits * "1"] ) / shots
noisy_expectation_value print(f"The noisy expectation value is <A> = ", noisy_expectation_value)
The ideal expectation value is <A> = 1.0
The noisy expectation value is <A> = 0.92038
# Need to create an execute function which takes a transpile circuit and runs it on the fake quantum computer
# The function needs to return the expectation value, this is used by the mitiq library
def execute(compiled_circuit):
# Print the depth of the circuit before executing it
print("Executing a circuit of depth:", compiled_circuit.depth())
= FakeJakarta()
noisy_backend = noisy_backend.run(compiled_circuit, shots=shots).result().get_counts()
noisy_counts = (noisy_counts[n_qubits * "0"] + noisy_counts[n_qubits * "1"]) / shots
noisy_expectation_value return noisy_expectation_value
# Ensure that this gives similar results to above
print(f"The noisy expectation value is <A> = {execute(compiled_circuit)}")
Executing a circuit of depth: 12
The noisy expectation value is <A> = 0.92472
# Perform zero noise extrapolation, need to pass the transpiled circuit, the execute function
# Can change the options to use different extrapolators and noise methods
= zne.execute_with_zne(compiled_circuit, executor=execute)
zne_value print(f"The error mitigated expectation value is <A> = ", zne_value)
Executing a circuit of depth: 12
Executing a circuit of depth: 26
Executing a circuit of depth: 34
The error mitigated expectation value is <A> = 0.9403999999999985
# Print the error from the ideal expectation value with and without error correction
print(f"Error without ZNE:", abs(ideal_expectation_value - noisy_expectation_value))
print(f"Error with ZNE:", abs(ideal_expectation_value - zne_value))
print("Difference:", abs(ideal_expectation_value - noisy_expectation_value) - abs(ideal_expectation_value - zne_value))
Error without ZNE: 0.07962000000000002
Error with ZNE: 0.05960000000000154
Difference: 0.020019999999998483
Example 2: Variational
import matplotlib.pyplot as plt
import numpy as np
# This time we will use fake noise that we can control the size of, specifically depolarization errors
from qiskit_aer.noise import NoiseModel
from qiskit_aer.noise.errors.standard_errors import depolarizing_error
from mitiq.zne import mitigate_executor
from mitiq.zne.inference import RichardsonFactory
def ansatz(theta):
"""
Returns a two-qubit circuit for a given variational parameter.
Inputs:
theta (a float): The variational parameter.
Returns:
circuit (a qiskit circuit): The two-qubit circuit with a fixed gamma.
"""
= QuantumCircuit(2)
circuit 0)
circuit.rx(theta, 0, 1)
circuit.cx(1)
circuit.rx(theta, 0, 1)
circuit.cx(0)
circuit.rx(theta,
return circuit
= ansatz(np.pi)
circuit "mpl", style="clifford") circuit.draw(
# Define the Hamiltonian of the system
# This will be used to calculate the expectation values
# Because it is z gates then no additional gates need to be
# added to the circuit before measuring the ansatz
= np.array([[1,0],[0,-1]])
z = np.kron(z, z)
hamiltonian
def noiseless_executor(circuit):
"""
Simulates the execution of a circuit without noise.
Inputs:
circuit (a qiskit circuit): The input circuit.
Returns:
expectation (a float): The expectation value of the ZZ observable.
"""
# avoid mutating the input circuit
= circuit.copy()
circ # Set up the circuit to save the density matrix
circ.save_density_matrix()
# execute experiment without noise, we want the density matrix returned
= AerSimulator(method="density_matrix")
simulator
# Get the density matrix
= simulator.run(circ, shots=1).result().data()["density_matrix"]
rho
# Compute the expectation value with the Hamiltonian and the density matrix
# and return the value
= np.real(np.trace(rho @ hamiltonian))
expectation return expectation
# strength of noise channel
= 0.04
noise_level
def executor_with_noise(circuit: QuantumCircuit) -> float:
"""
Simulates the execution of a circuit with depolarizing noise.
Inputs:
circuit (a qiskit circuit): The input circuit.
Returns:
expectation (a float): The expectation value of the hamiltonian.
"""
# avoid mutating the input circuit
= circuit.copy()
circ
circ.save_density_matrix()
# Initialize qiskit noise model. In this case a depolarizing
# noise model with the same noise strength on all gates, remember that rx is
# a 1 qubit gate and cx is a two qubit gate
= NoiseModel()
noise_model 1), ["rx"])
noise_model.add_all_qubit_quantum_error(depolarizing_error(noise_level, 2), ["cx"])
noise_model.add_all_qubit_quantum_error(depolarizing_error(noise_level,
# execute experiment with depolarizing noise, get the density matrix
= AerSimulator(method="density_matrix", noise_model=noise_model)
backend
# Transpile the circuit so it can be properly run, optimization level of 0 here so qiskit
# does not try to optmize the circuit
= transpile(circ, backend, basis_gates=noise_model.basis_gates + ["save_density_matrix"],
exec_circuit =0)
optimization_level
# Get the density matrix, calculate expectation value and return
= backend.run(exec_circuit, shots=1).result().data()["density_matrix"]
rho
= np.real(np.trace(rho @ hamiltonian))
expectation return expectation
# Perform the noiseless simulation to determine the theta that gives the ground
# state energy
= np.linspace(0, 2 * np.pi, 500)
thetas_noiseless = [noiseless_executor(ansatz(t)) for t in thetas_noiseless]
noiseless_expectations
="g", linewidth=3, label="Noiseless")
plt.plot(thetas_noiseless, noiseless_expectations, colorr"Ansatz angle $\theta$", fontsize=16)
plt.xlabel(r"$\langle H \rangle(\theta)$", fontsize=16)
plt.ylabel(=14) plt.legend(fontsize
# Perform the simultion with the noisy circuit and compare to noiseless
= np.linspace(0, 2 * np.pi, 50)
thetas = [executor_with_noise(ansatz(g)) for g in thetas]
expectations
="g", linewidth=3, label="Noiseless")
plt.plot(thetas_noiseless, noiseless_expectations, color="r", label="Unmitigated")
plt.scatter(thetas, expectations, colorr"Ansatz angle $\theta$", fontsize=16)
plt.xlabel(r"$\langle H \rangle(\theta)$", fontsize=16)
plt.ylabel(=14) plt.legend(fontsize
# Richardson extrapolation is a method of extrapolation that can be used for ZNE
# The combination of a Richardson extrapolation with ZNE reduces the time (need less
# points for the extrapolation) and somewhat increases the accuracy
= RichardsonFactory(scale_factors=[1, 3, 5])
fac = mitigate_executor(executor_with_noise, factory=fac)
mitigated_executor = [mitigated_executor(ansatz(g)) for g in thetas]
mitigated_expectations
="g", linewidth=3, label="Noiseless")
plt.plot(thetas_noiseless, noiseless_expectations, color="r", label="Unmitigated")
plt.scatter(thetas, expectations, color="b", label="Mitigated")
plt.scatter(thetas, mitigated_expectations, colorr"Variational angle $\theta$", fontsize=16)
plt.xlabel(r"$\langle H \rangle(\theta)$", fontsize=16)
plt.ylabel(=14) plt.legend(fontsize
print("Minimum of noiseless landscape:", round(min(noiseless_expectations), 3))
print("Minimum of the noisy landscape:", round(min(expectations), 3))
print("Minimum of the mitigated landscape:", round(min(mitigated_expectations), 3))
print("Theoretical ground state energy:", min(np.linalg.eigvals(hamiltonian)))
Minimum of noiseless landscape: -1.0
Minimum of the noisy landscape: -0.807
Minimum of the mitigated landscape: -0.976
Theoretical ground state energy: -1.0