This is a minimal-theory introduction into the software side of quantum computing. Unfortunately, “minimal-theory” is still a non-trivial amount. Modern quantum computer programming is analogous to classical circuit programming — we define some inputs and shove them through logic gates. As such, the scope of this article is similar in scope to an article that would discuss how information is stored and processed on classical bits.

This article is intended to pique your interest in the subject with a high-level overview of quantum computing fundamentals and associated programming examples.

Although Google claimed “quantum supremacy” in 2019, the worlds most advanced quantum compute hardware is in its infancy. Memory is limited and processing is unstable. Lucky for us, it is possible to emulate quantum computers on classical computers. For this tutorial, we will use one of those emulators for programming quantum circuits: Cirq.

The Qubit

The quantum bit (or qubit) is the basic unit of quantum computation in the same way that a binary bit is the basic unit of classical computation. It is a logical, stateful entity that can behave quantum mechanically. In the context of computing, there are two primary quantum mechanical properties of interest: superposition and entanglement. These will be discussed later.

Similar to a bit that exists in either state 0 or 1, a qubit can exist in states |0⟩ or |1⟩. The notation used for the qubit states is called bra-ket notation and denotes a quantum state (as opposed to a notation-less, scalar state). While quantum states exist on a continuum (of infinite states), we will start by manipulating qubits in such a way that that they precisely remain in one of these two quantum states. Restricting the state of our qubits to either |0⟩ or |1⟩ allows us to operate on them similarly to how we would operate on a classical bit.

The following diagram provides an abstract representation of a quantum circuit. Each of the 8 horizontal lines represent a qubit. Each rectangle represents a logic gate. Gates are processed from left-to-right (similar to how you are processing each word of this sentence from left-to-right). The circuit is processed in abstract time slices that we call moments. In this diagram, there are 13 moments. A single moment activates at most one logic gate per qubit. The circuit marches in lockstep through the moments from left-to-right. In other words: all gates in moment-0 must complete before activating any of the gates in moment-1.

While Cirq can generate diagrams for programmatically constructed circuits, the diagrams are ASCII art. As we work through the following code snippets, I would recommend referring back to the above graphical diagram as an example of what the generated ASCII art represents.

Let’s start by defining a circuit that we can place qubits on. Run the following script through your Python interpreter.

import cirq circuit = cirq.Circuit()

print(circuit)

The expected output is an empty string, bummer. The circuit doesn’t have any qubits, so there is nothing to print. Let’s add a single qubit to the circuit. Cirq will not materialize qubits on the circuit unless they are associated with at least one logic gate. We’ll start by using using a simple unary logic gate: the identity gate. This gate simply outputs the input state without mutation.

import cirq circuit = cirq.Circuit() q0 = cirq.LineQubit(0)

circuit.append(cirq.I(q0)) print(circuit)

Output:

0: ───I───

Great! We have printed an ASCII art representation of our circuit. The 0 in the diagram represents qubit q0 that we constructed at index 0. The horizontal line contains all logic gates that the qubit will step through. We have a single identity gate represented by I on the line.

Let’s explore the moment dimension by applying another identity operation.

import cirq circuit = cirq.Circuit() q0 = cirq.LineQubit(0)

circuit.append(cirq.I(q0))

circuit.append(cirq.I(q0)) print(circuit)

Output:

0: ───I───I───

Qubit q0 still exists in the geometric domain at the same index of 0 . However, we now have two elements in the moment domain. The moment at index 0 (leftmost) contains an identity operation on q0 . The moment at index 1 (rightmost) contains an identity operation on q0 . If we were to run a simulation on this grid, q0 will be measured in its starting state of |0⟩. Note that qubits are implicitly initialized in the |0⟩ state in Cirq. Let’s run the simulation now.

import cirq circuit = cirq.Circuit() q0 = cirq.LineQubit(0)

circuit.append(cirq.I(q0))

circuit.append(cirq.I(q0)) print(circuit) simulator = cirq.Simulator()

result = simulator.run(circuit)

print(“

Result:”)

print(result)

Output:

0: ───I───I───

Traceback (most recent call last):

File “cirq_demo.py”, line 12, in <module>

result = simulator.run(circuit)

File “/Users/joe/env/lib/python3.7/site-packages/cirq/work/sampler.py”, line 50, in run

repetitions)[0]

File “/Users/joe/env/lib/python3.7/site-packages/cirq/sim/simulator.py”, line 79, in run_sweep

raise ValueError(“Circuit has no measurements to sample.”)

ValueError: Circuit has no measurements to sample.

Cirq is complaining that the circuit “has no measurements” because it expects at least one qubit on the circuit to pass through a “measurement” gate. The measurement gate performs two actions:

Collapses the state of the qubit into either |0⟩ or |1⟩. Stores the collapsed state as the result state for the associated qubit.

Action #1 will be discussed in excruciating detail in the next section. For now, it is sufficient to know that this is effectively a no-op because |0⟩ collapses into |0⟩ and |1⟩ collapses into |1⟩.

Action #2 is immediately useful because we would otherwise not have any output to report after running the simulation.

Note that the measurement gate has a unique property it can be applied at most one time on an individual qubit. Multiple qubits can be measured on a single circuit.

Let’s add a measurement gate and re-run the simulation.

import cirq circuit = cirq.Circuit() q0 = cirq.LineQubit(0)

circuit.append(cirq.I(q0))

circuit.append(cirq.I(q0))

circuit.append(cirq.measure(q0)) print(circuit) simulator = cirq.Simulator()

result = simulator.run(circuit)

print(“

Result:”)

print(result)

Output:

0: ───I───I───M─── Result:

0=0

We now have three moments in our circuit. q0 was value initialized to |0⟩.

Moment-0 applied an identity operation on q0 , leaving it as |0⟩ .

, leaving it as . Moment-1 applied an identity operation on q0 , leaving it as |0⟩ .

