Quantum Circuits

The QuantumCircuit, QuantumRegister, and ClassicalRegister are the main objects for Qiskit Terra. Most users will be able to do all they want with these objects.

NOTE THIS ONLY WORKS ON QISKIT 0.7


In [1]:
import numpy as np
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit import Aer, execute
from qiskit.quantum_info import Pauli, state_fidelity, basis_state, process_fidelity

Quantum and Classical Registers

Quantum and Classical Registers are declared using the following:


In [2]:
q0 = QuantumRegister(2, 'q0')
c0 = ClassicalRegister(2, 'c0')
q1 = QuantumRegister(2, 'q1')
c1 = ClassicalRegister(2, 'c1')
q_test = QuantumRegister(2, 'q0')

The name is optional. If not given Qiskit will name it $qi$ where $i$ is an interger which will count from 0. The name and size can be returned using the following:


In [3]:
print(q0.name)
print(q0.size)


q0
2

You can test if the register are the same or different.


In [4]:
q0==q0


Out[4]:
True

In [5]:
q0==q_test


Out[5]:
True

In [6]:
q0==q1


Out[6]:
False

Quantum Circuits

Quantum Circuits are made using registers. Either when initiated or by using the add_register command.


In [7]:
circ = QuantumCircuit(q0, q1)
circ.x(q0[1])
circ.x(q1[0])
circ.draw()


Out[7]:
                   
q0_0: |0>──────────
              ┌───┐
q0_1: |0>─────┤ X ├
         ┌───┐└───┘
q1_0: |0>┤ X ├─────
         └───┘     
q1_1: |0>──────────
                   

is the same as


In [8]:
circ2 = QuantumCircuit()
circ2.add_register(q0)
circ2.add_register(q1)
circ2.x(q0[1])
circ2.x(q1[0])
circ2.draw()


Out[8]:
                   
q0_0: |0>──────────
              ┌───┐
q0_1: |0>─────┤ X ├
         ┌───┐└───┘
q1_0: |0>┤ X ├─────
         └───┘     
q1_1: |0>──────────
                   
Note: The order of registers in the list is the order they are initiated or added (**not** the tensor product for quantum registers).

In [9]:
from copy import deepcopy

q3 = QuantumRegister(2, 'q3')
circ3 = deepcopy(circ)
circ3.add_register(q3)
circ3.draw()


Out[9]:
                   
q0_0: |0>──────────
              ┌───┐
q0_1: |0>─────┤ X ├
         ┌───┐└───┘
q1_0: |0>┤ X ├─────
         └───┘     
q1_1: |0>──────────
                   
q3_0: |0>──────────
                   
q3_1: |0>──────────
                   
Note: The circuit drawer has the last register added at the top and if we add a new register it will add it to the top of the circuit.

TODO: CHECK AFTER UPDATE THAT THIS IS CORRECT

Extending a circuit

In many situations you may have two circuits that you want to concatenate together to form a new circuit. This is very useful when one circuit has no measurements and the final circuit represents a measurement.


In [10]:
meas = QuantumCircuit(q0, q1, c0, c1)
meas.measure(q0, c0)
meas.measure(q1, c1)

qc = circ + meas

qc.draw()


Out[10]:
                            ┌─┐
q0_0: |0>───────────────────┤M├
                    ┌───┐┌─┐└╥┘
q0_1: |0>───────────┤ X ├┤M├─╫─
            ┌───┐┌─┐└───┘└╥┘ ║ 
q1_0: |0>───┤ X ├┤M├──────╫──╫─
         ┌─┐└───┘└╥┘      ║  ║ 
q1_1: |0>┤M├──────╫───────╫──╫─
         └╥┘      ║       ║  ║ 
 c0_0: 0 ═╬═══════╬═══════╬══╩═
          ║       ║       ║    
 c0_1: 0 ═╬═══════╬═══════╩════
          ║       ║            
 c1_0: 0 ═╬═══════╩════════════
          ║                    
 c1_1: 0 ═╩════════════════════
                               

In [11]:
meas2 = QuantumCircuit()
meas2.add_register(q0)
meas2.add_register(q1)
meas2.add_register(c0)
meas2.add_register(c1)
meas2.measure(q0, c0)
meas2.measure(q1, c1)

