Ways to run a Q# program

One of the Quantum Development Kit's greatest strengths is its flexibility across platforms and development environments. However, this flexibility also means that new Q# users may find themselves confused or overwhelmed by the numerous options found in the install guide. This page explains what happens when a Q# program is run and compares the different ways in which users can do so.

A primary distinction is that Q# can be run:

  • as a standalone application, where Q# is the only language involved and the program is invoked directly. Two methods actually fall in this category:
    • the command-line interface
    • Q# Jupyter Notebooks
  • with an extra host program, written in Python or a .NET language (for example, C# or F#), which then invokes the program and can further process returned results.

To understand these processes and their differences better, let's consider a Q# program and compare the ways it can be run.

Basic Q# program

A basic quantum program might consist of preparing a qubit in an equal superposition of states $\ket{0}$ and $\ket{1}$, measuring it, and returning the result. The result will be randomly either one of these two states with equal probability. Indeed, this process is at the core of the quantum random number generator quickstart.

In Q#, the random number generation would be performed by the following code:

        use q = Qubit();     // allocates qubit for use (automatically in |0>)
        H(q);                // puts qubit in superposition of |0> and |1>
        return MResetZ(q);   // measures qubit, returns result (and resets it to |0> before deallocation)

However, this code alone can't be run by Q#. To run it needs to make up the body of an operation, which is then run when called---either directly or by another operation. Hence, you can write an operation of the following form:

    operation MeasureSuperposition() : Result {
        use q = Qubit();     // allocates qubit for use (automatically in |0>)
        H(q);                // puts qubit in superposition of |0> and |1>
        return MResetZ(q);   // measures qubit, returns result (and resets it to |0> before deallocation)
    }

You've defined an operation, MeasureSuperposition, which takes no inputs and returns a value of type Result.

In addition to operations, Q# also allows you to encapsulate deterministic computations into functions. Aside from the determinism guarantee that implies that computations that act on qubits need to be encapsulated into operations rather than functions, there's little difference between operations and functions. We refer to them collectively as callables.

Callable defined in a Q# file

The callable is precisely what's called and run by Q#. However, it requires a few more additions to comprise a full *.qs Q# file.

All Q# types and callables, both the ones you define and those intrinsic to the language, are defined within namespaces, which provide each a full name that can then be referenced.

For example, the H and MResetZ operations are found in the Microsoft.Quantum.Instrinsic and Microsoft.Quantum.Measurement namespaces (part of the Q# Standard Libraries). As such, they can always be called via their full names, Microsoft.Quantum.Intrinsic.H(<qubit>) and Microsoft.Quantum.Measurement.MResetZ(<qubit>), but always doing this full specification would lead to cluttered code.

Instead, open statements allow callables to be referenced with more concise shorthand, as it's done in the operation body above. The full Q# file containing our operation would therefore consist of defining our own namespace, opening the namespaces for the callables our operation uses, and then the operation:

namespace Superposition {
    open Microsoft.Quantum.Intrinsic;     // for the H operation
    open Microsoft.Quantum.Measurement;   // for MResetZ

    operation MeasureSuperposition() : Result {
        use q = Qubit();     // allocates qubit for use (automatically in |0>)
        H(q);                // puts qubit in superposition of |0> and |1>
        return MResetZ(q);   // measures qubit, returns result (and resets it to |0> before deallocation)
    }
}

Note

Namespaces can also be aliased when opened, which can be helpful if callable/type names in two namespaces conflict. For example, one could instead use open Microsoft.Quantum.Instrinsic as NamespaceWithH; above, and then call H via NamespaceWithH.H(<qubit>).

Note

One exception to all of this is the Microsoft.Quantum.Core namespace, which is always automatically opened. Therefore, callables like Length can always be used directly.

Running on target machines

Now the general run model of a Q# program becomes clear.


Q# program execution diagram

The specific callable to be run has access to any other callables and types defined in the same namespace. It also accesses those items from any of the Q# libraries, but those items must be referenced either via their full name, or by using open statements described above.

The callable itself is then run on a target machine. Such target machines can be actual quantum hardware or the multiple simulators available as part of the QDK. For the purposes here, the most useful target machine is an instance of the full-state simulator, QuantumSimulator, which calculates the program's behavior as if it were being run on a noise-free quantum computer.

So far, you've seen what happens when a specific Q# callable is being run. Regardless of whether Q# is used in a standalone application or with a host program, this general process is more or less the same---hence the QDK's flexibility. The differences between the ways of calling into the Quantum Development Kit therefore reveal themselves in how that Q# callable is invoked, and in what manner any results are returned. More specifically, the differences revolve around:

  • Indicating which Q# callable is to be run
  • How potential callable arguments are provided
  • Specifying the target machine on which to run it
  • How any results are returned

In the following sections, you'll learn how this is done with the Q# standalone application from the command prompt. Then you'll proceed to using Python and C# host programs. The standalone application of Q# Jupyter Notebooks will be reserved for last, because unlike the first three, its primary functionality doesn't center around a local Q# file.

Note

Although it is not illustrated in these examples, one commonality between the run methods is that any messages printed from inside the Q# program (by way of Message or DumpMachine, for example) will typically always be printed to the respective console.

Q# from the command prompt

One of the easiest ways to get started writing Q# programs is to avoid worrying about separate files and a second language altogether. Using Visual Studio Code or Visual Studio with the QDK extension allows for a seamless work flow in which we run Q# callables from only a single Q# file.

For this, you'll ultimately run the program by entering

dotnet run

at the command prompt. The simplest workflow is when the terminal's directory location is the same as the Q# file, which can be easily handled alongside Q# file editing by using the integrated terminal in VS Code, for example. However, the dotnet run command accepts numerous options, and the program can also be run from a different location by providing --project <PATH> with the location of the Q# file.

Add entry point to Q# file

Most Q# files will contain more than one callable, so naturally we need to let the compiler know which callable to run when we provide the dotnet run command. This specification is done with a simple change to the Q# file itself; you need to add a line with @EntryPoint() directly preceding the callable.

The file from above would therefore become:

namespace Superposition {
    open Microsoft.Quantum.Intrinsic;     // for the H operation
    open Microsoft.Quantum.Measurement;   // for MResetZ

    @EntryPoint()
    operation MeasureSuperposition() : Result {
        use q = Qubit();     // allocates qubit for use (automatically in |0>)
        H(q);                // puts qubit in superposition of |0> and |1>
        return MResetZ(q);   // measures qubit, returns result (and resets it to |0> before deallocation)
    }
}

Now, a call of dotnet run from the command prompt leads to MeasureSuperposition being run, and the returned value is then printed directly to the terminal. So, you'll see either One or Zero printed.

It doesn't matter if you have more callables defined below it, only MeasureSuperposition will be run. Additionally, it's no problem if your callable includes documentation comments before its declaration, the @EntryPoint() attribute can be placed above them.

Callable arguments

So far, this article has only considered an operation that takes no inputs. Suppose you wanted to perform a similar operation, but on multiple qubits---the number of which is provided as an argument. Such an operation can be written as:

namespace MultiSuperposition {
    open Microsoft.Quantum.Intrinsic;     // for the H operation
    open Microsoft.Quantum.Measurement;   // for MResetZ
    open Microsoft.Quantum.Canon;         // for ApplyToEach
    open Microsoft.Quantum.Arrays;        // for ForEach
    
    @EntryPoint()
    operation MeasureSuperpositionArray(n : Int) : Result[] {
        use qubits = Qubit[n];               // allocate a register of n qubits in |0> 
        ApplyToEach(H, qubits);              // apply H to each qubit in the register
        return ForEach(MResetZ, qubits);     // perform MResetZ on each qubit, returns the resulting array
    }
}

where the returned value is an array of the measurement results. The ApplyToEach and ForEach are in the Microsoft.Quantum.Canon and Microsoft.Quantum.Arrays namespaces, requiring another open statement for each.

If one moves the @EntryPoint() attribute to precede this new operation (note there can only be one such line in a file), attempting to run it with simply dotnet run results in an error message that indicates what command-line options are required, and how to express them.

The general format for the command line is actually dotnet run [options], and callable arguments are provided there. In this case, the argument n is missing, and it shows that we need to provide the option -n <n>. To run MeasureSuperpositionArray for n=4 qubits, you need to run:

dotnet run -n 4

yielding an output similar to

[Zero,One,One,One]

This of course extends to multiple arguments.

Note

Argument names defined in camelCase are slightly altered by the compiler to be accepted as Q# inputs. For example, if instead of n, you decided to use the name numQubits above, then this input would be provided in the command line via --num-qubits 4 instead of -n 4.

The error message also provides other options that can be used, including how to change the target machine.

Different target machines

As the outputs from our operations thus far have been the expected results of their action on real qubits, it's clear that the default target machine from the command line is the full-state quantum simulator, QuantumSimulator. However, callables can be instructed to run on a specific target machine with the option --simulator (or the shorthand -s).

Command line run summary


Q# program from command line

Non-Q# dotnet run options

As it's briefly mentioned above with the --project option, the dotnet run command also accepts options unrelated to the Q# callable arguments. If providing both kinds of options, the dotnet-specific options must be provided first, followed by a delimiter --, and then the Q#-specific options. For example, specifying a path along with a number of qubits for the operation above would be run via dotnet run --project <PATH> -- -n <n>.

Q# with host programs

With the Q# file in hand, an alternative to calling an operation or function directly from the command prompt is to use a host program in another classical language. Specifically, this invocation can be done with either Python or a .NET language such as C# or F# (for the sake of brevity we'll only detail C# here). A little more setup is required to enable the interoperability, but those details can be found in the install guides.

In a nutshell, the situation now includes a host program file (for example, *.py or *.cs) in the same location as our Q# file. It's now the host program that gets run. While it's running, it can call specific Q# operations and functions from the Q# file. The core of the interoperability is based on the Q# compiler making the contents of the Q# file accessible to the host program so that they can be called.

One of the main benefits of using a host program is that the classical data returned by the Q# program can then be further processed in the host language. This handling could consist of some advanced data processing, for example, something that can't be done internally in Q#, and then calling further Q# actions based on those results, or something as simple as plotting the Q# results.

The general scheme is shown down here, and the discussion of the specific implementations for Python and C# is below. A sample using an F# host program can be found at the .NET interoperability samples.


Q# program from a host program

Note

The @EntryPoint() attribute used for Q# applications cannot be used with host programs. An error will be raised if it is present in the Q# file being called by a host.

To work with different host programs, there are no changes required to a *.qs Q# file. The following host program implementations all work with the same Q# file:

namespace Superposition {
    open Microsoft.Quantum.Intrinsic;     // for H
    open Microsoft.Quantum.Measurement;   // for MResetZ
    open Microsoft.Quantum.Canon;         // for ApplyToEach
    open Microsoft.Quantum.Arrays;        // for ForEach

    operation MeasureSuperposition() : Result {
        use q = Qubit();     // allocates qubit for use (automatically in |0>)
        H(q);                // puts qubit in superposition of |0> and |1>
        return MResetZ(q);   // measures qubit, returns result (and resets it to |0> before deallocation)
    }

    operation MeasureSuperpositionArray(n : Int) : Result[] {
        use qubits = Qubit[n];
        ApplyToEach(H, qubits); 
        return ForEach(MResetZ, qubits);    
    }
}

Select the tab corresponding to your host language of interest.

A Python host program is constructed as follows:

  1. Import the qsharp module, which registers the module loader for Q# interoperability. This import allows Q# namespaces to appear as Python modules, from which we can "import" Q# callables. It's technically not the Q# callables themselves that are imported, but rather Python stubs that allow calling into them. These stubs behave as objects of Python classes. One uses methods on these objects to specify the target machines that the operation is sent to when running the program.

  2. Import those Q# callables that we'll directly invoke---in this case, MeasureSuperposition and MeasureSuperpositionArray.

    import qsharp
    from Superposition import MeasureSuperposition, MeasureSuperpositionArray
    

    With the qsharp module imported, you can also import callables directly from the Q# library namespaces.

  3. Alongside regular Python code, you can now run those callables on specific target machines, and assign their return values to variables for further use:

    random_bit = MeasureSuperposition.simulate()
    print(random_bit)
    

Diagnostics

As with Q# standalone notebooks, you can also use diagnostics like DumpMachine and DumpOperation from Python notebooks to learn how your Q# program work and to help diagnose issues and bugs in your Q# programs.

namespace DumpOperation {
    open Microsoft.Quantum.Diagnostics;

    operation DumpPlusState() : Unit {
        use q = Qubit();
        within {
            H(q);
        } apply {
            DumpMachine();
        }
    }
}
from  DumpOperation import DumpPlusState
print(DumpPlusState.simulate())

Calling DumpMachine function generates the following output:

# wave function for qubits with ids (least to most significant): 0
∣0❭:     0.707107 +  0.000000 i  ==     ***********          [ 0.500000 ]     --- [  0.00000 rad ]
∣1❭:     0.707107 +  0.000000 i  ==     ***********          [ 0.500000 ]     --- [  0.00000 rad ]

The Q# package also allows you to capture these diagnostics and manipulate them as Python objects:

with qsharp.capture_diagnostics() as diagnostics:
    DumpPlusState.simulate()
print(diagnostics)
[{'diagnostic_kind': 'state-vector',
  'div_id': 'dump-machine-div-7d3eac24-85c5-4080-b123-4a76cacaf58f',
  'qubit_ids': [0],
  'n_qubits': 1,
  'amplitudes': [{'Real': 0.7071067811865476,
    'Imaginary': 0.0,
    'Magnitude': 0.7071067811865476,
    'Phase': 0.0},
   {'Real': 0.7071067811865476,
    'Imaginary': 0.0,
    'Magnitude': 0.7071067811865476,
    'Phase': 0.0}]}]

Working with raw JSON for diagnostics can be inconvenient, so the capture_diagnostics function also supports converting diagnostics into quantum objects using the QuTiP library:


with qsharp.capture_diagnostics(as_qobj=True) as diagnostics:
    DumpPlusState.simulate()
diagnostics[0]
Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket
Qobj data =
[[0.707]
 [0.707]]

To learn more about the diagnostics features offered by Q# and the Quantum Development Kit, see testing and debugging.

Specifying target machines

Running Q# operations on a specific target machine is done by invoking Python methods directly on the imported operation object. Thus, there's no need to create an object for the run target (such as a simulator). Instead, invoke one of the following methods to run the imported Q# operation:

For more information about local target machines, see Quantum simulators.

Passing arguments to callables in Q#

Arguments for the Q# callable should be provided in the form of a keyword argument, where the keyword is the argument name in the Q# callable definition. That is, MeasureSuperpositionArray.simulate(n=4) is valid, whereas MeasureSuperpositionArray.simulate(4) would throw an error.

Therefore, the Python host program

import qsharp
from Superposition import MeasureSuperposition, MeasureSuperpositionArray

single_qubit_result = MeasureSuperposition.simulate()
single_qubit_resources = MeasureSuperposition.estimate_resources()

multi_qubit_result = MeasureSuperpositionArray.simulate(n=4)
multi_qubit_resources = MeasureSuperpositionArray.estimate_resources(n=4)

print('Single qubit:\n' + str(single_qubit_result))
print(single_qubit_resources)

print('\nMultiple qubits:\n' + str(multi_qubit_result))
print(multi_qubit_resources)

results in an output as follows:

Single qubit:
1
{'CNOT': 0, 'QubitClifford': 1, 'R': 0, 'Measure': 1, 'T': 0, 'Depth': 0, 'Width': 1, 'BorrowedWidth': 0}

Multiple qubits:
[0, 1, 1, 1]
{'CNOT': 0, 'QubitClifford': 4, 'R': 0, 'Measure': 4, 'T': 0, 'Depth': 0, 'Width': 4, 'BorrowedWidth': 0}

Passing arrays in a similar manner is also possible. You can see an example in the Reversible Logic Synthesis sample.

Passing qubits as arguments from classical code isn't possible. Any logic that relates to Q# types like Qubit should live in your Q# code. If you want your Python code to specify the number of qubits, you could have something like nQubits : Int parameter to your Q# operation. Your Python code could pass the number of qubits as an integer and then your Q# code could allocate the array of the appropriate number of qubits.

For the Pauli and Result types, there are actually Python enums defined such that you could pass those values directly if you want to. See qsharp.Pauli and qsharp.Result.

Using Q# code from other projects or packages

By default, the import qsharp command loads all of the .qs files in the current folder and makes their Q# operations and functions available for use from inside the Python script.

To load Q# code from another folder, the qsharp.projects API can be used to add a reference to a .csproj file for a Q# project (that is, a project that references Microsoft.Quantum.Sdk). This command will compile any .qs files in the folder containing the .csproj and its subfolders. It will also recursively load any packages referenced via PackageReference or Q# projects referenced via ProjectReference in that .csproj file.

As an example, the following Python code imports an external project, referencing its path relative to the current folder, and invokes one of its Q# operations:

import qsharp
qsharp.projects.add("../qrng/Qrng.csproj")
from Qrng import SampleQuantumRandomNumberGenerator
print(f"Qrng result: {SampleQuantumRandomNumberGenerator.simulate()}")

This code results in output as follows:

Adding reference to project: ../qrng/Qrng.csproj
Qrng result: 0

To load external packages containing Q# code, use the qsharp.packages API.

If the Q# code in the current folder depends on external projects or packages, you may see errors when running import qsharp, since the dependencies have not yet been loaded. To load required external packages or Q# projects during the import qsharp command, ensure that the folder with the Python script contains a .csproj file that references Microsoft.Quantum.Sdk. In that .csproj, add the property <IQSharpLoadAutomatically>true</IQSharpLoadAutomatically> to the <PropertyGroup>. This change will instruct IQ# to recursively load any ProjectReference or PackageReference items found in that .csproj during the import qsharp command.

For example, here is a simple .csproj file that causes IQ# to automatically load the Microsoft.Quantum.Chemistry package:

<Project Sdk="Microsoft.Quantum.Sdk/0.17.2105143879">
    <PropertyGroup>
        <OutputType>Library</OutputType>
        <TargetFramework>netstandard2.1</TargetFramework>
        <IQSharpLoadAutomatically>true</IQSharpLoadAutomatically>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Microsoft.Quantum.Chemistry" Version="0.17.2105143879" />
    </ItemGroup>
</Project>

Note

Currently this custom <IQSharpLoadAutomatically> property is required by Python hosts, but in the future, this may become the default behavior for a .csproj file located in the same folder as the Python script.

Note

Currently the <QsharpCompile> setting in the .csproj is ignored by Python hosts, and all .qs files in the folder of the .csproj (including subfolders) are loaded and compiled. Support for .csproj settings will be improved in the future (for more information, see iqsharp#277).