, leaving it as . Moment-2 applied a measurement operation on q0 , collapsing it to |0⟩ and recording a as the result state.

Great! We have ran our first quantum circuit simulation. Let’s mutate our qubit by using the second (and final) classical unary logic gate: the NOT-gate. Replace the first identity gate with the NOT-gate.

import cirq circuit = cirq.Circuit() q0 = cirq.LineQubit(0)

circuit.append(cirq.X(q0))

circuit.append(cirq.I(q0))

circuit.append(cirq.measure(q0)) print(circuit) simulator = cirq.Simulator()

result = simulator.run(circuit)

print("

Result:")

print(result)

Output:

0: ───X───I───M─── Result:

0=1

Moment-0 applied a NOT operation on q0 , mutating it to |1⟩ .

, mutating it to . Moment-1 applied an identity operation on q0 , leaving it as |1⟩ .

, leaving it as . Moment-2 applied a measurement operation to q0 , collapsing it to |1⟩ and recording this as a result state.

Just as a NOT-gate in classical circuitry flips a 0 to 1 , a quantum NOT-gate flips a |0⟩ to a |1⟩.

Let’s add second qubit.

import cirq circuit = cirq.Circuit() q0 = cirq.LineQubit(0)

circuit.append(cirq.X(q0))

circuit.append(cirq.I(q0))

circuit.append(cirq.measure(q0)) q1 = cirq.LineQubit(1)

circuit.append(cirq.measure(q1)) print(circuit) simulator = cirq.Simulator()

result = simulator.run(circuit)

print("

Result:")

print(result)

Output:

0: ───X───I───M─── 1: ───M─────────── Result:

0=1

1=0

We now have two qubits. q0 was measured as |1⟩ and q1 was measured as |1⟩.

Now that we have two qubits to work with, we can graduate from unary logic gates to binary logic gates. Let’s look at the controlled-not gate. This gate accepts two inputs: a control qubit, and a target qubit. If the control qubit is in state |1⟩, the gate will output the resulting state of applying a NOT operation on target qubit. If the control qubit is in state |0⟩, the gate outputs the state of the target bit without mutation.

import cirq circuit = cirq.Circuit() q0 = cirq.LineQubit(0)

q1 = cirq.LineQubit(1) # Moment 0

circuit.append(cirq.X(q0)) # Moment 1

circuit.append(cirq.CNOT(q0, q1)) # Moment 2

circuit.append(cirq.X(q0)) # Moment 3

circuit.append(cirq.CNOT(q0, q1)) # Moment 4

circuit.append(cirq.measure(q0))

circuit.append(cirq.measure(q1)) print(circuit) simulator = cirq.Simulator()

result = simulator.run(circuit)

print(“

Result:”)

print(result)

Output:

0: ───X───@───X───@───M───

│ │

1: ───────X───────X───M─── Result:

0=0

1=1

Moment-0 applied a NOT operation on q0 , mutating it to |1⟩ .

, mutating it to . Moment-1 applied a CNOT operation on q1 , using q0 as the control qubit, mutating q1 to |1⟩ .

, using q0 as the control qubit, mutating q1 to . Moment-2 applied a NOT operation on q0 , mutating it to |0⟩ .

, mutating it to . Moment-3 applied a CNOT operation on q1 , using q0 as the control qubit, leaving q1 as |1⟩ .

, using q0 as the control qubit, leaving q1 as . Moment-4 applied a measurement operation to q0 , collapsing it to |0⟩ and recording this as a result state. Moment-4 applied a measurement operation to q1 , collapsing it to |1⟩ and recording this as a result state.

We have now learned how to construct a quantum circuit, populate it with qubits, and manipulate them with logic gates. Aside from briefly (and incompletely) discussing quantum state measurement, we’ve yet to explore the functionality enabled by the quantum mechanical nature of a qubit.

Superposition

Any other situation in quantum mechanics, it turns out, can always be explained by saying “You remember the case of the experiment with the two holes? It’s the same thing”. — Richard Feynmen

The double-slit experiment demonstrates:

The behavior of a quantum object is dependent on whether or not the object is observed (or in another word: “measured”).

The probabilistic nature of quantum mechanics.

There are a few interpretations of this experiment. The most popular is the Copenhagen Interpretation. This interpretation states:

Physical systems generally do not have definite properties prior to being measured, and quantum mechanics can only predict the probability distribution of a given measurement’s possible results. The act of measurement affects the system, causing the set of probabilities to reduce to only one of the possible values immediately after the measurement. This feature is known as wave function collapse.

We’re going to accept this interpretation at face-value to explain the current topic: superposition.

Superposition is one of the two distinguishing features that allow a quantum computer to represent more state than a classical computer. When a quantum object is in superposition, it does not have a definite classical state. A quantum object can only exist in superposition while unobserved. When the quantum object in superposition is observed, it falls out of superposition and collapses into a definite state. If you’re wondering what exactly qualifies as “measurement” — you’re not alone. The lack of a widely accepted definition of “measurement” is known as the measurement problem. For our purposes, we will just say that measurement is exposure of a quantum object to the external world.

On our quantum circuit, each qubit is initialized and remains in an unobserved state until it observed by a measurement gate. Well, at least that’s the intent. Live hardware is not stable and may accidentally observe a qubit before it intends to do so — spoiling the circuit. This is one of many reasons that we are using our simulator, Cirq. Prior to measurement, qubits can be in superposition. While in superposition, a qubit does not definitely exist in either the |0⟩ or |1⟩ states. When measured, a qubit collapses into exactly one of these two states. We call these states the basis states (sometimes called “computational states” or “eigenstates” in similar literature).

Imagine flipping a coin that can land on either a head or a tail. Now, imagine that this coin flip takes place in a zero-gravity vacuum. A qubit in superposition is similar to the state of this coin — it is not in a definite state because it has not landed on either head or a tail. Measuring a qubit is similar to introducing gravity into our vacuum — the coin will land (or “collapse”) into a definite state of either a head or a tail.

It is important to note that a qubit in superposition has a definite quantum mechanical state. It does not have a definite classical mechanical state. This should sufficiently imply that a qubit in superposition does not have a hidden state, but let’s make that clear: a qubit in superposition is not in a hidden classical state that is discovered by observation. It is truly in a state that can not be modeled by the point-model physics that we’re used to.

It’s not uncommon for introductory and/or pop-science literature to state that an object in superposition is capable of being in two states at once: exhibit #1. This is absolutely not true, but may be a useful model if not taken too literally. Just as a superpositioned qubit is not in either the |0⟩ or |1⟩ states, it also does not exist in both the |0⟩ or |1⟩ states. It exists in a superposition of the |0⟩ and |1⟩ states — I realize this is a circular definition, but it illustrates my point: it is an entirely foreign state to the absolute states that we’re comfortable with.

When a qubit is in superposition, it has the possibility of collapsing into either the |0⟩ or|1⟩ states. How do we determine which state it will collapse into? We model the probability of the unobserved, superposition state collapsing into one of these states as |ψ⟩. We define this as the following for a single qubit:

|ψ⟩ = a|0⟩ + b|1⟩

The squared value of each coefficient (a and b) determines the probability that the qubit will collapse into the associated state when measured.

The probability that the qubit will collapse into state |0⟩ is calculated with the following formula:

a² / (a² + b²)

The probability that the qubit will collapse into state |0⟩ is calculated with the following formula:

b² / (a² + b²)

Consider a qubit in the superposition state modeled by |ψ⟩ = 0.6|0⟩ + 0.8|1⟩. Using the above formula, we calculate the probability that the qubit will be measured as |0⟩ as 36% (0.6² / (0.6.² + 0.8²) = 0.36). We calculate the probability that the qubit will be measured as |1⟩ as 64% (0.8² / (0.6.² + 0.8²) = 0.64).

Note that the formula for calculating the probability of measuring any given state is just the associated squared coefficient if the|ψ⟩ formula is normalized (the sum of all squared coefficient magnitudes is 1). This example is normalized (a² + b² = 1), so the 36% chance of measuring |0⟩ can be calculated with b² = 0.6² = 0.36. We will only work with normalized |ψ⟩ functions in this article. If we were to consider non-normalized functions, these two |ψ⟩ states are considered identical if they normalize to the same coefficients (a and b).

Consider a qubit in the quantum state modeled by |ψ⟩ = 1.0|0⟩ + 0.0|1⟩. This has a 100% chance of collapsing into the |0⟩ state. In this scenario, |ψ⟩ is |0⟩.

Consider a qubit in the quantum state modeled by |ψ⟩ = 0.0|0⟩ + 1.0|1⟩. This has a 100% chance of collapsing into the |1⟩ state. In this scenario, |ψ⟩ is |1⟩.

These two states have a special name: computational basis states. All other superposition states (those where both coefficients a and b are non-zero) exist on a continuum between them.

In our prior programming examples, the qubits we were working with were unobserved but not considered to be in superposition. We only consider a qubit to be in superposition when both a and b are non-zero (i.e. the qubit has a non-deterministic measured state). In Cirq (and by convention), qubits are initialized in the deterministic, basis state |0⟩. The gates introduced so far (identity, NOT, and CNOT) are not capable of mutating our qubit from a deterministic state into superposition (i.e. a non-basis state). This was intentional so that we could begin programming quantum circuits while remaining ignorant of superposition.

Given an arbitrary qubit in a collapsed basis state, it is impossible to model the superposition state it was in before measurement. That information is lost upon measurement. A measured qubit does not have any state that can not be represented with a classical digit bit.

While superposition allows a qubit to two states at once (a and b), it comes with the caveat that the additional state is lost when measured.

Superposition In Practice

Let’s start programming again with a fresh snippet:

import cirq def place_qubits(circuit):

pass def build_circuit():

circuit = cirq.Circuit()

place_qubits(circuit)

return circuit def simulate_circuit(circuit):

print(circuit) simulator = cirq.Simulator()

result = simulator.run(circuit)

print("

Result:")

print(result) circuit = build_circuit()

simulate_circuit(circuit)

We’ll use this skeleton going forward so that we can isolate the scope of our incremental changes to individual routines (most frequently: place_qubits ). This is just to make the snippets slightly less verbose as we modify the code.

While we’ve only explored qubits in the basis states, let’s introduce a gate that will mutate a qubit into superposition: the Hadamard gate. Let’s experiment with this gate before diving into the theory.

Place a qubit on the circuit and then measure it after applying a Hadamard gate by implementing place_qubits as:

def place_qubits(circuit):

q0 = cirq.LineQubit(0) circuit.append(cirq.H(q0))

circuit.append(cirq.measure(q0))

Output:

0: ───H───M─── Result:

0=0

If you got as 0=1 as the result, that’s an equally valid output as well.

Moment-0 applied a Hadamard operation on q0 , mutating into a superposition state that may collapse into either |0⟩ or |1⟩.

, mutating into a superposition state that may collapse into either or Moment-1 applied a measurement operation to q0 , collapsing it to either |0⟩ or |1⟩ and recording this as a result state.

Re-run the program enough times to see a different measured state than the one in your first simulation. If you run the program enough times, you will eventually convince yourself that there is a 50% probability that q0 is measured as either |0⟩ or |1⟩. We could model the output superposition state of q0 at moment-0 as |ψ⟩ = 0.707|0⟩ + 0.707|1⟩.

You may have noticed that the probability formulas don’t change if the coefficients are negative. For example, for a = b = -0.707 has the same 50/50 probability distribution as above because -0.707² = 0.5. While this may seem like a nuanced discussion at this point, it will become important later. For now, take my word that q0 is in state |ψ⟩ = 0.707|0⟩ + 0.707|1⟩ and not in state |ψ⟩ = -0.707|0⟩ + -0.707|1⟩.

The Hadamard gate is deterministic — it will always mutate the initial basis state |ψ⟩ = 1.0|0⟩ + 0.0|1⟩ = |0⟩ into |ψ⟩ = 0.707|0⟩ + 0.707|1⟩. Measuring this state is non-deterministic.

Ok — so we’ve started building a truth table for the H-gate:

H(|0⟩) = 0.707|0⟩ + 0.707|1⟩

Let’s continue building our truth table by applying a second H-gate.

def place_qubits(circuit):

q0 = cirq.LineQubit(0) circuit.append(cirq.H(q0))

circuit.append(cirq.H(q0))

circuit.append(cirq.measure(q0))

Output:

0: ───H───H───M─── Result:

0=0

This is the only valid output. Re-run the program enough times to convince yourself that the result is now deterministic. It will always measure 0.

Moment-0 applied a Hadamard operation on q0 , mutating it to 0.707|0⟩ + 0.707|1⟩ .

, mutating it to . Moment-1 applied a Hadamard operation on q0 , mutating it to |0⟩ .

, mutating it to . Moment-2 applied a measurement operation to q0 , collapsing it to |0⟩ and recording this as a result state.

Let’s expand our truth table:

H(|0⟩) = 0.707|0⟩ + 0.707|1⟩

H(0.707|0⟩ + 0.707|1⟩) = |0⟩

You may have noticed a pattern — all gates that we have worked with so far (except the measurement gate) are reversible. This is universally true for all quantum gates, including the H-gate. Use the truth table to convince yourself that H(H(|0⟩)) = H(H(H(H(|0⟩)))).

While all quantum gates must be reversible, it does not strictly enforce that applying the same gate to the output will return the input — it only enforces that some gate must exist that reverses the output. This implies that every gate must has a single, unique output for a given input among all possible inputs.

We could continue applying H-gates to q0 , but that won’t help us build our truth table. Let’s try applying the H-gate on the other basis state, |1⟩.

Replace the first H-gate in our program with an X-gate. This will allow our second H-gate to apply its mutation to |1⟩.

def place_qubits(circuit):

q0 = cirq.LineQubit(0) circuit.append(cirq.X(q0))

circuit.append(cirq.H(q0))

circuit.append(cirq.measure(q0))

Output:

0: ───X───H───M─── Result:

0=1

We’re playing the probability game again. 0=0 is an equally valid result.

Moment-0 applied a Hadamard operation on q0 , mutating it to a superposition state that may collapse into either |0⟩ or |1⟩.

, mutating it to a superposition state that may collapse into either or Moment-1 applied a measurement operation to q0 , collapsing it to either |0⟩ or |1⟩ and recording this as a result state.

As before, you could re-run your experiment enough times to convince yourself there is a 50% chance of measuring either basis state. Like H(|0⟩), we could model H(|1⟩) (the output state of q0 at moment-0) as |ψ⟩ = 0.707|0⟩ + 0.707|1⟩. We know that this is not the correct model because quantum gates must be reversible. As we stated before, a gate must have a unique output for each input: H(|0⟩) and H(|1⟩) can’t have the same output.

The only possible way to model H(|1⟩) is with at least one negative coefficient in the formula for |ψ⟩. This is the second time that I’m going to ask you accept a superposition state without an immediate explanation: H(|1⟩) = 0.707|0⟩ + -0.707|1⟩. Note that the first coefficient is still positive. If both of the coefficients are negative, it is the same state as if they are both positive. When b is negative, the superposition is said to be in a negative phase.

We can now complete the H-gate truth table to the extent of the states we can explore with the gates that have been introduced so far. The complete truth table would have an infinite number of entries because we can apply the H-gate to an infinite number of superposition states.

H(|0⟩) = 0.707|0⟩ + 0.707|1⟩

H(0.707|0⟩ + 0.707|1⟩) = |0⟩

H(|1⟩) = 0.707|0⟩ + -0.707|1⟩

H(0.707|0⟩ + -0.707|1⟩) = |1⟩

The H(|0⟩) and H(|1⟩) states are common enough that they have their own short-hand notation: |+⟩ and |-⟩ respectively. We can re-write our truth table to be a little less verbose:

H(|0⟩) = |+⟩

H(|+⟩) = |0⟩

H(|1⟩) = |-⟩

H(|-⟩) = |1⟩

Bloch Sphere

Before we go any further with circuits, let’s introduce the conventional graphical representation of a qubit: the Bloch sphere.

In quantum mechanics, the Bloch sphere is a geometrical representation of the pure state space of a two-level quantum mechanical system (qubit), named after the physicist Felix Bloch.

Let’s introduce our first Bloch sphere diagram with the four superposition states that we’re familiar with: |0⟩, |1⟩, |+⟩, and |-⟩.

If you’re up-to-date on basic geometry, you may have noticed that the above Bloch “sphere” is missing a third dimension. This is accurate, there is an extra dimension that Bloch sphere diagrams represent— but we’re not going to use it in this article. We will continue to use this diagram — a cross-section of the sphere centered on 3rd dimension.

The 3D Bloch sphere is capable of representing all superposition states on its surface. Our 2D Bloch circle is capable of representing all the superposition states modeled by |ψ⟩ = a|0⟩ + b|1⟩, where a and b do not contain imaginary parts. Coefficients in this equation are complex numbers, but we will continue to use them as real numbers. Any point on the circumference of our 2D circle represents a superposition state where the coefficients are real numbers. The third dimension of the sphere can be reached with the non-zero imaginary parts, which we are ignoring.

The above diagram represents a qubit in its starting state, |0⟩, because the arrow is pointing directly to |0⟩. The arrow always points to a point on the circumference of the circle, therefore the arrow always points to a superposition state. Let’s take a closer look at the dimensions of this circle.

Although we conventionally represent 2D grids with x and y, our cross-section of the Bloch sphere is centered on the y-axis, limiting our 2D view to the x and z axes. The z-axis is positive above the center of the circle and the x-axis is positive right of the center. Although we won’t be plotting states on the y-axis, we will use it for discussing rotations around the y-axis.

Our Bloch sphere representation is a unit circle: the magnitude of the vector between the center and the circumference is always 1. Here is another look at our Bloch sphere with coordinates instead of superposition states.

Since we’re working with a 2-coefficient formula to describe a qubit state, |ψ⟩ = a|0⟩ + b|1⟩, you may be tempted to think of the coefficients as coordinates on the circle. If you look at the positions of our 4 known states on our first diagram of the Bloch sphere, you will find that their coefficients do not map to the coordinates above.

The projection formula from a|0⟩ + b|1⟩ to coordinates (x, z) is:

x = 2(b/a) / ((b/a)² + 1)

z = (1 - (b/a)²) / ((b/a)² + 1)

Using these formulas, we can calculate the coordinates (x, z) for our basis states:

|0⟩ → (0,1)

|+⟩ → (1,0)

|1⟩ → (0,-1)

|-⟩ →(-1,0)

If you ran through the above calculations, you will have encountered a divide-by-zero error when calculating the coordinates for |1⟩. The explanation is outside of the scope of this article, so we’ll just use |1⟩ → (0,-1) as a known projection. All other superposition states are defined by the formulas.

All quantum gates can be defined by rotations around one or more the axises. For example, the NOT-gate is a π rad rotation around the x-axis. For example, here’s a diagram of the NOT(|0⟩) = |1⟩.

The H-gate is a π/2 rad rotation around the y-axis followed by a π/2 rad around the x-axis. The following sphere diagrams H(|0⟩) = |+⟩.

Since we’re only concerned with states that plot onto our 2D circle (i.e. states where x = 0), we can reach any state from another just by rotating around x-axis. Cirq provides a gate for performing an arbitrary rotation for each axis. The gate for rotating around the y-axis is the RY-gate. Re-open your terminal and use this gate to rotate the starting qubit (π/4) rad around the x-axis.

def place_qubits(circuit):

q0 = cirq.LineQubit(0) circuit.append(cirq.ry(rads=3.14/4)(q0))

circuit.append(cirq.measure(q0))

This rotates the state to coordinate pair (0.707, 0.707) on our Bloch sphere. These coordinates are well known since we’ve rotated by 45 degrees, but you could use the standard polar-to-rectangular formulas to determine the coordinates on the unit circle.

We must reverse the projection from the coordinates to determine the measurement probability distribution. If we solve for the normalized values of a and b in our first projection formula, x = 2(b/a) / ((b/a)² + 1)) using x = 0.707 from above, we will solve for a = 0.921 and b = .387. The superposition state |ψ⟩ = 0.921|0⟩ + 0.387|1⟩ has an 85% chance of measuring |0⟩ and a 15% chance of measuring |1⟩.

Let’s test it out in Cirq. Using our last implementation of place_qubits , let’s re-write our simulate_circuit routine to repeat our simulation 10,000 times and print the probability distribution for q0 :

def simulate_circuit(circuit):

print(circuit) simulator = cirq.Simulator()

result = simulator.run(circuit, repetitions=10000)

print(“

Result:”)

print_qubit_result(result, 0)

Let’s define our new helper routine print_qubit_result as:

def print_qubit_result(simulation_result, qid):

measured_0s = 0

measured_1s = 0 for basis_state in simulation_result.measurements[‘0’]:

if basis_state == 0:

measured_0s += 1

if basis_state == 1:

measured_1s += 1 measured_0_probability = \

100 * measured_0s / (measured_0s + measured_1s)

measured_1_probability = \

100 * measured_1s / (measured_0s + measured_1s) print(“q” + str(qid) + “ probability distribution: “ +

“0 = “ +

str(measured_0_probability) + “%, “ +

“1 = “ +

str(measured_1_probability) + “%”)

Output:

0: ───Ry(0.25π)───M─── Result:

q0 probability distribution: 0 = 84.89%, 1 = 15.11%

We have experimentally measured our expected probability distribution. You may use the RY-gate to reach any superposition state on the (x, z) coordinate plane. The gates for rotating around the other axises are the RX-gate and RZ-gate, but we will not use them because we want to stay on our 2D unit circle for this article.

Entanglement

Superposition alone doesn’t enable a quantum computer to have much of advantage over classical computers. If this was the end of the article, you wouldn’t find much use for a quantum computer beyond generating truly random numbers. The real quantum computing doesn’t begin until we’ve discussed our next property of quantum mechanics: entanglement.

If you’re incredibly perceptive, you will have noticed a contradiction in our programming example that used the CNOT-gate. The CNOT-gate applies a NOT operation on a target qubit depending on the state of a control qubit. We have learned that measuring a qubit will collapse its state, but our example continued operating on the control qubit as if it was never measured. This is not a mistake in our circuit — the control qubit was not measured by the CNOT-gate. If the control qubit was never measured, how did the CNOT-gate use its state to conditionally apply a NOT operation to the target bit?

Quantum entanglement is the physical phenomenon that occurs when a pair or group of particles is generated, interact, or share spatial proximity in a way such that the quantum state of each particle of the pair or group cannot be described independently of the state of the others, even when the particles are separated by a large distance.

When quantum objects are entangled, they become inseparably correlated. If quantum objects A and B are entangled, acting on object A will instantaneously affect object B — even at arbitrary long distances, perhaps A and B are lightyears apart. Einstein famously rejected this “spooky action at a distance” because it seemingly violated his theory of special relatively. Special relatively states that communication can not travel faster than the speed of light and Einstein argued that entanglement allowed for faster-than-light communication among entangled quantum objects. In 1935, he wrote a paper that upgraded his argument into a formal paradox. Today, most theorists use a nuance in the definition of “communication” to accept that entanglement does not violate special relatively. Suffice it to say — things are about to get weird.

The most pragmatic way to begin understanding entanglement is to begin by exploring the behavior of two entangled qubits. Qubits can be entangled using a single gate. You can probably guess the gate because we’ve already used it and I alluded to it a few paragraphs ago: the CNOT-gate.

Let’s start by implementing our place_qubits to place two qubits on our circuit, q0 and q1 , and entangling them with the CNOT-gate. Note that entanglement is specific to a set of qubits in superposition. The CNOT-gate must be applied on a target qubit using a control qubit that is in superposition. Using a control qubit in a basis state (i.e. not in superposition) will not entangle the qubits. We will put our control bit into superposition with the H-gate (recall that this gate puts a qubit in a basis state into a superposition state with a 50/50 probability distribution).

def place_qubits(circuit):

q0 = cirq.LineQubit(0)

q1 = cirq.LineQubit(1) # Moment 0

circuit.append(cirq.H(q0)) # Moment 1

circuit.append(cirq.CNOT(q0, q1)) # Moment 2

circuit.append(cirq.measure(q0))

circuit.append(cirq.measure(q1))

Let’s modify our simulate_circuit routine to run 10 simulations and print the results for each simulation.

def simulate_circuit(circuit):

print(circuit) simulator = cirq.Simulator()

result = simulator.run(circuit, repetitions=10)

print(“

Result:”)

print(result)

Output:

0: ───H───@───M───

│

1: ───────X───M─── Result:

0=1100111100

1=1100111100

The sampled results are read left-to-right. So my first simulation measured 0=1, 1=1 , my second simulation measured 0=1, 1=1 , and my third simulation measured 0=0, 1=0. Your sampled results will almost certainly be different — but the measured states of both qubits will always be identical for each run of the simulation. Re-run your simulation enough times to convince yourself this is true.

Moment-0 applied a Hadamard operation on q0 , mutating it to |-⟩ .

, mutating it to . Moment-1 applied a CNOT operation on q1 , using q0 as the control qubit, mutating q1 to |-⟩ .

, using as the control qubit, mutating to . Moment-2 applied a measurement operation to q0 , collapsing it either |0⟩ or |1⟩ and recording this as a result state. Moment-2 applied a measurement operation to q1 that collapsed into the same state that was measured from q1 .

You may have expected that entangling two qubits would put them in the same superposition state — this is universally true.

However, you may not have expected that the measured states of entangled qubits would always be identical — this is not universally true. However, it is true for this particular example: the entangled qubits will always collapse into the same state when measured.

Could we prepare two entangled qubits in such a way that they will always collapse in the opposite basis states? Let’s add a NOT-gate to q1 before we entangle it with q0 .

def place_qubits(circuit):

q0 = cirq.LineQubit(0)

q1 = cirq.LineQubit(1) # Moment 0

circuit.append(cirq.H(q0))

circuit.append(cirq.X(q1)) # Moment 1

circuit.append(cirq.CNOT(q0, q1)) # Moment 2

circuit.append(cirq.measure(q0))

circuit.append(cirq.measure(q1))

Output:

0: ───H───@───M───

│

1: ───X───X───M─── Result:

0=1111010011

1=0000101100

Moment-0 applied a Hadamard operation on q0 , mutating it to |-⟩ . Moment-0 applied a NOT operation on q1 , mutating it to |1⟩ .

, mutating it to . Moment-0 applied a NOT operation on , mutating it to . Moment-1 applied a CNOT operation on q1 , using q0 as the control qubit, mutating q1 to |-⟩ .

, using as the control qubit, mutating to . Moment-2 applied a measurement operation to q0 , collapsing it either |0⟩ or |1⟩ and recording this as a result state. Moment-2 applied a measurement operation to q1 that collapsed into the opposite state that was measured from q1 .

In this example, q0 was mutated into the same superposition state as q1 . As we already know — this is universally true. Entangled qubits are always represented by a single superposition state (i.e. have the same coefficients in the |ψ⟩ formula).

What we have learned in this example is that entangled qubits do not universally collapse into the same state. In this particular example, they collapse into opposite states. Likewise, we know that entangled qubits do not universally collapse into opposite states.

Entanglement between two qubits is more than just preparing two separate qubits in identical superpositions. Preparing two qubits in identical superpositions is now be a trivial exercise for us: create two qubits, rotate each qubit by an identical angle around x-axis (with either the H-gate or RY-gate), and measure them. This would not entangle them — their measured states are not correlated.

Although we are working with two trivial exercises, they demonstrate the inseparable correlation between entangled qubits. Depending on how entangled qubits are prepared, entangled qubits will either always measure into the same state or into opposite states — this is universally true. In other words: if we know how two entangled qubits are prepared, we can determine the measured state of the second qubit just by measuring the first qubit.

Entangled qubits retain their entangled properties even when physically separated by arbitrarily long distances. Let’s say we we’ve prepared two qubits as we have in the current exercise: q0 and q1 will always measure in opposite states. Let’s say we separate them by a long distance: lightyears apart, in different universes. If we measured q0 to be |0⟩, we instantly know that q1 will be measured as |1⟩ — this is the foundation for teleporting information.

This is also the crux of Einstein’s objection to quantum mechanics because of his interpretation that measuring q0 instantly affects q1 . The principle of locality requires a physical medium to carry influence between two objects. Einstein’s special theory of relativity prohibits the speed of this influence from exceeding the speed of light. Today, we know that q0 does not physically affect (i.e. "influence”) q1 because entangled qubits are manifestations of the same entity. There is no influence: q0 and q1 are the same object. They are entangled.

Multi-qubit Systems

We’re finally ready to see how a quantum computer can store exponentially more in-flight state than a classical computer. So far, the only memory advantage we’ve seen is that a qubit in superposition can store twice the state of a classical bit using coefficients a and b in the|ψ⟩ formula. If we have N number of separate qubits, we can store 2N states while all qubits are in superposition.

Let’s consider a system with N=3 qubits:

|ψ₁⟩ = a₁|0⟩ + b₁|1⟩

|ψ₂⟩ = a₂|0⟩ + b₂|1⟩

|ψ₃⟩ = a₃|0⟩ + b₃|1⟩

There are 2ᴺ=8 possible states that this system could be measured in:

|000⟩, |010⟩, |100⟩, |110⟩, |001⟩, |011⟩, |101⟩, |111⟩

We’ll skip the math to get here, but we can model the relative probabilities among these measured system states with:

|ψ⟩ = a₁a₂a₃|000⟩ + a₁b₂a₃|010⟩ + b₁a₂a₃|100⟩ + b₁b₂a₃|110⟩ + a₁a₂b₃|001⟩ + a₁b₂b₃|011⟩ + b₁a₂b₃|101⟩ + b₁b₂b₃|111⟩

The above shows that the system stores 2N=6 independent states: a₁, a₂, a₃, b₁, b₂, b₃. These properties hold for any system of N separate qubits. For example, a system of N=30 separate qubits in superposition represents 2N=60 states but has 2ᴺ=1,073,741,824 possible measurement states.

Note that each final state probability is dependent on the superposition of all three qubits. Convince yourself this is true by looking at the formula and noting that each coefficient triplet (e.g. the a₁a₂a₃ associated with |000⟩) contains either an a or b from each of the three qubit states. From the opposite perspective, we could say that each qubit state affects each final state probability. Consider |ψ₁⟩: one of the two coefficients (a₁ or b₁) for |ψ₁⟩ appear in the coefficient triple for each final state. These are properties of a disjoint system: the qubit states are separate and unrelated.

Let’s now consider a joint system by relating 3 qubit states through entanglement.

|ψ₁⟩ = |ψ₂⟩ = |ψ₃⟩ = a|0⟩ + b|1⟩

This system has the same 2ᴺ=8 possible measured states:

|000⟩, |010⟩, |100⟩, |110⟩, |001⟩, |011⟩, |101⟩, |111⟩

Again — We’ll skip the math to get here, but we can model the relative probabilities among these measured system states with:

|ψ⟩ = c|000⟩ + d|010⟩ + e|100⟩ + f|110⟩ + g|001⟩ +h|011⟩ + i|101⟩ + j|111⟩

Unlike the first system we examined that could store 2N=6 states, this entangled system can store 2ᴺ=8 states: c, d, e, f, g, h, i , j. These properties hold for any system of N entangled qubits. For example, a system of N=30 entangled qubits in superposition represents both 2ᴺ=1,073,741,824 states and 2ᴺ=1,073,741,824 possible measurement states.

The state of a system of entangled qubits increases exponentially with the number of qubits.

You can model the uniform 3-qubit system|ψ⟩ with the following snippet. Each of the 8 states has an independent12.5% chance of collapsing into any given state.

def place_qubits(circuit):

q0 = cirq.LineQubit(0)

q1 = cirq.LineQubit(1)

q2 = cirq.LineQubit(2) # Moment 0

circuit.append(cirq.H(q0)) # Moment 1

circuit.append(cirq.CNOT(q0, q1)) # Moment 2

circuit.append(cirq.I(q0))

circuit.append(cirq.CNOT(q1, q2)) # Moment 3

circuit.append(cirq.I(q0))

circuit.append(cirq.H(q1))

circuit.append(cirq.H(q2)) # Moment 4

circuit.append(cirq.measure(q0))

circuit.append(cirq.measure(q1))

circuit.append(cirq.measure(q2))

We can modify our simulate_circuit routine to sample the system state and print the probabilities.

def simulate_circuit(circuit):

print(circuit) simulator = cirq.Simulator()

repetitions = 100000

result = simulator.run(circuit, repetitions=repetitions)

print(“

Result:”) state_000 = 0

state_010 = 0

state_100 = 0

state_110 = 0

state_001 = 0

state_011 = 0

state_101 = 0

state_111 = 0 for i in range(0, repetitions):

if result.measurements[“0”][i] == 0 and \

result.measurements[“1”][i] == 0 and \

result.measurements[“2”][i] == 0:

state_000 += 1 if result.measurements[“0”][i] == 0 and \

result.measurements[“1”][i] == 1 and \

result.measurements[“2”][i] == 0:

state_010 += 1 if result.measurements[“0”][i] == 1 and \

result.measurements[“1”][i] == 0 and \

result.measurements[“2”][i] == 0:

state_100 += 1 if result.measurements[“0”][i] == 1 and \

result.measurements[“1”][i] == 1 and \

result.measurements[“2”][i] == 0:

state_110 += 1 if result.measurements[“0”][i] == 0 and \

result.measurements[“1”][i] == 0 and \

result.measurements[“2”][i] == 1:

state_001 += 1 if result.measurements[“0”][i] == 0 and \

result.measurements[“1”][i] == 1 and \

result.measurements[“2”][i] == 1:

state_011 += 1 if result.measurements[“0”][i] == 1 and \

result.measurements[“1”][i] == 0 and \

result.measurements[“2”][i] == 1:

state_101 += 1 if result.measurements[“0”][i] == 1 and \

result.measurements[“1”][i] == 1 and \

result.measurements[“2”][i] == 1:

state_111 += 1 print(“state_000: “ + str(state_000 * 100 / repetitions) + “%”)

print(“state_010: “ + str(state_010 * 100 / repetitions) + “%”)

print(“state_100: “ + str(state_100 * 100 / repetitions) + “%”)

print(“state_110: “ + str(state_110 * 100 / repetitions) + “%”)

print(“state_001: “ + str(state_001 * 100 / repetitions) + “%”)

print(“state_011: “ + str(state_011 * 100 / repetitions) + “%”)

print(“state_101: “ + str(state_101 * 100 / repetitions) + “%”)

print(“state_111: “ + str(state_111 * 100 / repetitions) + “%”)

Output:

0: ───H───@───I───I───M───

│

1: ───────X───@───H───M───

│

2: ───────────X───H───M─── Result:

state_000: 12.492%

state_010: 12.369%

state_100: 12.388%

state_110: 12.597%

state_001: 12.804%

state_011: 12.402%

state_101: 12.544%

state_111: 12.404%

You may experiment with rotations on the qubits to choreograph the relative probabilities (c, d, e, f, g, h, i, and j from above). Additionally, you can continue the pattern of entangling qubits to create an even larger N-qubit system. Many quantum algorithms use these 2ᴺ states to store computational output for 2ᴺ unique inputs, this is commonly referred to as quantum parallelism. Given that we can only measure one state, quantum computers lend themselves well to problems that reduce a large number of inputs to a single output (for instance, searching for a single element in list of input elements).

Quantum Computing Applications

In the spirit of this article’s breadth-over-depth approach, we’re going to end this article with a high-level look at a few practical applications of quantum computing. When we refer to preparing input states in a multi-qubit system, we mean that we are applying transformations to the qubits in such a way that the measurement probability coefficients (e.g. c, d, e, f, g, h, i, and j in the 3-qubit system) encode the input states.

Quantum Search

There are many problems in computer science where we are given an arbitrary function and want to determine the unique input that produces a desired output. For example, the arbitrary function could be a query statement that evaluates to true when an input matches a certain name, address, etc. If our input is unstructured, we have a time complexity of O(N) on a classical computer where N is the number of inputs to try.

On a quantum computer, we can use Grover’s algorithm to reduce the time complexity to in O(√N).

Step #1 is to encode the list of N inputs into log₂(N) entangled qubits in superposition. If we were to measure the system, we have a 1/N chance of measuring the correct input. The algorithm works by performing O(√N) iterations on the input qubits where each iteration applies the arbitrary function and amplifies the amplitudes of the systems superposition state so that it becomes increasingly more likely to measure the correct input.

When the final measurement is performed, it is near-certain that the measured result is the input that produces desired output. We can trivially check this by a single execution of the arbitrary function to determine if we measured the correct input. In the extremely unlikely scenario that it’s wrong, just re-run Grover’s algorithm again.

Prime Number Factorization

The most widely used encryption schemes (e.g. RSA) rely on the assumption that calculating the prime factors of a large number is computationally infeasible to perform in a reasonable amount of time. On a classical computer, the naïve algorithm for prime factorization has an exponential time complexity — however, there are known algorithms that can solve this in sub-exponential time. On a quantum computer, prime factorization can be solved within a polynomial time complexity using Shor’s algorithm. The danger should be obvious: a stable quantum computer can use this algorithm to break the majority of modern encryption.

Shor’s algorithm expresses prime factorization for an arbitrary number as calculation to determine the period for periodic functions. There are seven steps to this algorithm. Six of the seven steps can execute efficiently on a classical computer. The other step can leverage quantum parallelism to determine the period of a given periodic function. This works by storing input points in the state of an entangled qubit system, evaluating the periodic function for each input point, and using a quantum Fourier transformation to introduce interference between the evaluations that increases the probability of measuring the input that is the period of the function.

While we have not covered enough material for the following circuit to readable, I have provided an example circuit diagram that is used in Shor’s algorithm from Wikipedia to demonstrate that some of the material we have covered is used in this circuit.

Encryption

The most well-known example of quantum encryption is called quantum key distribution. This is a method for two parties (e.g. Alice and Bob) to share a private key that can be used for encrypting messages. If Alice wants to create a 64-bit key, she will need at least 64 pairs of qubits. Alice will entangle each qubit pair and prepare them in one of the following states: |0⟩, |1⟩, |+⟩, or |-⟩. For each entangled pair, Alice will keep one and send the other to Bob.

Recall that we have established that an entangled qubit pair will always measure into the same state. One detail that we omitted (for simplification) is that there are two ways to measure qubits. For the entirety of the article, we’ve been consistently measuring qubits in the computational basis. It is also possible to measure qubits in the Hadamard basis. I will revise my earlier statement to: entangled qubits will always collapse into the same state when measured in the same basis. Additionally, measuring a collapsed qubit in the opposite basis will again introduce probability into the measurement: a collapsed qubit in the computational basis will have a 50/50 chance of measuring 0 or 1 in the Hadamard basis.

Alice and Bob will each measure their qubit, but choose one of the two basis states to measure the qubit in at random. After measurement, they will communicate over a classical channel (e.g. TCP/IP) to reveal which measurement state they measured in. If they measured in the same basis, they know that they have both measured the same state and use that as a character in their secret key. If they measured in different states, they throw out the measurement and Alice will prepare another entangled qubit pair and repeat the process.

The motivation for choosing a random measurement basis is to hide the secret key from an eavesdropper. Let’s consider the channel where both the quantum and classical channels are compromised by an eavesdropper. The eavesdropper knows when to drop qubits that Alice and Bob are dropping — we’ll only consider the scenario when Alice and Bob have measured in the same basis and agreed on a bit for their key.

The eavesdropper will decided a measurement basis before Alice and Bob have chosen theirs. This implies that the eavesdropper has a 50% chance of measuring in the wrong basis. In this scenario, the eavesdropper must guess the value that Alice and Bob measured. The eavesdropper has a 50% of guessing the right measurement. Even when both channels are compromised, the eavesdropper will statistically have a key where 25% of the bits are incorrect.

Recall that qubits are difficult to keep stable. Transporting a qubit from Alice to Bob will be extremely difficult. In practice, it easier to entangle at a distance than it is to move an entangled qubit over a distance. This is the premise for large scale quantum networks.

Continued Reading