Skip to content
This repository was archived by the owner on Jan 12, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/Jupyter/Visualization/DisplayableHtmlElementEncoder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#nullable enable

using Microsoft.Jupyter.Core;

namespace Microsoft.Quantum.IQSharp.Jupyter
{
/// <summary>
/// Represents an HTML string to be rendered as an HTML element.
/// </summary>
public class DisplayableHtmlElement
{
/// <summary>
/// Initializes <see cref="DisplayableHtmlElement"/> with the given HTML string.
/// </summary>
public DisplayableHtmlElement(string html) => this.Html = html;

/// <summary>
/// HTML string to be rendered.
/// </summary>
public string Html { get; }
}

/// <summary>
/// Encodes <see cref="DisplayableHtmlElement" /> instances as HTML elements.
/// </summary>
public class DisplayableHtmlElementEncoder : IResultEncoder
{
/// <inheritdoc />
public string MimeType => MimeTypes.Html;

/// <summary>
/// Checks if a given display object is an <see cref="DisplayableHtmlElement"/>,
/// and if so, returns its HTML element.
/// </summary>
public EncodedData? Encode(object displayable) =>
(displayable is DisplayableHtmlElement dis)
? dis.Html.ToEncodedData() as EncodedData?
: null;
}
}
1 change: 1 addition & 0 deletions src/Kernel/IQSharpEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ IReferences references
RegisterDisplayEncoder(new DataTableToTextEncoder());
RegisterDisplayEncoder(new DisplayableExceptionToHtmlEncoder());
RegisterDisplayEncoder(new DisplayableExceptionToTextEncoder());
RegisterDisplayEncoder(new DisplayableHtmlElementEncoder());

RegisterJsonEncoder(
JsonConverters.AllConverters
Expand Down
180 changes: 180 additions & 0 deletions src/Kernel/Magic/TraceMagic.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Threading.Tasks;
using Microsoft.Jupyter.Core;
using Microsoft.Jupyter.Core.Protocol;
using Microsoft.Quantum.IQSharp.Core.ExecutionPathTracer;
using Microsoft.Quantum.IQSharp.Jupyter;
using Microsoft.Quantum.Simulation.Simulators;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.Quantum.IQSharp.Kernel
{
/// <summary>
/// Contains the JSON representation of the <see cref="ExecutionPath"/>
/// and the ID of the HTML div that will contain the visualization of the
/// execution path.
/// </summary>
public class ExecutionPathVisualizerContent : MessageContent
{
/// <summary>
/// Initializes <see cref="ExecutionPathVisualizerContent"/> with the
/// given <see cref="ExecutionPath"/> (as a <see cref="JToken"/>) and HTML div ID.
/// </summary>
public ExecutionPathVisualizerContent(JToken executionPath, string id)
{
this.ExecutionPath = executionPath;
this.Id = id;
}

/// <summary>
/// The <see cref="ExecutionPath"/> (as a <see cref="JToken"/>) to be rendered.
/// </summary>
[JsonProperty("executionPath")]
public JToken ExecutionPath { get; }

/// <summary>
/// ID of the HTML div that will contain the visualization.
/// </summary>
[JsonProperty("id")]
public string Id { get; }
}

/// <summary>
/// A magic command that can be used to visualize the execution
/// path of operations and functions traced out by the simulator.
/// </summary>
public class TraceMagic : AbstractMagic
{
private const string ParameterNameOperationName = "__operationName__";
private const string ParameterNameDepth = "depth";

/// <summary>
/// Constructs a new magic command given a resolver used to find
/// operations and functions, and a configuration source used to set
/// configuration options.
/// </summary>
public TraceMagic(ISymbolResolver resolver, IConfigurationSource configurationSource) : base(
"trace",
new Documentation
{
Summary = "Outputs the HTML-based visualization of an execution path of the given operation.",
Description = $@"
This magic command renders an HTML-based visualization of a runtime execution path of the
given operation using the QuantumSimulator.

#### Required parameters

- Q# operation or function name. This must be the first parameter, and must be a valid Q# operation
or function name that has been defined either in the notebook or in a Q# file in the same folder.
- Arguments for the Q# operation or function must also be specified as `key=value` pairs.

#### Optional parameters

- `{ParameterNameDepth}=<integer>` (default=1): The depth at which to render operations along
the execution path.
".Dedent(),
Examples = new []
{
@"
Visualize the execution path of a Q# operation defined as `operation MyOperation() : Result`:
```
In []: %trace MyOperation
Out[]: <HTML visualization of the operation>
```
".Dedent(),
@"
Visualize the execution path of a Q# operation defined as `operation MyOperation(a : Int, b : Int) : Result`:
```
In []: %trace MyOperation a=5 b=10
Out[]: <HTML visualization of the operation>
```
".Dedent(),
$@"
Visualize operations at depth 2 on the execution path of a Q# operation defined
as `operation MyOperation() : Result`:
```
In []: %trace MyOperation {ParameterNameDepth}=2
Out[]: <HTML visualization of the operation>
```
".Dedent(),
}
})
{
this.SymbolResolver = resolver;
this.ConfigurationSource = configurationSource;
}

/// <summary>
/// The symbol resolver used by this magic command to find the operation
/// to be visualized.
/// </summary>
public ISymbolResolver SymbolResolver { get; }

/// <summary>
/// The configuration source used by this magic command to control
/// visualization options.
/// </summary>
public IConfigurationSource ConfigurationSource { get; }

/// <inheritdoc />
public override ExecutionResult Run(string input, IChannel channel) =>
RunAsync(input, channel).Result;

/// <summary>
/// Outputs a visualization of a runtime execution path of an operation given
/// a string with its name and a JSON encoding of its arguments.
/// </summary>
public async Task<ExecutionResult> RunAsync(string input, IChannel channel)
{
// Parse input parameters
var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameOperationName);

var name = inputParameters.DecodeParameter<string>(ParameterNameOperationName);
var symbol = SymbolResolver.Resolve(name) as IQSharpSymbol;
if (symbol == null) throw new InvalidOperationException($"Invalid operation name: {name}");

var depth = inputParameters.DecodeParameter<int>(ParameterNameDepth, defaultValue: 1);
if (depth <= 0) throw new ArgumentOutOfRangeException($"Invalid depth: {depth}. Must be >= 1.");

var tracer = new ExecutionPathTracer(depth);

// Simulate operation and attach `ExecutionPathTracer` to trace out operations performed
// in its execution path
using var qsim = new QuantumSimulator()
.WithJupyterDisplay(channel, ConfigurationSource)
.WithStackTraceDisplay(channel)
.WithExecutionPathTracer(tracer);
var value = await symbol.Operation.RunAsync(qsim, inputParameters);

// Retrieve the `ExecutionPath` traced out by the `ExecutionPathTracer`
var executionPath = tracer.GetExecutionPath();

// Convert executionPath to JToken for serialization
var executionPathJToken = JToken.FromObject(executionPath,
new JsonSerializer() { NullValueHandling = NullValueHandling.Ignore });

// Render empty div with unique ID as cell output
var divId = $"execution-path-container-{Guid.NewGuid().ToString()}";
var content = new ExecutionPathVisualizerContent(executionPathJToken, divId);
channel.DisplayUpdatable(new DisplayableHtmlElement($"<div id='{divId}' />"));

// Send execution path to JavaScript via iopub for rendering
channel.SendIoPubMessage(
new Message
{
Header = new MessageHeader
{
MessageType = "render_execution_path"
},
Content = content,
}
);

return ExecuteStatus.Ok.ToExecutionResult();
}
}
}
18 changes: 16 additions & 2 deletions src/Kernel/client/ExecutionPathVisualizer/pathVisualizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ const style = `
*
* @returns HTML representation of circuit.
*/
const jsonToHtml = (json: ExecutionPath): string => {
const _jsonToHtml = (json: ExecutionPath): string => {
const { qubits, operations } = json;
const { qubitWires, registers, svgHeight } = formatInputs(qubits);
const { metadataList, svgWidth } = processOperations(operations, registers);
Expand All @@ -118,4 +118,18 @@ const jsonToHtml = (json: ExecutionPath): string => {
</html>`;
};

