diff --git a/src/Core/ExecutionPathTracer/ExecutionPath.cs b/src/Core/ExecutionPathTracer/ExecutionPath.cs new file mode 100644 index 0000000000..1cc533db6d --- /dev/null +++ b/src/Core/ExecutionPathTracer/ExecutionPath.cs @@ -0,0 +1,150 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Quantum.IQSharp.Core.ExecutionPathTracer +{ + /// + /// Represents the qubit resources and operations traced out in an execution path of a Q# operation. + /// + public class ExecutionPath + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A list of that represents the declared qubits used in the execution path. + /// + /// + /// A list of that represents the operations used in the execution path. + /// + public ExecutionPath(IEnumerable qubits, IEnumerable operations) + { + this.Qubits = qubits; + this.Operations = operations; + } + + /// + /// A list of that represents the declared qubits used in the execution path. + /// + [JsonProperty("qubits")] + public IEnumerable Qubits { get; } + + /// + /// A list of that represents the operations used in the execution path. + /// + [JsonProperty("operations")] + public IEnumerable Operations { get; } + + /// + /// Serializes into its JSON representation. + /// + /// + /// Pretty prints the JSON (i.e. with white space and indents) if true. + /// + public string ToJson(bool prettyPrint = false) => + JsonConvert.SerializeObject(this, + (prettyPrint) ? Formatting.Indented : Formatting.None, + new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + } + ); + } + + /// + /// Represents a qubit resource used in an execution path. + /// + public class QubitDeclaration + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Id of qubit. + /// + /// + /// Number of associated classical registers. + /// + public QubitDeclaration(int id, int numChildren = 0) + { + this.Id = id; + this.NumChildren = numChildren; + } + + /// + /// Id of qubit. + /// + [JsonProperty("id")] + public int Id { get; } + + /// + /// Number of associated classical registers. + /// + [JsonProperty("numChildren")] + public int NumChildren { get; } + + /// + /// Used by to determine if + /// should be included in the JSON serialization. + /// + public bool ShouldSerializeNumChildren() => NumChildren > 0; + } + + /// + /// Represents an operation used in an execution path. + /// + public class Operation + { + /// + /// Label of gate. + /// + [JsonProperty("gate")] + public string Gate { get; set; } = ""; + + /// + /// Arguments (except types) provided to gate that + /// will be displayed by the visualizer. + /// + [JsonProperty("displayArgs")] + public string? DisplayArgs { get; set; } + + /// + /// Group of operations for each classical branch. + /// + /// + /// Currently not used as this is intended for classically-controlled operations. + /// + [JsonProperty("children")] + public IEnumerable>? Children { get; set; } + + /// + /// True if operation is a controlled operations. + /// + [JsonProperty("controlled")] + public bool Controlled { get; set; } + + /// + /// True if operation is an adjoint operations. + /// + [JsonProperty("adjoint")] + public bool Adjoint { get; set; } + + /// + /// List of control registers. + /// + [JsonProperty("controls")] + public IEnumerable Controls { get; set; } = new List(); + + /// + /// List of target registers. + /// + [JsonProperty("targets")] + public IEnumerable Targets { get; set; } = new List(); + } +} diff --git a/src/Core/ExecutionPathTracer/ExecutionPathTracer.cs b/src/Core/ExecutionPathTracer/ExecutionPathTracer.cs new file mode 100644 index 0000000000..3944f2c475 --- /dev/null +++ b/src/Core/ExecutionPathTracer/ExecutionPathTracer.cs @@ -0,0 +1,150 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Quantum.Simulation.Core; + +#nullable enable + +namespace Microsoft.Quantum.IQSharp.Core.ExecutionPathTracer +{ + /// + /// Traces through the operations in a given execution path of a Q# program by hooking on + /// to a simulator via the event listeners and + /// , and generates the corresponding . + /// + public class ExecutionPathTracer + { + private int currentDepth = 0; + private int renderDepth; + private IDictionary qubitRegisters = new Dictionary(); + private IDictionary> classicalRegisters = new Dictionary>(); + private List operations = new List(); + + /// + /// Initializes a new instance of the class with the depth to render operations at. + /// + /// + /// The depth at which to render operations. + /// + public ExecutionPathTracer(int depth = 1) => this.renderDepth = depth + 1; + + /// + /// Returns the generated . + /// + public ExecutionPath GetExecutionPath() => + new ExecutionPath( + this.qubitRegisters.Keys + .OrderBy(k => k) + .Select(k => new QubitDeclaration(k, (this.classicalRegisters.ContainsKey(k)) + ? this.classicalRegisters[k].Count + : 0 + )), + this.operations + ); + + /// + /// Provides the event listener to listen to + /// 's + /// OnOperationStart event. + /// + public void OnOperationStartHandler(ICallable operation, IApplyData arguments) + { + this.currentDepth++; + + // Parse operations at specified depth + if (this.currentDepth == this.renderDepth) + { + var metadata = operation.GetRuntimeMetadata(arguments); + var parsedOp = this.MetadataToOperation(metadata); + if (parsedOp != null) this.operations.Add(parsedOp); + } + } + + /// + /// Provides the event listener to listen to + /// 's + /// OnOperationEnd event. + /// + public void OnOperationEndHandler(ICallable operation, IApplyData result) => this.currentDepth--; + + /// + /// Retrieves the associated with the given or create a new + /// one if it doesn't exist. + /// + private QubitRegister GetQubitRegister(Qubit qubit) => + this.qubitRegisters.GetOrCreate(qubit.Id, new QubitRegister(qubit.Id)); + + private List GetQubitRegisters(IEnumerable qubits) => + qubits.Select(this.GetQubitRegister).ToList(); + + /// + /// Creates a new and associate it with the given . + /// + private ClassicalRegister CreateClassicalRegister(Qubit measureQubit) + { + var qId = measureQubit.Id; + var cId = this.classicalRegisters.GetOrCreate(qId).Count; + + var register = new ClassicalRegister(qId, cId); + + // Add classical register under the given qubit id + this.classicalRegisters[qId].Add(register); + + return register; + } + + /// + /// Retrieves the most recent associated with the given . + /// + /// + /// Currently not used as this is intended for classically-controlled operations. + /// + private ClassicalRegister GetClassicalRegister(Qubit controlQubit) + { + var qId = controlQubit.Id; + if (!this.classicalRegisters.ContainsKey(qId) || this.classicalRegisters[qId].Count == 0) + { + throw new Exception("No classical registers found for qubit {qId}."); + } + + // Get most recent measurement on given control qubit + var cId = this.classicalRegisters[qId].Count - 1; + return this.classicalRegisters[qId][cId]; + } + + /// + /// Parse into its corresponding . + /// + private Operation? MetadataToOperation(RuntimeMetadata? metadata) + { + if (metadata == null) return null; + + var op = new Operation() + { + Gate = metadata.Label, + DisplayArgs = (metadata.FormattedNonQubitArgs.Length > 0) ? metadata.FormattedNonQubitArgs : null, + Children = metadata.Children?.Select(child => child.Select(this.MetadataToOperation).WhereNotNull()), + Controlled = metadata.IsControlled, + Adjoint = metadata.IsAdjoint, + Controls = this.GetQubitRegisters(metadata.Controls), + Targets = this.GetQubitRegisters(metadata.Targets), + }; + + // Create classical registers for measurement operations + if (metadata.IsMeasurement) + { + var measureQubit = metadata.Targets.ElementAt(0); + var clsReg = this.CreateClassicalRegister(measureQubit); + // TODO: Change this to using IsMeasurement + op.Gate = "measure"; + op.Controls = op.Targets; + op.Targets = new List() { clsReg }; + } + + return op; + } + } +} diff --git a/src/Core/ExecutionPathTracer/Extensions.cs b/src/Core/ExecutionPathTracer/Extensions.cs new file mode 100644 index 0000000000..f836620843 --- /dev/null +++ b/src/Core/ExecutionPathTracer/Extensions.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Quantum.Simulation.Common; +using Microsoft.Quantum.Simulation.Core; + +namespace Microsoft.Quantum.IQSharp.Core.ExecutionPathTracer +{ + /// + /// Extension methods to be used with and by . + /// + public static class Extensions + { + /// + /// Attaches event listeners to the simulator to generate + /// the of the operation performed by the simulator. + /// + public static T WithExecutionPathTracer(this T sim, ExecutionPathTracer tracer) + where T : SimulatorBase + { + sim.OnOperationStart += tracer.OnOperationStartHandler; + sim.OnOperationEnd += tracer.OnOperationEndHandler; + return sim; + } + + /// + /// Gets the value associated with the specified key and creates a new entry with the defaultVal if + /// the key doesn't exist. + /// + public static TValue GetOrCreate(this IDictionary dict, TKey key, TValue defaultVal) + { + TValue val; + if (!dict.TryGetValue(key, out val)) + { + val = defaultVal; + dict.Add(key, val); + } + return val; + } + + /// + /// Gets the value associated with the specified key and creates a new entry of the default type if + /// the key doesn't exist. + /// + public static TValue GetOrCreate(this IDictionary dict, TKey key) + where TValue : new() => dict.GetOrCreate(key, new TValue()); + } +} diff --git a/src/Core/ExecutionPathTracer/Register.cs b/src/Core/ExecutionPathTracer/Register.cs new file mode 100644 index 0000000000..a3560b73a3 --- /dev/null +++ b/src/Core/ExecutionPathTracer/Register.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Quantum.IQSharp.Core.ExecutionPathTracer +{ + /// + /// Enum for the 2 types of registers: Qubit and Classical. + /// + public enum RegisterType + { + /// + /// Qubit register that holds a qubit. + /// + Qubit, + /// + /// Classical register that holds a classical bit. + /// + Classical, + } + + /// + /// Represents a register used by an . + /// + public class Register + { + /// + /// Type of register. + /// + [JsonProperty("type")] + public virtual RegisterType Type { get; } + + /// + /// Qubit id of register. + /// + [JsonProperty("qId")] + public virtual int QId { get; protected set; } + + /// + /// Classical bit id of register. null if register is a qubit register. + /// + [JsonProperty("cId")] + public virtual int? CId { get; protected set; } + } + + /// + /// Represents a qubit register used by an . + /// + public class QubitRegister : Register + { + /// + /// Creates a new with the given qubit id. + /// + /// + /// Id of qubit register. + /// + public QubitRegister(int qId) => this.QId = qId; + + /// + public override RegisterType Type => RegisterType.Qubit; + } + + /// + /// Represents a classical register used by an . + /// + public class ClassicalRegister : Register + { + /// + /// Creates a new with the given qubit id and classical bit id. + /// + /// + /// Id of qubit register. + /// + /// + /// Id of classical register associated with the given qubit id. + /// + public ClassicalRegister(int qId, int cId) + { + this.QId = qId; + this.CId = cId; + } + + /// + public override RegisterType Type => RegisterType.Classical; + } +} diff --git a/src/Kernel/Extensions.cs b/src/Kernel/Extensions.cs index 057ccecde8..1fd81e18e6 100644 --- a/src/Kernel/Extensions.cs +++ b/src/Kernel/Extensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Jupyter; diff --git a/src/Kernel/client/ExecutionPathVisualizer/executionPath.ts b/src/Kernel/client/ExecutionPathVisualizer/executionPath.ts index 314230fb84..eb74cb57f6 100644 --- a/src/Kernel/client/ExecutionPathVisualizer/executionPath.ts +++ b/src/Kernel/client/ExecutionPathVisualizer/executionPath.ts @@ -25,8 +25,8 @@ export interface Qubit { export interface Operation { /** Gate label. */ gate: string; - /** Gate arguments as string. */ - argStr?: string, + /** Formatted gate arguments to be displayed. */ + displayArgs?: string, /** Classically-controlled gates. * - children[0]: gates when classical control bit is 0. * - children[1]: gates when classical control bit is 1. diff --git a/src/Kernel/client/ExecutionPathVisualizer/formatters/gateFormatter.ts b/src/Kernel/client/ExecutionPathVisualizer/formatters/gateFormatter.ts index 5132866b57..ca2b22a3d8 100644 --- a/src/Kernel/client/ExecutionPathVisualizer/formatters/gateFormatter.ts +++ b/src/Kernel/client/ExecutionPathVisualizer/formatters/gateFormatter.ts @@ -42,12 +42,12 @@ const formatGates = (opsMetadata: Metadata[]): string => { * @returns SVG representation of gate. */ const _formatGate = (metadata: Metadata): string => { - const { type, x, controlsY, targetsY, label, argStr, width } = metadata; + const { type, x, controlsY, targetsY, label, displayArgs, width } = metadata; switch (type) { case GateType.Measure: return _measure(x, controlsY[0], targetsY[0]); case GateType.Unitary: - return _unitary(label, x, targetsY, width, argStr); + return _unitary(label, x, targetsY, width, displayArgs); case GateType.Swap: if (controlsY.length > 0) return _controlledGate(metadata); else return _swap(x, targetsY); @@ -89,12 +89,12 @@ const _measure = (x: number, qy: number, cy: number): string => { * @param x x coord of gate. * @param y Array of y coords of registers acted upon by gate. * @param width Width of gate. - * @param argStr Arguments passed in to gate. + * @param displayArgs Arguments passed in to gate. * @param renderDashedLine If true, draw dashed lines between non-adjacent unitaries. * * @returns SVG representation of unitary gate. */ -const _unitary = (label: string, x: number, y: number[], width: number, argStr?: string, renderDashedLine: boolean = true): string => { +const _unitary = (label: string, x: number, y: number[], width: number, displayArgs?: string, renderDashedLine: boolean = true): string => { if (y.length === 0) return ""; // Sort y in ascending order @@ -116,7 +116,7 @@ const _unitary = (label: string, x: number, y: number[], width: number, argStr?: const unitaryBoxes: string[] = regGroups.map((group: number[]) => { const maxY: number = group[group.length - 1], minY: number = group[0]; const height: number = maxY - minY + gateHeight; - return _unitaryBox(label, x, minY, width, height, argStr); + return _unitaryBox(label, x, minY, width, height, displayArgs); }); // Draw dashed line between disconnected unitaries @@ -135,19 +135,19 @@ const _unitary = (label: string, x: number, y: number[], width: number, argStr?: * @param y y coord of gate. * @param width Width of gate. * @param height Height of gate. - * @param argStr Arguments passed in to gate. + * @param displayArgs Arguments passed in to gate. * * @returns SVG representation of unitary box. */ -const _unitaryBox = (label: string, x: number, y: number, width: number, height: number = gateHeight, argStr?: string): string => { +const _unitaryBox = (label: string, x: number, y: number, width: number, height: number = gateHeight, displayArgs?: string): string => { y -= gateHeight / 2; const uBox: string = box(x - width / 2, y, width, height); - const labelY = y + height / 2 - ((argStr == null) ? 0 : 7); + const labelY = y + height / 2 - ((displayArgs == null) ? 0 : 7); const labelText: string = text(label, x, labelY); const elems = [uBox, labelText]; - if (argStr != null) { + if (displayArgs != null) { const argStrY = y + height / 2 + 8; - const argText: string = text(argStr, x, argStrY, argsFontSize); + const argText: string = text(displayArgs, x, argStrY, argsFontSize); elems.push(argText); } const svg: string = group(elems); @@ -194,7 +194,7 @@ const _cross = (x: number, y: number): string => { */ const _controlledGate = (metadata: Metadata): string => { const targetGateSvgs: string[] = []; - const { type, x, controlsY, targetsY, label, argStr, width } = metadata; + const { type, x, controlsY, targetsY, label, displayArgs, width } = metadata; // Get SVG for target gates switch (type) { case GateType.Cnot: @@ -204,7 +204,7 @@ const _controlledGate = (metadata: Metadata): string => { targetsY.forEach(y => targetGateSvgs.push(_cross(x, y))); break; case GateType.ControlledUnitary: - targetGateSvgs.push(_unitary(label, x, targetsY, width, argStr, false)); + targetGateSvgs.push(_unitary(label, x, targetsY, width, displayArgs, false)); break; default: throw new Error(`ERROR: Unrecognized gate: ${label} of type ${type}`); diff --git a/src/Kernel/client/ExecutionPathVisualizer/metadata.ts b/src/Kernel/client/ExecutionPathVisualizer/metadata.ts index e436d89722..2fe9bd7619 100644 --- a/src/Kernel/client/ExecutionPathVisualizer/metadata.ts +++ b/src/Kernel/client/ExecutionPathVisualizer/metadata.ts @@ -16,7 +16,7 @@ export interface Metadata { /** Gate label. */ label: string; /** Gate arguments as string. */ - argStr?: string, + displayArgs?: string, /** Gate width. */ width: number; /** Classically-controlled gates. diff --git a/src/Kernel/client/ExecutionPathVisualizer/process.ts b/src/Kernel/client/ExecutionPathVisualizer/process.ts index 0fce86488e..14c94f8104 100644 --- a/src/Kernel/client/ExecutionPathVisualizer/process.ts +++ b/src/Kernel/client/ExecutionPathVisualizer/process.ts @@ -185,7 +185,7 @@ const _opToMetadata = (op: Operation | null, registers: RegisterMap): Metadata = if (op == null) return metadata; - let { gate, argStr, controlled, adjoint, controls, targets, children } = op; + let { gate, displayArgs, controlled, adjoint, controls, targets, children } = op; // Set y coords metadata.controlsY = controls.map(reg => _getRegY(reg, registers)); @@ -233,7 +233,7 @@ const _opToMetadata = (op: Operation | null, registers: RegisterMap): Metadata = if (adjoint && metadata.label.length > 0) metadata.label += "'"; // If gate has extra arguments, display them - if (argStr != null) metadata.argStr = argStr; + if (displayArgs != null) metadata.displayArgs = displayArgs; // Set gate width metadata.width = getGateWidth(metadata); diff --git a/src/Kernel/client/ExecutionPathVisualizer/utils.ts b/src/Kernel/client/ExecutionPathVisualizer/utils.ts index 44d8515b23..2468d514d3 100644 --- a/src/Kernel/client/ExecutionPathVisualizer/utils.ts +++ b/src/Kernel/client/ExecutionPathVisualizer/utils.ts @@ -14,7 +14,7 @@ import { * * @returns Width of given gate (in pixels). */ -const getGateWidth = ({ type, label, argStr, width }: Metadata): number => { +const getGateWidth = ({ type, label, displayArgs, width }: Metadata): number => { switch (type) { case GateType.ClassicalControlled: // Already computed before. @@ -25,7 +25,7 @@ const getGateWidth = ({ type, label, argStr, width }: Metadata): number => { return minGateWidth; default: const labelWidth = _getStringWidth(label); - const argsWidth = (argStr != null) ? _getStringWidth(argStr, argsFontSize) : 0; + const argsWidth = (displayArgs != null) ? _getStringWidth(displayArgs, argsFontSize) : 0; const textWidth = Math.max(labelWidth, argsWidth) + labelPadding * 2; return Math.max(minGateWidth, textWidth); } diff --git a/src/Kernel/client/__tests__/ExecutionPathVisualizerTests/gateFormatter.test.ts b/src/Kernel/client/__tests__/ExecutionPathVisualizerTests/gateFormatter.test.ts index 2a5a761ac5..014346aa4b 100644 --- a/src/Kernel/client/__tests__/ExecutionPathVisualizerTests/gateFormatter.test.ts +++ b/src/Kernel/client/__tests__/ExecutionPathVisualizerTests/gateFormatter.test.ts @@ -449,7 +449,7 @@ describe("Testing _formatGate", () => { controlsY: [], targetsY: [startY], label: 'Ry', - argStr: '(0.25)', + displayArgs: '(0.25)', width: 52, }; expect(_formatGate(metadata)).toMatchSnapshot(); @@ -473,7 +473,7 @@ describe("Testing _formatGate", () => { controlsY: [], targetsY: [startY, startY + registerHeight], label: 'U', - argStr: "('foo', 'bar')", + displayArgs: "('foo', 'bar')", width: 77, }; expect(_formatGate(metadata)).toMatchSnapshot(); @@ -529,7 +529,7 @@ describe("Testing _formatGate", () => { controlsY: [startY], targetsY: [startY + registerHeight], label: 'U', - argStr: "('foo', 'bar')", + displayArgs: "('foo', 'bar')", width: 77, }; expect(_formatGate(metadata)).toMatchSnapshot(); diff --git a/src/Kernel/client/__tests__/ExecutionPathVisualizerTests/process.test.ts b/src/Kernel/client/__tests__/ExecutionPathVisualizerTests/process.test.ts index bd9107c075..b5a6870d2d 100644 --- a/src/Kernel/client/__tests__/ExecutionPathVisualizerTests/process.test.ts +++ b/src/Kernel/client/__tests__/ExecutionPathVisualizerTests/process.test.ts @@ -513,7 +513,7 @@ describe("Testing _opToMetadata", () => { }; let op: Operation = { gate: "RX", - argStr: "(0.25)", + displayArgs: "(0.25)", controlled: false, adjoint: false, controls: [], @@ -525,7 +525,7 @@ describe("Testing _opToMetadata", () => { controlsY: [], targetsY: [startY], label: "RX", - argStr: "(0.25)", + displayArgs: "(0.25)", width: 52 }; expect(_opToMetadata(op, registers)).toEqual(metadata); @@ -533,7 +533,7 @@ describe("Testing _opToMetadata", () => { // Test long argument op = { gate: "RX", - argStr: "(0.25, 1.0, 'foobar', (3.14, 6.67))", + displayArgs: "(0.25, 1.0, 'foobar', (3.14, 6.67))", controlled: false, adjoint: false, controls: [], @@ -545,7 +545,7 @@ describe("Testing _opToMetadata", () => { controlsY: [], targetsY: [startY], label: "RX", - argStr: "(0.25, 1.0, 'foobar', (3.14, 6.67))", + displayArgs: "(0.25, 1.0, 'foobar', (3.14, 6.67))", width: 188 }; expect(_opToMetadata(op, registers)).toEqual(metadata); @@ -553,7 +553,7 @@ describe("Testing _opToMetadata", () => { // Test controlled op = { gate: "RX", - argStr: "(0.25)", + displayArgs: "(0.25)", controlled: true, adjoint: false, controls: [{ type: RegisterType.Qubit, qId: 1 }], @@ -565,7 +565,7 @@ describe("Testing _opToMetadata", () => { controlsY: [startY + registerHeight], targetsY: [startY], label: "RX", - argStr: "(0.25)", + displayArgs: "(0.25)", width: 52 }; expect(_opToMetadata(op, registers)).toEqual(metadata); @@ -578,7 +578,7 @@ describe("Testing _opToMetadata", () => { }; let op: Operation = { gate: "U", - argStr: "('foo', 'bar')", + displayArgs: "('foo', 'bar')", controlled: false, adjoint: false, controls: [], @@ -593,7 +593,7 @@ describe("Testing _opToMetadata", () => { controlsY: [], targetsY: [startY, startY + registerHeight], label: "U", - argStr: "('foo', 'bar')", + displayArgs: "('foo', 'bar')", width: 77 }; expect(_opToMetadata(op, registers)).toEqual(metadata); @@ -601,7 +601,7 @@ describe("Testing _opToMetadata", () => { // Test long argument op = { gate: "U", - argStr: "(0.25, 1.0, 'foobar', (3.14, 6.67))", + displayArgs: "(0.25, 1.0, 'foobar', (3.14, 6.67))", controlled: false, adjoint: false, controls: [], @@ -616,7 +616,7 @@ describe("Testing _opToMetadata", () => { controlsY: [], targetsY: [startY, startY + registerHeight], label: "U", - argStr: "(0.25, 1.0, 'foobar', (3.14, 6.67))", + displayArgs: "(0.25, 1.0, 'foobar', (3.14, 6.67))", width: 188 }; expect(_opToMetadata(op, registers)).toEqual(metadata); @@ -624,7 +624,7 @@ describe("Testing _opToMetadata", () => { // Test controlled op = { gate: "U", - argStr: "('foo', 'bar')", + displayArgs: "('foo', 'bar')", controlled: true, adjoint: false, controls: [{ type: RegisterType.Qubit, qId: 1 }], @@ -639,7 +639,7 @@ describe("Testing _opToMetadata", () => { controlsY: [startY + registerHeight], targetsY: [startY, startY + registerHeight * 2], label: "U", - argStr: "('foo', 'bar')", + displayArgs: "('foo', 'bar')", width: 77 }; expect(_opToMetadata(op, registers)).toEqual(metadata); @@ -1192,7 +1192,7 @@ describe("Testing processOperations", () => { }, { gate: "RX", - argStr: "(0.25)", + displayArgs: "(0.25)", controlled: false, adjoint: false, controls: [], @@ -1234,7 +1234,7 @@ describe("Testing processOperations", () => { controlsY: [], targetsY: [startY + registerHeight], label: "RX", - argStr: "(0.25)", + displayArgs: "(0.25)", width: rxWidth, } ]; diff --git a/src/Kernel/client/__tests__/ExecutionPathVisualizerTests/utils.test.ts b/src/Kernel/client/__tests__/ExecutionPathVisualizerTests/utils.test.ts index 52c21c6f4c..6edea89911 100644 --- a/src/Kernel/client/__tests__/ExecutionPathVisualizerTests/utils.test.ts +++ b/src/Kernel/client/__tests__/ExecutionPathVisualizerTests/utils.test.ts @@ -27,7 +27,7 @@ describe("Testing getGateWidth", () => { expect(getGateWidth(Object.assign({ type: GateType.Unitary, label: 'zz' }))) .toEqual(minGateWidth)); test("unitary gate with arguments", () => - expect(getGateWidth(Object.assign({ type: GateType.Unitary, argStr: '(0.25)', label: 'RX' }))) + expect(getGateWidth(Object.assign({ type: GateType.Unitary, displayArgs: '(0.25)', label: 'RX' }))) .toEqual(52)); test("invalid", () => expect(getGateWidth(Object.assign({ type: GateType.Invalid, label: '' }))) diff --git a/src/Kernel/tsconfig.json b/src/Kernel/tsconfig.json index 2d8de5d0dc..1a6932e15a 100644 --- a/src/Kernel/tsconfig.json +++ b/src/Kernel/tsconfig.json @@ -12,6 +12,6 @@ "module": "AMD", "moduleResolution": "node" }, - "exclude": ["node_modules", "wwwroot", "client/__test__"], + "exclude": ["node_modules", "wwwroot", "client/__tests__"], "include": ["client/**/*"] } diff --git a/src/Tests/ExecutionPathTracerTests.cs b/src/Tests/ExecutionPathTracerTests.cs new file mode 100644 index 0000000000..fea5a8c3c4 --- /dev/null +++ b/src/Tests/ExecutionPathTracerTests.cs @@ -0,0 +1,521 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.Quantum.IQSharp; +using Microsoft.Quantum.Simulation.Simulators; +using Microsoft.Quantum.IQSharp.Core.ExecutionPathTracer; + +namespace Tests.IQSharp +{ + [TestClass] + public class IntrinsicTests + { + public Workspace InitWorkspace() + { + var ws = Startup.Create("Workspace.ExecutionPathTracer"); + ws.Reload(); + Assert.IsFalse(ws.HasErrors); + return ws; + } + + public ExecutionPath GetExecutionPath(string name, int depth = 1) + { + var ws = InitWorkspace(); + var op = ws.AssemblyInfo.Operations.SingleOrDefault(o => o.FullName == $"Tests.ExecutionPathTracer.{name}"); + Assert.IsNotNull(op); + + var tracer = new ExecutionPathTracer(depth); + using var qsim = new QuantumSimulator().WithExecutionPathTracer(tracer); + op.RunAsync(qsim, new Dictionary()).Wait(); + + return tracer.GetExecutionPath(); + } + + + [TestMethod] + public void HTest() + { + var path = GetExecutionPath("HCirc"); + var qubits = new QubitDeclaration[] + { + new QubitDeclaration(0), + }; + var operations = 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) }, + }, + }; + var expected = new ExecutionPath(qubits, operations); + Assert.AreEqual(expected.ToJson(), path.ToJson()); + } + + [TestMethod] + public void MTest() + { + var path = GetExecutionPath("MCirc"); + var qubits = new QubitDeclaration[] + { + new QubitDeclaration(0, 1), + }; + var operations = new Operation[] + { + 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()); + } + + [TestMethod] + public void CnotTest() + { + var path = GetExecutionPath("CnotCirc"); + var qubits = new QubitDeclaration[] + { + new QubitDeclaration(0), + new QubitDeclaration(1), + }; + var operations = new Operation[] + { + new Operation() + { + Gate = "X", + Controlled = true, + Controls = new List() { new QubitRegister(0) }, + Targets = new List() { new QubitRegister(1) }, + }, + new Operation() + { + Gate = "ResetAll", + Targets = new List() { new QubitRegister(0), new QubitRegister(1) }, + }, + }; + var expected = new ExecutionPath(qubits, operations); + Assert.AreEqual(expected.ToJson(), path.ToJson()); + } + + [TestMethod] + public void CcnotTest() + { + var path = GetExecutionPath("CcnotCirc"); + var qubits = new QubitDeclaration[] + { + new QubitDeclaration(0), + new QubitDeclaration(1), + new QubitDeclaration(2), + }; + var operations = new Operation[] + { + new Operation() + { + Gate = "X", + Controlled = true, + Controls = new List() { new QubitRegister(0), new QubitRegister(2) }, + Targets = new List() { new QubitRegister(1) }, + }, + new Operation() + { + Gate = "ResetAll", + Targets = new List() + { + new QubitRegister(0), + new QubitRegister(1), + new QubitRegister(2), + }, + }, + }; + var expected = new ExecutionPath(qubits, operations); + Assert.AreEqual(expected.ToJson(), path.ToJson()); + } + + [TestMethod] + public void SwapTest() + { + var path = GetExecutionPath("SwapCirc"); + var qubits = new QubitDeclaration[] + { + new QubitDeclaration(0), + new QubitDeclaration(1), + }; + var operations = new Operation[] + { + new Operation() + { + Gate = "SWAP", + Targets = new List() { new QubitRegister(0), new QubitRegister(1) }, + }, + }; + var expected = new ExecutionPath(qubits, operations); + Assert.AreEqual(expected.ToJson(), path.ToJson()); + } + + [TestMethod] + public void RxTest() + { + var path = GetExecutionPath("RxCirc"); + var qubits = new QubitDeclaration[] + { + new QubitDeclaration(0), + }; + var operations = new Operation[] + { + new Operation() + { + Gate = "Rx", + DisplayArgs = "(2)", + Targets = new List() { new QubitRegister(0) }, + }, + 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 AdjointHTest() + { + var path = GetExecutionPath("AdjointHCirc"); + var qubits = new QubitDeclaration[] + { + new QubitDeclaration(0), + }; + var operations = new Operation[] + { + new Operation() + { + Gate = "H", + Adjoint = true, + Targets = new List() { new QubitRegister(0) }, + }, + 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 ControlledXTest() + { + var path = GetExecutionPath("ControlledXCirc"); + var qubits = new QubitDeclaration[] + { + new QubitDeclaration(0), + new QubitDeclaration(1), + }; + var operations = new Operation[] + { + new Operation() + { + Gate = "X", + Controlled = true, + Controls = new List() { new QubitRegister(0) }, + Targets = new List() { new QubitRegister(1) }, + }, + new Operation() + { + Gate = "ResetAll", + Targets = new List() { new QubitRegister(0), new QubitRegister(1) }, + }, + }; + var expected = new ExecutionPath(qubits, operations); + Assert.AreEqual(expected.ToJson(), path.ToJson()); + + // JSON should be the same as CNOT's + var path2 = GetExecutionPath("CnotCirc"); + Assert.AreEqual(path.ToJson(), path2.ToJson()); + } + + [TestMethod] + public void ControlledAdjointSTest() + { + var path = GetExecutionPath("ControlledAdjointSCirc"); + var qubits = new QubitDeclaration[] + { + new QubitDeclaration(0), + new QubitDeclaration(1), + }; + var operations = new Operation[] + { + new Operation() + { + Gate = "S", + Controlled = true, + Adjoint = true, + Controls = new List() { new QubitRegister(0) }, + Targets = new List() { new QubitRegister(1) }, + }, + new Operation() + { + Gate = "ResetAll", + Targets = new List() { new QubitRegister(0), new QubitRegister(1) }, + }, + }; + var expected = new ExecutionPath(qubits, operations); + Assert.AreEqual(expected.ToJson(), path.ToJson()); + } + + [TestMethod] + public void FooTest() + { + var path = GetExecutionPath("FooCirc"); + var qubits = new QubitDeclaration[] + { + new QubitDeclaration(0), + }; + var operations = new Operation[] + { + new Operation() + { + Gate = "Foo", + DisplayArgs = "(2.1, (\"bar\"))", + Targets = new List() { new QubitRegister(0) }, + }, + }; + var expected = new ExecutionPath(qubits, operations); + Assert.AreEqual(expected.ToJson(), path.ToJson()); + } + + [TestMethod] + public void ControlledFooTest() + { + var path = GetExecutionPath("ControlledFooCirc"); + var qubits = new QubitDeclaration[] + { + new QubitDeclaration(0), + new QubitDeclaration(1), + }; + var operations = new Operation[] + { + new Operation() + { + Gate = "Foo", + DisplayArgs = "(2.1, (\"bar\"))", + Controlled = true, + Controls = new List() { new QubitRegister(0) }, + Targets = new List() { new QubitRegister(1) }, + }, + }; + var expected = new ExecutionPath(qubits, operations); + Assert.AreEqual(expected.ToJson(), path.ToJson()); + } + + [TestMethod] + public void UnusedQubitTest() + { + var path = GetExecutionPath("UnusedQubitCirc"); + var qubits = new QubitDeclaration[] + { + new QubitDeclaration(0), + new QubitDeclaration(2), + }; + var operations = new Operation[] + { + new Operation() + { + Gate = "X", + Controlled = true, + Controls = new List() { new QubitRegister(2) }, + Targets = new List() { new QubitRegister(0) }, + }, + new Operation() + { + Gate = "Reset", + Targets = new List() { new QubitRegister(0) }, + }, + new Operation() + { + Gate = "Reset", + Targets = new List() { new QubitRegister(2) }, + }, + }; + var expected = new ExecutionPath(qubits, operations); + Assert.AreEqual(expected.ToJson(), path.ToJson()); + } + + [TestMethod] + public void Depth2Test() + { + var path = GetExecutionPath("Depth2Circ", 2); + var qubits = new QubitDeclaration[] + { + new QubitDeclaration(0, 1), + }; + var operations = 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) }, + }, + 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()); + } + + [TestMethod] + public void PartialOpTest() + { + var path = GetExecutionPath("PartialOpCirc"); + var qubits = new QubitDeclaration[] + { + new QubitDeclaration(0), + new QubitDeclaration(1), + new QubitDeclaration(2), + }; + var operations = new Operation[] + { + new Operation() + { + Gate = "H", + Controlled = true, + Controls = new List() { new QubitRegister(0), new QubitRegister(1) }, + Targets = new List() { new QubitRegister(2) }, + }, + new Operation() + { + Gate = "Ry", + DisplayArgs = "(2.5)", + Targets = new List() { new QubitRegister(0) }, + }, + new Operation() + { + Gate = "ResetAll", + Targets = new List() { + new QubitRegister(0), + new QubitRegister(1), + new QubitRegister(2), + }, + }, + }; + var expected = new ExecutionPath(qubits, operations); + Assert.AreEqual(expected.ToJson(), path.ToJson()); + } + + [TestMethod] + public void EmptyTest() + { + var path = GetExecutionPath("EmptyCirc"); + var qubits = new QubitDeclaration[] { }; + var operations = new Operation[] { }; + var expected = new ExecutionPath(qubits, operations); + Assert.AreEqual(expected.ToJson(), path.ToJson()); + } + + [TestMethod] + public void BigTest() + { + var path = GetExecutionPath("BigCirc"); + var qubits = new QubitDeclaration[] + { + new QubitDeclaration(0, 1), + new QubitDeclaration(1), + new QubitDeclaration(2), + }; + var operations = new Operation[] + { + new Operation() + { + Gate = "H", + Targets = new List() { new QubitRegister(0) }, + }, + new Operation() + { + Gate = "Ry", + DisplayArgs = "(2.5)", + Targets = new List() { new QubitRegister(1) }, + }, + new Operation() + { + Gate = "Bar", + DisplayArgs = "((1, 2.1), (\"foo\"))", + Targets = new List() { new QubitRegister(0) }, + }, + new Operation() + { + Gate = "X", + Targets = new List() { new QubitRegister(0) }, + }, + new Operation() + { + Gate = "X", + Controlled = true, + Controls = new List() { new QubitRegister(0), new QubitRegister(1) }, + Targets = new List() { new QubitRegister(2) }, + }, + new Operation() + { + Gate = "X", + Controlled = true, + Controls = new List() { new QubitRegister(0), new QubitRegister(1) }, + Targets = new List() { new QubitRegister(2) }, + }, + new Operation() + { + Gate = "Bar", + DisplayArgs = "((1, 2.1), (\"foo\"))", + Controlled = true, + Adjoint = true, + Controls = new List() { new QubitRegister(2) }, + Targets = new List() { new QubitRegister(0) }, + }, + new Operation() + { + Gate = "measure", + Controls = new List() { new QubitRegister(0) }, + Targets = new List() { new ClassicalRegister(0, 0) }, + }, + new Operation() + { + Gate = "ResetAll", + Targets = new List() { + new QubitRegister(0), + new QubitRegister(1), + new QubitRegister(2), + }, + }, + }; + var expected = new ExecutionPath(qubits, operations); + Assert.AreEqual(expected.ToJson(), path.ToJson()); + } + } +} diff --git a/src/Tests/Tests.IQsharp.csproj b/src/Tests/Tests.IQsharp.csproj index 6b8f79b98e..90536f1c09 100644 --- a/src/Tests/Tests.IQsharp.csproj +++ b/src/Tests/Tests.IQsharp.csproj @@ -48,6 +48,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + diff --git a/src/Tests/Workspace.ExecutionPathTracer/Intrinsic.qs b/src/Tests/Workspace.ExecutionPathTracer/Intrinsic.qs new file mode 100644 index 0000000000..3811173584 --- /dev/null +++ b/src/Tests/Workspace.ExecutionPathTracer/Intrinsic.qs @@ -0,0 +1,140 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Tests.ExecutionPathTracer { + + open Microsoft.Quantum.Intrinsic; + + operation HCirc() : Unit { + using (q = Qubit()) { + H(q); + Reset(q); + } + } + + operation MCirc() : Unit { + using (q = Qubit()) { + let res = M(q); + } + } + + operation CnotCirc() : Unit { + using (qs = Qubit[2]) { + CNOT(qs[0], qs[1]); + ResetAll(qs); + } + } + + operation CcnotCirc() : Unit { + using (qs = Qubit[3]) { + CCNOT(qs[0], qs[2], qs[1]); + ResetAll(qs); + } + } + + operation SwapCirc() : Unit { + using (qs = Qubit[2]) { + SWAP(qs[0], qs[1]); + } + } + + operation RxCirc() : Unit { + using (q = Qubit()) { + Rx(2.0, q); + Reset(q); + } + } + + operation AdjointHCirc() : Unit { + using (q = Qubit()) { + Adjoint H(q); + Reset(q); + } + } + + operation ControlledXCirc() : Unit { + using (qs = Qubit[2]) { + Controlled X([qs[0]], qs[1]); + ResetAll(qs); + } + } + + operation ControlledAdjointSCirc() : Unit { + using (qs = Qubit[2]) { + Controlled Adjoint S([qs[0]], qs[1]); + ResetAll(qs); + } + } + + // Custom operation + operation Foo(theta : Double, (qubit : Qubit, bar : String)) : Unit + is Adj + Ctl { + } + + operation FooCirc() : Unit { + using (q = Qubit()) { + Foo(2.1, (q, "bar")); + } + } + + operation ControlledFooCirc() : Unit { + using (qs = Qubit[2]) { + Controlled Foo([qs[0]], (2.1, (qs[1], "bar"))); + } + } + + operation UnusedQubitCirc() : Unit { + using (qs = Qubit[3]) { + CNOT(qs[2], qs[0]); + Reset(qs[0]); + Reset(qs[2]); + } + } + + operation EmptyCirc() : Unit { + using (qs = Qubit[3]) { + } + } + + operation FooBar(q : Qubit) : Unit { + H(q); + X(q); + H(q); + } + + operation Depth2Circ() : Unit { + using (q = Qubit()) { + FooBar(q); + Reset(q); + } + } + + operation PartialOpCirc() : Unit { + using (qs = Qubit[3]) { + (Controlled H(qs[0..1], _))(qs[2]); + ((Ry(_, _))(2.5, _))(qs[0]); + ResetAll(qs); + } + } + + operation Bar((alpha : Double, beta : Double), (q : Qubit, name : String)) : Unit + is Adj + Ctl { + } + + operation BigCirc() : Unit { + using (qs = Qubit[3]) { + H(qs[0]); + Ry(2.5, qs[1]); + Bar((1.0, 2.1), (qs[0], "foo")); + X(qs[0]); + CCNOT(qs[0], qs[1], qs[2]); + Controlled CNOT([qs[0]], (qs[1], qs[2])); + Controlled Adjoint Bar([qs[2]], ((1.0, 2.1), (qs[0], "foo"))); + let res = M(qs[0]); + ResetAll(qs); + } + } + +} + + diff --git a/src/Tests/Workspace.ExecutionPathTracer/Measurement.qs b/src/Tests/Workspace.ExecutionPathTracer/Measurement.qs new file mode 100644 index 0000000000..01efa8d5f8 --- /dev/null +++ b/src/Tests/Workspace.ExecutionPathTracer/Measurement.qs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Tests.ExecutionPathTracer { + + open Microsoft.Quantum.Measurement; + + operation MResetXCirc() : Unit { + using (q = Qubit()) { + let res = MResetX(q); + } + } + + operation MResetYCirc() : Unit { + using (q = Qubit()) { + let res = MResetY(q); + } + } + + operation MResetZCirc() : Unit { + using (q = Qubit()) { + let res = MResetZ(q); + } + } + +} + +