Qiskit basics

Qiskit is an open-source framework for working with quantum computers. It is maintained and developed by IBM. It provides tools for creating, manipulating and simulating quantum circuits. Qiskit also includes libraries for different quantum algorithms. It has a developer community (available on Slack) and detailed documentation (Qiskit documentation).

The foundation of the Qiskit is module Terra, which provides the essential tools to build and run quantum circuits. In addition, Terra defines the interfaces for an end-user experience and the layers of optimization, pulse scheduling, and backend communication. The detailed and more comprehensive tutorial can be found here.

Aer module has different emulators that can imitate the working quantum computer. Aer provides a high-performance emulator framework for quantum circuits using the Qiskit software stack. It contains optimized C++ emulator backends for executing circuits compiled in Terra.

To create a quantum circuit, we need to create an instance of the QuantumCircuit class and specify the number of qubits.

[1]:
from qiskit import QuantumCircuit

circuit = QuantumCircuit(4) # Create a QuantumCircuit object with 4 qubits
circuit.draw() # Visualize the circuit (now, it is an empty circuit, but one should notice that the designation of the qubits starts from zero)
[1]:
q_0:

q_1:

q_2:

q_3:
     

After creating the empty circuit with its quantum registers, we can add the operators (gates) to develop and execute the circuit. In Qiskit, operators can be added to the circuit one by one. The complete list of available gates and their usage can be found in Qiskit’s documentation.

For instance, see: qiskit gates.

It is important to note that the gates (operators) are added to the circuit as a method call on the same circuit (QuantumCircuit) object.

[2]:
circuit.h(2)     # Add a H (Hadamard) gate on qubit 3
circuit.cx(2, 3) # Add a CX (CNOT) gate on control qubit 3, and target qubit 4
circuit.x(0)     # Add Pauli X gate on the first qubit
circuit.measure_all()
circuit.draw()   # Draw the circuit
[2]:
        ┌───┐      ░ ┌─┐
   q_0: ┤ X ├──────░─┤M├─────────
        └───┘      ░ └╥┘┌─┐
   q_1: ───────────░──╫─┤M├──────
        ┌───┐      ░  ║ └╥┘┌─┐
   q_2: ┤ H ├──■───░──╫──╫─┤M├───
        └───┘┌─┴─┐ ░  ║  ║ └╥┘┌─┐
   q_3: ─────┤ X ├─░──╫──╫──╫─┤M├
             └───┘ ░  ║  ║  ║ └╥┘
meas: 4/══════════════╩══╩══╩══╩═
                      0  1  2  3 
[3]:
circuit.draw('mpl') # Drawing a circuit using matplotlib (prettier drawing)
[3]:
_images/1._Short_introduction_to_Qiskit_4_0.png

Using Qiskit’s Aer module, we can get the results of a perfect simulation of the desired quantum circuit. It means we will get the results without noise or errors, which is invalid for a real quantum computer in the NISQ era.

To perform a simulation using the Aer emulator, first, we must choose which backend will be used. In this context, the backend represents the machine for executing our circuit.

In this example, the quantum circuit will run on a statevector emulator backend which will execute on the local machine. After choosing the backend, we run the circuit by calling the run() method, which returns a job object that encapsulates all the necessary information about the circuit submitted for execution.

[4]:
from qiskit_aer import AerSimulator

backend = AerSimulator()
job = backend.run(circuit)

The job object created after running the circuit on some backend has two essential methods. One is status(), which returns the status of the job, and the other is result(), which returns the job’s result. Knowing that the job runs asynchronously is crucial, so we do not get the results automatically but have to wait for them.

There are two possible approaches for getting the result of one job: - Call the status() method to check the current status of a job. If the status of a job is finished, we can get its results; if not, we can wait some time and recheck the status until the job is finished. In the end, we can get the job results. - Call the result() function, which will block while it waits for a job to finish. It will return when the job finishes its execution.

[5]:
from qiskit.providers import JobStatus
import time

# First approach
job_final_states = [JobStatus.DONE, JobStatus.ERROR, JobStatus.CANCELLED]
def get_job_result(job_obj):
    status = job_obj.status()
    if status in job_final_states:
        return job_obj.result()
    time.sleep(1)

result = get_job_result(job)
print(f"Counts: {result.get_counts(circuit)}")

# Second approach
result = job.result()
print(f"Statevector: {result.get_counts(circuit)}")
Counts: {'1101': 503, '0001': 521}
Statevector: {'1101': 503, '0001': 521}

OpenQASM

We can obtain the quantum circuit’s OpenQASM format using the Qiskit library.

OpenQASM stands for the Open Quantum Assembly Language developed by IBM. It is a textual language that describes quantum circuits implemented on a quantum computer or simulated on a classical computer. It is a low-level language that allows quantum algorithms to be expressed in a way that is independent of the specific hardware being used.

qasm()in Qiskit should convert our quantum circuit to the QASM format, but its functionality has known problems. For example, it could be possible that the obtained QASM string is wrong, especially for the larger circuits, so we need to pay attention when using this method and verify its results.

[8]:
from qiskit.qasm2 import dumps
qasm_str = dumps(circuit)
print(qasm_str)
OPENQASM 2.0;
include "qelib1.inc";
qreg q[4];
creg meas[4];
h q[2];
cx q[2],q[3];
x q[0];
barrier q[0],q[1],q[2],q[3];
measure q[0] -> meas[0];
measure q[1] -> meas[1];
measure q[2] -> meas[2];
measure q[3] -> meas[3];
[9]:
circuit2 = QuantumCircuit.from_qasm_str(qasm_str)
circuit2.draw('mpl')
[9]:
_images/1._Short_introduction_to_Qiskit_11_0.png
[ ]: