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);
+ }
+ }
}