Path: blob/main/quick-start-to-quantum/01_quick_start_to_quantum.ipynb
574 views
Quick Start to Quantum Computing with CUDA-Q
Lab 1 - Start Small: Quantum programming with one qubit
Overview
This lab starts with an introduction to quantum computing by comparing it to classical computation. It specifically defines the concepts of qubits and quantum operations using both visual and mathematical representations. These two concepts are all you need to begin coding. By the end of the lab, you'll have written not just your first, but several quantum programs using CUDA-Q!
What you'll do:
Define and visualize single-qubit states and operations
Write your first quantum programs
Interpret the results of a quantum program
Terminology you'll use:
quantum state
probability amplitudes
superposition
kernel
quantum circuit
measurement
sample
quasi-probability distribution
sampling error
CUDA-Q syntax you'll use:
defining a quantum kernel:
@cudaq.kernel
qubit initialization:
cudaq.qvector
andcudaq.qubit
quantum gates:
x
andh
extracting information from a kernel:
get_state
,sample
visualization tools:
add_to_bloch_sphere
,show
,draw
Execute the cells below to load all the necessary packages for this lab.
1.1 Overview of Quantum Computing
Watch this presentation to understand quantum computing fundamentals and discover the current challenges in developing practical quantum computers. You'll learn how GPUs and CUDA-Q are essential for both quantum simulation and the development of future accelerated quantum supercomputers. This overview might inspire you to continue learning how to program in CUDA-Q.
1.2 Define single-qubit states and operations
Computation involves storing and manipulating information. What distinguishes quantum computing from classical computing is the way in which information is represented and manipulated. We'll first review how information is represented in classical or digital computing and then contrast this with how information is represented in a quantum program.
1.2.1 Classical bits
The basic unit of information in classical computing is a bit. A bit stores information by taking on the value (sometimes referred to as a state) of either 0 or 1. We can use the notation to describe a bit in the state . One way to visualize this is an arrow (or a unit vector). If the vector is pointing up, the bit is in the state 0 and if the vector points down, the bit is in the state 1. Classical computing can change the state of a bit by changing it from a 0 to a 1 or vice versa. This operation is known as a bit-flip, and visually looks like flipping the direction of the vector from up to down or vice versa.
1.2.2 Qubits
In quantum computing, instead of bits, information is stored in qubits. While a single bit can only be in one of 2 states at a given time, a single qubit can be in one of infinitely many states! This suggests that we might be able to handle information more efficiently with qubits than we could with bits.
There is a catch. While there are infinitely many quantum states, we can't necessarily access all the information stored in these infinitely many states. This is due to the uncertainty principle in quantum mechanics. Restriciting our focus on how uncertainty arises in quantum computation, let's look at the general structure of a quantum program. Many quantum programs start with initializing a quantum state, manipulating the state, and then measuring it. When we measure a qubit, the act of measuring can change the quantum state of the qubit. For example, measuring may collapse the quantum state to of a single qubit to one of two classical states (e.g., 0 or 1). Measurement is how we extract useful information from a qubit. We'll explore this idea later in the notebook.
To visualize qubits, we will extend the idea of representing a bit as a unit vector pointing up or down. A qubit can be thought of as a unit vector pointing in any direction in 3D. In other words, we can visualize a qubit as a vector on a sphere. The literature refers to this sphere as the Bloch sphere. As a generalization of bits, we'll identify the vector pointing up (i.e., in the direction of the positive -axis) with the zero state (see figure below).
The zero state is denoted by and this notation is read as "ket zero" or "zero ket". The word "ket" comes from Dirac notation, which we'll describe in more detail later. The vector pointing down will represent the one state, which is abbreviated as and referred to as "ket one" or "one ket".
In addition to the and states, there are infinitely many other quantum states. Each of the infinitely many vectors on the Bloch sphere represents a different state.
Execute the code block below to generate a widget that you can interact with to visualize rotating the state about the , , and axis, in that order, by angles , , and , respectively. You can control the sliders with your mouse, or type in a value in the box to the right of the slider. For now, do not concern yourself with the code itself. We'll explain it in due time. For now, think of it as a preview to CUDA-Q. How many different quantum states can you generate starting with the state and just using rotations about the , , and axis?
FAQ: How does "infinitely many states" of a qubit differ from "analog computing"? That is also infinite, no?
Answer Qubits can demonstrate the quantum properties of superposition, entanglement, and interference. Additionally, in analog computing, the way in which information is extracted from an analog state differs from what happens in quantum computing. This is due to the way states are measured and the limitations imposed by the uncertainty principle in quantum mechanics. We'll experiment with measurement and superposition in this notebook. Entanglement and interference are covered in the next notebook.
Now that we have a visual representation of a qubit, let's establish some notation that will allow us to describe states and carry out quantum operations on these states. We've already introduced the states and . We can identify with the vector and with the vector . Then, for the purposes of this tutorial, any other quantum state on the Bloch sphere can be written as a linear combination of and . In other words, quantum states take the form
where and are complex numbers satisfying the equation . The coefficients and are referred to as probability amplitudes, or amplitudes for short. The restriction that ensures that the state is on the Bloch sphere.
Let's see how the expression maps to the Bloch sphere. First, we rewrite the coefficients and in the following manner:
where is a value in the interval and is a value in the interval . Using spherical coordinates we can draw the state on the Bloch sphere following the convention in the image below:
Execute the cell block below to open up an interactive tool to visualize for various values of and . As you experiment, think about the following questions: What effect does changing have on the position of the vector on the Bloch Sphere? What combination of values of and produces a state pointing along the -axis?
For example, the state can be rewritten as where and . For an explanation of how and are found in general, check out this source. Once we have found and , we can draw the state on the Bloch sphere. In particular, the state is a vector pointing in the direction of the negative -axis, which gives some explanation for the jargon: "minus state."
Take note: In order to do any computation, we will need a mechanism for changing the state of a qubit. Quantum computing allows us to change the state of a single qubit through quantum operations which can be visualized as rotations of the sphere. We'll explore this in more detail later in this notebook.
1.2.3 Using CUDA-Q to define and visualize a quantum state
We are now ready to use CUDA-Q!
FAQ: What is CUDA-Q?
Answer: CUDA-Q is a platform designed for hybrid application development. That is, CUDA-Q allows programming in a heterogeneous environment that leverages not only quantum processors and quantum emulators, but also CPUs and GPUs. CUDA-Q is interoperable with CUDA and the CUDA software ecosystem for GPU-accelerated applications. CUDA-Q consists of both C++ and Python extensions. In these notebooks, we'll use Python.
Let's use CUDA-Q to create the quantum states of single qubits and visualize them on the Bloch sphere. CUDA-Q uses the cudaq.kernel
decorator on functions to define quantum states, and, as we will see later, CUDA-Q kernels can also define quantum circuits.
Once we have defined a kernel, we can call upon the get_state
command to read out the state from the kernel. Then, we can add this state to a Bloch sphere using add_to_bloch_sphere
which is displayed with the show
command and the file can be saved using the save
option.
Exercise 1: Create a Bloch sphere showing the states: , and .
Hint: and .
CUDA-Q Quick Tip: The
add_to_bloch_sphere
command can accept a single state as we demonstrated above, or a list of states that can be graphed in an array of separate Bloch spheres. You can read more about this visualization tool here.
1.3 Writing your first quantum programs
Now that we understand qubits and have created quantum state representations in CUDA-Q, let's move on to actual quantum computation. We're ready to write our first quantum program!
The general structure of a quantum program is:
Encode information into the quantum state by initializing qubit(s)
Manipulate the quantum state of the qubit(s) with quantum gate(s)
Extract information from the quantum state by measuring the state of the qubit(s)
These three steps are outlined in the diagram below:
In this section, we'll look at a few simple examples of quantum programs to illustrate these steps. Throughout the notebook, we'll apply this template to generate and run more interesting examples. Let's begin with two very simple quantum programs: a bit flip program and a Hello World program. Through these examples, you'll learn about two other important concepts: measurement and superposition.
1.3.1 Bit Flip
Let's walk through the three steps of writing the Bit Flip program in CUDA-Q. This program is a quantum version of the classical NOT
operation that flips a bit from to and vice versa.
Outline of the Bit Flip program: Going from to .
Initialize the zero state
Manipulate the quantum state by applying the bit flip gate (the
x
gate)Measure the qubit
Let's start by defining a kernel and specifying that this kernel only contains one qubit.
CUDA-Q Quick Tip: The
cudaq.qvector
command has two purposes. Whencudaq.qvector
accepts alist
as we saw in theminus_state
example in the previous section, the kernel is initialized in the state corresponding to the coefficients from the list. In the example below,cudaq.qvector
will accept anint
value and will allocate that number of qubits to the kernel. These qubits, by default, are initialized in the zero-state.
Let's check to see that the code above initiated a qubit in the zero state by getting the state and plotting it on the Bloch sphere.
Next we want to manipulate the zero state, , and change it to the one state, . One way of doing this is by rotating the Bloch sphere 180 degrees around the x-axis. Mathematically, this can be carried out through matrix multiplication. Recall that and . Notice that the matrix has the property that we're looking for. Namely, multiplying the zero state by returns the one state:
In general, we can change a quantum state through multiplication by a unitary matrix. (The definition of a unitary matrix is not critical for this notebook, but if you're curious you can learn more in chapter 1 of Quantum Computer Science.) In the context of a quantum program, we refer to matrix multiplication by a unitary matrix as applying a -gate. In CUDA-Q, the -gate is implemented with the syntax x
.
CUDA-Q Quick Tip: You can view all the built-in quantum gate operations in CUDA-Q here.
Let's go ahead and apply the x
-gate to the qubit in our kernel.
Let's use the get_state
command again to check that the bitflip
kernel does what we expect, that is, change the state from to
The process of visualizing the state of a kernel on a Bloch sphere using get_state
is not very efficient and won't be useful when we have many qubits. Firstly, Bloch spheres can only represent the state of a single qubit. But also, the get_state
command may be overkill for the amount of information we can or need to recover from several qubits. As we'll see in the next lab, to describe the state of qubits we need to compute probability amplitudes. This may be prohibilitively expensive to compute in terms of time and memory resources.
Fortunately, there are other visualization tools that we can use to check the kernels that we create. Some kernels can be represented as quantum circuit diagrams. CUDA-Q includes the cudaq.draw
command to generate an ascii image of the circuit diagram of a kernel. Basically, each row of the circuit diagram represents a qubit. In this case, we only have one qubit, which by default is named q0
in the diagram. Operations applied to the qubit are shown as boxes and are read from left to right. Our kernel has only one gate, the x
gate. We'll discuss these diagrams in more detail later after we've seen a few other examples.
FAQ: What's the difference between a quantum kernel and a quantum circuit?
Answer: Quantum kernels are more general than quantum circuits. That is, every quantum circuit is a quantum kernel, but not every quantum kernel is a quantum circuit. Quantum kernels are defined as functions that are executed on a quantum processing unit (QPU) or a simulated QPU. Quantum kernels can be combined with classical functions to create quantum-classical applications that can be executed on a heterogeneous system of QPUs, GPUs, and CPUs to solve real-world problems.
The final step of our first quantum program is to extract information from the quantum state of the qubit. This is done with the sample
command. The sample command runs the kernel and takes a measurement shots_count
-many times
In our example, applying the x
-gate to the zero state will always result in the one state, unless of course there are errors in the circuit execution. That's a topic for another day. Now, we'll assume that all of our kernel executions are error free. In this case, we would expect to see the one state returned for each kernel execution. Run the cell block below to see the results.
You may notice that the result of executing the bitflip
kernel is a (classical) , which is technically a different type than the quantum state . Let's look at the Hello World example below which will help illustrate what is going on with the sample
command and how information is extracted from a quantum state.
CUDA-Q Quick Tip: The
sample
command runs the kernel and takes a measurementshots_count
-many times. The result of thesample
command is aSampleReslult
dictionary of the distribution of the bitstrings resulting from the sampling. To create a standard Python dictionary of type dict from an object (saysample_results
) of typecudaq.SampleResult
, you can use a command such asasDict = {k:v for k,v in sample_results.items()}
.
1.3.3 Hello World example
To get a better appreciation how quantum programs differ from classical programs, let's look at a Hello World program, which revisits the minus state kernel and will give us the opportunity to discuss two important concepts: superposition and measurement.
Hello World: Generate and measure the Minus State
Initialize one qubit in the one-state
Manipulate the quantum state by transforming it into the minus state
Extract information from the quantum state by taking measurement(s)
We already created a kernel (minus_state
) to initialize the minus state by passing a list of coefficients to the cudaq.qvector
command. There are other ways to initialize a state if we know the gate operations that generate it. The Hadamard operator applied to generates the state .
The Hadamard operator is the matrix
Notice that if we multiply and we get :
This calculation suggests a way to generate the minus state:
First intialize the one state and then apply the Hadamard gate using the CUDA-Q command
h
.This leaves the question: how do we initialize the one state? The answer is implicit in the previous example: apply the
x
-gate to the zero-state.
We now have a plan, so let's code it up.
EXERCISE 2: Edit the code block below to check your work by graphing the Bloch sphere of the state generated by minus_kernel
.
Let's sample the minus_kernel
and examine the results.
Notice how these results differ from what we saw in the Bit Flip program. We'll investigate what is going on in this example in more detail in the following section.
1.4 Interpret the results of a quantum program
1.4.1 Born's Rule
Let's further explore the minus_kernel
example from the previous section to better understand the sample
command. We've copied the codeblock from the previous section here, but we've edited the shots
variable to equal . Execute the cell block below a couple of times and see if you notice anything.
What do you notice when you run this code block a couple of times? The result varies. This is because quantum systems are inherently probabilistic in nature. You might see that roughly half of the time the outcome will be and the remaining times, the outcome will be a . This non-deterministic behavior can be explained by Born's Rule.
Born's Rule roughly states that if a qubit is in the quantum state with and complex values satisfying , then when measured, the outcome will be with probability and with probability . Furthermore upon measurement, the state of the qubit collapses; that is, the state of the qubit will change to if a is measured and the qubit will change to if a is measured.
Explore the connection between the Bloch Sphere representation of the state and the probabilities of measuring a 0 or 1. What effect does changing the angle have on the probability amplitudes?
Exercise 3: Based on Born's rule, what do you expect the probability of measuring the minus state as to be? How does this compare to the results of your execution of the minus_kernel
in the cell block above? You might notice that the percentage of times you measure a is close, but not exactly equal to what Born's rule would predict. Edit the ###FIX_ME###
line in the code block below to achieve sampling results more closely equal Born's rules prediction.
Hint: Think about the Central Limit Theorem for Proportions from statistics. What do you notice as you increase the number of shots?
1.4.2 Superposition
The concept that distinguishes the Bit Flip program from the Hello World program is superposition.
Superposition is one of the fundamental concepts in quantum mechanics that differentiates quantum computation from classical computation. A state is in superposition, if both and are non-zero. In other words, has a non-zero probability of being measured as a , but also a non-zero probability of being measured as a . It's important to note that if qubit is in a state of superposition, when it is measured, it will collapse to either or and will no longer be in superposition.
The state that we examined in the previous section is a state in superposition with the likelihood of measuring a to be and the likelihood of measuring a to be . There are other states that are distinct from that are also in a state of equal superposition. Let's take a look at one of those, the plus state (i.e., ). In addition to creating and sampling a kernel for the plus state in the code block below, we've used this example to illustrate two other CUDA-Q programming tips:
CUDA-Q Quick Tip: When allocating just one qubit, you can use
qubit()
in place ofqvector(1)
.
CUDA-Q Quick Tip: By default, when not specified, qubit measurements are conducted on the standard computational basis, which is along the -axis. In the cell block below, we have added the measurement explicitly with the
mz
command. There are other measurement optionsmx
andmy
built into CUDA-Q that we'll experiment with in the next section.
1.5 More about Measurement (optional)
How do the results of sampling the plus_kernel
compare to those obtained for the minus_kernel
in the previous section? What does this seem to apply concerning our ability to distinguish from ?
If qubits store information, we would like to be able distinguish two qubits that might be storing different information. For example we might want to distinguish the one-qubit state from the state . We'll resolve this issue in the next example by changing the way we measure the two qubits.
Exercise 4: Create two quantum kernels to sample. The first will prepare the state and then measure with mx
. The second kernel will prepare the state and then measure with mx
.
Notice that the results dictionaries from sampling the and , respectively differ from one another. And thus, by switching to measurement with the mx
command we are able to distinguish these two states. However, there is a drawback — measuring with mx
doesn't allow us to distinguish from , because of the Uncertainty Principle in quantum mechanics. Test this claim out for yourself by creating and sampling kernels for the and states using the mx
measurements.
What is happening when we take a measurement? What distinguishes the mz
command from the mx
command? Abstractly when we are measuring a state with mz
, we are applying Born's rule to the state . This act of measurement will collapse the state to , with a probability and will collapse the state of the qubit to with probability . You can graphically visualize this collapse under a mz
measurement on the Bloch sphere as projecting the state onto the axis.
Analogously, when measuring with mx
we will be collapsing our state to either or along the -axis of the Bloch sphere. We can use Born's Rule for this measurement, too. The only difference is now instead of writing in terms of computational basis states and , we'll find coefficients and so that . With the mx
measurement, the probabilities of measuring a and are determined by and , respectively. So for the case of , the probability of measuring with mx
is , and for the probability of measuring a with mx
is . Therefore, mx
gives us a way to distinguish and with only one shot, assuming there is no noise.
For the remainder of this series of labs, we'll only be measuring with mz
. To learn more about measurement, check out chapter 4 of Introduction to Quantum Information Science or section 2.2.3 of Introduction to Classical and Quantum Computing.
Next
There's not a whole lot we can do with just one qubit. In fact, one of the fundamental quantum properites (entanglement) requires more than one qubit. Let's move to Lab 2, where we start programming with more than one qubit and learn about entanglement.