Here, we provide an overview of working with Qiskit Terra. Qiskit Terra provides the basic building blocks necessary to program quantum computers. The basic concept of Qiskit Terra is an array of quantum circuits. A workflow using Terra consists of two stages: Build and Execute. Build allows you to make different quantum circuits that represent the problem you are solving, and Execute allows you to run them on different backends. After the jobs have been run, the data is collected. There are methods for putting this data together, depending on the program. This either gives you the answer you wanted, or allows you to make a better program for the next instance (e.g., when running the variational quantum eigensolver (VQE) algorithm).
Contents
Code imports
In [ ]:
import numpy as np
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit import execute
In [2]:
# Create a Quantum Register with 3 qubits.
q = QuantumRegister(3, 'q')
# Create a Quantum Circuit acting on the q register
circ = QuantumCircuit(q)
After you create the circuit with its registers, you can add gates ("operations") to manipulate the registers. (See Summary of Quantum Operations for a thorough discussion of quantum gates.) As you proceed through the documentation you will find more gates and circuits but the below is an example of a quantum circuit that makes a three-qubit GHZ state
$$|\psi\rangle = \left(|000\rangle+|111\rangle\right)/\sqrt{2}.$$To create such a state, we start with a 3-qubit quantum register. By default, each qubit in the register is initialized to $|0\rangle$. To make the GHZ state, we apply the following gates:
On an ideal quantum computer, the state produced by running this circuit would be the GHZ state above.
In Qiskit Terra, operations can be added to the circuit one-by-one, as shown below.
In [3]:
# Add a H gate on qubit 0, putting this qubit in superposition.
circ.h(q[0])
# Add a CX (CNOT) gate on control qubit 0 and target qubit 1, putting
# the qubits in a Bell state.
circ.cx(q[0], q[1])
# Add a CX (CNOT) gate on control qubit 0 and target qubit 2, putting
# the qubits in a GHZ state.
circ.cx(q[0], q[2])
Out[3]:
In [4]:
from qiskit.tools.visualization import circuit_drawer
circuit_drawer(circ)
Out[4]:
In this circuit, the qubits are put in order with qubit zero at the top and qubit three at the bottom. The circuit is read left-to-right (meaning that gates which are applied earlier in the circuit show up further to the left).
Qiskit Aer is our package for simulating quantum circuits. It provides many different backends for doing a simulation.
The most common backend in Qiskit Aer is the statevector_simulator
. This simulator returns the quantum
state which is a complex vector of dimensions $2^n$ where $n$ is the number of qubits
(so be careful using this as it will quickly get too large to run on your machine).
To run the above circuit using the statevector simulator first you need to import Aer and then set the backend to statevector_simulator
.
In [5]:
# Import Aer
from qiskit import Aer
# Run the quantum circuit on a statevector simulator backend
backend = Aer.get_backend('statevector_simulator')
Now we have chosen the backend its time to compile and run the quantum circuit. In Qiskit Terra we provide the execute
function for this. execute
returns a job
object that encapsulates information about the job submitted to the backend.
In [6]:
# Create a Quantum Program for execution
job = execute(circ, backend)
When you run a program a job object is made that has the following two useful methods
job.status()
and job.result()
which returns the status of the job a result object respectiviely.
In [7]:
result = job.result()
The results object contains the data and Qiskit Terra provides the method
result.get_statevector(circ)
to return the statevector for the quantum circuit.
In [8]:
outputstate = result.get_statevector(circ)
print("simulation: ", result )
print(np.around(outputstate,3))
Qiskit Terra also provides a visualization toolbox to allow you to view these results. See Qiskit visualizations for more information.
Below, we use the visualization function to plot the real and imaginary components of the state vector.
In [9]:
from qiskit.tools.visualization import plot_state
plot_state(outputstate)
Qiskit Aer also includes a unitary_simulator
that works provided all the elements in the circuit are unitary operations. This backend calculates the $2^n \times 2^n$ matrix representing the gates in the quantum circuit.
In [10]:
# Run the quantum circuit on a unitary simulator backend
backend = Aer.get_backend('unitary_simulator')
job = execute(circ, backend)
result = job.result()
# Show the results
print("simulation: ", result )
print(np.around(result.get_unitary(circ), 3))
The simulators above are useful because they provide information about the state output by the ideal circuit and the matrix representation of the circuit. However, a real experiment terminates by measuring each qubit (usually in the computational $|0\rangle, |1\rangle$ basis). Without measurement, we cannot gain information about the state. Measurements cause the quantum system to collapse into classical bits.
For example, suppose we make independent measurements on each qubit of the three-qubit GHZ state $$|\psi\rangle = |000\rangle +|111\rangle)/\sqrt{2},$$ and let $xyz$ denote the bitstring that results. Recall that, under the qubit labeling used by Qiskit, $x$ would correspond to the outcome on qubit 2, $y$ to the outcome on qubit 1, and $z$ to the outcome on qubit 0. This representation of the bitstring puts the most significant bit (MSB) on the left, and the least significant bit (LSB) on the right. This is the standard ordering of binary bitstrings. We order the qubits in the same way, which is why Qiskit uses a non-standard tensor product order.
The probability of obtaining outcome $xyz$ is given by $$\mathrm{Pr}(xyz) = |\langle xyz | \psi \rangle |^{2}.$$ By explicit computation, we see there are only two bitstrings that will occur: $000$ and $111$. If the bitstring $000$ is obtained, the state of the qubits is $|000\rangle$, and if the bitstring is $111$, the qubits are left in the state $|111\rangle$. The probability of obtaining 000 or 111 is the same; namely, 1/2: $$\begin{align} \mathrm{Pr}(000) &= |\langle 000 | \psi \rangle |^{2} = \frac{1}{2}\\ \mathrm{Pr}(111) &= |\langle 111 | \psi \rangle |^{2} = \frac{1}{2}. \end{align}$$
To simulate a circuit that includes measurement, we need to add measurements to the original circuit above, and use a different Aer backend.
In [11]:
# Create a Classical Register with 3 bits.
c = ClassicalRegister(3, 'c')
# Create a Quantum Circuit
meas = QuantumCircuit(q, c)
meas.barrier(q)
# map the quantum measurement to the classical bits
meas.measure(q,c)
# The Qiskit circuit object supports composition using
# the addition operator.
qc = circ+meas
#drawing the circuit
circuit_drawer(qc)
Out[11]:
This circuit adds a classical register, and three measurements that are used to map the outcome of qubits to the classical bits.
To simulate this circuit, we use the qasm_simulator
in Qiskit Aer. Each run of this circuit will yield either the bitstring 000 or 111. To build up statistics about the distribution of the bitstrings (to, e.g., estimate $\mathrm{Pr}(000)$), we need to repeat the circuit many times. The number of times the circuit is repeated can be specified in the execute
function, via the shots
keyword.
In [12]:
# Use Aer's qasm_simulator
backend_sim = Aer.get_backend('qasm_simulator')
# Execute the circuit on the qasm simulator.
# We've set the number of repeats of the circuit
# to be 1024, which is the default.
job_sim = execute(qc, backend_sim, shots=1024)
# Grab the results from the job.
result_sim = job_sim.result()
Once you have a result object, you can access the counts via the function get_counts(circuit)
. This gives you the aggregated binary outcomes of the circuit you submitted.
In [13]:
counts = result_sim.get_counts(qc)
print(counts)
Approximately 50 percent of the time the output bitstring is 000. Qiskit Terra also provides a function plot_histogram
which allows you to view the outcomes.
In [14]:
from qiskit.tools.visualization import plot_histogram
plot_histogram(counts)
The estimated outcome probabilities $\mathrm{Pr}(000)$ and $\mathrm{Pr}(111)$ are computed by taking the aggregate counts and dividing by the number of shots (times the circuit was repeated). Try changing the shots
keyword in the execute
function and see how the estimated probabilities change.
To faciliate access to real quantum computing hardware, we have provided a simple API interface. To access IBMQ devices, you'll need an API token. For the public IBM Q devices, you can generate an API token here (create an account if you don't already have one). For Q Network devices, login to the q-console, click your hub, group, and project, and expand "Get Access" to generate your API token and access url.
Our IBMQ provider lets you run your circuit on real devices or on our HPC simulator. Currently, this provider exists within Qiskit, and can be imported as shown below. For details on the provider, see The IBMQ Provider.
In [15]:
from qiskit import IBMQ
After generating your API token, call, IBMQ.save_account('MY_TOKEN')
. For Q Network users, you'll also need to include your access url: IBMQ.save_account('MY_TOKEN', 'URL')
This will store your IBMQ credentials in a local file. Unless your registration information has changed, you only need to do this once. You may now load your accounts by calling,
In [16]:
IBMQ.load_accounts()
Once your account has been loaded, you can view the list of backends available to you.
In [17]:
print("Available backends:")
IBMQ.backends()
Out[17]:
Today's quantum information processors are small and noisy, but are advancing at a fast pace. They provide a great opportunity to explore what noisy, intermediate-scale quantum (NISQ) computers can do.
The IBMQ provider uses a queue to allocate the devices to users. We now choose a device with the least busy queue which can support our program (has at least 3 qubits).
In [18]:
from qiskit.backends.ibmq import least_busy
large_enough_devices = IBMQ.backends(filters=lambda x: x.configuration()['n_qubits'] > 3 and
not x.configuration()['simulator'])
backend = least_busy(large_enough_devices)
print("The best backend is " + backend.name())
To run the circuit on the backend, we need to specify the number of shots and the number of credits we are willing to spend to run the circuit. Then, we execute the circuit on the backend using the execute
function.
In [19]:
shots = 1024 # Number of shots to run the program (experiment); maximum is 8192 shots.
max_credits = 3 # Maximum number of credits to spend on executions.
job_exp = execute(qc, backend=backend, shots=shots, max_credits=max_credits)
job_exp
has a .result()
method that lets us get the results from running our circuit.
In [20]:
result_exp = job_exp.result()
Like before, the counts from the execution can be obtained using get_counts(qc)
In [21]:
counts_exp = result_exp.get_counts(qc)
plot_histogram(counts_exp)
In [22]:
backend = IBMQ.get_backend('ibmq_qasm_simulator')
In [23]:
shots = 1024 # Number of shots to run the program (experiment); maximum is 8192 shots.
max_credits = 3 # Maximum number of credits to spend on executions.
job_hpc = execute(qc, backend=backend, shots=shots, max_credits=max_credits)
In [24]:
result_hpc = job_hpc.result()
In [25]:
counts_hpc = result_hpc.get_counts(qc)
plot_histogram(counts_hpc)
In [26]:
jobID = job_exp.job_id()
print('JOB ID: {}'.format(jobID))
Given a job ID, that job object can be later reconstructed from the backend using retrieve_job:
In [27]:
job_get=backend.retrieve_job(jobID)
and then the results can be obtained from the new job object.
In [28]:
job_get.result().get_counts(qc)
Out[28]:
In [ ]: