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.
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).
For example, one could run it on ResourcesEstimator:
dotnet run -n 4 -s ResourcesEstimator
The printed output is then
Metric Sum
CNOT 0
QubitClifford 4
R 0
Measure 4
T 0
Depth 0
Width 4
BorrowedWidth 0
For details on what these metrics indicate, see Resource estimator: metrics reported.
Command line run summary
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.
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:
Import the
qsharpmodule, 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.Import those Q# callables that we'll directly invoke---in this case,
MeasureSuperpositionandMeasureSuperpositionArray.import qsharp from Superposition import MeasureSuperposition, MeasureSuperpositionArrayWith the
qsharpmodule imported, you can also import callables directly from the Q# library namespaces.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:
.simulate(<args>)uses the full state simulator to simulate the operation for an ideal quantum computer. (API reference for.simulate())..simulate_sparse(<args>)uses the sparse simulator to simulate the operation for an ideal quantum computer. (API reference for.simulate_sparse())..estimate_resources(<args>)uses the resources estimator to compute various quantum resources required by the program. (API reference for.estimate_resources())..toffoli_simulate(<args>)uses the Toffoli simulator to provide a more efficient simulation method for a restricted class of quantum programs. (API reference for.toffoli_simulate())..simulate_noise(<args>)uses the noise simulator to simulate the operation in an open quantum system under the influence of noise. You can enable the use of the noise simulator by callingqsharp.experimental.enable_noisy_simulation().
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).
Q# Jupyter Notebooks
Q# Jupyter Notebooks make use of the IQ# kernel, which allows you to define, compile, and run Q# callables in a single notebook---all alongside instructions, notes, and other content.
This means that while it's possible to import and use the contents of *.qs Q# files, they are not necessary in the run model.
Here, it's detailed how to run the Q# operations defined above. But a more broad introduction to using Q# Jupyter Notebooks is provided at Set up a Q# standalone environment.
Defining operations
In a Q# Jupyter Notebook, you enter Q# code just as we would from inside the namespace of a Q# file.
So, one can enable access to callables from the Q# standard libraries with open statements for their respective namespaces.
Upon running a cell with such a statement, the definitions from those namespaces are available throughout the workspace.
Note
Callables from Microsoft.Quantum.Intrinsic and Microsoft.Quantum.Canon (for example, H and ApplyToEach) are automatically available to operations defined within cells in Q# Jupyter Notebooks.
However, this is not true for code brought in from external Q# source files (a process shown at Intro to Q# and Jupyter Notebooks).
Similarly, defining operations requires only writing the Q# code and running the cell.
The output then lists those operations, which can then be called from future cells.
Target machines
The functionality to run operations on specific target machines is provided via IQ# Magic Commands.
For example, %simulate makes use of the QuantumSimulator, and %estimate uses the ResourcesEstimator:
Passing arguments to callables in Q#
To pass inputs to the Q# operations, the arguments can be passed as key=value pairs to the run magic command.
So, to run MeasureSuperpositionArray with four qubits, we can run %simulate MeasureSuperpositionArray n=4:
This pattern can be used similarly with %estimate and other run commands.
Note
Passing callables in a similar manner with run commands such as %simulate or %estimate is not possible.
Using Q# code from other projects or packages
By default, a Q# Jupyter Notebook loads all of the .qs files in the current folder and makes their Q# operations and functions
available for use from inside the notebook. The %who magic command lists all
currently available Q# operations and functions.
To load Q# code from another folder, the %project magic command 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 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 cells simulate a Q# operation from an external project, where the project path is referenced relative to the current folder:
To load external packages containing Q# code, use the %package magic command.
Loading a package will also make available any custom magic commands or display encoders that are contained in any assemblies
that are part of the package.
To load external packages or Q# projects at notebook initialization time, ensure that the notebook folder contains
a .csproj file that references Microsoft.Quantum.Sdk. In that .csproj, add the property
<IQSharpLoadAutomatically>true</IQSharpLoadAutomatically> to the <PropertyGroup>. This property will instruct IQ# to recursively
load any ProjectReference or PackageReference items found in that .csproj at notebook load time.
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 Q# Jupyter Notebook hosts,
but in the future, this may become the default
behavior for a .csproj file located in the same folder as the notebook file.
Note
Currently the <QsharpCompile> setting in the .csproj is ignored by Q# Jupyter Notebook 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).
Feedback
Issottometti u ara feedback għal