Skip to content
This repository has been archived by the owner on Jan 12, 2024. It is now read-only.

Made Width and Depth compatible in current version of the tracer #404

Merged
merged 12 commits into from
Oct 22, 2020
Merged
1 change: 1 addition & 0 deletions src/Simulation/EntryPointDriver.Tests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ let private resourceSummary =
T 0 0
Depth 0 0
Width 1 1
QubitCount 1 1
bettinaheim marked this conversation as resolved.
Show resolved Hide resolved
BorrowedWidth 0 0"

[<Fact>]
Expand Down
73 changes: 54 additions & 19 deletions src/Simulation/QCTraceSimulator/DepthCounter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,23 @@ namespace Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime
{
public class DepthCounter : IQCTraceSimulatorListener, ICallGraphStatistics
{
class OperationCallRecord
private class OperationCallRecord
{
public HashedString OperationName;
public OperationFunctor FunctorSpecialization;
public double MinOperationStartTime;
public double MaxOperationStartTime;
public double ReleasedQubitsAvailableTime;
public double ReturnedQubitsAvailableTime;
public long MaxQubitIdAtStart = -1;
DmitryVasilevsky marked this conversation as resolved.
Show resolved Hide resolved
public QubitTimeMetrics[] InputQubitMetrics;
}

StatisticsCollector<CallGraphEdge> stats;
Stack<OperationCallRecord> operationCallStack;
QubitAvailabilityTimeTracker qubitAvailabilityTime = new QubitAvailabilityTimeTracker(
initialCapacity: 128, // Reasonable number to preallocate.
defaultAvailabilityTime: 0.0);

public IStatisticCollectorResults<CallGraphEdge> Results => stats;

Expand All @@ -44,25 +48,30 @@ public DepthCounter(IDoubleStatistic[] statisticsToCollect = null)
operationCallStack.Push(opRec);
}

public class Metrics
public static class Metrics
{
public const string Depth = "Depth";
public const string StartTimeDifference = "StartTimeDifference";
public const string Width = "Width";
DmitryVasilevsky marked this conversation as resolved.
Show resolved Hide resolved
}

public double[] StatisticsRecord(double Depth, double StartTimeDifference)
public double[] StatisticsRecord(double Depth, double StartTimeDifference, double Width)
{
return new double[] { Depth, StartTimeDifference };
return new double[] { Depth, StartTimeDifference, Width };
}

#region IQCTraceSimulatorListener implementation
public object NewTracingData(long qubitId)
{
return new QubitTimeMetrics();
return new QubitTimeMetrics(qubitId);
}

public void OnAllocate(object[] qubitsTraceData)
{
foreach (QubitTimeMetrics metric in qubitsTraceData.Cast<QubitTimeMetrics>())
{
qubitAvailabilityTime.MarkQubitIdUsed(metric.QubitId);
}
}

public void OnBorrow(object[] qubitsTraceData, long newQubitsAllocated)
Expand All @@ -74,12 +83,11 @@ public void OnOperationEnd(object[] returnedQubitsTraceData)
double maxReturnedQubitsAvailableTime = 0;
if ( returnedQubitsTraceData != null )
{
QubitTimeMetrics[] qubitsMetrics = Utils.UnboxAs<QubitTimeMetrics>(returnedQubitsTraceData);
maxReturnedQubitsAvailableTime = QubitsMetricsUtils.MaxQubitAvailableTime(qubitsMetrics);
maxReturnedQubitsAvailableTime = MaxAvailableTime(returnedQubitsTraceData.Cast<QubitTimeMetrics>());
}
OperationCallRecord opRec = operationCallStack.Pop();
Debug.Assert(operationCallStack.Count != 0, "Operation call stack must never get empty");
double inputQubitsAvailableTime = QubitsMetricsUtils.MaxQubitAvailableTime(opRec.InputQubitMetrics);
double inputQubitsAvailableTime = MaxAvailableTime(opRec.InputQubitMetrics);
double operationEndTime =
Max(
Max(maxReturnedQubitsAvailableTime, opRec.ReturnedQubitsAvailableTime),
Expand All @@ -93,7 +101,8 @@ public void OnOperationEnd(object[] returnedQubitsTraceData)
double[] metrics =
StatisticsRecord(
Depth : operationEndTime - opRec.MaxOperationStartTime,
StartTimeDifference: opRec.MaxOperationStartTime - opRec.MinOperationStartTime );
StartTimeDifference: opRec.MaxOperationStartTime - opRec.MinOperationStartTime,
Width: qubitAvailabilityTime.GetMaxQubitId() - opRec.MaxQubitIdAtStart );
DmitryVasilevsky marked this conversation as resolved.
Show resolved Hide resolved

stats.AddSample(new CallGraphEdge(opRec.OperationName, callerName,opRec.FunctorSpecialization, caller.FunctorSpecialization), metrics);
}
Expand All @@ -105,33 +114,59 @@ public void OnOperationStart(HashedString name, OperationFunctor functorSpeciali
opRec.FunctorSpecialization = functorSpecialization;
opRec.OperationName = name;
opRec.InputQubitMetrics = Utils.UnboxAs<QubitTimeMetrics>(qubitsTraceData);
opRec.MaxOperationStartTime = QubitsMetricsUtils.MaxQubitAvailableTime(opRec.InputQubitMetrics);
opRec.MinOperationStartTime = QubitsMetricsUtils.MinQubitAvailableTime(opRec.InputQubitMetrics);
opRec.MaxOperationStartTime = MaxAvailableTime(opRec.InputQubitMetrics);
opRec.MinOperationStartTime = MinAvailableTime(opRec.InputQubitMetrics);
opRec.MaxQubitIdAtStart = qubitAvailabilityTime.GetMaxQubitId();
operationCallStack.Push(opRec);
}

