Three principles of quantum computing with Amazon Braket

Daniel Lenton
Credera Engineering
21 min readApr 27, 2022
AWS Logo, next to those of D:Wave, IONQ and Rigetti

Quantum computing is rapidly becoming less of a sci-fi buzzword and more of a realistic option to solve problems in industry. This article by McKinsey Digital outlines that both adoption and investment in quantum computing has dramatically accelerated over the past five years, and its uses have already reached commercial application.

Volkswagen have partnered with Google to use quantum computing to predict traffic volumes and optimise autonomous vehicles. PayPal and IBM use quantum computing to detect and prevent financial fraud. Perhaps even more importantly, quantum computing could render RSA cryptography obsolete — with the US National Institute of Standards and Technology (NIST) set to release standards for quantum-safe cryptography in the next couple of years, as referenced in this UK government whitepaper. Indeed, OpenSSH just released version 9.0, implementing protection against ‘hack now, decrypt later’ attacks — where hackers could collect encrypted data now in the hope to decrypt it at a time where sophisticated enough quantum computers are readily available. Despite all of this progress, there is a severe talent shortage in the field.

Fortunately, there are not only a wealth of resources for learning about quantum computing, but AWS Braket offers a service allowing users to actually run quantum algorithms on real quantum computers.

In this blog, I will be exploring three powerful concepts of quantum computing with AWS Braket — namely superposition, entanglement, and phase — and conclude by submitting a quantum algorithm to a Rigetti quantum computer. I will also explain these concepts using a fast-food product familiar to all of us. To avoid any accidental copyright infringement, let’s call it the ‘McDaniels Joyful Meal ©’.

Introduction

In my early youth, I recall being disappointed to the extent of a noisy tantrum that I had received the wrong toy in my McDaniels Joyful Meal. There was a 50% chance that I could receive a Transformer toy inside the box, and a 50% chance that I could receive one of the rats from ‘Flushed Away’, but there was no way to know which toy I was going to receive until I had already purchased the meal. While the pain I felt that day still keeps me awake at night, I also recognised the anecdote as a valuable opportunity to explain quantum computing from the perspective of a small child.

But first, let’s take a step back and think about how classical computing works using this analogy. Classical computers are digital, meaning that they can only interpret information in a binary format — something is either true (0) or false (1). Of course, binary digits can be (and are) combined to represent more complex data types, but on the level of individual bits, a classical computer knows only these two values.

The classical computer, then, is much like my 6-year-old self. I knew only happiness (Sid the Rat, 0) or disappointment (Transformer, 1). Further, this binary reaction was determined entirely by the input of the McDaniels crew member who put the toy inside my box — just like the action of a classical computer is determined completely by some input source.

So how does this analogy differ when representing a quantum computer? For quantum computers, this smallest unit of information is not the binary bit, but the qubit. In some ways, qubits are very similar to bits — when you measure them, the measurement will be a result of either 0 or 1. However, the difference lies in how the state can be prepared before it is measured. In a quantum McDaniels, the employee could prepare my toy to be in a superposition of both a transformer and a rat. Until I opened the box and ‘measured’ the qubit, it wouldn’t simply be one toy or the other - it would truly be, in a sense, both a transformer and a rat at the same time. Now that definitely would have made me cry. You may already have heard a far more barbaric analogy of this property of quantum systems in the form of Schrödinger’s cat.

The ‘Sid the Rat’ McDonalds toy, standing proudly.
Majestic…

It’s important to highlight the main difference between the two systems here. In the classical system, I might receive a transformer 50% of the time and a rat 50% of the time, but whichever I receive is determined at the point where the McDaniels employee prepares the meal. If they put in a rat, I would be delighted with 100% certainty, whilst if they put in a transformer, I would be devastated with 100% certainty. In the quantum system, the blame does not lie with McDaniels — the worker prepares the same state every time. Yet, if I went to McDaniels every day, I would only throw a tantrum on approximately 50% of those days.

You might be wondering how this property is beneficial to a quantum computer. It seems that regardless of what state the qubit is prepared in before measurement, the computer still only has a classical bit (true or false) to work with after measurement. To demonstrate the power of quantum computing, let’s set up AWS Braket in order to run an algorithm on a real quantum computer.

