Chapter 2. Running Q# Programs

This chapter starts by discussing the structure of quantum applications and the typical stages of quantum software development process. Some of these steps you’ll recognize from classical software development, and some are going to be uniquely quantum.

After that I introduce quantum simulators - tools commonly used for testing and evaluating quantum programs prior to running them on quantum hardware. Quantum simulators are an integral part of quantum software development workflows, and understanding their capabilities and role will be essential for the rest of the book.

The main part of the chapter describes the various ways to run Q# programs, on their own or interwoven with classical programs, in different environments, and the ways to choose the best environment for your purposes.

Quantum applications

Quantum computers are often considered to be a type of coprocessors - specialized processors used to augment the functions of the main processor. A coprocessor, such as a GPU, performs certain kinds of processor-intensive tasks, such as graphics acceleration, better than the main processor, and offloading those tasks to the coprocessor can improve the overall system performance. Similarly, quantum computers are going to offer quantum speedups for certain types of problems, but by no means for all of them.

Most quantum computing applications will use a hybrid workflow, combining classical and quantum code. This way, the quantum computer solves the computationally heavy sub-tasks it is best suited for, and the classical computer handles the rest of the sub-tasks, such as interacting with the user, querying databases, preparing the data for the quantum sub-task, and so on. Q# emphasizes this separation of quantum and classical code: being a domain-specific language, it doesn’t support a lot of functionality that general-purpose languages offer, such as reading data from a file or accepting user input.

Typical workflow of a quantum application
Figure 2-1. Typical workflow of a quantum application.
Note

Let’s consider ground state chemistry - an example of a practical application that can take advantage of quantum computing.

TODO: complete the example; the inputs to the quantum program describe the structure of the molecule to be modeled, and you’re not going to enter those by hand!

This means that Q# programs need to be tightly integrated with code written in classical programming languages, so as to make this hybrid workflow as smooth and easy to implement as possible.

Quantum software development

A typical development process for this kind of hybrid quantum-classical applications looks as follows.

Quantum software development process
Figure 2-2. Quantum software development process.

In part I of this book we focused on the first step of the process, discussing the Q# language and the fundamentals of writing quantum code with it. However, the code you’ll write with that knowledge alone is likely to be fairly low-level, and you’ll have to develop it from scratch. Microsoft Quantum Development Kit offers a rich collection of Q# libraries that implement a lot of building blocks commonly used in quantum programs, allowing you to keep your code high-level and to focus on the logic of your algorithm. In Chapter 7 we will discuss Q# libraries in more detail.

You can see that the quantum software development process includes writing both quantum and classical code. Sometimes, if your application is extremely simple and does not require heavy pre- or post-processing, you can get away with skipping the classical code altogether, but in most practical applications the classical part will play a significant role. In this chapter we’ll discuss the tools you can use to integrate your Q# code with classical code. I won’t talk about developing classical code more generally, since it’s a much broader topic than can fit in one chapter!

The third step of the process involves using a variety of tools to validate and evaluate your quantum program to make sure that 1) it is correct, and 2) it is viable to run on a quantum computer you have access to. This is an extremely important step, especially in the current era, when the small sizes of available quantum devices set hard limitations on the scale of the quantum programs you can run on them, and their scarcity demands that you debug your programs before you run them on hardware. In this chapter I’ll only introduce the tools available for running Q# programs locally on your classical computer, since they are essential for the discussion of running Q# programs in general. We will discuss testing and debugging tools and techniques in greater detail in Chapter 8.

The fourth and final step of the quantum software development process is running your application on a quantum computer. Q# applications are designed to run on Azure Quantum - Microsoft’s cloud ecosystem supporting quantum computing. Using Azure Quantum is out of scope of this book; it is a young ecosystem, and the pool of tools and services it offers grows rapidly.

Quantum simulators

Before we dive deeper into the various scenarios of running Q# code, let’s talk about tools that turn out to be extremely useful for quantum software development - quantum simulators. Quantum simulators are classical programs that allow you to run small simulations of quantum systems and thus to execute quantum programs without access to quantum hardware.

A typical quantum software development process will use simulators to validate and evaluate programs before running them on quantum hardware.

Microsoft Quantum Development Kit includes multiple quantum simulators that serve different purposes. Let’s look at each one in turn.

Full state simulator

The full state simulator performs mathematically accurate simulation of the quantum gates and measurements applied by your program to the qubits it allocates.

Internally the full state simulator represents the quantum state of the program as a vector of complex numbers - the amplitudes of the basis states of the system. Quantum gates change those amplitudes following the rules for matrix multiplication. The simulator performs measurements by choosing a random measurement outcome based on the outcome probabilities computed using the amplitudes, and adjusting the state of the system that matches the measurement result to reflect the “collapse” of the state caused by the measurement.

The full state simulator is a perfect replacement for an actual quantum computer - up to a certain point. Allocating each extra qubit doubles the number of the amplitudes that describe the system and thus the amount of memory required to store the full state of the quantum system, as long as the system is entangled. It also increases the time required to apply each individual gate, since in entangled systems applying a simple gate might involve updating every amplitude. This means that while an average laptop can easily run the full state simulator for programs of 20-30 qubits, simulating over 45 qubits can require concerted efforts of a whole computer cluster, and simulating a general program that uses several hundreds of qubits is well beyond the capabilities of the world’s aggregate classical computational power!

In practice, the full state simulator is widely used to validate and verify programs. In addition to the program state simulation, this simulator performs a broad range of runtime checks. Some of these will be familiar to you from classical programming, such as accessing a qubit that has already been released. Others are quantum-specific, such as checking that the same qubit is not used as both a control and a target for a controlled gate.

Note

One of the runtime checks the full state simulator does is the check for the released qubits not being entangled with the qubits still in use. (Up to the version [TODO] it was implemented as the requirement for the released qubits to be in the |0⟩ state.)

Qubits are a very limited resource in quantum computers, and we expect them to remain scarce for quite some time to come. This means that the runtime for quantum programs is designed to reuse the temporarily allocated qubits as much as possible, assigning the same hardware qubit to a different logical one once it is required. If the released qubits remain entangled with other parts of the system upon their release, allocating them as fresh qubits and using them in a different part of the computation can affect the results of the whole program - without the developer being any the wiser.

This kind of bugs are really hard to track down, and the quantum hardware runtime cannot check whether a qubit is entangled with others when it is released! Running the program on a simulator that performs this kind of checks can be very helpful in catching such bugs early.

Quantum software developers rely on the full state simulator to run their programs on small instances of problems, to verify that they produce the expected answer, and to develop unit tests. For example, the Quantum Katas rely on the full state quantum simulator for verifying that the learners’ solutions to the programming problems are correct.

The program visualization tools provided by the full state simulator are also extremely useful for learning. They allow a person at the start of their quantum computing journey to write and run lots of small experimental programs to experiment with quantum computing, confirm their understanding of the basic principles, and find the gaps in their knowledge. We will learn more about the testing and debugging tools offered by the full state simulator in Chapter 8.

Resources estimator

The resources estimator is another commonly used simulator from the QDK toolkit. It estimates the resources required to run a Q# program on a quantum computer, even if the program is too large to be actually executed - either on the full state simulator or on the quantum hardware.

It is often important to get a good idea of the resources required by an algorithm beyond its asymptotic runtime and memory usage. Same as in classical computing, two algorithms for solving the same problem that have the same asymptotic complexity can differ widely in the actual resources consumed in different scenarios. Besides, the exact implementation details of an algorithm, such as choosing a data structure used on one of the steps of the algorithm, can alter its complexity dramatically.

Note

Consider, for example, the classical sorting task. There are multiple algorithms that sort an array of N numbers in O(N log N) operations in the average case. However, they differ in the memory usage, the number of operations required in the best or the worst case, as well as in other properties. For example, in-place sorting algorithms will require a single number worth of memory outside the given array, while others might require O(log N) or even O(N). All these properties might define which algorithm is most suitable for a certain application, so it is important to evaluate them before making the decision.

Historically, the authors of papers presenting quantum algorithms and their implementations did the estimates of the resources required to run them manually, and these estimates were hard to reproduce and to verify (not to mention to do accurately in the first place!) Given the limitations of the full state simulation we’ve discussed earlier, and the small size of the currently available quantum hardware, tools such as the resources estimator are often the only way to evaluate a specific implementation of an algorithm automatically.

Internally the resources estimator doesn’t actually simulate the program execution to produce the result, like the full state simulator does. Instead, it walks through the program, taking note of the qubits allocation and release, quantum gates and measurements, without performing any of these actions. The output of the resources estimator is a short list of the resources the program needs to run. Two main parameters in the list are the number of qubits used by the program (the “width” of the corresponding circuit) and the time it would take to run (the “depth” of the circuit). The others include the numbers of certain types of single-qubit gates, CNOTs, and measurements.

Trace simulator

Trace simulator is similar to the resources estimator (and is, in fact, the foundation on which the latter is built) in that it doesn’t simulate the program execution, but only traces the steps of this execution. It can perform more finely tuned resources estimation, such as estimating the depth of the circuit with different “execution time” assigned to different gates. (Resources estimator uses the default settings, in which the most “time-consuming” gate is the T gate, and the rest of the gates are ignored.) Trace simulator can also run some program validations, checking that the program never tries to use a qubit that is already released, or to call a multi-qubit gate with the same qubit in several roles. If you’re working with a small program that can be executed on a full state simulator, it will catch these bugs and throw an exception at runtime, but trace simulator is a good way to detect this kind of issues in larger programs.

Toffoli simulator

Toffoli simulator simulates the program execution gate-by-gate, as long as the program uses a very limited set of gates: X, CNOT, and their controlled variants. These gates are sometimes called “classical”, since a quantum system that starts in the |0⟩ state and uses only those gates can never get to a superposition state, transitioning only between the basis states of the system, which correspond to classical integers.

Since this simulator doesn’t need to represent the superposition states of the system, it can encode the states as an array of Boolean values, one per qubit. This makes the simulation very efficient, and allows to run programs that use tens of thousands of qubits. However, it cannot simulate the inherent randomness of quantum computation. Since the program running on Toffoli simulator has access only to the basis states of the system, all computations it simulates are deterministic.

Toffoli simulator turns out to be surprisingly useful for validating so-called reversible computations - quantum computations that implement classical functions, such as quantum oracles, quantum arithmetic, and so on.

Noisy simulators

Preview simulators simulate the program’s behavior in the presence of noise. You can use them to model noisy hardware devices - and currently all quantum hardware devices are noisy!

Running Q# programs

Now that we’ve covered the tools we can use to run Q# programs locally on your computer, let’s move on to the discussion of the different ways to do that.

The rest of this chapter will show you how to run a simple Q# program that simulates a series of biased coin flips. It takes two numbers - an integer n and a floating-point number pTrue - as an input and returns an array of n boolean values. Each of the return values will represent the result of a single flip of a biased coin, being true with the probability pTrue, and false with the probability 1 - pTrue.

Example 2-1 shows the basic Q# code that implements this example. Note that it uses two operations - the main operation FlipBiasedCoinN and the helper operation FlipBiasedCoinOnce that simulates a single coin flip.

Example 2-1. Biased coin flip program in Q#
namespace BiasedCoins {
    open Microsoft.Quantum.Arrays;
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Math;

    /// # Summary
    /// Generates a single random bit that represents
    /// an outcome of a biased coin flip.
    /// # Input
    /// ## pTrue
    /// The probability to generate true.
    operation FlipBiasedCoinOnce(
        pTrue : Double
    ) : Bool {
        use q = Qubit();
        Ry(2.0 * ArcSin(Sqrt(pTrue)), q);
        return M(q) == One;
    }

    /// # Summary
    /// Generates a series of random bits that represent
    /// outcomes of a biased coin flip.
    /// # Input
    /// ## n
    /// The number of bits to generate.
    /// ## pTrue
    /// The probability to generate true.
    operation FlipBiasedCoinN(
        n : Int, pTrue : Double
    ) : Bool[] {
        return DrawMany(FlipBiasedCoinOnce, n, pTrue);
    }
}

This example is not particularly exciting on its own, but it has all the key features I’ll need to discuss the ways to run Q# code and the way it needs to be modified in each scenario. In particular, it takes several parameters of different types, and it returns a non-trivial value.

Let’s see how to run this program using different methods.

Standalone Q#

The simplest way to run Q# code is standalone Q# mode. In this mode the project consists of:

  • Q# code in one or several .qs files, and

  • the .csproj file which defines the project type, the version of the QDK to use, and other project settings.

This minimalistic setup can be very convenient for quick prototyping and developing small projects or unit tests that don’t require heavy classical computations. However, it is not the most elegant way to implement an end-to-end hybrid quantum-classical application, since it does not allow you to integrate Q# code with classical code easily.

Setting up the project

Standalone Q# project is defined using a .csproj project file based on the MSBuild format, similar to .NET projects. Example 2-2 shows a typical example of a .csproj file defining a standalone Q# project.

Example 2-2. Project file for a standalone Q# project
<Project Sdk="Microsoft.Quantum.Sdk/0.18.2106148911">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>
</Project>
Project

The Sdk attribute set to Microsoft.Quantum.Sdk indicates that the project is a Q# project and should be built using the specified version of the Microsoft QDK.

OutputType

Specifies the project type: Exe indicates an executable, and omitting this property indicates a library or a unit test project.

TargetFramework

Specifies the target framework for the project: should be netcoreapp3.1 for Q# executables and unit test projects, or netstandard2.1 for Q# libraries.

The rest of the properties follow the standard MSBuild format.

If you need to use Q# code from other projects or NuGet packages in your project, you can use project references or package references, respectively, same as you would do in a classical .NET project. For example, to add a reference to the NuGet package Microsoft.Quantum.Xunit (a library for testing Q# programs using the xUnit framework), you add the following snippet to your .csproj file under the Project tag:

<ItemGroup>
  <PackageReference
    Include="Microsoft.Quantum.Xunit"
    Version="0.18.2106148911" />
</ItemGroup>

Writing the quantum code

In this mode, one of the operations in the Q# code has to be annotated with @EntryPoint attribute. This will let the compiler know which of the operations should be the entry point - the operation called first when the compiled program is executed.

@EntryPoint()
operation FlipBiasedCoinN(
    n : Int, pTrue : Double
) : Bool[] {
    return DrawMany(FlipBiasedCoinOnce, n, pTrue);
}

If the project contains unit tests, each of the unit test operations has to be annotated with @Test attribute. We will discuss writing unit tests in Q# in more detail in Chapter 8.

Running the program

You can run standalone Q# projects in the same way you run classical .NET projects. Let’s start with considering the case of the entry point operation taking no parameters.

If you’re using command line, type dotnet run to build and run your project. (You can first use dotnet build to build the project without running it.)

If you’re using Visual Studio, you can set your Q# project as the startup project, and press Ctrl+F5 (Start without debugging) or use the green Start button on the Visual Studio toolbar to run the program.

These commands will build your project and run it using the full state simulator. The return value of the entry point operation, as well as any debug information printed by the Q# code, will be printed to the standard output of the program.

If you want to choose a different simulator to run your program on, you can do so via the command line parameters. Use the option --simulator (or the shorthand -s), followed by the name of the simulator to use. For example,

dotnet run --simulator ResourcesEstimator
dotnet run -s ToffoliSimulator

dotnet run command also accepts a variety of options that are common for classical .NET projects and not specific to Q# code.

If you’re using Visual Studio to run your Q# projects, you can configure the command line parameters to pass to your code in as follows: right-click on your project, choose Properties > Debug and fill the command line parameters in the Application Arguments field.

Passing parameters to the quantum code

Now let’s recall that the entry point operation in our coin-flip example takes two parameters - an integer and a floating-point number.

operation FlipBiasedCoinN(
    n : Int, pTrue : Double
) : Bool[]

This means that you need to pass these parameters to the operation when running the project; otherwise you’ll get Option … is required error for each missing parameter.

You can pass the parameters to the Q# code from the command line as well. To do this, use command line options with names that match the names of the parameters. Names defined in camelCase are converted to kebab-case style. In our example, the parameters n and pTrue will be converted to command line options n and p-true.

dotnet run -n 10 -p-true 0.3

Table 2-1 shows how you can pass parameters of different data types to a Q# project.

Table 2-1. Passing parameters to a standalone Q# project via command line
Data type Example Notes

Int

-n-dec 10 -n-hex 0xAE

Can use decimal and hexadecimal literals.

BigInt

-n 9223372036854775808

No L postfix, unlike in Q# BigInt literals.

Double

-p-true 0.3

Can omit decimal point for integer values, unlike in Q# Double literals.

Bool

-b1 true -b2 FALSE

Supports arbitrary capitalization of values.

String

-s1 Hi -s2 “Hello World”

Can omit double quotes for strings without spaces.

Result

-r1 0 -r2 One

Supports arbitrary capitalization of values and 0/1 values.

Pauli

-basis PauliX

Supports arbitrary capitalization of values.

Range

-r1 1..3 -r2 9..-1..0

Does not support spaces inside range values.

Array

-a 1 2 3

Space-separated list of array elements.

Passing qubits, tuples, user-defined types, or callables to the entry point operation is not supported.

Q# with a classical host

As we’ve discussed earlier, combining Q# code with a classical host program is the most generic way to implement a quantum application. It allows to combine the strengths of existing classical libraries and tools with the computational speedup offered by quantum computing.

Currently Q# supports two kinds of classical host programs - .NET languages and Python. While the logic of integrating Q# with any language is similar, the project setup and the syntactic details differ depending on the language, so we will discuss these languages separately.

In both cases the Q# code itself doesn’t need to be modified compared to Example 2-1, unlike in the standalone Q# or in Q# Jupyter Notebooks cases. The classical host program will use the Q# code as a library that provides quantum computing functionality.

In both cases you will run the hybrid application the way you normally run applications using that language, be it from the command line or from a Python Jupyter Notebook.

Q# with .NET host

We will start with .NET languages, which includes C#, C++, F#, and Visual Basic.NET. For simplicity I will give the code samples in C#; the rest of the languages will use similar approach, adjusted for each language’s syntax. You can find the equivalent examples in the other .NET languages in the supplemental materials repository.

Setting up the project

To run Q# code from a .NET application, you need to define it as a library using a .csproj file. The contents of the file will slightly differ from the standalone Q# project we looked at in Example 2-2. Example 2-3 shows a typical example of a .csproj file defining a Q# library.

Example 2-3. Project file for a Q# library
<Project Sdk="Microsoft.Quantum.Sdk/0.18.2106148911">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>
</Project>

Omitted OutputType property indicates that this project is a library rather than an executable.

Note

In this example I discuss using Q# libraries as project dependencies, which is the most convenient method for developing quantum and classical code for the same application together. If you are looking to distribute your quantum code as a library to other Q# users, similar to the QDK libraries, you’ll want to release it as a NuGet package. In this case you’ll need to modify your project properties to build a NuGet package from it, following the general NuGet package creation guidelines. Your users will then add your package to their quantum or classical projects as a package dependency.

Once you’ve defined a Q# library, either as a project or as a NuGet package, you can use it as a dependency in your regular .NET application. Example 2-4 shows a typical example of a .csproj file defining a C# application with a dependency on a quantum library.

Example 2-4. Project file for a C# application using a Q# library
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include=
    "..quantum-libraryBiasedCoinsLibrary.csproj" />
  </ItemGroup>
</Project>

Notice that the C# project uses Microsoft.NET.Sdk as the SDK, same as purely classical C# projects. Using Microsoft.Quantum.Sdk is necessary only for Q# projects.

With this project dependency, you can call any non-internal operations/functions defined in your quantum code.

Calling Q# code from C#

To call a Q# operation from C# host program, you need to do two steps.

First, you create an instance of a simulator from Microsoft.Quantum.Simulation.Simulators namespace that you’ll use to run your code. You can use a using directive for that namespace to avoid writing out the fully qualified name of the simulator.

using Microsoft.Quantum.Simulation.Simulators;
...
var toffoli = new ToffoliSimulator();

Note that the full state simulator, implemented by the QuantumSimulator class, implements the IDisposable interface. This means that you need to call the Dispose method for the simulator instance after you’re done with it. The easiest way to do this is to rely on the C# using statement:

using var fullStateSim = new QuantumSimulator();
Note

This is the only scenario of running Q# code in which you can define the constructor parameters of the simulator, such as the random number seed for the full state simulator, or the number of qubits allocated by the Toffoli simulator. In all other scenarios the simulators are instantiated with their default parameters.

Second, call the Run method of the Q# operation you want to call. This method is automatically generated for all Q# callables. The first argument of this method is the simulator instance you want to use. The rest of the arguments match the parameters of the Q# operation you’re calling.

The Run method generated for the Q# operation is asynchronous, reflecting the fact that program execution on quantum hardware can potentially run for a long time. This means that you need to either use the await operator or the .Result property to wait until the method execution completes and to get its result.

var bits = FlipBiasedCoinN
              .Run(simulator, 10, 0.3).Result;

Passing parameters to the quantum code

The arguments accepted by the Q# operation called from the C# code are converted into parameters of the Run method, starting with the second parameter. The return value of the Q# operation is converted into the return value of the Run method of the corresponding C# data type. Table 2-2 shows the mapping of different Q# data types onto C#.

Table 2-2. Q# data types representation in C# host
Q# data type C# data type Notes

Int

long

BigInt

System.Numerics.BigInteger

Double

double

Bool

bool

String

string

Result

Microsoft.Quantum.Simulation.Core.Result

Enum: Result.Zero and Result.One

Pauli

Microsoft.Quantum.Simulation.Core.Pauli

Enum: Pauli.PauliI, Pauli.PauliX, Pauli.PauliY, and Pauli.PauliZ

Range

Microsoft.Quantum.Simulation.Core.QRange

Class: constructor takes either 2 parameters (start and stop) or 3 parameters (start, step, and stop)

Array

Microsoft.Quantum.Simulation.Core.QArray

Class: constructor takes a C# array literal of a matching type, for example, new QArray<long>(new long[]{1, 2, 3});

Tuple

tuple

You can not pass qubits, user-defined types, or callables to the Q# operation called from a .NET host.

Q# with Python host

Now let’s take a look at how to do all these things with Python as the classical host program.

Setting up the project

To run Q# code from a Python program, you need to place the .qs files in the same folder as the .py file(s) defining the host program. You can use a supplementary .csproj file to define any dependencies used by Q# code, but it is not required to set up interaction with the Python host.

Calling Q# code from Python

To call a Q# operation from Python host program, you need to do two steps.

First, you import qsharp module to enable Q# interoperability - using Q# operations in the Python program. After this the namespaces defined in Q# code will be available to Python code as new modules, allowing you to import the operations and functions you intend to use.

import qsharp
from BiasedCoins import FlipBiasedCoinN

Once you’ve imported the Q# operation, it behaves as a Python class which implements a certain set of methods. You call a method of the Q# operation that runs it on one of the available simulators. Table 2-3 lists the methods available and the simulators they invoke. All the simulator instances are created with their default parameters.

Table 2-3. Python methods for running the operation on different simulators
Method Simulator

.simulate()

Full state simulator

.estimate_resources()

Resources estimator

.toffoli_simulate()

Toffoli simulator

Running an operation of a full state simulator will look as follows:

bits = FlipBiasedCoinN.simulate(n=10, pTrue=0.3)

Passing parameters to the quantum code

The arguments accepted by the Q# operation called from the Python code are converted into parameters of the corresponding methods of the Python version of this operation. Note that the arguments of the Q# operation become keyword only arguments of the Python methods, with keywords matching the names of the arguments in Q#. The return value of the Q# operation is converted into the return value of the corresponding method.

Table 2-4 shows the mapping of different Q# data types onto C#.

Table 2-4. Q# data types representation in Python host
Q# data type Python data type Notes

Int

int

BigInt

int

Double

float

Bool

bool

String

str

Result

int

Does not support Zero/One literals.

Pauli

int

Supports integers 0 through 3 as literals.

Array

list

Tuple

tuple

You can not pass ranges, qubits, user-defined types, or callables to the Q# operation called from a Python host.

Q# Jupyter Notebooks

Q# Jupyter Notebooks are a web-based interactive environment for working with Q#. They allow you to define and import Q# code, run it on simulators, and visualize it using tools that are not available in other Q# environments (we’ll discuss them in Chapter 8). They also provide the standard Jupyter Notebooks functionality: creating documentation cells with rich text and math formulas using Markdown and LaTeX, and inserting media such as images and videos.

Q# Jupyter Notebooks offer a convenient setup for quick prototyping and experimenting with small code snippets. Because they allow you to combine interactive code with text and media, they are perfect for creating tutorials, interactive learning tools, demos, and presentations. For example, the Quantum Katas use Q# Jupyter Notebooks extensively to offer tutorials that combine theoretical material, interactive exercises, and solutions explanations.

Setting up the notebook

A Q# Jupyter Notebook exists on its own, without an underlying Q# project. The only file required is the .ipynb file that defines the notebook itself.

You can also use the notebook as a “frontend” for the “backend” Q# code defined in .qs files existing in the same folder. In this case the IQ# kernel will compile those files upon its startup, and make their contents available to the notebook cells.

If you need to use Q# code from another project or package in your code cells, you can use %project or %package magic commands. Both commands will load the references on the fly, when the cell is executed.

Alternatively, you can define a .csproj file on the Q# “backend” used with the Q# Jupyter Notebook “frontend”, add package and project references you need to it, and add <IQSharpLoadAutomatically>true</IQSharpLoadAutomatically> to the <PropertyGroup> tag. In this case the IQ# kernel will load the references upon its startup. This approach works great when combined with Q# “backend” files in the same project, allowing you to do all the project setup work in the background and only bring to the notebook the portions of Q# code and magic commands that you want to demonstrate.

Writing the quantum code

Q# code written in Jupyter Notebooks doesn’t need to be wrapped in a namespace declaration. Q# Jupyter Notebooks define a working namespace automatically, and all Q# code written in the notebook cells is compiled as part of that namespace.

You can open namespaces in the code cells in a same way as in the other modes of Q# code. Two namespaces, Microsoft.Quantum.Intrinsic and Microsoft.Quantum.Canon, are open by default, so you don’t need to re-open them in your code. You also don’t need to re-open namespaces in each cell; it is sufficient to open a namespace once in one of the cells, and it will remain available to the cells executed later.

The notebooks don’t support annotations such as @EntryPoint or @Test to define the operations that should be executed. Instead, you need to call the operations you want to execute explicitly.

Running the program

Both code compilation and code execution in Jupyter Notebooks are done via cell execution.

Once you’ve written some Q# code in a notebook cell, execute this cell using Ctrl+Enter (⌘+Enter on macOS). This will compile the code and output the compilation errors, if any. If the code compiles successfully, the cell execution will output a list of operations, functions, and user defined types defined in this cell that are now available to the cells executed later.

To run the code, you need to use a magic command - one of the special commands that control Jupyter Notebooks - followed by the name of the operation you want to run. Table 2-5 lists the most commonly used magic commands which run the Q# operations on various simulators. All the simulator instances are created with their default parameters.

Table 2-5. Magic commands for running the operation on different simulators
Command Simulator

%simulate

Full state simulator

%estimate

Resources estimator

%toffoli

Toffoli simulator

You can find a complete list of IQ# magic commands in the documentation. I will return to them in Chapter 8 when I discuss the program visualization tools offered by Q# Jupyter Notebooks.

Passing parameters to the quantum code

To pass the parameters to the operation simulated using one of the magic commands, you will use parameter=value pairs. The parameter names are spelled in the same way as in the Q# code. In our biased coins example, running the operation with the right parameters on the full state simulator looks as follows:

%simulate FlipBiasedCoinN n=10 pTrue=0.3

Table 2-6 shows how you can pass parameters of different data types to an operation in Q# Jupyter Notebooks.

Table 2-6. Passing parameters to Q# operations in Jupyter Notebooks
Data type Example Notes

Int

n=10

Can use only decimal literals.

BigInt

n=9223372036854775808

No L postfix, unlike in Q# BigInt literals.

Double

pTrue=0.3

Can omit decimal point for integer values, unlike in Q# Double literals.

Bool

b1=true b2=FALSE

Supports arbitrary capitalization of values.

String

s1=Hi s2="Hello World”

Can omit double quotes for strings without spaces.

Result

r1=0 r2=1

Does not support Zero/One literals.

Pauli

basis=PauliX

Supports arbitrary capitalization of values or integers 0 through 3.

Q# Jupyter Notebooks do not support passing qubits, ranges, tuples, arrays, user-defined types, or callables to the operation called via a magic command. If you need to pass more complicated parameters to the operation, consider defining a parameterless wrapper operation that defines those parameters and passes them to your original operation.

Conclusion

In this chapter you’ve learned a lot about the quantum software development process and about the tools available to you to run Q# programs.

In the next chapter, we will return to discussing developing Q# programs. You will learn about the Q# libraries included in the Microsoft Quantum Development Kit and about using them to write high-level Q# code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
54.162.130.75