private double MinAvailableTime(IEnumerable<QubitTimeMetrics> qubitTimeMetrics)
{
Debug.Assert(qubitTimeMetrics != null);
double min = Double.MaxValue;
foreach (QubitTimeMetrics metric in qubitTimeMetrics)
{
min = Min(min, qubitAvailabilityTime[metric.QubitId]);
}
return min != Double.MaxValue ? min : 0;
}

private double MaxAvailableTime(IEnumerable<QubitTimeMetrics> qubitTimeMetrics)
{
Debug.Assert(qubitTimeMetrics != null);
double max = 0;
foreach (QubitTimeMetrics metric in qubitTimeMetrics)
{
max = Max(max, qubitAvailabilityTime[metric.QubitId]);
}
return max;
}

public void OnPrimitiveOperation(int id, object[] qubitsTraceData, double primitiveOperationDuration)
{
QubitTimeMetrics[] qubitsMetrics = Utils.UnboxAs<QubitTimeMetrics>(qubitsTraceData);
double startTime = QubitsMetricsUtils.MaxQubitAvailableTime(qubitsMetrics);
foreach (QubitTimeMetrics q in qubitsMetrics )
IEnumerable<QubitTimeMetrics> qubitsMetrics = qubitsTraceData.Cast<QubitTimeMetrics>();

double startTime = MaxAvailableTime(qubitsMetrics);
foreach (QubitTimeMetrics q in qubitsMetrics)
{
q.RecordQubitUsage(startTime, primitiveOperationDuration);
qubitAvailabilityTime[q.QubitId] = startTime + primitiveOperationDuration;
}
}

public void OnRelease(object[] qubitsTraceData)
{
OperationCallRecord opRec = operationCallStack.Peek();
QubitTimeMetrics[] qubitsMetrics = Utils.UnboxAs<QubitTimeMetrics>(qubitsTraceData);
opRec.ReleasedQubitsAvailableTime = Max(opRec.ReleasedQubitsAvailableTime, QubitsMetricsUtils.MaxQubitAvailableTime(qubitsMetrics));
opRec.ReleasedQubitsAvailableTime = Max(
opRec.ReleasedQubitsAvailableTime,
MaxAvailableTime(qubitsTraceData.Cast<QubitTimeMetrics>()));
}

public void OnReturn(object[] qubitsTraceData, long qubitReleased)
{
OperationCallRecord opRec = operationCallStack.Peek();
QubitTimeMetrics[] qubitsMetrics = Utils.UnboxAs<QubitTimeMetrics>(qubitsTraceData);
opRec.ReturnedQubitsAvailableTime = Max(opRec.ReturnedQubitsAvailableTime, QubitsMetricsUtils.MaxQubitAvailableTime(qubitsMetrics));
opRec.ReturnedQubitsAvailableTime = Max(
opRec.ReturnedQubitsAvailableTime,
MaxAvailableTime(qubitsTraceData.Cast<QubitTimeMetrics>()));
}

/// <summary>
Expand Down
9 changes: 8 additions & 1 deletion src/Simulation/QCTraceSimulator/QCTraceSimulatorCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ public class QCTraceSimulatorCoreConfiguration
/// of the call graph.
/// </summary>
public uint CallStackDepthLimit = uint.MaxValue;

/// <summary>
/// Constrols if depth or width optimization is favored.
DmitryVasilevsky marked this conversation as resolved.
Show resolved Hide resolved
/// If set to true, resulting circuit is optimized for depth by discouraging qubit reuse.
/// If set to false, resulting circuit is optimized for width by encouraging qubit reuse.
/// </summary>
public bool OptimizeDepth = false;
}
DmitryVasilevsky marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
Expand Down Expand Up @@ -101,7 +108,7 @@ QCTraceSimulatorCoreConfiguration config
tracingDataInQubitsIsNeeded = listenerNeedsTracingData[i] || tracingDataInQubitsIsNeeded;
}

