From 4e832ab08d1f680930b3564292594dbea5744fca Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Tue, 14 Jul 2020 11:12:20 -0400 Subject: [PATCH 01/10] Add view magic command --- .../Visualization/ExecutionPathEncoder.cs | 42 ++++++ src/Kernel/IQSharpEngine.cs | 1 + src/Kernel/Magic/ViewMagic.cs | 130 ++++++++++++++++++ .../ExecutionPathVisualizer/pathVisualizer.ts | 18 ++- src/Kernel/client/kernel.ts | 13 +- 5 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 src/Jupyter/Visualization/ExecutionPathEncoder.cs create mode 100644 src/Kernel/Magic/ViewMagic.cs diff --git a/src/Jupyter/Visualization/ExecutionPathEncoder.cs b/src/Jupyter/Visualization/ExecutionPathEncoder.cs new file mode 100644 index 0000000000..0b1d0c4745 --- /dev/null +++ b/src/Jupyter/Visualization/ExecutionPathEncoder.cs @@ -0,0 +1,42 @@ +using Microsoft.Jupyter.Core; +using Microsoft.Quantum.IQSharp.Core.ExecutionPathTracer; + +namespace Microsoft.Quantum.IQSharp.Jupyter +{ + /// + /// Contains the ID of the div used to populate the + /// visualization. + /// + public class ExecutionPathDisplayable + { + /// + /// Initializes with the given id. + /// + public ExecutionPathDisplayable(string id) => + this.Id = id; + + /// + /// ID of the HTML div that will contain the visualization. + /// + public string Id { get; } + } + + /// + /// Encodes instances as HTML divs. + /// + public class ExecutionPathToHtmlEncoder : IResultEncoder + { + /// + public string MimeType => MimeTypes.Html; + + /// + /// Checks if a given display object is an , + /// and if so, returns the HTML div with the corresponding id that will contain the + /// visualization. + /// + public EncodedData? Encode(object displayable) => + (displayable is ExecutionPathDisplayable dis) + ? $"
".ToEncodedData() as EncodedData? + : null; + } +} diff --git a/src/Kernel/IQSharpEngine.cs b/src/Kernel/IQSharpEngine.cs index a79b3d1894..ffb8786452 100644 --- a/src/Kernel/IQSharpEngine.cs +++ b/src/Kernel/IQSharpEngine.cs @@ -63,6 +63,7 @@ IReferences references RegisterDisplayEncoder(new DataTableToTextEncoder()); RegisterDisplayEncoder(new DisplayableExceptionToHtmlEncoder()); RegisterDisplayEncoder(new DisplayableExceptionToTextEncoder()); + RegisterDisplayEncoder(new ExecutionPathToHtmlEncoder()); RegisterJsonEncoder( JsonConverters.AllConverters diff --git a/src/Kernel/Magic/ViewMagic.cs b/src/Kernel/Magic/ViewMagic.cs new file mode 100644 index 0000000000..1535314269 --- /dev/null +++ b/src/Kernel/Magic/ViewMagic.cs @@ -0,0 +1,130 @@ +// 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; + +namespace Microsoft.Quantum.IQSharp.Kernel +{ + /// + /// Contains the JSON representation of the + /// and the ID of the HTML div that will contain the visualization. + /// + public class ExecutionPathVisualizerContent : MessageContent + { + /// + /// Initializes with the + /// given JSON string and HTML div ID. + /// + public ExecutionPathVisualizerContent(string json, string id) + { + this.Json = json; + this.Id = id; + } + + /// + /// JSON representation of the . + /// + [JsonProperty("json")] + public string Json { get; } + + /// + /// ID of the HTML div that will contain the visualization. + /// + [JsonProperty("id")] + public string Id { get; } + } + + /// + /// A magic command that can be used to visualize the execution + /// paths of operations and functions. + /// + public class ViewMagic : AbstractMagic + { + private const string ParameterNameOperationName = "__operationName__"; + + /// + /// Constructs a new magic command given a resolver used to find + /// operations and functions, and a configuration source used to set + /// configuration options. + /// + public ViewMagic(ISymbolResolver resolver, IConfigurationSource configurationSource) : base( + "view", + new Documentation + { + Summary = "Outputs an HTML-based visualization of the execution path of a given operation." + }) + { + this.SymbolResolver = resolver; + this.ConfigurationSource = configurationSource; + } + + /// + /// The symbol resolver used by this magic command to find the operation + /// to be visualized. + /// + public ISymbolResolver SymbolResolver { get; } + + /// + /// The configuration source used by this magic command to control + /// visualization options. + /// + public IConfigurationSource ConfigurationSource { get; } + + /// + public override ExecutionResult Run(string input, IChannel channel) => + RunAsync(input, channel).Result; + + /// + /// Outputs a visualization of an operation given a string with its name and a JSON + /// encoding of its arguments. + /// + public async Task RunAsync(string input, IChannel channel) + { + // Parse input parameters + var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameOperationName); + + var name = inputParameters.DecodeParameter(ParameterNameOperationName); + var symbol = SymbolResolver.Resolve(name) as IQSharpSymbol; + if (symbol == null) throw new InvalidOperationException($"Invalid operation name: {name}"); + + var tracer = new ExecutionPathTracer(); + + // Simulate operation and attach `ExecutionPathTracer` to keep track of 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(); + + // Render empty div with unique ID as cell output + var divId = $"execution-path-container-{Guid.NewGuid().ToString()}"; + var content = new ExecutionPathVisualizerContent(executionPath.ToJson(), divId); + channel.DisplayUpdatable(new ExecutionPathDisplayable(content.Id)); + + // 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(); + } + } +} diff --git a/src/Kernel/client/ExecutionPathVisualizer/pathVisualizer.ts b/src/Kernel/client/ExecutionPathVisualizer/pathVisualizer.ts index d110f6b716..6e8315a461 100644 --- a/src/Kernel/client/ExecutionPathVisualizer/pathVisualizer.ts +++ b/src/Kernel/client/ExecutionPathVisualizer/pathVisualizer.ts @@ -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); @@ -118,4 +118,18 @@ const jsonToHtml = (json: ExecutionPath): string => { `; }; -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; diff --git a/src/Kernel/client/kernel.ts b/src/Kernel/client/kernel.ts index 675387474e..b49af31408 100644 --- a/src/Kernel/client/kernel.ts +++ b/src/Kernel/client/kernel.ts @@ -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..."); @@ -225,6 +225,16 @@ class Kernel { }); Telemetry.initAsync(); } + + initExecutionPathVisualizer() { + IPython.notebook.kernel.register_iopub_handler( + "render_execution_path", + message => { + const { json, id } = message.content; + renderExecutionPath(JSON.parse(json), id); + } + ); + } } export function onload() { @@ -232,4 +242,3 @@ export function onload() { let kernel = new Kernel(); console.log("Loaded IQ# kernel-specific extension!"); } - From ad08f76d2f7ecba34d8e7fc6e12c4ea52ce29496 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Wed, 15 Jul 2020 15:31:28 -0400 Subject: [PATCH 02/10] Rename ExecutionPathEncoder to DisplayableHtmlEncoder --- .../DisplayableHtmlElementEncoder.cs | 42 +++++++++++++++++++ .../Visualization/ExecutionPathEncoder.cs | 42 ------------------- src/Kernel/IQSharpEngine.cs | 2 +- src/Kernel/Magic/ViewMagic.cs | 21 ++++++---- src/Kernel/client/kernel.ts | 4 +- 5 files changed, 58 insertions(+), 53 deletions(-) create mode 100644 src/Jupyter/Visualization/DisplayableHtmlElementEncoder.cs delete mode 100644 src/Jupyter/Visualization/ExecutionPathEncoder.cs diff --git a/src/Jupyter/Visualization/DisplayableHtmlElementEncoder.cs b/src/Jupyter/Visualization/DisplayableHtmlElementEncoder.cs new file mode 100644 index 0000000000..91a7034ad3 --- /dev/null +++ b/src/Jupyter/Visualization/DisplayableHtmlElementEncoder.cs @@ -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 +{ + /// + /// Represents an HTML string to be rendered as an HTML element. + /// + public class DisplayableHtmlElement + { + /// + /// Initializes with the given HTML string. + /// + public DisplayableHtmlElement(string html) => this.Html = html; + + /// + /// HTML string to be rendered. + /// + public string Html { get; } + } + + /// + /// Encodes instances as HTML elements. + /// + public class DisplayableHtmlElementEncoder : IResultEncoder + { + /// + public string MimeType => MimeTypes.Html; + + /// + /// Checks if a given display object is an , + /// and if so, returns its HTML element. + /// + public EncodedData? Encode(object displayable) => + (displayable is DisplayableHtmlElement dis) + ? dis.Html.ToEncodedData() as EncodedData? + : null; + } +} diff --git a/src/Jupyter/Visualization/ExecutionPathEncoder.cs b/src/Jupyter/Visualization/ExecutionPathEncoder.cs deleted file mode 100644 index 0b1d0c4745..0000000000 --- a/src/Jupyter/Visualization/ExecutionPathEncoder.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.Jupyter.Core; -using Microsoft.Quantum.IQSharp.Core.ExecutionPathTracer; - -namespace Microsoft.Quantum.IQSharp.Jupyter -{ - /// - /// Contains the ID of the div used to populate the - /// visualization. - /// - public class ExecutionPathDisplayable - { - /// - /// Initializes with the given id. - /// - public ExecutionPathDisplayable(string id) => - this.Id = id; - - /// - /// ID of the HTML div that will contain the visualization. - /// - public string Id { get; } - } - - /// - /// Encodes instances as HTML divs. - /// - public class ExecutionPathToHtmlEncoder : IResultEncoder - { - /// - public string MimeType => MimeTypes.Html; - - /// - /// Checks if a given display object is an , - /// and if so, returns the HTML div with the corresponding id that will contain the - /// visualization. - /// - public EncodedData? Encode(object displayable) => - (displayable is ExecutionPathDisplayable dis) - ? $"
".ToEncodedData() as EncodedData? - : null; - } -} diff --git a/src/Kernel/IQSharpEngine.cs b/src/Kernel/IQSharpEngine.cs index ffb8786452..54f27b2870 100644 --- a/src/Kernel/IQSharpEngine.cs +++ b/src/Kernel/IQSharpEngine.cs @@ -63,7 +63,7 @@ IReferences references RegisterDisplayEncoder(new DataTableToTextEncoder()); RegisterDisplayEncoder(new DisplayableExceptionToHtmlEncoder()); RegisterDisplayEncoder(new DisplayableExceptionToTextEncoder()); - RegisterDisplayEncoder(new ExecutionPathToHtmlEncoder()); + RegisterDisplayEncoder(new DisplayableHtmlElementEncoder()); RegisterJsonEncoder( JsonConverters.AllConverters diff --git a/src/Kernel/Magic/ViewMagic.cs b/src/Kernel/Magic/ViewMagic.cs index 1535314269..b2c4bebee3 100644 --- a/src/Kernel/Magic/ViewMagic.cs +++ b/src/Kernel/Magic/ViewMagic.cs @@ -9,6 +9,7 @@ using Microsoft.Quantum.IQSharp.Jupyter; using Microsoft.Quantum.Simulation.Simulators; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Microsoft.Quantum.IQSharp.Kernel { @@ -20,19 +21,19 @@ public class ExecutionPathVisualizerContent : MessageContent { /// /// Initializes with the - /// given JSON string and HTML div ID. + /// given (as a ) and HTML div ID. /// - public ExecutionPathVisualizerContent(string json, string id) + public ExecutionPathVisualizerContent(JToken executionPath, string id) { - this.Json = json; + this.ExecutionPath = executionPath; this.Id = id; } /// - /// JSON representation of the . + /// The (as a ) to be rendered. /// - [JsonProperty("json")] - public string Json { get; } + [JsonProperty("executionPath")] + public JToken ExecutionPath { get; } /// /// ID of the HTML div that will contain the visualization. @@ -107,10 +108,14 @@ public async Task RunAsync(string input, IChannel channel) // 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(executionPath.ToJson(), divId); - channel.DisplayUpdatable(new ExecutionPathDisplayable(content.Id)); + var content = new ExecutionPathVisualizerContent(executionPathJToken, divId); + channel.DisplayUpdatable(new DisplayableHtmlElement($"
")); // Send execution path to JavaScript via iopub for rendering channel.SendIoPubMessage( diff --git a/src/Kernel/client/kernel.ts b/src/Kernel/client/kernel.ts index b49af31408..644ee61d80 100644 --- a/src/Kernel/client/kernel.ts +++ b/src/Kernel/client/kernel.ts @@ -230,8 +230,8 @@ class Kernel { IPython.notebook.kernel.register_iopub_handler( "render_execution_path", message => { - const { json, id } = message.content; - renderExecutionPath(JSON.parse(json), id); + const { executionPath, id } = message.content; + renderExecutionPath(JSON.parse(executionPath), id); } ); } From 3066c89308d6d6f33726f5daec096b9e762d91b8 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Wed, 15 Jul 2020 16:15:10 -0400 Subject: [PATCH 03/10] Add documentation to view command --- src/Kernel/Magic/ViewMagic.cs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Kernel/Magic/ViewMagic.cs b/src/Kernel/Magic/ViewMagic.cs index b2c4bebee3..0de8de0c5b 100644 --- a/src/Kernel/Magic/ViewMagic.cs +++ b/src/Kernel/Magic/ViewMagic.cs @@ -59,7 +59,34 @@ public ViewMagic(ISymbolResolver resolver, IConfigurationSource configurationSou "view", new Documentation { - Summary = "Outputs an HTML-based visualization of the execution path of a given operation." + Summary = "Outputs an HTML-based visualization of the execution path of the given operation.", + Description = @" + This magic command renders an HTML-based visualization of the 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. + ".Dedent(), + Examples = new [] + { + @" + Visualize a Q# operation defined as `operation MyOperation() : Result`: + ``` + In []: %view MyOperation + Out[]: + ``` + ".Dedent(), + @" + Visualize a Q# operation defined as `operation MyOperation(a : Int, b : Int) : Result`: + ``` + In []: %view MyOperation a=5 b=10 + Out[]: + ``` + ".Dedent(), + } }) { this.SymbolResolver = resolver; From 51558be892cf98c8c1196d61e4791a1c3c1bcd5b Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Wed, 15 Jul 2020 18:19:39 -0400 Subject: [PATCH 04/10] Rename %view to %trace --- .../Magic/{ViewMagic.cs => TraceMagic.cs} | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) rename src/Kernel/Magic/{ViewMagic.cs => TraceMagic.cs} (85%) diff --git a/src/Kernel/Magic/ViewMagic.cs b/src/Kernel/Magic/TraceMagic.cs similarity index 85% rename from src/Kernel/Magic/ViewMagic.cs rename to src/Kernel/Magic/TraceMagic.cs index 0de8de0c5b..32752bf9ad 100644 --- a/src/Kernel/Magic/ViewMagic.cs +++ b/src/Kernel/Magic/TraceMagic.cs @@ -15,7 +15,8 @@ namespace Microsoft.Quantum.IQSharp.Kernel { /// /// Contains the JSON representation of the - /// and the ID of the HTML div that will contain the visualization. + /// and the ID of the HTML div that will contain the visualization of the + /// execution path. /// public class ExecutionPathVisualizerContent : MessageContent { @@ -44,9 +45,9 @@ public ExecutionPathVisualizerContent(JToken executionPath, string id) /// /// A magic command that can be used to visualize the execution - /// paths of operations and functions. + /// path of operations and functions traced out by the simulator. /// - public class ViewMagic : AbstractMagic + public class TraceMagic : AbstractMagic { private const string ParameterNameOperationName = "__operationName__"; @@ -55,13 +56,13 @@ public class ViewMagic : AbstractMagic /// operations and functions, and a configuration source used to set /// configuration options. ///
- public ViewMagic(ISymbolResolver resolver, IConfigurationSource configurationSource) : base( - "view", + public TraceMagic(ISymbolResolver resolver, IConfigurationSource configurationSource) : base( + "trace", new Documentation { - Summary = "Outputs an HTML-based visualization of the execution path of the given operation.", + Summary = "Outputs the HTML-based visualization of an execution path of the given operation.", Description = @" - This magic command renders an HTML-based visualization of the runtime execution path of the + This magic command renders an HTML-based visualization of a runtime execution path of the given operation using the QuantumSimulator. #### Required parameters @@ -73,16 +74,16 @@ or function name that has been defined either in the notebook or in a Q# file in Examples = new [] { @" - Visualize a Q# operation defined as `operation MyOperation() : Result`: + Visualize the execution path of a Q# operation defined as `operation MyOperation() : Result`: ``` - In []: %view MyOperation + In []: %trace MyOperation Out[]: ``` ".Dedent(), @" - Visualize a Q# operation defined as `operation MyOperation(a : Int, b : Int) : Result`: + Visualize the execution path of a Q# operation defined as `operation MyOperation(a : Int, b : Int) : Result`: ``` - In []: %view MyOperation a=5 b=10 + In []: %trace MyOperation a=5 b=10 Out[]: ``` ".Dedent(), @@ -110,8 +111,8 @@ public override ExecutionResult Run(string input, IChannel channel) => RunAsync(input, channel).Result; /// - /// Outputs a visualization of an operation given a string with its name and a JSON - /// encoding of its arguments. + /// Outputs a visualization of a runtime execution path of an operation given + /// a string with its name and a JSON encoding of its arguments. /// public async Task RunAsync(string input, IChannel channel) { @@ -124,7 +125,7 @@ public async Task RunAsync(string input, IChannel channel) var tracer = new ExecutionPathTracer(); - // Simulate operation and attach `ExecutionPathTracer` to keep track of operations performed + // Simulate operation and attach `ExecutionPathTracer` to trace out operations performed // in its execution path using var qsim = new QuantumSimulator() .WithJupyterDisplay(channel, ConfigurationSource) From 842d9dada2a4d64627950f6f0499c740183ac7b2 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Thu, 16 Jul 2020 15:07:33 -0400 Subject: [PATCH 05/10] Fix JSON parsing --- src/Kernel/client/kernel.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Kernel/client/kernel.ts b/src/Kernel/client/kernel.ts index 644ee61d80..c9d1b9dd38 100644 --- a/src/Kernel/client/kernel.ts +++ b/src/Kernel/client/kernel.ts @@ -125,6 +125,7 @@ class Kernel { IPython.notebook.kernel.events.on("kernel_ready.Kernel", args => { this.requestEcho(); this.requestClientInfo(); + this.initExecutionPathVisualizer(); }); } @@ -231,7 +232,7 @@ class Kernel { "render_execution_path", message => { const { executionPath, id } = message.content; - renderExecutionPath(JSON.parse(executionPath), id); + renderExecutionPath(executionPath, id); } ); } From 1c61c1aca6a0517eb4ea9766f042a8547b03f396 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Thu, 16 Jul 2020 16:35:57 -0400 Subject: [PATCH 06/10] Add TraceMagic test --- src/Tests/IQsharpEngineTests.cs | 47 +++++++++++++++++++++++++++++++-- src/Tests/Mocks.cs | 3 +++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/Tests/IQsharpEngineTests.cs b/src/Tests/IQsharpEngineTests.cs index 2d800b3bb9..6d510b850a 100644 --- a/src/Tests/IQsharpEngineTests.cs +++ b/src/Tests/IQsharpEngineTests.cs @@ -10,12 +10,11 @@ using Microsoft.Quantum.IQSharp; using Microsoft.Quantum.IQSharp.Jupyter; using Microsoft.Quantum.IQSharp.Kernel; -using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.IQSharp.Core.ExecutionPathTracer; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using System.Data; -using Microsoft.Quantum.IQSharp.AzureClient; #pragma warning disable VSTHRD200 // Use "Async" suffix for async methods @@ -86,6 +85,28 @@ public static async Task AssertEstimate(IQSharpEngine engine, string sni return response.Output?.ToString(); } + private async Task AssertTrace(string name, ExecutionPath expectedPath) + { + var engine = Init("Workspace.ExecutionPathTracer"); + var configSource = new ConfigurationSource(skipLoading: true); + var traceMagic = new TraceMagic(engine.SymbolsResolver, configSource); + var channel = new MockChannel(); + + var response = await traceMagic.Execute(name, channel); + Assert.AreEqual(ExecuteStatus.Ok, response.Status); + + var message = channel.iopubMessages.ElementAtOrDefault(0); + Assert.IsNotNull(message); + Assert.AreEqual("render_execution_path", message.Header.MessageType); + + var content = message.Content as ExecutionPathVisualizerContent; + Assert.IsNotNull(content); + + var path = content.ExecutionPath.ToObject(); + Assert.IsNotNull(path); + Assert.AreEqual(expectedPath.ToJson(), path.ToJson()); + } + [TestMethod] public async Task CompileOne() { @@ -458,6 +479,28 @@ public void TestResolveMagic() Assert.IsNotNull(resolver.Resolve("%azure.output")); Assert.IsNotNull(resolver.Resolve("%azure.jobs")); } + + [TestMethod] + public async Task TestTraceMagic() + { + await AssertTrace("HCirc", new ExecutionPath( + new QubitDeclaration[] { new QubitDeclaration(0) }, + new Operation[] + { + new Operation() + { + Gate = "H", + Targets = new List() { new QubitRegister(0) }, + }, + // TODO: Remove Reset/ResetAll gates once we don't need to zero out qubits + new Operation() + { + Gate = "Reset", + Targets = new List() { new QubitRegister(0) }, + }, + } + )); + } } } #pragma warning restore VSTHRD200 // Use "Async" suffix for async methods diff --git a/src/Tests/Mocks.cs b/src/Tests/Mocks.cs index 84d06abe64..87930ae423 100644 --- a/src/Tests/Mocks.cs +++ b/src/Tests/Mocks.cs @@ -102,6 +102,7 @@ public class MockChannel : IChannel { public List errors = new List(); public List msgs = new List(); + public List iopubMessages = new List(); public void Display(object displayable) { @@ -113,6 +114,8 @@ public IUpdatableDisplay DisplayUpdatable(object displayable) return new MockUpdatableDisplay(); } + public void SendIoPubMessage(Message message) => iopubMessages.Add(message); + public void Stderr(string message) => errors.Add(message); public void Stdout(string message) => msgs.Add(message); From bddd2bed773c2c9defa87db0105a6b96e265ee65 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Thu, 16 Jul 2020 19:25:10 -0400 Subject: [PATCH 07/10] Add flag for depth --- src/Kernel/Magic/TraceMagic.cs | 21 ++++++++- src/Tests/ExecutionPathTracerTests.cs | 43 +++++++++++++------ src/Tests/IQsharpEngineTests.cs | 37 +++++++++++++++- .../Intrinsic.qs | 9 +++- 4 files changed, 94 insertions(+), 16 deletions(-) diff --git a/src/Kernel/Magic/TraceMagic.cs b/src/Kernel/Magic/TraceMagic.cs index 32752bf9ad..54cf44477c 100644 --- a/src/Kernel/Magic/TraceMagic.cs +++ b/src/Kernel/Magic/TraceMagic.cs @@ -50,6 +50,7 @@ public ExecutionPathVisualizerContent(JToken executionPath, string id) public class TraceMagic : AbstractMagic { private const string ParameterNameOperationName = "__operationName__"; + private const string ParameterNameDepth = "depth"; /// /// Constructs a new magic command given a resolver used to find @@ -61,7 +62,7 @@ public TraceMagic(ISymbolResolver resolver, IConfigurationSource configurationSo new Documentation { Summary = "Outputs the HTML-based visualization of an execution path of the given operation.", - Description = @" + Description = $@" This magic command renders an HTML-based visualization of a runtime execution path of the given operation using the QuantumSimulator. @@ -70,6 +71,11 @@ This magic command renders an HTML-based visualization of a runtime execution pa - 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}=` (default=1): The depth at which to render operations along + the execution path. ".Dedent(), Examples = new [] { @@ -87,6 +93,14 @@ or function name that has been defined either in the notebook or in a Q# file in Out[]: ``` ".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[]: + ``` + ".Dedent(), } }) { @@ -123,7 +137,10 @@ public async Task RunAsync(string input, IChannel channel) var symbol = SymbolResolver.Resolve(name) as IQSharpSymbol; if (symbol == null) throw new InvalidOperationException($"Invalid operation name: {name}"); - var tracer = new ExecutionPathTracer(); + var depth = inputParameters.DecodeParameter(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 diff --git a/src/Tests/ExecutionPathTracerTests.cs b/src/Tests/ExecutionPathTracerTests.cs index fea5a8c3c4..46e0e9dd32 100644 --- a/src/Tests/ExecutionPathTracerTests.cs +++ b/src/Tests/ExecutionPathTracerTests.cs @@ -34,7 +34,6 @@ public ExecutionPath GetExecutionPath(string name, int depth = 1) return tracer.GetExecutionPath(); } - [TestMethod] public void HTest() { @@ -50,7 +49,6 @@ public void HTest() Gate = "H", Targets = new List() { new QubitRegister(0) }, }, - // TODO: Remove Reset/ResetAll gates once we don't need to zero out qubits new Operation() { Gate = "Reset", @@ -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() @@ -381,12 +376,6 @@ public void Depth2Test() Gate = "H", Targets = new List() { new QubitRegister(0) }, }, - new Operation() - { - Gate = "measure", - Controls = new List() { new QubitRegister(0) }, - Targets = new List() { new ClassicalRegister(0, 0) }, - }, }; var expected = new ExecutionPath(qubits, operations); Assert.AreEqual(expected.ToJson(), path.ToJson()); @@ -440,6 +429,36 @@ 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), + new QubitDeclaration(1), + }; + var operations = new Operation[] + { + new Operation() + { + Gate = "H", + Targets = new List() { new QubitRegister(0) }, + }, + new Operation() + { + Gate = "HCirc", + }, + new Operation() + { + Gate = "Reset", + Targets = new List() { new QubitRegister(0) }, + }, + }; + var expected = new ExecutionPath(qubits, operations); + Assert.AreEqual(expected.ToJson(), path.ToJson()); + } [TestMethod] public void BigTest() diff --git a/src/Tests/IQsharpEngineTests.cs b/src/Tests/IQsharpEngineTests.cs index 6d510b850a..54df19f1e1 100644 --- a/src/Tests/IQsharpEngineTests.cs +++ b/src/Tests/IQsharpEngineTests.cs @@ -492,7 +492,6 @@ public async Task TestTraceMagic() Gate = "H", Targets = new List() { new QubitRegister(0) }, }, - // TODO: Remove Reset/ResetAll gates once we don't need to zero out qubits new Operation() { Gate = "Reset", @@ -500,6 +499,42 @@ public async Task TestTraceMagic() }, } )); + + // Should only see depth-1 operations + await AssertTrace("Depth2Circ", new ExecutionPath( + new QubitDeclaration[] { new QubitDeclaration(0) }, + new Operation[] + { + new Operation() + { + Gate = "FooBar", + Targets = new List() { new QubitRegister(0) }, + }, + } + )); + + // Should see depth-2 operations + await AssertTrace("Depth2Circ depth=2", new ExecutionPath( + new QubitDeclaration[] { new QubitDeclaration(0) }, + new Operation[] + { + new Operation() + { + Gate = "H", + Targets = new List() { new QubitRegister(0) }, + }, + new Operation() + { + Gate = "X", + Targets = new List() { new QubitRegister(0) }, + }, + new Operation() + { + Gate = "H", + Targets = new List() { new QubitRegister(0) }, + }, + } + )); } } } diff --git a/src/Tests/Workspace.ExecutionPathTracer/Intrinsic.qs b/src/Tests/Workspace.ExecutionPathTracer/Intrinsic.qs index 3811173584..2796648a12 100644 --- a/src/Tests/Workspace.ExecutionPathTracer/Intrinsic.qs +++ b/src/Tests/Workspace.ExecutionPathTracer/Intrinsic.qs @@ -96,6 +96,14 @@ namespace Tests.ExecutionPathTracer { } } + operation NestedCirc() : Unit { + using (q = Qubit()) { + H(q); + HCirc(); + Reset(q); + } + } + operation FooBar(q : Qubit) : Unit { H(q); X(q); @@ -105,7 +113,6 @@ namespace Tests.ExecutionPathTracer { operation Depth2Circ() : Unit { using (q = Qubit()) { FooBar(q); - Reset(q); } } From a562e1bbcbf19b621d009d888c399c1848680d61 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Fri, 17 Jul 2020 00:25:28 -0400 Subject: [PATCH 08/10] Fix nested test --- src/Tests/ExecutionPathTracerTests.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Tests/ExecutionPathTracerTests.cs b/src/Tests/ExecutionPathTracerTests.cs index 46e0e9dd32..17fa0e084f 100644 --- a/src/Tests/ExecutionPathTracerTests.cs +++ b/src/Tests/ExecutionPathTracerTests.cs @@ -434,11 +434,7 @@ public void EmptyTest() public void NestedTest() { var path = GetExecutionPath("NestedCirc"); - var qubits = new QubitDeclaration[] - { - new QubitDeclaration(0), - new QubitDeclaration(1), - }; + var qubits = new QubitDeclaration[] { new QubitDeclaration(0) }; var operations = new Operation[] { new Operation() From 97804b2827941c7b2935c04864b951e73956951f Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Fri, 17 Jul 2020 10:38:53 -0400 Subject: [PATCH 09/10] Empty commit to trigger CI From de4af1df8f90a71cdf3e4afba4b091f42299bf94 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Fri, 17 Jul 2020 11:37:37 -0400 Subject: [PATCH 10/10] Empty commit to trigger CI