-
Notifications
You must be signed in to change notification settings - Fork 97
Circuit Diagrams from Q# Code
The QDK VS Code extension and qsharp
Python package both provide the ability to create circuit diagrams from Q# code.
These diagrams capture the quantum operations (gates) that have been applied during the execution of the program.
You can use the "Circuit" code lens to show a circuit diagram for a Q# program.
Clicking on this code lens will cause a panel titled "Q# Circuit" to open up.
In addition to showing the circuit for the whole program, you can also show the circuit for an individual operation that is declared in your code.
As long as the operation only takes parameters of type Qubit
or Qubit[]
, a circuit can be generated for it.
In the editor, look for a Circuit code lens above the operation.
Clicking on this code lens will show the circuit diagram for only that operation.
Note that this operation takes an array of qubits (Qubit[]
). Circuit generation will assume a length of 2 for qubit array arguments.
You can also view circuit diagrams during step-by-step debugging of Q# programs.
Click the "Debug" code lens (or invoke the "Debug Q# File" command) to start stepping through the Q# source code.
In the Run and Debug view, expand "Quantum Circuit" under the Variables pane.
The current quantum circuit will be shown in the Q# Circuit pane.
This circuit diagram represents the current state of the simulator, i.e. the gates that have been applied up until the current point of execution.
Refer to circuits.ipynb for a walkthrough of circuit diagrams in Python. (Note that the GitHub web viewer does not display the circuit images in this notebook. Open the notebook in VS Code or Jupyter to display the circuits).
Dynamic circuits are circuits that change based on the outcome of a measurement.
Circuit synthesis works by executing all the classical logic within a Q# program and tracing any qubits that have been allocated or gates that have been applied. Loops (e.g. for
) and conditionals (if
) are supported as long as they only deal with classical values.
However, programs that contain loops and conditional expressions that use qubit measurement results are trickier to represent with a circuit diagram. An expression like
H(q);
if (M(q) == One) {
X(q)
}
cannot be represented with a straightforward circuit diagram, since the gates are conditional on a measurement result. Such a circuit is called a dynamic circuit.
Circuit diagrams can be generated for dynamic circuits by running the program in the quantum simulator, and tracing the gates as they are applied. This is called trace mode, as the qubits and gates are being traced as simulation is being performed.
The downside of traced circuits is that they only capture the measurement outcome, and the consequent gate applications, for a single simulation.
In the above example, if the measurement outcome is One
, there is an X
gate in the diagram.
Another run of the simulation may yield a measurement outcome of Zero
, and now we don't see the X
gate in the diagram.
The currently selected target profile (set using qsharp.init()
in Python, and the status bar icon in VS Code) influences how the circuit is synthesized.
Specifically, gate decompositions are applied that would make the resulting circuit compatible with the capabilities of the target hardware. These are the same decompositions that would get applied during code (QIR) generation and submission to Azure Quantum.
As an example, take the Measurement.qs sample. When target profile is set to "Unrestricted", the gates displayed on the circuit correspond exactly to the quantum operations that are invoked in the Q# program.
When the target profile is "QIR: Base", the circuit looks different.
Since Base Profile targets do not allow qubit reuse after measurement, the measurement is now performed on an entangled qubit instead. Since Reset is not a supported gate in Base Profile, it's dropped. The resulting circuit matches what would be run on hardware if this program was submitted to Azure Quantum with this target profile.
- Target profile: Depending on the target chosen, the same program can synthesize to different circuits (see "Target profile" section).
- Result comparisons: If the program contains any conditionals that depend on measurement results, each run of the simulation may yield a different result (see "Dynamic circuits" section).
- Gate decomposition: When the target is set to "QIR: Base", synthesizing a circuit for the program ("Show Circuit" command) and showing the circuit for a simulator run ("Run file and show circuit") may yield different results. This is because gate decompositions are not applied when running under the simulator, to represent the behavior of the simulator. However during circuit synthesis these decompositions will be applied (see "Target profile" section).
- Only operations that take
Qubit
orQubit[]
arguments show the "Circuit" code lens. If your operation takes no arguments, any classical arguments, or qubit tuple arguments, directly synthesizing a circuit for them is not supported. -
internal
operations aren't supported (you can remove theinternal
keyword to enable the circuit).
When an operation takes a Qubit[]
argument, circuit synthesis assumes an array length of 2. It's possible that a specific operation makes assumptions about array length that simply doesn't work in this scenario. (e.g. Fact(Length(logicalQubit) == 3, "`logicalQubit` must be length 3");
) Circuits can't be generated for such operations.
As an alternative, you can call this operation from your entry point with the appropriate array length, and synthesize a circuit for the whole program, or in Python, you can declare an operation that calls this one with the appropriate register width, and synthesize a circuit for that operation instead.