Estimating the resources and visualizing execution traces of Q# programs are useful tasks. The trace simulator from the QDK and quantum-viz.js Javascript library help to perform those. In this blog post, we customize the trace simulator to output a circuit with hierarchy and resource information to be displayed with quantum-viz.js. The flame graph visualization from last year’s Q# advent calendar inspired this post.
Here is an example of the circuit we’re going to get for Shor’s algorithm sample. Note how each operation is annotated with the resources it requires to run.
The high-level steps are as follows. (You can find the complete implementation in the integer factorization sample of the Quantum repository.)
-
Model the circuit schema used by quantum-viz.js as C# classes with Json.NET. This allows us to readily generate JSON from it.
-
Implement the
IQCTraceSimulatorListener
interface for a custom listener that can be used with the trace simulator. It provides hooks whenever a primitive operation is executed, when an operation starts and ends, and when qubits are allocated.public class QuantumVizCounter : IQCTraceSimulatorListener
-
Whenever qubits are allocated, we add them to the primary inputs of the circuit. Note that all auxiliary qubits are also added as primary inputs to the circuit due to way the circuit model is defined in quantum-viz.js, but they are going to be re-used when possible.
public void OnAllocate(object[] qubitsTraceData) { foreach (var qubit in qubitsTraceData) { Circuit.Qubits.Add(new Qubit { /* ... */ }); } }
When a new operation starts, push a new operation instance to a call stack. We’re also keeping track of whether this operation is adjoint, because quantum-viz.js has a dedicated visualization for adjoint execution operations.
public void OnOperationStart(HashedString name, OperationFunctor variant, object[] qubitsTraceData) { var operation = new Operation { Gate = name }; foreach (var qubit in qubitsTraceData) { operation.Targets.Add((QubitReference)qubit); } operation.IsAdjoint = variant == OperationFunctor.Adjoint || variant == OperationFunctor.ControlledAdjoint; callStack.Push(operation); }
When a primitive operation is executed, the counter of the operation instance at the top of the stack are updated.
public void OnPrimitiveOperation(int id, object[] qubitsTraceData, double duration) { switch (id) { case (int)PrimitiveOperationsGroups.CNOT: callStack.Peek().CNOTCount += 1.0; break; // ... } }
When an operation ends, the most current operation instance is popped from the stack and added as a child to the new operation instance at the top of the stack (its parent operation).
public void OnOperationEnd(object[] returnedQubitsTraceData) { var lastOperation = callStack.Pop(); callStack.Peek().AddChild(lastOperation); }
-
We add this listener to a custom resource estimator, which then can be invoked on any Q# operation. After execution, the circuit object can be serialized to JSON.
var estimator = new QuantumVizEstimator(); MyOperation.Run(estimator).Wait(); var json = JsonConvert.SerializeObject(estimator.Circuit);
Next steps
Feel free to run the sample, generate the JSON representation of the circuit with resources annotations, and display it using quantum-viz.js. You can readily extend the existing example to use the generated JSON data (use JSON.parse(json)
to turn the JSON output into a JavaScript object).
Here are some suggestions for possible steps, especially considering free time during the holidays 🙂.
-
Use special visualizations for primitive gates such as
H
orCNOT
. -
Provide a command line argument to specify the call depth.
-
Instead of outputting the JSON data, one could generate an HTML file that contains all the code to display the circuit.
-
Color the operations based on the resource counts similar to the flame graph visualization. This requires changes to the quantum-viz.js library to modify the appearance of operations.
-
Extract the control qubits of a controlled operation. This requires non-trivial changes to the trace simulator (This file is a good starting point. One would need to extract the control qubits from
inputValue
and pass them separately in the listener’s call toOnOperationStart
. The latter requires a change in the interface.).
We are welcoming pull requests to either the samples repository or quantum-viz.js repository, if you are interested in implementing some of these or other suggestions in the existing sample.
🎄 Happy holidays!
0 comments