Quantum Circuits
qiskit.circuit
Overview
The fundamental element of quantum computing is the quantum circuit. A quantum circuit is a computational routine consisting of coherent quantum operations on quantum data, such as qubits. It is an ordered sequence of quantum gates, measurements and resets, which may be conditioned on real-time classical computation. A set of quantum gates is said to be universal if any unitary transformation of the quantum data can be efficiently approximated arbitrarily well as as sequence of gates in the set. Any quantum program can be represented by a sequence of quantum circuits and classical near-time computation.
In Qiskit, this core element is represented by the QuantumCircuit
class. Below is an example of a quantum circuit that makes a three-qubit GHZ state defined as:
from qiskit import QuantumCircuit
# Create a circuit with a register of three qubits
circ = QuantumCircuit(3)
# H gate on qubit 0, putting this qubit in a superposition of |0> + |1>.
circ.h(0)
# A CX (CNOT) gate on control qubit 0 and target qubit 1 generating a Bell state.
circ.cx(0, 1)
# CX (CNOT) gate on control qubit 0 and target qubit 2 resulting in a GHZ state.
circ.cx(0, 2)
# Draw the circuit
circ.draw()
┌───┐
q_0: ┤ H ├──■────■──
└───┘┌─┴─┐ │
q_1: ─────┤ X ├──┼──
└───┘┌─┴─┐
q_2: ──────────┤ X ├
└───┘
Supplementary Information
Quantum Circuit Properties
When constructing quantum circuits, there are several properties that help quantify the “size” of the circuits, and their ability to be run on a noisy quantum device. Some of these, like number of qubits, are straightforward to understand, while others like depth and number of tensor components require a bit more explanation. Here we will explain all of these properties, and, in preparation for understanding how circuits change when run on actual devices, highlight the conditions under which they change.
Consider the following circuit:
from qiskit import QuantumCircuit
qc = QuantumCircuit(12)
for idx in range(5):
qc.h(idx)
qc.cx(idx, idx+5)
qc.cx(1, 7)
qc.x(8)
qc.cx(1, 9)
qc.x(7)
qc.cx(1, 11)
qc.swap(6, 11)
qc.swap(6, 9)
qc.swap(6, 10)
qc.x(6)
qc.draw()
┌───┐
q_0: ┤ H ├──■────────────────────────────────────────────────────────
├───┤ │
q_1: ┤ H ├──┼────■───────────────────■────■─────────■────────────────
├───┤ │ │ │ │ │
q_2: ┤ H ├──┼────┼────■──────────────┼────┼─────────┼────────────────
├───┤ │ │ │ │ │ │
q_3: ┤ H ├──┼────┼────┼────■─────────┼────┼─────────┼────────────────
├───┤ │ │ │ │ │ │ │
q_4: ┤ H ├──┼────┼────┼────┼────■────┼────┼─────────┼────────────────
└───┘┌─┴─┐ │ │ │ │ │ │ │
q_5: ─────┤ X ├──┼────┼────┼────┼────┼────┼─────────┼────────────────
└───┘┌─┴─┐ │ │ │ │ │ │ ┌───┐
q_6: ──────────┤ X ├──┼────┼────┼────┼────┼─────────┼───X──X──X─┤ X ├
└───┘┌─┴─┐ │ │ ┌─┴─┐ │ ┌───┐ │ │ │ │ └───┘
q_7: ───────────────┤ X ├──┼────┼──┤ X ├──┼──┤ X ├──┼───┼──┼──┼──────
└───┘┌─┴─┐ │ ├───┤ │ └───┘ │ │ │ │
q_8: ────────────────────┤ X ├──┼──┤ X ├──┼─────────┼───┼──┼──┼──────
└───┘┌─┴─┐└───┘┌─┴─┐ │ │ │ │
q_9: ─────────────────────────┤ X ├─────┤ X ├───────┼───┼──X──┼──────
└───┘ └───┘ │ │ │
q_10: ───────────────────────────────────────────────┼───┼─────X──────
┌─┴─┐ │
q_11: ─────────────────────────────────────────────┤ X ├─X────────────
└───┘
From the plot, it is easy to see that this circuit has 12 qubits, and a collection of Hadamard, CNOT, X, and SWAP gates. But how to quantify this programmatically? Because we can do single-qubit gates on all the qubits simultaneously, the number of qubits in this circuit is equal to the width of the circuit:
qc.width()
12
We can also just get the number of qubits directly:
qc.num_qubits
12
For a quantum circuit composed from just qubits, the circuit width is equal to the number of qubits. This is the definition used in quantum computing. However, for more complicated circuits with classical registers, and classically controlled gates, this equivalence breaks down. As such, from now on we will not refer to the number of qubits in a quantum circuit as the width.
It is also straightforward to get the number and type of the gates in a circuit using QuantumCircuit.count_ops()
:
qc.count_ops()
OrderedDict([('cx', 8), ('h', 5), ('x', 3), ('swap', 3)])
We can also get just the raw count of operations by computing the circuits QuantumCircuit.size()
:
qc.size()
19
A particularly important circuit property is known as the circuit depth. The depth of a quantum circuit is a measure of how many “layers” of quantum gates, executed in parallel, it takes to complete the computation defined by the circuit. Because quantum gates take time to implement, the depth of a circuit roughly corresponds to the amount of time it takes the quantum computer to execute the circuit. Thus, the depth of a circuit is one important quantity used to measure if a quantum circuit can be run on a device.
The depth of a quantum circuit has a mathematical definition as the longest path in a directed acyclic graph (DAG). However, such a definition is a bit hard to grasp, even for experts. Fortunately, the depth of a circuit can be easily understood by anyone familiar with playing Tetris. Lets see how to compute this graphically:
We can verify our graphical result using QuantumCircuit.depth()
:
qc.depth()
9
Quantum Circuit API
Quantum Circuit Construction
QuantumCircuit (*regs[, name]) | Create a new circuit. |
QuantumRegister (size[, name]) | Implement a quantum register. |
Qubit (register, index) | Implement a quantum bit. |
ClassicalRegister (size[, name]) | Implement a classical register. |
Clbit (register, index) | Implement a classical bit. |
Gates and Instructions
Gate (name, num_qubits, params[, label]) | Unitary gate. |
ControlledGate (name, num_qubits, params[, …]) | Controlled unitary gate. |
Measure () | Quantum measurement in the computational basis. |
Reset () | Qubit reset. |
Instruction (name, num_qubits, num_clbits, params) | Generic quantum instruction. |
InstructionSet () | Instruction collection, and their contexts. |
EquivalenceLibrary (*[, base]) | A library providing a one-way mapping of Gates to their equivalent implementations as QuantumCircuits. |
Parametric Quantum Circuits
Parameter (_[, uuid]) | Parameter Class for variable parameters. |
ParameterVector (name[, length]) | ParameterVector class to quickly generate lists of parameters. |
ParameterExpression (symbol_map, expr) | ParameterExpression class to enable creating expressions of Parameters. |
Random Circuits
random.random_circuit (num_qubits, depth[, …]) | Generate random circuit of arbitrary size and form. |