export default jsonToHtml;
/**
* Renders the given `ExecutionPath` json into an HTML element and populates the div
* with the given `id` with it.
*
* @param json JSON received from simulator.
* @param id ID of div to populate.
*/
const renderExecutionPath = (json: ExecutionPath, id: string): void => {
const html: string = _jsonToHtml(json);
const container: HTMLElement = document.getElementById(id);
if (container == null) throw new Error(`Div with ID ${id} not found.`);
container.innerHTML = html;
};

export default renderExecutionPath;
14 changes: 12 additions & 2 deletions src/Kernel/client/kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { IPython } from "./ipython";
declare var IPython: IPython;

import { Telemetry, ClientInfo } from "./telemetry.js";
import jsonToHtml from "./ExecutionPathVisualizer/pathVisualizer.js";
import renderExecutionPath from "./ExecutionPathVisualizer/pathVisualizer.js";

function defineQSharpMode() {
console.log("Loading IQ# kernel-specific extension...");
Expand Down Expand Up @@ -125,6 +125,7 @@ class Kernel {
IPython.notebook.kernel.events.on("kernel_ready.Kernel", args => {
this.requestEcho();
this.requestClientInfo();
this.initExecutionPathVisualizer();
});
}

Expand Down Expand Up @@ -225,11 +226,20 @@ class Kernel {
});
Telemetry.initAsync();
}

initExecutionPathVisualizer() {
IPython.notebook.kernel.register_iopub_handler(
"render_execution_path",
message => {
const { executionPath, id } = message.content;
renderExecutionPath(executionPath, id);
}
);
}
}

export function onload() {
defineQSharpMode();
let kernel = new Kernel();
console.log("Loaded IQ# kernel-specific extension!");
}

39 changes: 27 additions & 12 deletions src/Tests/ExecutionPathTracerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ public ExecutionPath GetExecutionPath(string name, int depth = 1)
return tracer.GetExecutionPath();
}


[TestMethod]
public void HTest()
{
Expand All @@ -50,7 +49,6 @@ public void HTest()
Gate = "H",
Targets = new List<Register>() { new QubitRegister(0) },
},
// TODO: Remove Reset/ResetAll gates once we don't need to zero out qubits
new Operation()
{
Gate = "Reset",
Expand Down Expand Up @@ -360,10 +358,7 @@ public void UnusedQubitTest()
public void Depth2Test()
{
var path = GetExecutionPath("Depth2Circ", 2);
var qubits = new QubitDeclaration[]
{
new QubitDeclaration(0, 1),
};
var qubits = new QubitDeclaration[] { new QubitDeclaration(0) };
var operations = new Operation[]
{
new Operation()
Expand All @@ -381,12 +376,6 @@ public void Depth2Test()
Gate = "H",
Targets = new List<Register>() { new QubitRegister(0) },
},
new Operation()
{
Gate = "measure",
Controls = new List<Register>() { new QubitRegister(0) },
Targets = new List<Register>() { new ClassicalRegister(0, 0) },
},
};
var expected = new ExecutionPath(qubits, operations);
Assert.AreEqual(expected.ToJson(), path.ToJson());
Expand Down Expand Up @@ -440,6 +429,32 @@ public void EmptyTest()
var expected = new ExecutionPath(qubits, operations);
Assert.AreEqual(expected.ToJson(), path.ToJson());
}

[TestMethod]
public void NestedTest()
{
var path = GetExecutionPath("NestedCirc");
var qubits = new QubitDeclaration[] { new QubitDeclaration(0) };
var operations = new Operation[]
{
new Operation()
{
Gate = "H",
Targets = new List<Register>() { new QubitRegister(0) },
},
new Operation()
{
Gate = "HCirc",
},
new Operation()
{
Gate = "Reset",
Targets = new List<Register>() { new QubitRegister(0) },
},
};
var expected = new ExecutionPath(qubits, operations);
Assert.AreEqual(expected.ToJson(), path.ToJson());
}

[TestMethod]
public void BigTest()
Expand Down
Loading