qc2 = circ2 + meas2

qc2.draw()


Out[11]:
                            ┌─┐
q0_0: |0>───────────────────┤M├
                    ┌───┐┌─┐└╥┘
q0_1: |0>───────────┤ X ├┤M├─╫─
            ┌───┐┌─┐└───┘└╥┘ ║ 
q1_0: |0>───┤ X ├┤M├──────╫──╫─
         ┌─┐└───┘└╥┘      ║  ║ 
q1_1: |0>┤M├──────╫───────╫──╫─
         └╥┘      ║       ║  ║ 
 c0_0: 0 ═╬═══════╬═══════╬══╩═
          ║       ║       ║    
 c0_1: 0 ═╬═══════╬═══════╩════
          ║       ║            
 c1_0: 0 ═╬═══════╩════════════
          ║                    
 c1_1: 0 ═╩════════════════════
                               

It even works when the circuits have different registers. Let's start by making two new circuits:


In [12]:
circ4 = QuantumCircuit(q1)
circ4.x(q1)
circ4.draw()


Out[12]:
              ┌───┐
q1_0: |0>─────┤ X ├
         ┌───┐└───┘
q1_1: |0>┤ X ├─────
         └───┘     

In [13]:
circ5 = QuantumCircuit(q3)
circ5.h(q3)
circ5.draw()


Out[13]:
              ┌───┐
q3_0: |0>─────┤ H ├
         ┌───┐└───┘
q3_1: |0>┤ H ├─────
         └───┘     

The new register is added to the circuit:


In [14]:
(circ4+circ5).draw()


Out[14]:
                        ┌───┐
q1_0: |0>───────────────┤ X ├
                   ┌───┐└───┘
q1_1: |0>──────────┤ X ├─────
              ┌───┐└───┘     
q3_0: |0>─────┤ H ├──────────
         ┌───┐└───┘          
q3_1: |0>┤ H ├───────────────
         └───┘               

We have also overloaded += to the QuantumCircuit object:


In [15]:
circ4 += circ5
circ4.draw()


Out[15]:
                        ┌───┐
q1_0: |0>───────────────┤ X ├
                   ┌───┐└───┘
q1_1: |0>──────────┤ X ├─────
              ┌───┐└───┘     
q3_0: |0>─────┤ H ├──────────
         ┌───┐└───┘          
q3_1: |0>┤ H ├───────────────
         └───┘               

Outcomes of Quantum Circuits

In the circuit output, the most significant bit (MSB) is to the left and the least significant bit (LSB) is to the right (i.e. we follow the regular computer science little endian ordering). In this example:


In [16]:
circ.draw()


Out[16]:
                   
q0_0: |0>──────────
              ┌───┐
q0_1: |0>─────┤ X ├
         ┌───┐└───┘
q1_0: |0>┤ X ├─────
         └───┘     
q1_1: |0>──────────
                   

qubit register $Q_0$ is prepared in the state $|10\rangle$ and $Q_1$ is in the state $|01\rangle$ giving a total state $|0110\rangle$ ($Q1\otimes Q0$).

Note: The tensor order in Qiskit goes as $Q_n \otimes .. Q_1 \otimes Q_0$

That is the four qubit statevector of length 16 with the 6th element (int('0110',2)=6) being one. Note the element count starts from zero.


In [17]:
backend_sim = Aer.get_backend('statevector_simulator')
result = execute(circ, backend_sim).result()
state = result.get_statevector(circ)
print(state)


[0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+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]

To check the fidelity of this state with the basis_state in Qiskit Terra you can use:


In [19]:
state_fidelity(basis_state('0110', 4), state)


Out[19]:
1.0

We can also use Qiskit Terra to make the unitary operator representing the circuit (provided there are no measurements). This will be a $16\otimes16$ matrix equal to $I\otimes X\otimes X\otimes I$. To check this is correct we can use the Pauli class and the process_fidelity function.


In [20]:
backend_sim = Aer.get_backend('unitary_simulator')
result = execute(circ, backend_sim).result()
unitary = result.get_unitary(circ)
process_fidelity(Pauli(label='IXXI').to_matrix(), unitary)


Out[20]:
1.0

