Write your first quantum program

Learn how to write a quantum program using the Microsoft QDK. You will write an application to demonstrate quantum entanglement using a Bell state.

You start with the simplest program possible and build it up to demonstrate quantum superposition and quantum entanglement.

Pre-requisites

  • Install the Quantum Development Kit using your preferred language and development environment
  • If you already have the QDK installed, make sure you have updated to the latest version

What You'll Learn

  • How to set up a quantum solution and project
  • The components of a Q# operation
  • How to call a Q# operation from a host program
  • How to entangle two qubits

Setup

Applications developed with Microsoft's Quantum Development Kit consist of two parts:

  1. One or more quantum algorithms, implemented using the Q# quantum programming language.
  2. A host program, implemented in a programming language like Python or C# that serves as the main entry point and invokes Q# operations to execute a quantum algorithm.
  1. Choose a location for your application

  2. Create a file called Bell.qs. This file will contain your Q# code.

  3. Create a file called host.py. This file will contain your Python host code.

Write a Q# operation

Our goal is to prepare two qubits in a Bell state showing entanglement. We will build this up piece by piece to demonstrate qubit states, gates, and measurement.

Q# operation code

  1. Replace the contents of the Bell.qs file with the following code:

    namespace Quantum.Bell {
        open Microsoft.Quantum.Intrinsic;
        open Microsoft.Quantum.Canon;
    
        operation Set(desired : Result, q1 : Qubit) : Unit {
            if (desired != M(q1)) {
                X(q1);
            }
        }
    }
    

    This operation may now be called to set a qubit to a known state (Zero or One).

    We measure the qubit, if it's in the state we want, we leave it alone, otherwise, we flip it with the X gate.

About Q# operations

An operation is the basic unit of quantum execution in Q#. It is roughly equivalent to a function in C or C++ or Python, or a static method in C# or Java.

The arguments to an operation are specified as a tuple, within parentheses.

The return type of the operation is specified after a colon. In this case, the Set operation has no return, so it is marked as returning Unit. This is the Q# equivalent of unit in F#, which is roughly analogous to void in C#, and an empty tuple (Tuple[()]) in Python.

About quantum gates

You have used two quantum gates in your first Q# operation:

  • The M gate, which measures the state of the qubit
  • The X gate, which flips the state of a qubit

A quantum gate transforms the state of a qubit. The name comes from the analogy of a classical logic gate such as a NOT gate, which inverts the state of a classical bit.

Add Q# test code

  1. Add the following operation to the Bell.qs file, inside the namespace, after the end of the Set operation:

    operation TestBellState(count : Int, initial : Result) : (Int, Int) {
    
        mutable numOnes = 0;
        using (qubit = Qubit()) {
    
            for (test in 1..count) {
                Set(initial, qubit);
                let res = M(qubit);
    
                // Count the number of ones we saw:
                if (res == One) {
                    set numOnes += 1;
                }
            }
            Set(Zero, qubit);
        }
    
        // Return number of times we saw a |0> and number of times we saw a |1>
        return (count-numOnes, numOnes);
    }
    

    This operation (TestBellState) will loop for count iterations, set a specified initial value on a qubit and then measure (M) the result. It will gather statistics on how many zeros and ones we've measured and return them to the caller. It performs one other necessary operation. It resets the qubit to a known state (Zero) before returning it allowing others to allocate this qubit in a known state. This is required by the using statement.

About variables in Q#

Q# deals with variables in a unique way. By default, variables in Q# are immutable; their value may not be changed after they are bound. The let keyword is used to indicate the binding of an immutable variable. Operation arguments are always immutable.

If you need a variable whose value can change, such as numOnes in the example, you can declare the variable with the mutable keyword. A mutable variable's value may be changed using a set statement.

In both cases, the type of a variable is inferred by the compiler. Q# doesn't require any type annotations for variables.

About using statements in Q#

The using statement is also special to Q#. It is used to allocate qubits for use in a block of code. In Q#, all qubits are dynamically allocated and released, rather than being fixed resources that are there for the entire lifetime of a complex algorithm. A using statement allocates a set of qubits at the start, and releases those qubits at the end of the block.

Create the host application code

  1. Open the host.py file and add the following code:

    import qsharp
    
    from qsharp import Result
    from Quantum.Bell import TestBellState
    
    initials = (Result.Zero, Result.One)
    
    for i in initials:
      res = TestBellState.simulate(count=1000, initial=i)
      (num_zeros, num_ones) = res
      print(f'Init:{i: <4} 0s={num_zeros: <4} 1s={num_ones: <4}')
    

About the host application code

The Python host application has three parts:

  • Compute any arguments required for the quantum algorithm. In the example, count is fixed at a 1000 and initial is the initial value of the qubit.
  • Run the quantum algorithm by calling the simulate() method of the imported Q# operation.
  • Process the result of the operation. In the example, res receives the result of the operation. Here the result is a tuple of the number of zeros (num_zeros) and number of ones (num_ones) measured by the simulator. We deconstruct the tuple to get the two fields, and print the results.

Build and run

  1. Run the following command at your terminal:

    python host.py
    

    This command runs the host application, which simulates the Q# operation.

The results should be:

Init:0    0s=1000 1s=0   
Init:1    0s=0    1s=1000

Prepare superposition

Now we want to manipulate the qubit. First we'll just try to flip it. This is accomplished by performing an X gate before we measure it in TestBellState:

X(qubit);
let res = M(qubit);

Now the results (after pressing F5) are reversed:

Init:Zero 0s=0    1s=1000
Init:One  0s=1000 1s=0

However, everything we've seen so far is classical. Let's get a quantum result. All we need to do is replace the X gate in the previous run with an H or Hadamard gate. Instead of flipping the qubit all the way from 0 to 1, we will only flip it halfway. The replaced lines in TestBellState now look like:

H(qubit);
let res = M(qubit);

Now the results get more interesting:

Init:Zero 0s=484  1s=516
Init:One  0s=522  1s=478

Every time we measure, we ask for a classical value, but the qubit is halfway between 0 and 1, so we get (statistically) 0 half the time and 1 half the time. This is known as superposition and gives us our first real view into a quantum state.

Prepare entanglement

Now we'll prepare the promised Bell state and show off entanglement. The first thing we'll need to do is allocate 2 qubits instead of one in TestBellState:

using ((q0, q1) = (Qubit(), Qubit())) {

This will allow us to add a new gate (CNOT) before we measure (M) in TestBellState:

Set(initial, q0);
Set(Zero, q1);

H(q0);
CNOT(q0, q1);
let res = M(q0);

We've added another Set operation to initialize the first qubit to make sure that it's always in the Zero state when we start.

We also need to reset the second qubit before releasing it.

Set(Zero, q0);
Set(Zero, q1);

The full routine now looks like this:

    operation TestBellState(count : Int, initial : Result) : (Int, Int) {

        mutable numOnes = 0;
        using ((q0, q1) = (Qubit(), Qubit())) {
            for (test in 1..count) {
                Set (initial, q0);
                Set (Zero, q1);

                H(q0);
                CNOT(q0,q1);
                let res = M(q0);

                // Count the number of ones we saw:
                if (res == One) {
                    set numOnes += 1;
                }
            }
            
            Set(Zero, q0);
            Set(Zero, q1);
        }

        // Return number of times we saw a |0> and number of times we saw a |1>
        return (count-numOnes, numOnes);
    }

If we run this, we'll get exactly the same 50-50 result we got before. However, what we're interested in is how the second qubit reacts to the first being measured. We'll add this statistic with a new version of the TestBellState operation:

    operation TestBellState(count : Int, initial : Result) : (Int, Int, Int) {
        mutable numOnes = 0;
        mutable agree = 0;
        using ((q0, q1) = (Qubit(), Qubit())) {
            for (test in 1..count) {
                Set(initial, q0);
                Set(Zero, q1);

                H(q0);
                CNOT(q0, q1);
                let res = M(q0);

                if (M(q1) == res) {
                    set agree += 1;
                }

                // Count the number of ones we saw:
                if (res == One) {
                    set numOnes += 1;
                }
            }
            
            Set(Zero, q0);
            Set(Zero, q1);
        }

        // Return number of times we saw a |0> and number of times we saw a |1>
        return (count-numOnes, numOnes, agree);
    }

The new return value (agree) keeps track of every time the measurement from the first qubit matches the measurement of the second qubit. We also have to update the host application accordingly:

import qsharp

from qsharp import Result
from Quantum.Bell import TestBellState

initials = {Result.Zero, Result.One} 

for i in initials:
    res = TestBellState.simulate(count=1000, initial=i)
    (num_zeros, num_ones, agree) = res
    print(f'Init:{i: <4} 0s={num_zeros: <4} 1s={num_ones: <4} agree={agree: <4}')

Now when we run, we get something pretty amazing:

Init:Zero 0s=499  1s=501  agree=1000
Init:One  0s=490  1s=510  agree=1000

Our statistics for the first qubit haven't changed (50-50 chance of a 0 or a 1), but now when we measure the second qubit, it is always the same as what we measured for the first qubit. Our CNOT has entangled the two qubits, so that whatever happens to one of them, happens to the other. If you reversed the measurements (did the second qubit before the first), the same thing would happen. The first measurement would be random and the second would be in lock step with whatever was discovered for the first.

Congratulations, you've written your first quantum program!

What's next?

For more information about the metrics reported and accessing the data programmatically, take a look at the ResourcesEstimator documentation.

To learn more about the other type of simulators and target machines provided in the Quantum Development Kit, how they work and how to use them, take a look at the Managing quantum machines and drivers topic in the documentation.