Skip to content

Circuit builder

The CircuitBuilder is OpenSquirrel's programmatic API for constructing a circuit. It offers an alternative to writing out a cQASM string. Instead of describing your program as text, you assemble it instruction by instruction, directly in Python.

The CircuitBuilder may be more convenient when the structure of your program is more naturally and easily expressed with code than as a static string. Since you're building in Python, you have all of the language's programmatic tools at your disposal, such as loops, conditionals or list comprehensions, to generate your circuit.

To get started, import the CircuitBuilder from opensquirrel:

from opensquirrel import CircuitBuilder

Instantiating the builder

A builder is created by declaring the sizes of its qubit and (optionally) bit registers:

builder = CircuitBuilder(qubit_register_size=3, bit_register_size=2)

This reserves a qubit register q of size 3 and a bit register b of size 2. Qubits and bits are always referred to by their integer index into these registers, starting at 0.

This is the simplest way to get started. If you need more control, for instance to define multiple named registers, see Named registers below.

Adding instructions

Once the builder is instantiated, instructions are added by calling their name directly on the builder, passing the qubit and bit indices (and any parameters for e.g. parameterized gates) as arguments. The available instructions fall into three main categories:

  • Gates: the unitary instructions, from single-qubit gates such as H, X and Rz to two-qubit gates such as CNOT, abd CZ ,
  • Non-unitaries: init, measure and reset, and
  • Control instructions: barrier and wait.
builder.H(0)
builder.CNOT(0, 1)
builder.Rz(2, 3.14)

Instruction calls can also be chained together into a single expression:

builder.H(0).CNOT(0, 1).CNOT(0, 2)

The builder checks every call as it is made. Referring to a qubit or bit that lies outside its register raises an IndexError, calling an instruction that does not exist raises an AttributeError, and passing the wrong number or type of arguments raises a TypeError.

Instructions can also be appended with add_instruction, which accepts either a single instruction or an iterable of them:

from opensquirrel import CircuitBuilder, H, CNOT

builder = CircuitBuilder(2)
builder.add_instruction(H(0))
builder.add_instruction([H(1), CNOT(0, 1)])

Building the circuit

Calling to_circuit() finalizes the construction and returns the Circuit object:

from opensquirrel import CircuitBuilder

builder = CircuitBuilder(qubit_register_size=2, bit_register_size=2)
builder.add_instruction([H(0) CNOT(0, 1)])
builder.measure(1, 0).measure(0, 1)
circuit = builder.to_circuit()
print(circuit)
1
2
3
4
5
6
7
version 3.0
qubit[2] q
bit[2] b
H q[0]
CNOT q[0], q[1]
b[0] = measure q[1]
b[1] = measure q[0] 

From here on, one can proceed to, for instance, apply compilation passes to the circuit object.

Building circuits programmatically

Because you are building the circuit in Python, the whole language is available to generate the instructions. Loops, conditionals and list comprehensions make it easy to describe circuits whose size or structure depends on a parameter:

from opensquirrel import CircuitBuilder

qubit_register_size = 10
builder = CircuitBuilder(qubit_register_size)
for qubit_index in range(0, qubit_register_size, 2):
    builder.H(qubit_index)
circuit = builder.to_circuit()
print(circuit)
1
2
3
4
5
6
7
8
9
version 3.0

qubit[10] q

H q[0]
H q[2]
H q[4]
H q[6]
H q[8]

The tutorial works through a larger example of this pattern, generating a quantum Fourier transform of arbitrary size.

Single-gate-multiple-qubit notation (SGMQ)

Instructions accept a list of indices wherever they accept a single index, following the single-gate-multiple-qubit (SGMQ) notation. The builder unpacks such a call into separate, consecutive instructions:

builder = CircuitBuilder(3)
builder.H([0, 1, 2])

is equivalent to builder.H(0).H(1).H(2). Any parameters are shared across the expansion, so builder.Rx([0, 1, 2], math.pi / 2) applies the same rotation to each of the three qubits.

For two-operand instructions, such as two-qubit gates and measure, both operands may be lists, in which case they are zipped together and must be of equal length:

builder = CircuitBuilder(4)
builder.CNOT([0, 1], [2, 3])

adds CNOT q[0], q[2] followed by CNOT q[1], q[3].

Named registers

The register created by the constructor is always called q (and b for the bits). If you need more than one named register, for example to keep logical qubits separate from ancillas, start from an empty builder and add the registers yourself with add_register :

from opensquirrel import CircuitBuilder, QubitRegister, BitRegister

builder = CircuitBuilder()

data = QubitRegister(2, "data")
ancilla = QubitRegister(2, "ancilla")
bits = BitRegister(2, "measurement")

builder.add_register(data)
builder.add_register(ancilla)
builder.add_register(bits)

for d, a in zip(data, ancilla):
    builder.CNOT(d, a)
builder.measure(data, bits)

circuit = builder.to_circuit()