Circuits and Qubits


Let’s take a look at the core abstractions that, for the most part, serve as immutable containers for various quantum primitives. The top-level primitive is called Circuit. Each circuit can be thought of as a container for one QubitRegister—a group of qubits (Qubit)—and a sequence of quantum operations (Op).

Circuits can be combined sequentially:

val newCircuit = Circuit(H(0)).combine(Circuit(CNOT(0, 1))

QubitRegister is a collection of qubits. It can be initialized directly with qubits of your choosing:

QubitRegister(Qubit.one, Qubit.fiftyFifty, Qubit.zero)

You can also use a binary string or an integer to initiate it:

QubitRegister("1001")

// is equivalent to

QubitRegister(9)

The register uses a little-endian ordering of bits, meaning that the qubit on the right is the first qubit in the circuit (its index is 0).

All circuits come with an implicit qubit register where all qubits are initialized at $\lvert0\rangle$. The number of qubits is based on the highest operation index. For example, Circuit(H(5)) will be initialized with a register of six qubits (index starts at zero). You can always specify a custom register by applying withRegister to your circuit:

Circuit(CNOT(0, 2)).withRegister(QubitRegister(Qubit.one, Qubit.zero, Qubit.zero))

Circuit(CNOT(0, 2)).withRegister(Qubit.one, Qubit.zero, Qubit.zero)

Circuit(CNOT(0, 2)).withRegister("100")

Circuit(CNOT(0, 2)).withRegister(4)

Qubits are immutable containers with two Complex numbers describing their quantum state:

$\alpha$ and $\beta$ match Qubit a and b parameters. Since these parameters represent qubit probabilities their squares have to add up to one:

Complex case class is a convenient container to represent real and imaginary components of a complex number. Lots of framework methods and case classes work with Complex numbers. In performance-sensitive places, like the Superposition state and gate computations, Array[Float] is used instead. This is done to optimize for garbage collection and general memory usage during quantum computations.

All qubits in the register have to be accessed by their index in the underlying sequence. For easy access to the actual numerical indexes Circuit has a helper property indexes.

Qubits implement the Labeled trait, which means that you can apply a unique string label to any qubit:

val testQubit = Qubit.one("test")

val messageQubit = Qubit(Complex(0.8), Complex(0.6), "message")

Labels are useful for when you need to quickly differentiate between qubits and when you look at the final classical register readouts after the measurement.