/ Programming

Quantum Programming 101

Are you familiar with programming, but know absolutely nothing about quantum mechanics?

This was exactly my case when the IBM Quantum Experience was announced. For the first time, a quantum computer has been made publicly available, and anybody selected to participate could send programs to it.

I applied. I was selected. And then I had a really rough time attempting to decipher the user guide of the IBM 5Q quantum computer I had access to. The IBM staff tried to ease the learning curve, but I felt that the documentation made a lot of assumptions that a non-physicist do not have, and "intuitive" ideas presented to me (like the Bloch sphere) just confused me even more.

Therefore, I decided to compile below a generic introduction on quantum programming designed for classical programmers to ease their learning curve. I hope it will be useful to you and anybody else you share it to.

Be warned however that quantum programming requires some pretty advanced mathematical knowledge, but as long as you're familiar with probability, binary numbers, complex numbers and linear algebra, you should be fine.

Are you ready? Let's go!

Meet the qubit

The qubit is an extension of the bit. Just like the bit, it returns a value of 0 or 1 when read, and the returned value doesn't change when read multiple times consecutively.

Unlike the bit however, the qubit does not simply store a value of 0 or 1. In fact, its internals cannot be well-defined, as its output is in general dependent on global factors which cannot be fully known. For example, when qubits interact with each other, they may become entangled in a way which causes an operation on one to affect the output of the other regardless of the physical distance between them.

What is possible however is to determine the probability of a qubit returning 0 or 1 based on our partial knowledge of it and all the other qubits it has interacted with during previous operations.

Note that a qubit not guaranteed to return a specific value is said to be in a superposition state.

Probability amplitudes

A probability amplitude is a complex number directly linked to a possible qubits' output combination. The probability of reading a specific output combination is equal to the square of the absolute value of its probability amplitude.

Therefore, the known state of an entire set of qubits can be stored as a vector of all probability amplitudes of this set. By convention, this vector is a column vector ordered from lowest to highest possible binary outputs. Such a vector is called a quantum state.

Here is an example of a quantum state of 2 entangled qubits:

Quantum state for q 1 q 2 = [ Probability amplitude of 00 Probability amplitude of 01 Probability amplitude of 10 Probability amplitude of 11 ] = [ i / 2 0 0 i / 2 ] Probability of 00 = | Probability amplitude of 00 | 2 = | i / 2 | 2 = 1 / 2 Probability of 01 = | Probability amplitude of 01 | 2 = | 0 | 2 = 0 Probability of 10 = | Probability amplitude of 10 | 2 = | 0 | 2 = 0 Probability of 11 = | Probability amplitude of 11 | 2 = | i / 2 | 2 = 1 / 2 Quantum state for q_1 q_2 = left [ matrix{ Probability amplitude of 00 ## Probability amplitude of 01 ## Probability amplitude of 10 ## Probability amplitude of 11 } right ] = left [ matrix{ i / sqrt{2} ## 0 ## 0 ## -i / sqrt{2} } right ] newline Probability of 00 = {abs{ Probability amplitude of 00 }}^2 = {abs{ i / sqrt{2} }}^2 = 1/2 newline Probability of 01 = {abs{ Probability amplitude of 01 }}^2 = {abs{ 0 }}^2 = 0 newline Probability of 10 = {abs{ Probability amplitude of 10 }}^2 = {abs{ 0 }}^2 = 0 newline Probability of 11 = {abs{ Probability amplitude of 11 }}^2 = {abs{ -i / sqrt{2} }}^2 = 1/2

Initial state

A standard quantum computer initializes its qubits as independent quantum states with probability amplitudes of 1 for returning 0 and 0 for returning 1:

[ 1 0 ] left [ matrix{1 ## 0} right ]

In other words, all qubits are intialized to return 0.

System scaling

Quantum states can be combined as a single quantum state in order to operate on qubits between them. This is done by calculating the Kronecker (tensor) product of the quantum states:

[ 1 0 ] [ 1 0 ] = [ 1 0 0 0 ] left [ matrix{1 ## 0} right ] otimes left [ matrix{1 ## 0} right ] = left [ matrix{1 ## 0 ## 0 ## 0} right ]

Note that two groups of different qubits sharing a quantum state are entangled if and only if they cannot be described as independent quantum states.

Quantum gates

While classical computers uses logic gates to operate on bits, quantum computers uses quantum gates to operate on qubits. A quantum gate can be represented as a unitary matrix of side length of 2 to the power of the number of qubits it operates on.

The effect of a quantum gate is calculated by multiplying the quantum gate matrix with the quantum state of the qubit set going through it. The result is a new quantum state.

Here is an example of a 1-qubit quantum gate applied on an initial quantum state to make it always return 1:

X gate = [ 0 1 1 0 ] Quantum state for q 1 = [ 1 0 ] X ( q 1 ) [ 0 1 1 0 ] [ 1 0 ] = [ 0 1 ] X gate = left [ matrix{0 # 1 ## 1 # 0} right ] newline Quantum state for q_1 = left [ matrix{1 ## 0} right ] newline X (q_1) drarrow left [ matrix{0 # 1 ## 1 # 0} right ] left [ matrix{1 ## 0} right ] = left [ matrix{0 ## 1} right ]

In the case where a quantum gate operates on a subset of the total number of entangled qubits, the Kronecker product can again be used to expand the quantum gate matrix with a 2 by 2 identity matrix per qubit. This allows quantum computers to execute some programs significantly faster on average than what is possible on classical computers, since the execution time to apply a specific quantum gate instruction is constant.

For example, here is a 2-qubit quantum gate that reverses the second qubit's output probabilities only if the first qubit would return 1, but applied on a 3-qubit quantum state that always returns 100 to make it always return 110 instead:

CNOT gate = [ 1 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0 ] Quantum state for q 1 q 2 q 3 = [ 0 0 0 0 1 0 0 0 ] CNOT ( q 1 , q 2 ) ( [ 1 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0 ] [ 1 0 0 1 ] ) [ 0 0 0 0 1 0 0 0 ] = [ 0 0 0 0 0 0 1 0 ] CNOT gate = left [ matrix{ 1 # 0 # 0 # 0 ## 0 # 1 # 0 # 0 ## 0 # 0 # 0 # 1 ## 0 # 0 # 1 # 0 } right ] newline Quantum state for q_1 q_2 q_3 = left [ matrix{ 0 ## 0 ## 0 ## 0 ## 1 ## 0 ## 0 ## 0 } right ] newline CNOT(q_1,q_2) drarrow left ( left [ matrix{ 1 # 0 # 0 # 0 ## 0 # 1 # 0 # 0 ## 0 # 0 # 0 # 1 ## 0 # 0 # 1 # 0 } right ] otimes left [ matrix{ 1 # 0 ## 0 # 1 } right ] right ) left [ matrix{ 0 ## 0 ## 0 ## 0 ## 1 ## 0 ## 0 ## 0 } right ] = left [ matrix{ 0 ## 0 ## 0 ## 0 ## 0 ## 0 ## 1 ## 0 } right ]

Note that a quantum gate's matrix may require a change of basis (through standard linear algebra) if the order of qubits we want to operate on do not match those of the quantum state. This also applies for expanded quantum gate matrices.

Measurement

As mentioned earlier, the probability of reading a specific output is equal to the square of the absolute value of its probability amplitude. However, reading a qubit requires a measurement that collapses the output possibilities and alters the quantum state. In fact, the entire behavior of a quantum program can change by adding a single read operation in the middle of it.

In other words, a read operation also causes a write operation. This must be taken into consideration while debugging a quantum program.

When reading qubits out of a set of qubits, the quantum state collapses in the following way:

  • For possible output combinations that conflict with the read output, their probability amplitudes are set to (or multiplied by) 0.
  • For possible output combinations that match the read output, their probability amplitudes are normalized by dividing them by the square root of the probability of the read output event prior to the read operation.

Here is an example of a read collapse:

Quantum state for q 1 q 2 = [ i / 2 i / 2 i / 2 i / 2 ] Probability of 00 = | i / 2 | 2 = 1 / 4 Probability of 01 = | i / 2 | 2 = 1 / 4 Probability of q 1 returning 0 = 1 / 4 + 1 / 4 = 1 / 2 q 1 returns 0 [ ( i / 2 ) / 1 / 2 ( i / 2 ) / 1 / 2 ( i / 2 ) × 0 ( i / 2 ) × 0 ] = [ i / 2 i / 2 0 0 ] Quantum state for q_1 q_2 = left [ matrix{ i / 2 ## -i / 2 ## -i / 2 ## -i / 2 } right ] newline Probability of 00 = { abs{i/2} } ^ 2 = 1/4 newline Probability of 01 = { abs{-i/2} } ^ 2 = 1/4 newline Probability of q_1 returning 0 = 1/4 + 1/4 = 1/2 newline q_1 returns 0 drarrow left [ matrix{ ( i / 2 ) / sqrt{1/2} ## ( -i / 2 ) / sqrt{1/2} ## ( -i / 2 ) times 0 ## ( -i / 2 ) times 0 } right ] = left [ matrix{ i / sqrt 2 ## -i / sqrt 2 ## 0 ## 0 } right ]

Note that there is a way to describe a partially-collapsed quantum state without considering returned values by mixing the possible quantum states into a density matrix, but this is out of scope of this post. (It makes the mathematics even more complicated.)

Programming

After considering the limitations of the architecture of the quantum computer used, the only thing left is to write a desired quantum program in a compatible programming language, just like any classical program,

Here is an example of a program written in IBM's QASM assembly language:

//IBMQASM 1.1
qreg q,5;
gate h, [[0.7071067811865476,0.7071067811865476],[0.7071067811865476,-0.7071067811865476]];
gate cx, [[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]];
h q[1];
cx q[1], q[2];
measure q[1];
measure q[2];

This example sets two qubits in a popular quantum state (the Bell state) before measuring them. The expected output is 00 with 1/2 probability and 11 with 1/2 probability.

Hardware errors

One major problem in current quantum computers is the instability of qubits, and the process of error injections in a quantum state is called decoherence. In addition, qubit measurement devices also have error biases as well.

Until technology is perfected to reduce these errors to a trivial level, a quantum programmer should take into consideration error probabilities of the computer's architecture, and apply error correction algorithms to its programs when necessary.

Describing a mixed state of probabilities due to these error sources can be done with a density matrix, which is again out of scope of this post.

To be continued?

I hope that my explanation was clear enough for you to be able to design your own quantum programs. Quantum programming is a young science so there's still plenty to discover, but you may want to look at some existing quantum algorithms for inspiration.

Personally, I doubt this will be the end of my journey with quantum programming. I invite you to subscribe to my blog if you want to read follow-up articles on this topic or on other software-related subjects. Until next time!

Special thanks to the entire IBM Quantum Experience staff for introducing me to quantum computers and answering my technical questions!

Bonus content!

bra-ket notation

During my research, I often stumbled on quantum states defined in bra-ket notation. Here is what you should know if you would like to read other sources on the subject:

  • A ket is noted |x> and represents a column vector.
  • A bra is noted <x| and represents the conjugate transpose of |x>.
  • The matrix multiplication <a||b> can be written as <a|b>.
  • If x is a binary number, then |x> is the quantum state with a probability amplitude of 1 for x and probability amplitudes of 0 for everything else. Leading zeroes must be included when writing x in the ket to denote the number of qubits in the quantum state. For example, |000> is the quantum state of three qubits in their initial state.
  • Beware of an abuse of notation for two consecutive bras or two consecutive kets. It is equal to the Kronecker product between them if both bras or kets are quantum states independent from each other. This equality is not true in the general case, which is out of scope of this post.

Quantum gates supported by the IBM 5Q

X = [ 0 1 1 0 ] Z = [ 1 0 0 1 ] Y = [ 0 i i 0 ] H = 1 2 [ 1 1 1 1 ] S = [ 1 0 0 i ] S T = [ 1 0 0 i ] T = [ 1 0 0 1 + i 2 ] T T = [ 0 1 1 1 i 2 ] CNOT = [ 1 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0 ] X = left [ matrix{0 # 1 ## 1 # 0} right ] newline Z = left [ matrix{1 # 0 ## 0 # -1} right ] newline Y = left [ matrix{0 # -i ## i # 0} right ] newline H = {1} over { sqrt{2}} left [ matrix{1 # 1 ## 1 # -1} right ] newline S = left [ matrix{1 # 0 ## 0 # i} right ] newline S^{T} = left [ matrix{1 # 0 ## 0 # -i} right ] newline T = left [ matrix{1 # 0 ## 0 # {1 + i} over { sqrt{2}}} right ] newline T^{T} = left [ matrix{0 # 1 ## 1 # {-1 - i} over { sqrt{2}}} right ] newline CNOT = left [ matrix{1 # 0 # 0 # 0 ## 0 # 1 # 0 # 0 ## 0 # 0 # 0 # 1 ## 0 # 0 # 1 # 0} right ]