You Don’t Need To Be A Mathematician To Master Quantum Computing
You’ll do great as a programmer
Scientific papers and textbooks about quantum computing are full of mathematical formulae. Even blog posts on quantum computing are loaded with mathematical jargon. It starts with the first concept you encounter. The quantum superposition:

As a non-mathematician, this formula might already be too much. If you’re not familiar with the used Dirac-notation (|ψ⟩) or if you’re not used to working with vectors, then such a formula is as good as Egyptian hieroglyphs.
Don’t get me wrong. Math is a great way to describe technical concepts. Math is a concise yet precise language. Our natural languages, such as English, by contrast, are lengthy and imprecise. It takes a whole book full of natural language to explain a small collection of mathematical formulae.
But most of us are far better at understanding natural language than math. We learn our mother tongue as a young child and we practice it every single day. We even dream in our natural language. I couldn’t tell if some fellows dream in math, though. For most of us, math is, at best, a foreign language.
When we’re about to learn something new, it is easier for us if we use our mother tongue. It is hard enough to grasp the meaning of the new concept. If we’re taught in a foreign language, it is even harder. If not impossible.
Of course, math is the native language of quantum mechanics and quantum computing, if you will. But why should we teach quantum computing only in its own language? Shouldn’t we try to explain it in a way more accessible to the learner? I’d say “absolutely”!
Teaching something in the language of the learner doesn’t mean we should not have a look at the math. We should! But, we use math when its precision helps us to explain how things work.
In fact, math is not the only precise language we have. We have languages that are as precise as mathematical formulae. And nowadays, these languages come almost natural to many. These languages are programming languages.
I do not mean the syntax of a specific programming language. Rather, I refer to a way of thinking almost all programming languages share. From Python to Java, from Javascript to Ruby, even from C to Cobol. All these languages build upon boolean logic. Regardless of programming language, a programmer works a lot with boolean logic.
Most prominently, boolean logic appears in conditional statements: if then else
.
if x and y:# A statement to evaluate in boolean logic
doSomething () # if the statement evaluiates to True
else:
doSomethingElse () #otherwise
The if
-part of a conditional statement is pure boolean logic. Often, it contains the basic boolean operators not
, and
, and or
.
The if
-part of a conditional statement is pure boolean logic. Often, it contains the basic boolean operators not
, and
, and or
.
If some statement is True
, then its negation is False
. Conversely, if a statement is False
, then its negation is True
. If a statement consists of two parts P
and Q
, then P and Q
is only True
if P
is True
and Q
is True
. But P or Q
is True
if either P
or Q
is True
.
Here are three examples of boolean logic in Python.
P = True
Q = False
print('not P is {}'.format(not P))
print('P and Q is {}'.format(P and Q))
print('P or Q is {}'.format(P or Q))
print('P and not Q is {}'.format(P and not Q))
---------------------------
not P is False
P and Q is False
P or Q is True
P and not Q is True
While Python uses these exact keywords, in math, symbols represent these operators:
¬ means
not
∧ means
and
∨ means
or
If you’re not a mathematician, these symbols and all the other symbols you encounter on your quantum machine learning journey may appear cryptic. But while the representation of a concept may differ when you describe it in Python or in math, the concept itself is the same.
You don’t need to be a mathematician to understand boolean logic. Actually, you don’t need to be a programmer, either, because we can even describe the boolean logic by truth tables.
We have two variables, P
and Q
. Each variable is either true (T
) or false (F
). Depending on the combination of their values, we can deduce the value of any boolean statement. The following figure depicts the truth table for P
, Q
, not P
, not Q
, not P and not Q
, not (not P and not Q)
, and P or Q
.

This truth table reveals that P or Q
is equivalent to not (not P and not Q)
. This logical equivalence tells us that we do not even need the operator or
. We could replace it by not (not P and not Q)
.
But P or Q
is concise and much easier to understand.
“What if there was no or
operator in our programming language?"
The savvy programmer would write her own operator.
def my_or(p, q):
return not (not p and not q)
print('P | Q | P or Q')
print('--------------')
print('T | T | {}'.format(my_or(True, True)))
print('T | F | {}'.format(my_or(True, False)))
print('F | T | {}'.format(my_or(False, True)))
print('F | F | {}'.format(my_or(False, False)))
-------------------------
P | Q | P or Q
--------------
T | T | True
T | F | True
F | T | True
F | F | False
This is what programming is all about. Programmers write functions that produce a certain behavior. They use and combine these functions to create even more functions that exhibit even complex behavior. The whole program they write comes down to a set of functions savvily combined. Programmers have their compiler (or interpreter) to translate the higher-level functions down to the very basic boolean logic. And this basic boolean logic can be performed using electrical switches. The switches and their combination are called gates. When we connect gates, they form a circuit.
At a discrete interval, the computer sends a pulse of electricity through the circuit. If we receive a pulse of electricity at the appropriate time, we interpret it as 1
(true). If we don't receive a pulse, we interpret it as 0
(false).
Despite the name, there is nothing circular about circuits. They are linear and are read from left to right. Let’s look at an example that corresponds to the boolean functions that we looked at earlier
The following figure depicts the circuit diagram of not (not P and not Q)
. The circuit receives the input from the left and outputs it to the right.

Such gates and circuits are the building blocks of any modern computer. This includes quantum computers. While the world of quantum mechanics is different, the world of quantum computing is surprisingly similar.
Don’t let yourself be dazzled by all the mathematical formulae. They are representations of concepts. Not more, not less.
Let’s return to our introductory formula:

It is the mathematical notation of the quantum state |ψ⟩ (“psi”). While the state of a classical bit is boolean (either 0
meaning false or 1
meaning true), the state of the quantum bit (qubit) is the superposition of the quantum states |0⟩ and |1⟩ weighted by α and β.
In this state of superposition, the quantum system is neither 0
nor 1
unless you measure it. Only when you measure the qubit, the state collapses to either 0
or 1
. The squares of the two weights (α^2) and (β^2) denote the probabilities of measuring either 0
or 1
. The larger α is the higher the probability to measure 0
. Respectively, the larger β is the higher the probability to measure 1
.
The formula says something more. It says the quantum state is the vector of the two weights [α β].
In Python, a vector is an array. Thus, the state of a qubit is the array [alpha, beta]
. And, alpha
and beta
are numerical variables. The quantum state is an array of two numbers.
But an array of two numbers is a much more complex datatype than a boolean value is. A boolean is either True
or False
. You can transform boolean values with simple operators, such as not
, and
, and or
. You can reason about the transformation of boolean values in a truth table.
But how do you transform an array of two numbers? And how can you reason about such transformations?
The apparent answer is math. But it is not the only possible answer. Let’s use Python for that.
from math import sqrt
# define the initital states
psi = [0.5, sqrt(3)/2]
always_0 = [1, 0]
always_1 = [0, 1]
def transform(name, state, f):
print ('{}: [{:.2f}, {:.2f}] result: [{:.2f}, {:.2f}]'.format(name, *state, *f(state)))
def reverse_state(arr):
return list(reversed(arr))
print("----------- Reversed states: ----------")
transform("psi", psi, reverse_state)
transform("|0>", always_0, reverse_state)
transform("|1>", always_1, reverse_state)
----------- Reversed states: ----------
psi: [0.50, 0.87] result: [0.87, 0.50]
|0>: [1.00, 0.00] result: [0.00, 1.00]
|1>: [0.00, 1.00] result: [1.00, 0.00]
We start with the initialization of three states. Each state is an array of two numbers. The state psi
has the values 1/2 and √3/2 (line 4). The probability of measuring 0
in this state is (1/2)^2=1/4=0.25. The probability of measuring 1
is (√3/2)^2=3/4=0.75.
The state always_0
has the values 1
and 0
. The probability of measuring 0
in this state is 1^2=1 (line 5). The probability of measuring 1
is 0^2=0. When we measure a qubit in this state, we always measure it as 0
. The state always_1
is the respective opposite. We always measure it as 1
(line 6).
Next, we create a convenience function transform
(lines 8-9). Did I tell you writing functions to make things easier is what programming is all about? This is an example. The function takes a name of the quantum state (an arbitrary string to show), the state
and a function f
. transform
prints to the console the original state and the state after having applied the function f
on it.
Finally, we create a function reverse_state
we can feed into transform
(lines 11-12). reverse_state
calls Python's default reversed
function that returns an array of the same length in the opposite order.
In the output, we can see that the numbers in the state arrays have switched their positions. Thus, the probability of measuring 0
or 1
switched respectively. The reversed psi
has a 0.75
chance of measuring 0
and a 0.25
chance of measuring 1
. The reversed always_0
is similar to the original always_1
.
These are only three possible states. Listing all possible states in a kind of truth table is impossible. But I think the behavior of the reverse_state
function is quite clear. It is the behavior of the X-gate in quantum computing. It is one of the basic transformations of the quantum state.
Let’s have a look at this gate in practice. We use IBM’s quantum computing SDK Qiskit.
from qiskit import execute, Aer, QuantumCircuit
from qiskit.visualization import plot_histogram
# Create a quantum circuit with one qubit
qc = QuantumCircuit(1)
# Define initial_state
qc.initialize(psi, 0)
# Apply the X-gate
qc.x(0)
# Tell Qiskit how to simulate our circuit
backend = Aer.get_backend('statevector_simulator')
# Do the simulation, returning the result
result = execute(qc,backend).result()
counts = result.get_counts()
plot_histogram(counts)

The fundamental unit of Qiskit is the quantum circuit. A quantum circuit is a model for quantum computation. The program, if you will. Our circuit consists of a single one qubit (line 5).
We initialize our qubit with the state psi
(line 8) and we apply the X-gate on it (line 11).
Qiskit provides the Aer
package (that we import at line 1). It provides different backends for simulating quantum circuits. The most common backend is the statevector_simulator
(line 14).
The execute
-function (that we import at line 1, too) runs our quantum circuit (qc
) at the specified backend
. It returns a job
-object that has a useful method job.result()
(line 17). This returns the result
object once our program completes it.
Qiskit uses Matplotlib to provide useful visualizations. A simple histogram will do. The result
object provides the get_counts
method to obtain the histogram data of an executed circuit (line 18).
The method plot_histogram
returns a Matplotlib figure that Jupyter draws automatically (line 19).
We see we have a 75% chance of observing the value 0
and a 25% chance of observing the value 1
. The exact opposite of the initial state.
You can run the circuit with different initial states to get a better feeling for this gate.
In general, quantum circuits are not different from classical circuits. We can represent them in a diagram. Qiskit’s QuantumCircuit
class provides the draw
method that does the job for us.
qc.draw('mpl')

We can see our one and only qubit (q
), its initialization with the array [0.5, 0.866]
, and the applied X-gate.
You’ve completed the first step towards quantum computing mastery without being a mathematician. Getting a conceptual understanding of quantum gates as the quantumic peers of classical circuit gates does not depend on math. The combination of plain English and a little bit of Python is well-suited. And for many, this combination is much more accessible.
But, math remains paramount to quantum computing. If you want to gain a deep understanding of the concepts, you’ll cope with the mathematical formulae, sooner or later. And as I said, math is a great way to describe technical concepts.
Let’s have a look at the underlying math of the X-gate. Don’t worry, I don’t expect you to be a mathematician. A little affinity to algebra (that is the study of mathematical symbols and the rules from manipulating them) doesn’t hurt, though.
So far, we used Python’s built-in function reversed
. While this is convenient, we do not really see how it works internally. Let's use another function. A self-made function.
def adjust_weight(state, weights):
return state[0]*weights[0]+state[1]*weights[1]
print ('reversed psi: [{:.2f}, {:.2f}]'.format(
adjust_weight(psi, [0,1]),
adjust_weight(psi, [1,0])
))
-------------------------
reversed psi: [0.87, 0.50]
We define a function adjust_weight
(line 1). It takes a quantum state
and weights
. Both are arrays with two items. It multiplies the values at position 0
and it multiplies the values at position 1
. It returns the sum of these two products (line 2).
We can use this function to reverse psi
. For adjust_weight
returns a single number, we call it twice to get back an array of two items (lines 5 and 6). In this example, we do not explicitly create an array but we directly print these values to the console (line 4).
In both calls, we provide the original psi
as the state
parameter. In the first call, whose result is the first number of the reversed psi, we provide [0,1]
as weights
. It means, we get the sum of 0
times the first number of psi
and 1
time the second number of psi
. This sum is the second number of the original psi
.
In the second call, whose result is the second number of the reversed psi, we provide [1,0]
as weights
. This is 1
time the first number of psi
and 0
times the second number of psi
. This equals the first number of the original psi
.
With these weights, we have effectively switched the places of the numbers of psi
.
In math, this is a matrix multiplication. The general formula for multiplying a matrix M and a vector υ is:

a and b are the weights we used to calculate the first number of the resulting vector. c and d are the weights for the second number, respectively.
Mathematically, the X-gate quantum operator is the matrix:

Let’s apply this operator to exemplary states:
Reversing the state |0⟩ results in |1⟩:

And, applying the matrix at |ψ⟩ results in its reversal, too:

In classical computing, we have a small set of boolean operators whose behavior we can easily represent in truth tables. But in quantum computing, matrices denote the operators called gates. And there are myriads of possible matrices we can apply. Math is a concise yet precise way to describe these operators. But you don’t need to be a mathematician to use these operations.
Of course, it is desirable to have an understanding of the underlying math of a gate when you apply it. But more importantly, you need to have an understanding of what the gate does. If you know what the X-gate does, you don’t need to cope with the math all the time.