This tutorial introduces you to a variety of tools offered by the Microsoft Quantum Development Kit to visualize various elements of Q# programs. These tools are extremely helpful both for learners seeking to confirm their understanding of the behavior of the program and for experienced quantum software developers debugging their programs.
This tutorial covers the following topics:
Let's start with a simple scenario: a program that acts on a single qubit. The state of the quantum system used by this program can be represented as a complex vector of length 2, or, using Dirac notation,
$$\begin{bmatrix} \alpha \\ \beta \end{bmatrix} = \alpha|0\rangle + \beta|1\rangle$$If you need a refresher on single-qubit quantum systems and their states, please see the tutorial on the concept of a qubit.
If this program runs on a physical quantum system, there is no way to get the information about the values of $\alpha$ and $\beta$ at a certain point of the program execution from a single observation. You would need to run the program repeatedly up to this point, perform a measurement on the system, and aggregate the results of multiple measurements to estimate $\alpha$ and $\beta$.
However, at the early stages of quantum program development the program typically runs on a simulator - a classical program which simulates the behavior of a small quantum system while having complete information about its internal state. You can take advantage of this to do some non-physical things, such as peeking at the internals of the quantum system to observe its exact state without disturbing it!
DumpMachine function from Microsoft.Quantum.Diagnostics namespace allows you to do exactly that. This function is available in standalone Q# applications as well.
The following demo shows how to use DumpMachine
to output the state of the system at any point in the program without affecting the state.
Note that the Q# code doesn't have access to the output of
DumpMachine
, so you cannot write any non-physical code in Q#!
The output of DumpMachine
is accurate up to a global phase: sometimes you'll see that all amplitudes are multiplied by some complex number compared to the state you're expecting.
// Run this cell using Ctrl+Enter (⌘+Enter on Mac)
// Then run the next cell to see the output
open Microsoft.Quantum.Diagnostics;
operation SingleQubitDumpMachineDemo () : Unit {
// This line allocates a qubit in state |0⟩.
use q = Qubit();
Message("State |0⟩:");
// This line prints out the state of the quantum system.
// Since only one qubit is allocated, only its state is printed.
DumpMachine();
// This line changes the qubit to state |+⟩ = (1/sqrt(2))(|0⟩ + |1⟩).
// 1/sqrt(2) is approximately 0.707107.
H(q);
Message("State |+⟩:");
DumpMachine();
// This will put the qubit into an uneven superposition,
// where the amplitudes of |0⟩ and |1⟩ have different absolute values and relative phases.
Rx(1.0, q);
Ry(2.0, q);
Rz(3.0, q);
Message("Uneven superposition state:");
DumpMachine();
// This line returns the qubit to state |0⟩ before releasing it.
Reset(q);
}
%simulate SingleQubitDumpMachineDemo
Input: A qubit in an unknown state $|\psi\rangle = \alpha|0\rangle + \beta|1\rangle$. The amplitudes $\alpha$ and $\beta$ will be real and non-negative.
Output: A tuple of two numbers $(\alpha', \beta')$ - your estimates of the amplitudes $\alpha$ and $\beta$. The absolute errors $|\alpha - \alpha'|$ and $|\beta - \beta'|$ should be less than or equal to 0.001.
The test will call your code exactly once, with the fixed state parameter that will not change if you run the cell several times.
%kata T1_LearnSingleQubitState
operation LearnSingleQubitState (q : Qubit) : (Double, Double) {
// ...
}
Now let's take a look at the general case: a program that acts on $N$ qubits. The state of the quantum system used by this program can be represented as a complex vector of length $2^N$, or, using Dirac notation,
$$\begin{bmatrix} x_0 \\ x_1 \\ \vdots \\ x_{2^N-1}\end{bmatrix} = \sum_{k = 0}^{2^N-1} x_k |k\rangle$$Same as in the single-qubit case, DumpMachine
allows you to see the amplitudes $x_k$ for all basis states $|k\rangle$ directly.
Note the use of an integer in the ket notation instead of a bit string with one bit per qubit.
By default, DumpMachine
uses little-endian to convert bit strings to integers in the ket notation.
For more details on endiannes, see the multi-qubit systems tutorial.
open Microsoft.Quantum.Diagnostics;
operation MultiQubitDumpMachineDemo () : Unit {
// This line allocates two qubits in state |00⟩.
use qs = Qubit[2];
Message("State |00⟩:");
// This line prints out the state of the quantum system.
DumpMachine();
// X gate changes the second qubit into state |1⟩.
// The entire system is now in state |01⟩, or, in little-endian notation, |2⟩.
X(qs[1]);
Message("State |01⟩:");
DumpMachine();
CNOT(qs[1], qs[0]);
Rx(1.0, qs[0]);
Ry(2.0, qs[1]);
Rz(3.0, qs[1]);
Message("Uneven superposition state:");
DumpMachine();
// This line returns the qubits to state |0⟩ before releasing them.
ResetAll(qs);
}
%simulate MultiQubitDumpMachineDemo
Input: 2 qubits in an unknown state $|\psi\rangle = \sum_{k = 0}^{3} x_k |k\rangle$. The amplitudes $x_k$ will be real and non-negative.
Output: A tuple of two numbers $(x_1', x_2')$ - your estimates of the amplitudes of the state $|1\rangle$ and $|2\rangle$, respectively. The absolute errors $|x_1 - x_1'|$ and $|x_2 - x_2'|$ should be less than or equal to 0.001.
The test will call your code exactly once, with the fixed state parameter that will not change if you run the cell several times.
%kata T2_LearnBasisStateAmplitudes
operation LearnBasisStateAmplitudes (qs : Qubit[]) : (Double, Double) {
// ...
}
Sometimes you want to focus on certain properties of the quantum state, such as the relative phases of individual basis states, or on certain parts of the state, such as the basis states with larger amplitudes associated with them.
%config magic command allows you to tweak the format of DumpMachine
output (available in Q# Jupyter Notebooks only).
Here are some useful options that help you extract the right information:
dump.basisStateLabelingConvention
sets the way the basis states are labeled in the output. Here is how the basis state $|10\rangle$ will look like with different settings:
dump.basisStateLabelingConvention value | DumpMachine output (first two columns) |
---|---|
"LittleEndian" (default) | |
"BigEndian" | |
"Bitstring" |
dump.truncateSmallAmplitudes
, when set to true
, removes basis states with measurement probabilities less than a certain threshold from the output.By default the measurement probability has to be less than $10^{-10}$ for the state to be truncated; you can change that threshold using the dump.truncationThreshold
setting.
dump.measurementDisplayStyle
and dump.phaseDisplayStyle
set the style of display for "Measurement Probability" and "Phase" columns; you can choose between bar/arrow, number, both, or none for each of the columns.If you choose to display the numeric value of measurement probability, you can configure its probability using dump.measurementDisplayPrecision
.
Input: $N$ qubits in an unknown state $|\psi\rangle = \sum_{k = 0}^{2^N-1} x_k |k\rangle$. The amplitudes $x_k$ will be real and non-negative.
Output: An array of integers which represent the basis states which have amplitudes bigger than 0.01 (in little endian encoding). The integers can be returned in any order.
The test will call your code exactly once, with the fixed state parameter that will not change if you run the cell several times.
%kata T3_HighProbabilityBasisStates
operation HighProbabilityBasisStates (qs : Qubit[]) : Int[] {
// ...
}
Let's consider a more complex scenario: you've written a Q# operation and want to check that it implements exactly the matrix you're looking for. (An examples of such cases could be quantum oracle implementation, unitary gate synthesis, or more uncommon scenarios such as the UnitaryPatterns kata).
You could do this semi-manually, by applying the operation to each basis state in turn and checking the amplitudes of the resulting states. However, DumpOperation library operation offers you a shorter and more elegant way to do this.
DumpOperation
is available both in standalone Q# applications as well, but the outputs it produces differs slightly: in plain text mode it is printed with the real and the imaginary components separated into two matrices.
Similarly to DumpMachine
, the output of DumpOperation
is accurate up to a global phase.
DumpOperation
requires an operation that acts on an array of qubits.
If your operation acts on a single qubit, such as most intrinsic gates, or on a mix of individual qubits and qubit arrays, you'll need to write a wrapper for it that takes a single array of qubits as an argument and applies the operation to the right qubits of that array.
open Microsoft.Quantum.Diagnostics;
operation DumpOperationDemo () : Unit {
DumpOperation(2, ApplyToFirstTwoQubitsCA(CNOT, _));
}
%simulate DumpOperationDemo
Note that the matrix of the CNOT gate produced by
DumpOperation
in this demo might differ from the one you are used to seeing in quantum computing resources, such as the multi-qubit gates tutorial: $$\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \end{bmatrix}$$Similarly to
DumpMachine
, the wayDumpOperation
evaluates the matrix depends on the conventions used by the simulator. Q# full state simulator tends to use little-endian encoding for converting basis states to integers, andDumpOperation
uses the same convention for converting basis states to the indices of matrix elements. Thus, the second column of the CNOT matrix corresponds to the input state $|1\rangle_{LE} = |10\rangle$, which the CNOT gate converts to $|11\rangle = |3\rangle_{LE}$, meaning that the last element is going to be $1$.A lot of resources on quantum computing use big-endian encoding, so in them the second column of the CNOT matrix corresponds to the input state $|1\rangle_{BE} = |01\rangle$, which should be left unchanged by the CNOT gate.
In this task you will be interpreting the matrix of an operation MysteryOperation1
that implements a unitary operation $U$, defined in the Quantum.Kata.VisualizationTools
namespace.
This operation will not be passed as an input to your task; you can access it by creating a separate code cell with a new operation (or modifying DumpOperationDemo
), and using %simulate
for that operation.
Input: None.
Output: A single floating-point number: the amplitude of the $|10\rangle$ basis state in the state $U|01\rangle$ (i.e., the element of the matrix implemented by $U$ which describes the transformation $|01\rangle \rightarrow |10\rangle$). The result must be within 0.001 from the actual value.
%kata T4_ReadOperationMatrix
operation ReadMysteryOperationMatrix () : Double {
// ...
}
Quantum circuits are a very common way of visualizing quantum programs; you'll see them in a lot of tutorials, papers and books on quantum computing. Quantum circuits are a less powerful way of expressing the quantum computation compared to a quantum program. They don't offer a good way to show the values of classical variables and their evolution, the decisions made based on the classical parameters or the measurement results, or even flow control structures such as loops or conditional statements. At the same time, they can be convenient to get a quick idea of what the program did, or to compare your implementation to the one offered in a book.
%trace
magic command (available only in Q# Jupyter Notebooks) offers a way to trace one run of the Q# program and to build a circuit based on that execution.
Note that these circuits include only the quantum gates executed by the program.
They might differ in different runs of the same program, if that program takes parameters, has conditional branching or other behaviors that can change the sequence of gates applied by the program.
You can pass parameters to the operation traced with %trace
same as for %simulate
, by adding <parameterName>=<value>
for each parameter after the operation name.
Let's take a look at the circuit produced by the MultiQubitDumpMachineDemo
operation defined earlier.
If the cell below gives you an "Invalid operation name" error, return to the demo DumpMachine for multi-qubit systems and run it to define the operation.
Try clicking on the gates that show a magnifying glass when the cursor hovers over them to see their internal implementation.
%trace MysteryOperation2 N=3
In this task you will be interpreting the trace of an operation MysteryOperation2
that implements a certain quantum algorithm.
This operation will not be passed as an input to your task; you can access it by creating a separate code cell with a new operation or using %trace
for that operation directly.
MysteryOperation2
takes a single integer named $N$ as an input.
Input: None.
Output: A tuple of 3 integers that describe the behavior of MysteryOperation2
for its input $N = 3$:
%kata T5_ReadOperationTrace
operation ReadMysteryOperationTrace () : (Int, Int, Int) {
// ...
}
Finally, %debug
magic command (available only in Q# Jupyter Notebooks) allows you to combine tracing the program execution (as a circuit) and observing the program state at the same time.
You cannot switch to executing a different notebook cell in the middle of the debug session.
If you need to stop before it's complete, you can use Kernel -> Interrupt
menu item.
%debug MysteryOperation2 N=2
We hope you've learned a lot from this tutorial. With these tools you're well equipped to continue your quantum computing journey!