To map the information of the quantum state to the classial world we have to use the example with measurements qc:


In [21]:
qc.draw()


Out[21]:
                            ┌─┐
q0_0: |0>───────────────────┤M├
                    ┌───┐┌─┐└╥┘
q0_1: |0>───────────┤ X ├┤M├─╫─
            ┌───┐┌─┐└───┘└╥┘ ║ 
q1_0: |0>───┤ X ├┤M├──────╫──╫─
         ┌─┐└───┘└╥┘      ║  ║ 
q1_1: |0>┤M├──────╫───────╫──╫─
         └╥┘      ║       ║  ║ 
 c0_0: 0 ═╬═══════╬═══════╬══╩═
          ║       ║       ║    
 c0_1: 0 ═╬═══════╬═══════╩════
          ║       ║            
 c1_0: 0 ═╬═══════╩════════════
          ║                    
 c1_1: 0 ═╩════════════════════
                               

This will map the quantum state to the classical world and since the state has no superpositions it will be deterministic and equal to '01 10'. Here a space is used to separate the registers.


In [22]:
backend_sim = Aer.get_backend('qasm_simulator')
result = execute(qc, backend_sim).result()
counts = result.get_counts(qc)
print(counts)


{'01 10': 1024}

To show that it does not matter how you add the registers we run the same as above on the second example circuit:


In [23]:
backend_sim = Aer.get_backend('statevector_simulator')
result = execute(circ2, backend_sim).result()
states = result.get_statevector(circ2)

backend_sim = Aer.get_backend('qasm_simulator')
result = execute(qc2, backend_sim).result()
counts = result.get_counts(qc2)

backend_sim = Aer.get_backend('unitary_simulator')
result = execute(circ2, backend_sim).result()
unitary = result.get_unitary(circ2)

In [24]:
print(counts)


{'01 10': 1024}

In [25]:
state_fidelity(basis_state('0110', 4), state)


Out[25]:
1.0

In [26]:
process_fidelity(Pauli(label='IXXI').to_matrix(), unitary)


Out[26]:
1.0

Counting circuit resources

A QuantumCircuit object provides methods for inquiring its resource use. This includes the number of qubits, operations, and a few other things.


In [27]:
q = QuantumRegister(6)
circuit = QuantumCircuit(q)
circuit.h(q[0])
circuit.ccx(q[0], q[1], q[2])
circuit.cx(q[1], q[3])
circuit.x(q)
circuit.h(q[2])
circuit.h(q[3])
circuit.draw()


Out[27]:
                   ┌───┐               ┌───┐                    
q0_0: |0>──────────┤ H ├──■────────────┤ X ├────────────────────
                   └───┘  │            └───┘               ┌───┐
q0_1: |0>─────────────────■───────────────────■────────────┤ X ├
                        ┌─┴─┐┌───┐┌───┐       │            └───┘
q0_2: |0>───────────────┤ X ├┤ X ├┤ H ├───────┼─────────────────
                        └───┘└───┘└───┘     ┌─┴─┐┌───┐┌───┐     
q0_3: |0>───────────────────────────────────┤ X ├┤ X ├┤ H ├─────
              ┌───┐                         └───┘└───┘└───┘     
q0_4: |0>─────┤ X ├─────────────────────────────────────────────
         ┌───┐└───┘                                             
q0_5: |0>┤ X ├──────────────────────────────────────────────────
         └───┘                                                  

In [28]:
# total number of operations in the circuit. no unrolling is done.
circuit.size()


Out[28]:
11

In [29]:
# depth of circuit (number of ops on the critical path)
circuit.depth()


Out[29]:
5

In [30]:
# number of qubits in the circuit
circuit.width()


Out[30]:
6

In [32]:
# a breakdown of operations by type
circuit.count_ops()


Out[32]:
{'ccx': 1, 'cx': 1, 'h': 3, 'x': 6}

In [33]:
# number of unentangled subcircuits in this circuit.
# each subcircuit can in principle be executed on a different quantum processor!
circuit.num_tensor_factors()


Out[33]:
3

One useful application of this is that you can easily compare your original circuit with the circuit after it goes through transpilation.

Add an example of this after release and stabilization of transpile API.


In [ ]: