Getting Started with Qiskit Terra

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

Circuit basics

Simulating circuits with Qiskit Aer

Running circuits using the IBMQ provider

Code imports


In [ ]:
import numpy as np
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit import execute

Circuit Basics

Building the circuit

The basic elements needed for your first program are the QuantumCircuit, and QuantumRegister.


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)
Note: Naming the QuantumRegister is optional and not required.

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:

  • A Hadamard gate $H$ on qubit 0, which puts it into a superposition state.
  • A controlled-X operation ($C_{X}$) between qubit 0 and qubit 1.
  • A controlled-X operation between qubit 0 and qubit 2.

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]:
<qiskit.extensions.standard.cx.CnotGate at 0x1129a9828>

Visualize Circuit

You can visualize your circuit using Qiskit Terra circuit_drawer, which plots circuit in the form found in many textbooks.


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).

Simulating circuits using Qiskit Aer

Qiskit Aer is our package for simulating quantum circuits. It provides many different backends for doing a simulation.

Statevector backend

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).

When representing the state of a multi-qubit system, the tensor order used in qiskit is different than that use in most physics textbooks. Suppose there are $n$ qubits, and qubit $j$ is labeled as $Q_{j}$. In most textbooks (such as Nielsen and Chuang's "Quantum Computation and Information"), the basis vectors for the $n$-qubit state space would be labeled as $Q_{0}\otimes Q_{1} \otimes \cdots \otimes Q_{n}$. **This is not the ordering used by qiskit!** Instead, qiskit uses an ordering in which the $n^{\mathrm{th}}$ qubit is on the _left_ side of the tesnsor product, so that the basis vectors are labeled as $Q_n\otimes \cdots \otimes Q_1\otimes Q_0$. For example, if qubit zero is in state 0, qubit 1 is in state 0, and qubit 2 is in state 1, qiskit would represent this state as $|100\rangle$, whereas most physics textbooks would represent it as $|001\rangle$. This difference in labeling affects the way multi-qubit operations are represented as matrices. For example, qiskit represents a controlled-X ($C_{X}$) operation with qubit 0 being the control and qubit 1 being the target as $$C_X = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \\\end{pmatrix}.$$

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.

Tip: You can obtain the above parameters in Jupyter. Simply place the text cursor on a function and press Shift+Tab.

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.

Note: Jobs run asynchronous but when the result method is called it switches to synchronous and waits for it to finish before moving on to another task.

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))


simulation:  COMPLETED
[0.707+0.j 0.   +0.j 0.   +0.j 0.   +0.j 0.   +0.j 0.   +0.j 0.   +0.j
 0.707+0.j]

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)


Unitary backend

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))


simulation:  COMPLETED
[[ 0.707+0.j  0.707-0.j  0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j
   0.   +0.j  0.   +0.j]
 [ 0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j
   0.707+0.j -0.707+0.j]
 [ 0.   +0.j  0.   +0.j  0.707+0.j  0.707-0.j  0.   +0.j  0.   +0.j
   0.   +0.j  0.   +0.j]
 [ 0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j  0.707+0.j -0.707+0.j
   0.   +0.j  0.   +0.j]
 [ 0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j  0.707+0.j  0.707-0.j
   0.   +0.j  0.   +0.j]
 [ 0.   +0.j  0.   +0.j  0.707+0.j -0.707+0.j  0.   +0.j  0.   +0.j
   0.   +0.j  0.   +0.j]
 [ 0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j
   0.707+0.j  0.707-0.j]
 [ 0.707+0.j -0.707+0.j  0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j
   0.   +0.j  0.   +0.j]]

OpenQASM backend

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)


{'111': 524, '000': 500}

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.

Running circuits using the IBMQ provider

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()


Available backends:
Out[17]:
[<IBMQBackend('ibmqx4') from IBMQ()>,
 <IBMQBackend('ibmqx5') from IBMQ()>,
 <IBMQBackend('ibmqx2') from IBMQ()>,
 <IBMQBackend('ibmq_16_melbourne') from IBMQ()>,
 <IBMQBackend('ibmq_qasm_simulator') from IBMQ()>]

Running circuits on real devices

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())


The best backend is ibmqx4

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.

Note: When the .result() method is called, the code block will wait until the job has finished before releasing the cell.

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)


Simulating circuits using a HPC simulator

The IBMQ provider also comes with a remote optimized simulator called ibmq_qasm_simulator. This remote simulator is capable of simulating up to 32 qubits. It can be used the same way as the remote real backends.


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)


Retrieving a previously ran job

If your experiment takes longer to run then you have time to wait around, or if you simply want to retrieve old jobs back, the IBMQ backends allow you to do that. First you would need to note your job's ID:


In [26]:
jobID = job_exp.job_id()

print('JOB ID: {}'.format(jobID))


JOB ID: 5be8ae5e17436b0052751909

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]:
{'00000': 367,
 '00001': 10,
 '00010': 30,
 '00011': 27,
 '00100': 22,
 '00101': 83,
 '00110': 50,
 '00111': 435}

In [ ]: