Introduction to Quantum Programming | by Quentin Truong | Towards Data Science

Quantum computers exist! And so does quantum programming! In this article, I’ll walk you through everything you need to know to get started with quantum programming. I’ll start off with some context about how quantum computers differ from computers like your laptop, then explain the fundamentals of quantum programming, and finish with how you can run programs on a real quantum computer for free today.
Before we begin, please note that this article is intended for people who want to learn the full technical details of quantum programming. This article builds up from the mathematical foundation of qubits, quantum gates, and quantum circuit diagrams. This article will not explain quantum algorithms or their advantages, as those topics deserve their own articles.
Since we will be walking through the underlying math of quantum programming, readers will need to know what a vector, matrix, linear combination, and complex number is. I recommend 3Blue1Brown for learning linear algebra, and BetterExplained for learning what a complex number is.
Let’s start off by understanding what quantum computers really are and how they are different from other computers.
A quantum computer is a machine that uses quantum mechanics to perform calculations.
So how is this different from other computers? Well, a computer, in its most basic form, is simply a machine that performs calculations. There’s many different types of computers. In the early days of computers, we actually had mechanical computers — Charles Babbage designed such a machine to perform general-purpose calculations in 1837. Nowadays, our computers are based on digital electronics and operate using bits and logic gates. A quantum computer, conversely, uses quantum mechanics to perform computation. Rather than bits and logic gates, quantum computers use qubits and quantum gates.
So what is a qubit and a quantum gate? Physically, they can be any one of many different things — Google, IBM, Microsoft, and Rigetti all have their own implementations of qubits and quantum gates. For now, we won’t worry about the physical nature of qubits and quantum gates because it is not necessary when first learning about quantum programming.
Before we begin, I highly recommend that you approach quantum programming on a clean mental slate. Don’t go looking for how to declare and set variables, loop over code, create functions, etc. Any preconceptions you have about programming will probably not be useful. Quantum programming is not simply some way to make our existing programs run faster — quantum programming is fundamentally different from contemporary programming.
Let’s start with what a qubit is.
A qubit is a vector of two complex numbers with unit length.
Let’s walk through why qubits are this way and what it really means. Qubits are quite different from bits. For starters, a bit is either 0 or 1. There are no probabilities here, it’s either known to be a 0 or it’s known to be a 1. A qubit, conversely, is inherently probabilistic, meaning that two identical qubits may have different values once measured! Take a moment to really consider the gravity of this. This means that quantum computing is inherently probabilistic.
Now, here’s a second key difference. With bits, we can read the bit as many times as we want without affecting the state of the bit. But with qubits, once measured, it decoheres (loses its quantum properties) and collapses to one of two measurable states (hence the ‘bit’ in ‘qubit’). Hence, we cannot ‘unmeasure’ a qubit; once measured, the quantum nature is destroyed and cannot be recovered. We quantify the probabilistic nature of measuring a qubit using two numbers: |α|2, the probability that the qubit will be measured as 0, and |β|2, the probability that the qubit will be measured as 1.
Although |α|2 and |β|2 reflect the probabilities of what the qubit will be measured as, we think of the internal state of a qubit as two ‘probability amplitudes’, α and β. These are complex numbers which define a superposition between 0 and 1 (a superposition is a linear combination) and cannot be measured.
In other words, we think of a qubit as a vector of two complex numbers with unit length (length of the vector is equal to 1). We can concisely express this as math as shown in the following picture (the vector containing the alpha and beta is the qubit; the bar above alpha and beta denotes the complex conjugate):
To recap, qubits are a vector of two complex numbers, α and β, where the vector has unit length. The probability that the qubit will be measured as 0 is equal to the magnitude squared of α, |α|2. The probability that the qubit will be measured as 1 is equal to the magnitude squared of β, |β|2. A qubit’s state, α and β, cannot be measured. Only the value that a qubit collapses into can be measured.
We often denote qubits using Dirac notation, also known as Bra-ket notation. This notation is just a convenient way to write vectors. The bra represents row vectors and is denoted ⟨ ∣; the ket represents column vectors and is denoted ∣ ⟩. For example, we can write the ‘0’ and ‘1’ states of a qubit in Bra-ket notation as follows (be careful not to confuse what’s inside the bra/ket with what’s inside the vector!):
Qubits can either be in pure states or mixed states. If the state of a qubit can be fully described using a linear combination of ∣0⟩ and ∣1⟩, then we say its in a pure state. We often denote pure state qubits using the following notation:
Here are some examples of pure state qubits and common shorthand for denoting them.
Other qubits require mixtures of pure states to fully describe them, so we call them mixed state qubits. In other words, a mixed state qubit is described by a probability distribution over pure states. We’ll see an example of mixed state qubits later in this article (I’ll point it out).
Up until now, we have only defined the state of a single qubit. What does the combined state of multiple qubits look like?
The combined state of multiple qubits is the tensor product of all the qubits.
Don’t worry if you don’t know what a tensor product is; we’ll walk through an example (⊗ is the symbol for the tensor product operation).
In general, we can tensor product any two matrices by following two steps:
Scalar multiply each element in the first matrix by the entire second matrixCombine the resulting matrices according to the original position of their elements
Scalar multiply each element in the first matrix by the entire second matrix
Combine the resulting matrices according to the original position of their elements
Here’s a second example of how it works for 2-dimensional matrices:
We can also denote multiple qubits in Bra-ket notation as ∣0⟩⊗∣1⟩, for example. As a shorthand, we can omit the ⊗ and simply write ∣0⟩∣1⟩. As an even shorter shorthand, we can write just a single ket, ∣01⟩.
Now let’s consider what a quantum gate is.
A quantum gate is a unitary matrix.
Let’s get some context for why quantum gates are unitary matrices. First of all, quantum gates will be implemented by physical devices, and so they must abide by the laws of quantum physics. One relevant law of physics is that no information is ever lost when transitioning between points in the past and the future1. This is known as unitarity. Since our quantum gates define how we transition between states, they too must abide by unitarity.
Secondly, note that our quantum gates will be applied to qubits. We learned earlier that qubits are really just vectors, and so that means quantum gates must somehow operate on vectors. Fortunately, we recall that a matrix is actually just a linear transformation for vectors!
Combining these two ideas, we think of quantum gates as unitary matrices. A unitary matrix is any square matrix of complex numbers such that the conjugate transpose is equal to its inverse. As a quick refresher, the conjugate transpose of a matrix is found by taking the conjugate of each element in the matrix (a + bi → a — bi), and then taking the transpose of the matrix (element ij → element ji). We typically denote the conjugate transpose by the dagger, †.
A key observation about unitary matrices is that they preserve the norm (length of a vector). Suppose we allowed gates that changed the norm, then our qubit’s probabilities may sum to something other than one! That doesn’t make sense since the sum of all probabilities must always be equal to one.
Also notice that, by definition, unitary matrices have an inverse. One implication of this is that we cannot ‘assign’ qubits to arbitrary states. To understand why not, let’s pretend that we did have a quantum gate that could ‘assign’ values, hence, convert any vector of two complex numbers into a specific vector of two complex numbers. This quantum gate would have some underlying representation as a unitary matrix, and that matrix would have an inverse capable of converting a specific vector back into whatever state the qubit was before the operation! But the qubit could have been in any state before the operation and there’s no way to know which! Hence, we cannot ‘assign’ qubits to an arbitrary state. At a higher level, the fact that all quantum gates are invertible is why we often think of quantum computing as a form of reversible computing.
Lastly, notice that because our quantum gates are unitary matrices, they are square by definition, and so our quantum gates must have an equal number of input and output qubits (because square matrices map n standard basis vectors to n columns)! This is quite different from most logic gates; for example, the AND gate takes two inputs and produces one output.
Now that we know a little bit about what we are working with, let’s consider an example, the Hadamard gate, H.
We can check that H is unitary by checking that the conjugate transpose is equal to its inverse, or in other words, that H multiplied by its conjugate transpose is equal to the Identity matrix:
Another important quantum gate is the Controlled NOT gate, also known as CNOT. CNOT acts on two qubits, a control qubit and a target qubit. We can think of CNOT as an ‘if statement’ — if the control qubit is equal to 1, then CNOT applies NOT (the inverse gate) to the target qubit (hence the name, Controlled NOT).
Here is the matrix representing CNOT. This matrix treats the control qubit as the rightmost value inside the ket and the target qubit as the leftmost value.
Let’s see its effect on ∣00⟩.
In this example, we see that CNOT doesn’t modify the value of ∣00⟩. And this is the expected behavior, as CNOT only inverts the target if the control is 1.
Let’s see its effect on ∣01⟩.
Here, we can see that the control is equal to 1, so CNOT inverts the target. Hence, the result is ∣11⟩.
Try working out the other two cases, ∣10⟩ and ∣11⟩. You should find that CNOT has the following behavior:
∣00⟩ -> ∣00⟩
∣01⟩ -> ∣11⟩
∣10⟩ -> ∣10⟩
∣11⟩ -> ∣01⟩
And notice that this is precisely the behavior of applying NOT to the target bit when the control bit is 1.
To recap, we can think of quantum gates as unitary matrices. This unitarity enforces the constraint that a qubit’s probabilities sum to one and causes quantum computing to be reversible. Since unitary matrices are square, we find that quantum gates must have an equal number of input and output qubits. We learned about Hadamard and CNOT, which are two important quantum gates. There exist many more quantum gates.
Now that we know the basics of qubits and quantum gates, let’s see our first quantum circuit diagram.
Quantum circuit diagrams are how we think about quantum ‘programs’. We define the qubits as rows, and we apply quantum gates sequentially from left to right.
Let’s walk through every part of this diagram. First, we have two qubits. Each row corresponds to a qubit. The top row corresponds to the qubit named x0 and the bottom corresponds to the qubit named x1. We consider x0 to be the 0th qubit because we start counting from 0 (the same as in the rest of programming). We write x0 : ∣0⟩ and x1 : ∣0⟩ to mean that x0 and x1 start off in the state ∣0⟩.
The H is the Hadamard gate and is being applied to qubit x0. The ●-⊕ is the CNOT gate, the ● is the control qubit, and the ⊕ is the target qubit. The - is just to help us see which two qubits are affected. In other words, we are applying CNOT where the control is qubit x0 and the target is x1. Note, the order that we apply these gates is important. In this diagram, we applied H first and CNOT second.
The quantum circuit diagram is just one representation of our program. It helps us think about our quantum computation, but other representations can also be useful. We can translate our diagram into a string of symbols, which helps us when preparing to write it as computer code. Having it in string form also makes it easy to translate into the underlying math. This math will tell us the expected output of our program.
Let’s start off by converting our diagram into a string of symbols. Rather than writing our qubits as rows, we’ll use Bra-ket notation. The 0th qubit will be the rightmost qubit in ∣00⟩, just like when writing out binary numbers2. This means that qubit x1 is the leftmost qubit in ∣00⟩. (Note, the quantum physics people tend to reverse this ordering3. Always check the qubit ordering as it’s an incredibly common source of errors.)
We also need to translate the gates. Since we are applying H to qubit x0 and not applying anything to qubit x1 (which is equivalent to applying the Identity gate, I), we’ll write this as (I⊗H). Lastly, we translate the CNOT, specifying which qubit is the control and which is the target. The result is CNOT[control=0, target=1] (I⊗H) ∣00⟩ (note, this string is read from right-to-left). Great! This will be useful when writing the code that’ll be run on the quantum computer.
Having the string representation of the quantum circuit diagram makes it easy to translate our program into the underlying math. There are three pieces, the CNOT[control=0, target=1], (I⊗H), and ∣00⟩. Each piece can be translated into a matrix, as shown in the first row of the following image:
We can even multiply out our matrices to find the resulting state vector, as shown above. This state vector is the expected state of our two qubits after the quantum computation has completed. Alternatively, we can think of it as the output of our program. It tells us the probability amplitudes for each measurable state.
Also, remember our mixed state qubits? Notice that we can’t actually write qubit x0 and qubit x1 in pure states anymore because there isn’t any way to break up the vector with a tensor product. So our qubits are in a mixed state!
What if we measured our qubits now? What would we receive? We can find out by decomposing the state vector into each of the measurable states. We’ll measure our qubits in the standard basis, also known as ∣0⟩ and ∣1⟩ (there are other bases we could measure in, but don't worry about that for now). Hence, our two qubit system’s measurable states are ∣00⟩, ∣01⟩, ∣10⟩, and ∣11⟩.
We can determine the probabilities of the measured values in the same way we used|α|2 to determine the probability of ∣0⟩ for a single qubit. Since ∣01⟩ and ∣10⟩ have a 0 probability amplitude, we know that we will never measure that state. And we will measure both ∣00⟩ and ∣11⟩ with probability (1/sqrt(2))2 = 1/2.
Now, suppose we were to separate these two qubits by a large distance, and then we measured either one. The instant that we measured it, we would know the value of the other qubit too! This is because we know that the qubits can only be ∣00⟩ or ∣11⟩.
This is what Einstein referred to as ‘spooky action at a distance’, also known as quantum entanglement. We think of the information as being correlated rather than traveling. If it were traveling, then it could potentially travel faster than light, which breaks the laws of physics.
Now that we understand what’s happening under the hood with qubits, quantum gates, and quantum circuit diagrams, let’s see how to run on a real quantum computer. I’ll be using Rigetti’s quantum computer since they are currently giving out free credit to beta users. Alternatively, we can also use IBM’s quantum computer.
Here’s a basic overview of the Rigetti quantum programming process:
Write a Python program that specifies your quantum circuit and any additional code necessaryTest that Python program using a quantum simulatorReserve time on Rigetti’s quantum computerSend your program over to Rigetti’s serversExecute your program on Rigetti’s server (they’ll send your quantum program to their quantum computer for you)
Write a Python program that specifies your quantum circuit and any additional code necessary
Test that Python program using a quantum simulator
Reserve time on Rigetti’s quantum computer
Send your program over to Rigetti’s servers
Execute your program on Rigetti’s server (they’ll send your quantum program to their quantum computer for you)
Here’s the Python version of our quantum circuit diagram from above.
The results will look similar to this:
[(0, 0), (1, 1), (1, 1), (0, 0), (0, 0), (0, 0), (1, 1), (0, 0), (0, 0), (1, 1)][(0, 0), (0, 1), (1, 1), (1, 1), (1, 1), (0, 0), (0, 0), (1, 1), (1, 0), (0, 0)]
The first row corresponds to the simulator, and the results seem reasonable — we get [0, 0] roughly half the time and [1, 1] the remaining times. However, with the real quantum computer, aside from the expected [0, 0] and [1, 1], we also receive [0, 1] and [1, 0]. According to the math, we should only ever receive [0, 0] and [1, 1], so what’s going on?
The issue is that real quantum computers today (July 2019) are still quite error-prone.4 For example, we might see an error rate of 2–3% when trying to initialize qubits to 0. And we might have another 1–2% error rate per single-qubit gate operation, and around 3–4% for two-qubit gate operations. We even have error rates when measuring the qubit! In practice, these errors accumulate and result in incorrect values.
In this article, we learned that quantum computers actually do exist and work today, albeit with rather high error rates. And while the physical implementation of these machines varies substantially across companies, many of the concepts for programming them remain the same.
We think of qubits as a vector of two complex numbers with unit length, and we think of quantum gates as unitary matrices. We remember that quantum computing is probabilistic since two identical qubits could have different values once measured. And since quantum gates are unitary, we know that quantum computing is inherently reversible. At a high level, we can think of quantum programming as applied linear algebra on complex numbers.
We used quantum circuit diagrams to denote our quantum program and then converted it into Python to be run on a real quantum computer.
I hope you learned something, and I would be happy to hear any comments or suggestions you might have!
Q) I googled the matrix form of CNOT and it looks different from yours, why?
A) If you are looking at [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]], its because they reversed the target and control qubit ordering. If you see something other than what I wrote above or in this article, then it’s just wrong.
Q) So we have bits and trits for regular computers, does there exist something similar for qubits?
A) Yes, qutrits. Qubits are part of a two-level quantum system. Qutrits are parts of a three-level quantum system. There’s also qudits, which generalize the number of levels.
[1] L. Susskind, Lecture 1 Quantum Entanglements, Part 1 (2008)
[2] Basis vector ordering in Qiskit (2019), Qiskit
[3] R. Smith, Someone Shouts “01000”! Who is excited? (2017), arxiv
[4] Qubit Quality (2019), Quantum Computing Report