qubitManager = (IQubitManager) new TraceableQubitManager(qubitDataInitializers);
qubitManager = (IQubitManager) new TraceableQubitManager(qubitDataInitializers, configuration.OptimizeDepth);
callStackDepthLimit = Math.Max( 1, configuration.CallStackDepthLimit );
}

Expand Down
78 changes: 78 additions & 0 deletions src/Simulation/QCTraceSimulator/QubitAvailabilityTimeTracker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime
{
/// <summary>
/// Tracks time when qubits were last used and therefore, time when qubits become available.
/// Tracking is done by qubit id, which survives during reuse of qubit.
/// </summary>
internal class QubitAvailabilityTimeTracker
DmitryVasilevsky marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// Availability time of all qubits starts at 0.
/// </summary>
private double DefaultAvailabilityTime = 0.0;

/// <summary>
/// This tracks time when a qubit was last used, indexed by qubit id.
/// </summary>
private List<double> QubitAvailableAt;
DmitryVasilevsky marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Maximum qubit id seen so far.
/// </summary>
private long MaxQubitId = -1;

internal QubitAvailabilityTimeTracker(int initialCapacity, double defaultAvailabilityTime)
{
DefaultAvailabilityTime = defaultAvailabilityTime;
QubitAvailableAt = new List<double>(initialCapacity);
DmitryVasilevsky marked this conversation as resolved.
Show resolved Hide resolved
}

internal double this[long qubitId]
{
get
{
if (qubitId < QubitAvailableAt.Count)
{
return QubitAvailableAt[(int)qubitId];
}
return DefaultAvailabilityTime;
}
set
{
if (qubitId == QubitAvailableAt.Count)
{
QubitAvailableAt.Add(value);
return;
}
else if (qubitId >= int.MaxValue)
{
throw new IndexOutOfRangeException("Too many qubits to track.");
}
else if (qubitId > QubitAvailableAt.Count)
{
QubitAvailableAt.AddRange(Enumerable.Repeat(DefaultAvailabilityTime, (int)qubitId - QubitAvailableAt.Count + 1));
}
QubitAvailableAt[(int)qubitId] = value;
}
}

internal long GetMaxQubitId()
{
return MaxQubitId;
}

internal void MarkQubitIdUsed(long qubitId)
DmitryVasilevsky marked this conversation as resolved.
Show resolved Hide resolved
{
MaxQubitId = System.Math.Max(MaxQubitId, qubitId);
}

}

}
24 changes: 7 additions & 17 deletions src/Simulation/QCTraceSimulator/QubitMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,14 @@ namespace Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime
/// </summary>
public class QubitTimeMetrics
DmitryVasilevsky marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// Time when the qubit becomes available
/// </summary>
public double AvailableAt { get; private set; } = 0;
// TODO: Qubit Ids are already available in qubits, but DepthCounter doesn't have access to it
// in OnPrimitiveOperation because it's not part of IQCTraceSimulatorListener interface.
// Consider changing architecture to pass qubits rather than metrics in IQCTraceSimulatorListener.
public long QubitId { get; }

public QubitTimeMetrics()
public QubitTimeMetrics(long qubitId)
{
}

/// <param name="timeAt">Beginning of the execution of the primitive operation on the qubit</param>
/// <param name="duration">Duration of the primitive operation</param>
public void RecordQubitUsage(double timeAt, double duration)
{
if (timeAt < AvailableAt)
{
throw new QubitTimeMetricsException();
}
AvailableAt = timeAt + duration;
QubitId = qubitId;
}
}
}
}
44 changes: 0 additions & 44 deletions src/Simulation/QCTraceSimulator/QubitsMetricsUtils.cs

This file was deleted.

4 changes: 2 additions & 2 deletions src/Simulation/QCTraceSimulator/TraceableQubitManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ class TraceableQubitManager : QubitManagerTrackingScope
/// The qubit manager makes sure that trace data array for qubits
/// is initialized with objects created by qubitTraceDataInitializers callbacks
/// </summary>
public TraceableQubitManager( Func<long,object>[] qubitTraceDataInitializers )
: base(NumQubits, mayExtendCapacity : true, disableBorrowing : false)
public TraceableQubitManager( Func<long,object>[] qubitTraceDataInitializers, bool optimizeDepth )
: base(NumQubits, mayExtendCapacity : true, disableBorrowing : false, encourageReuse: !optimizeDepth)
{
this.qubitTraceDataInitializers = qubitTraceDataInitializers.Clone() as
Func<long, object>[];
Expand Down
2 changes: 1 addition & 1 deletion src/Simulation/QCTraceSimulator/WidthCounter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public WidthCounter( IDoubleStatistic[] statisticsToCollect = null )
AddToCallStack(CallGraphEdge.CallGraphRootHashed, OperationFunctor.Body);
}

public class Metrics
public static class Metrics
{
public const string InputWidth = "InputWidth";
public const string ExtraWidth = "ExtraWidth";
Expand Down
Loading