Setting up

If you want to follow the steps I set out in this blog, there are a few things you’ll need to set up first. If you haven’t already, you’ll need to create an AWS account and download/configure the AWS CLI tool. You will also have to visit the AWS Braket page in the console to enable use of the service. This will require the creation of an S3 bucket in order to store the output of any jobs you submit to a Braket device. When you do so, you’ll be met with a page that looks somewhat like below.

These are the devices that one can submit tasks to using Braket. There are a range of simulators (devices that are cheaper and act as a useful means of testing code) and real quantum processing units (QPUs). Notice that some of the QPUs are offline whilst others are not available for some time, so it’s worth checking the status of this page before you run any algorithms on these devices.

­­Finally, you’ll need to install the Braket python SDK. Assuming you already have Python configured on your machine (at least version 3.7.2), this should only require running the following command.

pip install amazon-braket-sdk

And that’s it! You’re ready to submit tasks to the Braket devices.

Superposition

The first concept I want to demonstrate with Braket is superposition. As mentioned earlier, this property of quantum systems permits them to be in a combination of states. To set up a qubit to be in a superposition of states, we can use a Hadamard gate. But first, what is a gate from the perspective of classical computing?

At the lowest level, all algorithms boil down to different operations on bits, with manipulation of data coming from manipulation of the bit strings that represent that data. Suppose you have a classical bit in the state 0. There are only really two things that you can do to this bit — you can flip it to be a 1, or you can leave it as it is. Such operations can be represented with the NOT gate and the IDENTITY gate respectively. As you may have guessed, the NOT gate changes the state of a bit, whilst the IDENTITY gate preserves it.

When we start to consider multiple classical bits, we can introduce different gates. The AND gate performs an operation on two classical bits, returning the state 1 if both input states are 1, and returning the state 0 otherwise. You can probably figure out the action of other gates such as OR yourself — but whilst already on the topic of classical gates, there’s one more I’d like to introduce to you — namely the controlled not (CNOT) gate.

The CNOT gate acts on two input qubits to produce two output qubits. Of the two input qubits, one is the control and one is the target. If the control qubit is in a state 1, then the target bit is flipped. Conversely, if the control qubit is in a state 0, the target bit is preserved. To illustrate this clearly, I have tabulated the possible outputs of the CNOT gate below, where the first bit is the control and second bit is the target.

Gates like this can be combined to form a circuit. A circuit diagram is a handy tool for visualising how gates mesh together into circuits, with each line representing a bit. Below is an example of a simple circuit using classical AND and NOT gates. Two bits are fed into the circuit as inputs, with a single output bit returned.

Simple circuit diagram, showing 2 classical bits fed into an AND gate, the result then fed into a NOT gate. The 2 classical bits are in states 0 and 1, the final result of the circuit is a single bit in state 1.

With qubits, the freedom that superposition introduces to the system results in more complex circuits than this. To build an effective algorithm that we can run on a quantum computer, we first need to understand how to use gates to manipulate qubits. I aim to rely on mathematics as little as possible in this article, but to help explain the action of these gates, I’ll introduce the concept of a ket vector. The state of a qubit may be represented by such a ket vector as shown below.

Qubit in state 0: |0⟩

Qubit in state 1: |1⟩

Qubit in superposition of those states:

Another valid superposition of those states:

If you aren’t a fan of maths, don’t worry about the √2’s — they aren’t important for the purposes of this article. Simply put, they contain information on the probability that, when measured, the qubit will return a 0 or a 1. In this case, they tell us that our measurement has a 50% chance of resulting in a 0, and a 50% chance of resulting in a 1.

So, back to the Hadamard gate. What is it? It is precisely the gate that transforms the input state |0⟩ into the superposed state |M+⟩, and the input state |1⟩ into the superposed state |M-⟩. For those of you who are falling asleep and desperate to see some coding, I’ll end the suffering and put this all into a Python script.

Superposition demo

First, we import all the modules we need. matplotlib is a handy one for illustrating the outputs of our algorithms as graphs. Braket Circuits allow us to mesh gates together to later be applied to qubits. Finally, until we’ve got a quantum program ready to use, the LocalSimulator module is handy for testing or experimenting without submitting to AWS-managed devices.

import matplotlib.pyplot as plt
from braket.circuits import Circuit
from braket.devices import LocalSimulator

Next, we need to define an instance of the device we wish to use, and an empty circuit on which we can append gates to transform qubits.

device = LocalSimulator()
circuit = Circuit()

Now we can put the Hadamard gate into our circuit. We need to specify a qubit to apply it to — in this case, we only need to work with one qubit, so we can use the index 0.

circuit.h(0)
print(circuit)

Let’s see the output of that print command.

T : |0|
q0: -H-
T : |0|

This is a representation of our very simple circuit. The first and final lines of text are used to label moments— in this case, we only have one moment, which is the point in time at which our Hadamard gate is applied, labelled with a 0. ‘q0’ represents the qubit that will be fed into this circuit. By default, all such qubits begin in a state of |0⟩. Now let’s prepare the circuit to be run on our local simulator.

# Run with 100 shots
result = device.run(circuit, shots=100).result()
# The result contains a field called measurement counts that
# measures the final state of the qubits across those 1000 runs
counts = result.measurement_counts
# print the results
print(counts)

The first line here stores the result of executing our circuit on our chosen device in a variable. But what does ‘shots=100’ mean? Well, as discussed previously, measurement of a quantum system is truly random. The McDaniels employee can prepare their box in exactly the same way every day, and the child opening the box will only weep a certain percentage of the time. In the same way, when we prepare a qubit in the state |M+⟩, there is a 50% chance that when measured, it will be a 0 and a 50% chance it will be measured to be a 1. The 100 shots therefore indicate that the algorithm should be run 100 times and the outputs tallied. Such a tally can be accessed through the ‘measurement_counts’ attribute of the result.

Counter({‘0’: 57, ‘1’: 43})

Or, by running the same script again…

Counter({‘0’: 52, ‘1’: 48})

Indeed, running the same code gives a different result each time. We are simulating a random process. In fact, measurement of a qubit in a superposition of base states is one of the only truly random processes in the universe. Let’s make this article prettier and represent this with a bar graph.

Congrats! You just ran your first quantum program. Or at least, you pretended to.

You may be wondering at this point what the difference is between using the local simulator and submitting a job to a real QPU. The local simulator is a fantastic way to make sure all your code works as intended, but the most advanced classical supercomputers on earth can only simulate a quantum system of size up to 60 qubits. This is because the number of bits required to simulate a number of qubits scales exponentially. Yes, only two classical bits are required to simulate a single qubit , but 2⁶⁰ are required are required to simulate 60 qubits.

D-Wave has a quantum computer with 5,000 qubits… the number of bits required to simulate that is so large that it messed up the formatting of my article when I tried pasting it in. Hopefully this demonstrates that the local simulator is only really a useful tool for algorithms involving a small number of qubits.

Entanglement

So far, I haven’t done a good job of illustrating the power of quantum computing — we’ve written what essentially simulates flipping a coin. Another property of quantum systems that can help push our programme a little further is entanglement. Let’s see if the already tired ‘McDaniels’ analogy can take the strain.

Suppose my sister and I both went to McDaniels, and both ordered a Joyful Meal. When we open our boxes, there are four possible outcomes…

In a classical system, these outcomes are decided at precisely the moment when the McDaniels employee puts the toys in the boxes and hands them over to us. But let’s go back to quantum McDaniels. The worker can prepare some very intriguing states. To define them properly, we can use the concept of the ket vector. In the table above, I have tried to illustrate how we can label ket vectors for a 2 qubit system, with the first number representing the state of the first qubit, and the second number representing the state of the second qubit. With these two numbers, we have moved from 2 base states to 4 base states, and the quantum system can indeed be a combination of any of those 4 base states. In the examples below, |X⟩⊗|Y⟩ denotes a system where the first qubit is in state |X⟩ and the second qubit is in state |Y⟩, so |X⟩⊗|Y⟩=|XY⟩.

When you look at the states |A⟩ and |E⟩, the difference may appear trivial, but they represent very different systems. The difference is, as you may have guessed, one of these systems is entangled and the other is not. I have tried to indicate this with state |A⟩ — you can see that by factorising, we can separate the 2 qubit system into 2 single qubit systems. The McDaniels employee has prepared my box in state |0⟩, and my sister’s box in state |M+⟩. No such separation is possible for state |E⟩ — the state of my box and the state of my sister’s box are linked by entanglement.

