diff --git a/src/ExecutionPathTracer/ExecutionPath.cs b/src/ExecutionPathTracer/ExecutionPath.cs index 38330b5d89..9e598419ec 100644 --- a/src/ExecutionPathTracer/ExecutionPath.cs +++ b/src/ExecutionPathTracer/ExecutionPath.cs @@ -118,9 +118,6 @@ public class Operation /// /// 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; } @@ -130,6 +127,12 @@ public class Operation [JsonProperty("isMeasurement")] public bool IsMeasurement { get; set; } + /// + /// True if operation is a classically-controlled operations. + /// + [JsonProperty("isConditional")] + public bool IsConditional { get; set; } + /// /// True if operation is a controlled operations. /// diff --git a/src/ExecutionPathTracer/ExecutionPathTracer.cs b/src/ExecutionPathTracer/ExecutionPathTracer.cs index 9b0bc57190..aa32920ba3 100644 --- a/src/ExecutionPathTracer/ExecutionPathTracer.cs +++ b/src/ExecutionPathTracer/ExecutionPathTracer.cs @@ -69,9 +69,9 @@ public void OnOperationStartHandler(ICallable operation, IApplyData arguments) var metadata = operation.GetRuntimeMetadata(arguments); - // If metadata is a composite operation (i.e. want to trace its components instead), + // If metadata is a composite/conditional operation (i.e. want to trace its components instead), // we recursively create a tracer that traces its components instead - if (metadata != null && metadata.IsComposite) + if (metadata != null && (metadata.IsComposite || metadata.IsConditional)) { var remainingDepth = this.renderDepth - this.currentDepth; this.compositeTracer = new ExecutionPathTracer(remainingDepth); @@ -81,6 +81,8 @@ public void OnOperationStartHandler(ICallable operation, IApplyData arguments) this.currCompositeOp = operation; // Set currentOperation to null so we don't render higher-depth operations unintentionally. this.currentOperation = null; + + if (metadata.IsConditional) this.currentOperation = this.MetadataToOperation(metadata); return; } @@ -179,6 +181,15 @@ private void AddCompositeOperations() if (this.compositeTracer == null) throw new NullReferenceException("ERROR: compositeTracer not initialized."); + if (this.currentOperation != null && this.currentOperation.IsConditional) + { + var numChildren = this.compositeTracer.operations.Count(); + if (numChildren != 2) throw new IndexOutOfRangeException($"ERROR: Found only {numChildren} children for conditional operation."); + this.currentOperation.Children = this.compositeTracer.operations + .Select(op => new List() { op }); + this.operations.Add(this.currentOperation); + return; + } // The composite tracer has done its job and we retrieve the operations it traced this.operations.AddRange(this.compositeTracer.operations); } @@ -202,6 +213,7 @@ private void AddCompositeOperations() Gate = metadata.Label, DisplayArgs = displayArgs, Children = metadata.Children?.Select(child => child.Select(this.MetadataToOperation).WhereNotNull()), + IsConditional = metadata.IsConditional, IsControlled = metadata.IsControlled, IsAdjoint = metadata.IsAdjoint, Controls = this.GetQubitRegisters(metadata.Controls), diff --git a/src/ExecutionPathTracer/Extensions.cs b/src/ExecutionPathTracer/Extensions.cs index 103bbbf7e6..f703d2776b 100644 --- a/src/ExecutionPathTracer/Extensions.cs +++ b/src/ExecutionPathTracer/Extensions.cs @@ -3,11 +3,50 @@ #nullable enable +using System; using System.Collections.Generic; using Microsoft.Quantum.Simulation.Common; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.QuantumProcessor.Extensions; namespace Microsoft.Quantum.IQSharp.ExecutionPathTracer { + /// + /// Custom ApplyIfElse used by Tracer to overrides the default behaviour and executes both branches + /// of the conditional statement. + /// + public class TracerApplyIfElse : ApplyIfElseR + { + private SimulatorBase Simulator { get; } + + /// + /// Initializes a new instance of the class. + /// + public TracerApplyIfElse(SimulatorBase m) : base(m) + { + this.Simulator = m; + } + + /// + public override Func<(Result, (ICallable, Qubit), (ICallable, Qubit)), QVoid> Body => (q) => + { + (Result measurementResult, (ICallable onZero, Qubit one), (ICallable onOne, Qubit two)) = q; + onZero.Apply(one); + onOne.Apply(two); + + return QVoid.Instance; + }; + + /// + public override RuntimeMetadata? GetRuntimeMetadata(IApplyData args) + { + var metadata = base.GetRuntimeMetadata(args); + if (metadata == null) throw new NullReferenceException($"Null RuntimeMetadata found for {this.ToString()}."); + metadata.IsComposite = true; + return metadata; + } + } + /// /// Extension methods to be used with and by . /// @@ -22,6 +61,10 @@ public static T WithExecutionPathTracer(this T sim, ExecutionPathTracer trace { sim.OnOperationStart += tracer.OnOperationStartHandler; sim.OnOperationEnd += tracer.OnOperationEndHandler; + sim.Register( + typeof(ApplyIfElseR), + typeof(TracerApplyIfElse) + ); return sim; } diff --git a/src/Tests/ExecutionPathTracerTests.cs b/src/Tests/ExecutionPathTracerTests.cs index e626a557d2..a9ef35a929 100644 --- a/src/Tests/ExecutionPathTracerTests.cs +++ b/src/Tests/ExecutionPathTracerTests.cs @@ -614,6 +614,56 @@ public void BigTest() var expected = new ExecutionPath(qubits, operations); Assert.AreEqual(expected.ToJson(), path.ToJson()); } + + [TestMethod] + public void IfTest() + { + var path = GetExecutionPath("IfCirc"); + var qubits = new QubitDeclaration[] { new QubitDeclaration(0, 1) }; + var operations = new Operation[] + { + new Operation() + { + Gate = "M", + IsMeasurement = true, + Controls = new List() { new QubitRegister(0) }, + Targets = new List() { new ClassicalRegister(0, 0) }, + }, + new Operation() + { + Gate = "ApplyIfElseR", + DisplayArgs = "(Zero, (X), (Z))", + Controls = new List() { new ClassicalRegister(0, 0) }, + Targets = new List() { new QubitRegister(0) }, + Children = new List>() + { + new List() + { + new Operation() + { + Gate = "X", + Targets = new List() { new QubitRegister(0) }, + }, + }, + new List() + { + new Operation() + { + Gate = "Z", + 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()); + } } [TestClass] diff --git a/src/Tests/Workspace.ExecutionPathTracer/Circuits.qs b/src/Tests/Workspace.ExecutionPathTracer/Circuits.qs index 40c01b8675..6a6777c29f 100644 --- a/src/Tests/Workspace.ExecutionPathTracer/Circuits.qs +++ b/src/Tests/Workspace.ExecutionPathTracer/Circuits.qs @@ -4,6 +4,7 @@ namespace Tests.ExecutionPathTracer { open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Simulation.QuantumProcessor.Extensions; // Custom operation operation Foo(theta : Double, (qubit : Qubit, bar : String)) : Unit @@ -94,5 +95,13 @@ namespace Tests.ExecutionPathTracer { ResetAll(qs); } } + + operation IfCirc() : Unit { + using (q = Qubit()) { + let res = M(q); + ApplyIfElseR(res, (X, q), (Z, q)); + Reset(q); + } + } }