Why is this interesting? Given I open my box before my sister does, the toy I receive is still truly random (just as before). However, as soon as I observe my toy, I know exactly which toy is in my sister’s box. State |E⟩ is a combination of the scenarios where we either both have rats, |00⟩, or both have transformers, |11⟩. So if I open my box and see a transformer, and I know the quantum state that our boxes were prepared in, then I know for certain that my sister will also have a transformer. This gets stranger the longer you think about it.

Suppose my sister and I each visited McDaniels, left our joyful meals closed, and then were mercilessly bundled into rockets that transported us to opposite ends of the universe. Until the moment I open my box, my sister could open her box and still observe either toy at complete random. But the moment I open my toy, that randomness is lost, and she is guaranteed to receive the same toy when she opens hers. It is as though my box sent information all the way across the universe in an instant to tell the other box what to do.

Sid the Rat on a rocket ship
Flushed Away 2 looks out of this world

Entanglement demo

In a later section, we will write an algorithm that exploits this strange property of entangled systems and apparent information transfer to send two classical bits of information using a single qubit. But for now, let’s use Braket just to enhance our understanding of what entanglement is.

import matplotlib.pyplot as plt
from braket.circuits import Circuit
from braket.devices import LocalSimulator
# Running algorithm on local simulator
device = LocalSimulator()
circuit = Circuit()
# Apply Hadamard gate to first qubit
circuit.h(0)

These first six lines are the same as before — setting up a circuit to be run on a local simulator and applying a Hadamard gate to the qubit with index 0. So now we have a single qubit in the state we previously called |M+⟩. To demonstrate entanglement, we need to introduce a second qubit.

# Apply a cnot gate to the qubits
circuit.cnot(control=0, target=1)

That reminds me — I knew I talked about the CNOT gate for a reason. Recall that the classical CNOT gate flips a target bit if the control bit is a 1. So how does the quantum CNOT gate act on our qubits in states |M+⟩ and |0⟩ respectively? Let’s use the ket vector notation to write down the state of the system now, |S⟩.

Referring to the table from earlier, we know that the classical CNOT gate does nothing to the classical bit string 00, and transforms 10 to 11. If you compare the equations above and below, you’ll notice that the quantum CNOT acts on ket vectors in the state |S⟩ in the same way. It flips the second qubit dependent on the value on the first bit — but the first qubit here is in a combination of states. In a sense, it is in both states |0⟩ and |1⟩ at the same time. Similarly, the CNOT gate both flips and does not flip the second qubit at the same time. Sometimes it can be less mind-bending to just look at the maths…

That is, we have the entangled state we discussed earlier. Nice.

# Run with 1000 shots
result = device.run(circuit, shots=1000).result()
counts = result.measurement_counts
# print the results.
print(counts)
# Use matplotlib to print the data.
plt.bar(counts.keys(), counts.values())
plt.xlabel(‘Measurement Results’)
plt.ylabel(‘Counts’)
plt.show()

The rest of the code should look familiar too — in this example running the circuit 1000 times, printing the distribution of results, and plotting a graph with matplotlib.

T : |0|1|
q0: -H-C-
|
q1: ---X-
T : |0|1|

This is the result of printing the circuit to the console. On this occasion, there are two qubits, q0 and q1, that are each fed into the circuit in the default state |0⟩. We also have two ‘moments’ — the first labelled 0, during which the Hadamard gate is applied, and the second labelled 1, during which the CNOT gate is applied. The notation here in that the ‘C’ is the control qubit, attached to the ‘X’ on the target qubit, which is the circuit representation of the quantum NOT gate.

Counter({‘11’: 542, ‘00’: 458})

This is the result of printing the tally of results to the console. Once again, it seems we have simulated the flipping of a coin with an approximately 50/50 split in outcomes between each state. Notice that the system counts zero instances of ‘10’ or ‘01’ — indeed, whatever state the first qubit is measured to be in, the second must also be in the same state once that measurement has been made.

See the graph above showing a 50/50 split. I won’t try and make that seem more interesting than it is, but hopefully the next and final demonstration will illustrate better the cool stuff going on behind the scenes.

Superdense coding

The name of this algorithm might sound exactly how I would describe my own programming ability on a bad day, but it is one that is simultaneously simple and fascinating. The inspiration for using this algorithm came from this handy repository full of examples for AWS Braket that I would strongly recommend the interested reader take a look at — the examples are well written and extremely well documented.

As mentioned earlier, this algorithm exploits the property of entanglement to send two bits of classical information via only a single qubit. Let’s go back to McDaniels, with my sister and I exiled to lonely lives at the boundaries of the cosmos with only our tauntingly named ‘joyful’ meals for company. Suppose I wanted to send my sister a message to tell her how miserable I was in my empty, confined rocket ship. I’d be in luck, thanks to the magic of superdense coding! Let’s take a look at my options.

What an assortment of options! But what is the final column of the table? Well, to send my message, I first need to apply a gate to my own qubit. If I want to send the message ‘I am double lonely’, I can simply do nothing to my qubit. If I want to send ‘I am lonely and miserable’, I can simply flip my qubit by applying the X (NOT) gate. However, the other options involve applying a gate I haven’t yet introduced called the Z gate. There’s a reason I haven’t introduced the Z gate — it introduces another strange concept of quantum computing called ‘phase’.

So what does that mean? If my McDaniels box were in the state |1⟩, I would be 100% certain to find a transformer inside. Equally, if my box were in the state -|1⟩, I would be 100% certain to find a transformer inside. So nothing measurable has changed — yet. The little minus sign in front of the state affects superposition of states. Consider the action of the Hadamard gate on the states|M+⟩ and Z|M+⟩.

So, while the Z gate doesn’t have an immediate measurable effect on the state of the system, it changes the behaviour of the state when it is acted on by other gates. This is a handy tool for the superdense coding algorithm.

I’ve applied the right gates to my McDaniels box. Now I send my qubit to my sister somehow — perhaps using some sort of McDaniels trans-universal delivery service. How does she decode my message? She simply has to apply a CNOT gate using my qubit as the control, followed by a Hadamard gate on my qubit. Then, she should be able to open the boxes and find my message inside — two transformers (|00⟩) for double miserable. I only sent over a single qubit, and yet I have been able to transfer two classical bits of information. I have successfully saved 50% on the steep galactic McDaniels delivery fee.

Superdense coding demo

This algorithm is something worth running on AWS Braket. First, let’s import everything we need.

from braket.circuits import Circuit
from braket.aws import AwsDevice
from braket.devices import LocalSimulator
import matplotlib.pyplot as plt
device = LocalSimulator()

This is the same as before, except that I have added a line to import ‘AwsDevice’ — we’re going to try and run this on a real quantum computer in a moment. However, for now, I’m still using the LocalSimulator() device to make sure our algorithm works as expected.

prep_circ = Circuit()
prep_circ.h([0])
prep_circ.cnot(0, 1)

This part of the algorithm is the McDaniels employee working their magic and setting up our qubits to be in the entangled state |B⟩. Once this has run, my sister and I are pushed into our cramped rocket ships and launched into the abyss.

dan_messages = {
“00”: Circuit().i(0),
“01”: Circuit().x(0),
“10”: Circuit().z(0),
“11”: Circuit().x(0).z(0)
}
dan_message = “01”
prep_circ.add_circuit(dan_messages[dan_message])

This part of the algorithm is me carefully deciding which message to send, and finally deciding on ‘I am lonely and miserable’ (01). So, I apply the X gate to my qubit. One this has run, I launch my ‘Joyful Meal’ across the cosmos towards my sister.

sister_circ = prep_circ.cnot(0, 1)
sister_circ.h([0])
print(sister_circ)
task = device.run(sister_circ, shots=1)
result = task.result()
counts = result.measurement_counts
print(counts)

Finally, my sister receives the joyful meal and executes her gates on it. She opens the box and finds…

T: |0|1|2|3|4|
q0:-H-C-X-C-H-
| |
q1:---X---X---
T: |0|1|2|3|4|
Counter({‘01’: 1})

Indeed, she has received my message 01, in the form of a plastic anthropomorphic rat and a plastic anthropomorphic car. The output also shows a depiction of the circuit split into five moments — each the application of a different gate. The moments 0 and 1 are the McDaniels employee preparing the state, while moment 2 is me applying my chosen gate(s) to my qubit. Moments 3 and 4 then represent my sister applying her gates to the qubits before measuring the results. Just to be clear, I only ever own and act on the single qubit q0, never touching q1, and yet I have managed to send two classical bits of information using q0.

Personally, I think this is already pretty cool, but under the hood, the local simulator is not actually sending two classical bits of information with a single qubit. Let’s try and use AWS Braket to run this algorithm with real qubits.

The grand finale

Okay, we’re finally ready to run something meaningful on an actual QPU. First, we need to find the AWS ARN for the QPU we want. This can be found in the AWS Braket console by selecting the device you’d like. In this case, I’ve selected the Rigetti device, adding this line into our superdense coding algorithm.

device = AwsDevice(“arn:aws:braket:us-west- 1::device/qpu/rigetti/Aspen-M-1”)

However, there are further modifications to make to the algorithm, as our task will have to wait in a queue before it is executed by the device. Here is a function that will continuously poll the status of our task — once again inspired by this handy repository of Braket examples.

def run_task(device, circuit):  # Submit job to be run for 100 shots
task = device.run(circuit, shots=100)
# Get ID of task
task_id = task.id
print(‘Task ID :’, task_id)
status_list = []
current_state = task.state()
status_list += [current_state]
print(“Current state of task is “ + current_state)
# Poll task status until completed
while current_state != 'COMPLETED':
current_state = task.state()
if current_state != status_list[-1]:
print("Current state of task is " + current_state)
status_list += [current_state]
# Get results
result = task.result()
counts = result.measurement_counts
# Print measurement results
print('Sisters measurements:', counts)
# Plot measurement results
plt.bar(counts.keys(), counts.values())
plt.xlabel('Message Received by Sister')
plt.ylabel('Counts')
plt.xticks(rotation=90)
plt.show()
return 0

In this function, we use the ID of the submitted task to probe the status of the job so that we can wait until it has completed before moving onto printing the results. The only thing left to do now is to call the function.

run_task(device, sister_circ)

After waiting some time for the device to become available, we have some results from a real quantum computer!

Current state of task is CREATEDCurrent state of task is QUEUEDCurrent state of task is RUNNINGCurrent state of task is COMPLETEDSisters measurements: Counter({‘01’: 93, ‘11’: 6, ‘00’: 1})

Interestingly, the measurements were not 100% consistent with what we expected. Why did my sister sometimes receive the messages 11 and 00?

The qubits in the real quantum computer are coupled to ‘noisy environments’, and sometimes the state of a qubit will be altered by that environment. For example, a qubit in a superposition of states may experience ‘decoherence’, interacting with its environment in such a way that it collapses to a base state |0⟩ or |1⟩. This was the purpose behind running 100 shots; repeating the algorithm a large number of times revealed a large peak around the bit string we wanted to send. Okay, it might somewhat detract from the coolness of sending two classical bits of information with a single qubit, but there are certain algorithms where the computational advantage of using a quantum computer is worth dealing with the noisy systems.

For example, in 2019, Google claimed that on a quantum computer it ran a “series of operations in 200 seconds that would take a supercomputer about 10,000 years to complete”. It is these algorithms where quantum computing offers the greatest application and benefit — the tricky part is figuring out which ones they are.

In a nutshell

Quantum computing is already being used successfully in industry for a variety of purposes and is only anticipated to become increasingly relevant in the near future.

The qubit properties of superposition, entanglement, and phase offer flexibility in computation that is not available with classical bits — the difficulty lies in uncovering which algorithms can be enhanced by these properties, and how they can be implemented practically. If such algorithms are to be developed and the potential of quantum computing fully realised, software engineering talent needs to be effectively applied in the field.

AWS Braket offers access to quantum computers and simulators for those that are interested. Hopefully this article has demonstrated that getting started is as simple as opening the lid on a ‘McDaniels Joyful Meal ©’.

Interested in joining us?

Credera is currently hiring! View our open positions and apply here.

Got a question?

Please get in touch to speak to a member of our team.

--

--