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

Add more metrics to resource estimator #801

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#nullable enable
adithyabsk marked this conversation as resolved.
Show resolved Hide resolved

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.Quantum.Simulation.Core;
using Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime;
using Microsoft.Quantum.Simulation.Simulators;
using Microsoft.Quantum.Simulation.Simulators.QCTraceSimulators;

namespace Simulator
{
public class ResourcesEstimatorWithAdditionalPrimitiveOperations : ResourcesEstimator
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic in this class should be merged directly with the code in the ResourcesEstimator class.

{
public ResourcesEstimatorWithAdditionalPrimitiveOperations() : this(ResourcesEstimator.RecommendedConfig())
{
}

public ResourcesEstimatorWithAdditionalPrimitiveOperations(QCTraceSimulatorConfiguration config) : base(WithoutPrimitiveOperationsCounter(config))
{
}

private static QCTraceSimulatorConfiguration WithoutPrimitiveOperationsCounter(QCTraceSimulatorConfiguration config)
{
config.UsePrimitiveOperationsCounter = false;
return config;
}

protected virtual IDictionary<string, IEnumerable<Type>>? AdditionalOperations { get; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was intentionally made a property to be generic for arbitrary additional operations. I think it's okay to hard code the additional operators and not providing this property when merging it with the ResourcesEstimator class.


protected override void InitializeQCTracerCoreListeners(IList<IQCTraceSimulatorListener> listeners)
{
base.InitializeQCTracerCoreListeners(listeners);

// add custom primitive operations listener
var primitiveOperationsIdToNames = new Dictionary<int, string>();
Utils.FillDictionaryForEnumNames<PrimitiveOperationsGroups, int>(primitiveOperationsIdToNames);

var operationNameToId = new Dictionary<string, int>();

if (AdditionalOperations != null)
{
foreach (var name in AdditionalOperations.Keys)
{
var id = primitiveOperationsIdToNames.Count;
operationNameToId[name] = id;
primitiveOperationsIdToNames.Add(id, name);
}
}

var cfg = new PrimitiveOperationsCounterConfiguration { primitiveOperationsNames = primitiveOperationsIdToNames.Values.ToArray() };
var operationsCounter = new PrimitiveOperationsCounter(cfg);
tCoreConfig.Listeners.Add(operationsCounter);

if (AdditionalOperations != null)
{
var compare = new AssignableTypeComparer();
this.OnOperationStart += (callable, data) => {
var unwrapped = callable.UnwrapCallable();
foreach (var (name, types) in AdditionalOperations)
{
if (types.Contains(unwrapped.GetType(), compare))
{
var adjName = $"Adjoint{name}";

var key = (callable.Variant == OperationFunctor.Adjoint || callable.Variant == OperationFunctor.ControlledAdjoint) && AdditionalOperations.ContainsKey(adjName)
? adjName
: name;

operationsCounter.OnPrimitiveOperation(operationNameToId[key], new object[] { }, 0.0);
break;
}
}
};
}
}

private class AssignableTypeComparer : IEqualityComparer<Type>
{
public bool Equals([AllowNull] Type x, [AllowNull] Type y)
{
return x != null && x.IsAssignableFrom(y);
}

public int GetHashCode([DisallowNull] Type obj)
{
return obj.GetHashCode();
}
}
}
}
62 changes: 62 additions & 0 deletions src/Simulation/Simulators/ResourcesEstimator/RuntimeCounter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#nullable enable
adithyabsk marked this conversation as resolved.
Show resolved Hide resolved

using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Quantum.Simulation.Core;
using Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime;

namespace Simulator
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The namespace should be Microsoft.Quantum.Simulation.Simulators

{
public class RuntimeCounter : IQCTraceSimulatorListener, ICallGraphStatistics
{
public RuntimeCounter()
{
AddToCallStack(CallGraphEdge.CallGraphRootHashed, OperationFunctor.Body);
stats = new StatisticsCollector<CallGraphEdge>(
new [] { "Runtime" },
StatisticsCollector<CallGraphEdge>.DefaultStatistics()
);
}

public bool NeedsTracingDataInQubits => false;

public object? NewTracingData(long qubitId) => null;

public void OnAllocate(object[] qubitsTraceData) {}

public void OnRelease(object[] qubitsTraceData) {}

public void OnBorrow(object[] qubitsTraceData, long newQubitsAllocated) {}

public void OnReturn(object[] qubitsTraceData, long qubitReleased) {}

public void OnOperationStart(HashedString name, OperationFunctor variant, object[] qubitsTraceData)
{
AddToCallStack(name, variant);
operationCallStack.Peek().Watch.Start();
}

public void OnOperationEnd(object[] returnedQubitsTraceData)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no documentation in this file. At least the non-empty methods should contain some information on what they are implementing.

{
var record = operationCallStack.Pop();
record.Watch.Stop();
Debug.Assert(operationCallStack.Count != 0, "Operation call stack must never get empty");
stats.AddSample(new CallGraphEdge(record.OperationName, operationCallStack.Peek().OperationName, record.FunctorSpecialization, operationCallStack.Peek().FunctorSpecialization), new [] { (double)record.Watch.ElapsedMilliseconds });
}

public void OnPrimitiveOperation(int id, object[] qubitsTraceData, double primitiveOperationDuration) {}

public IStatisticCollectorResults<CallGraphEdge> Results { get => stats as IStatisticCollectorResults<CallGraphEdge>; }

private record OperationCallRecord(HashedString OperationName, OperationFunctor FunctorSpecialization)
{
public Stopwatch Watch { get; } = new();
}

private readonly Stack<OperationCallRecord> operationCallStack = new Stack<OperationCallRecord>();
private readonly StatisticsCollector<CallGraphEdge> stats;

private void AddToCallStack(HashedString operationName, OperationFunctor functorSpecialization) =>
operationCallStack.Push(new OperationCallRecord(operationName, functorSpecialization));
}
}
87 changes: 87 additions & 0 deletions src/Simulation/Simulators/ResourcesEstimator/Simulator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Data;
using Microsoft.Quantum.Simulation.Simulators;
using Microsoft.Quantum.Simulation.Core;
using Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime;
using Microsoft.Quantum.Simulation.Simulators.QCTraceSimulators;

// using System.IO;
// using System.Threading.Tasks;

namespace Simulator
{
public class AdvancedSimulator : ResourcesEstimatorWithAdditionalPrimitiveOperations
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class and its logic should be merged with the existing ResourcesEstimator class.

{
// public override Task<O> Run<T, I, O>(I args)
// {
// var result = base.Run<T, I, O>(args).Result;
// var name = typeof(T).Name;
// File.WriteAllText($"{name}.txt", ToTSV());
// return Task.Run(() => result);
// }
Comment on lines +16 to +22
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// public override Task<O> Run<T, I, O>(I args)
// {
// var result = base.Run<T, I, O>(args).Result;
// var name = typeof(T).Name;
// File.WriteAllText($"{name}.txt", ToTSV());
// return Task.Run(() => result);
// }


protected override IDictionary<string, IEnumerable<Type>> AdditionalOperations { get; } =
new Dictionary<string, IEnumerable<Type>> {
["CCZ"] = new [] { typeof(Microsoft.Quantum.Simulation.Simulators.QCTraceSimulators.Circuits.CCZ) },
["And"] = new [] { typeof(Microsoft.Quantum.Canon.ApplyAnd), typeof(Microsoft.Quantum.Canon.ApplyLowDepthAnd) },
["AdjointAnd"] = Array.Empty<Type>()
};

protected override void InitializeQCTracerCoreListeners(IList<IQCTraceSimulatorListener> listeners)
{
base.InitializeQCTracerCoreListeners(listeners);
tCoreConfig.Listeners.Add(new RuntimeCounter());
}

// CCNOT(a, b, c);
// T(a);
// T(b);

// Original QDK ResEst. -> 9 Ts
// New QDK ResEst. -> 1 CCZ, 2 Ts
Comment on lines +37 to +42
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment should contain some context of what it is trying to express. Also referring to Original and New estimator may be misleading.


public override DataTable Data
{
get
{
var data = base.Data;

var androw = data.Rows.Find("And");
var adjandrow = data.Rows.Find("AdjointAnd");
var cczrow = data.Rows.Find("CCZ");
var trow = data.Rows.Find("T");

// Update T count
trow["Sum"] = (double)trow["Sum"] - 4 * (double)androw["Sum"] - 7 * (double)cczrow["Sum"];
trow["Max"] = (double)trow["Max"] - 4 * (double)androw["Max"] - 7 * (double)cczrow["Max"];

// TODO: update CNOT, QubitClifford, and Measure as well
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One should measure the cost of the other metrics in ApplyAnd, CCZ and Adjoint ApplyAnd to correctly adjust the cost metrics.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Best approach is to run a simple Q# project, e.g.,

namespace ResEst {
  operation Count() : Unit {
    use (a, b, c) = (Qubit(), Qubit(), Qubit());
    Microsoft.Quantum.Canon.ApplyAnd(a, b, c);
  }
}

and run this against the current resources estimator to retrieve the numbers to subtract from the other metrics.


return data;
}
}

#region Direct access to counts
public long CNOT => (long)(double)Data!.Rows!.Find("CNOT")![1];
public long QubitClifford => (long)(double)Data!.Rows!.Find("QubitClifford")![1];
public long T => (long)(double)Data!.Rows!.Find("T")![1];
public long Measure => (long)(double)Data!.Rows!.Find("Measure")![1];
public long QubitCount => (long)(double)Data!.Rows!.Find("QubitCount")![1];
public long Depth => (long)(double)Data!.Rows!.Find("Depth")![1];
public long CCZ => (long)(double)Data!.Rows!.Find("CCZ")![1];
public long And => (long)(double)Data!.Rows!.Find("And")![1];
public long AdjointAnd => (long)(double)Data!.Rows!.Find("AdjointAnd")![1];
#endregion
Comment on lines +65 to +75
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to remove this part and possibly add it as another pull request that targets on making such properties available.


public override O Execute<T, I, O>(I args)
{
var result = base.Execute<T, I, O>(args);
Console.WriteLine("");
Console.WriteLine("---BEGIN TABLE---");
Console.WriteLine(ToTSV());
Console.WriteLine("---END TABLE---");
return result;
}
Comment on lines +77 to +85
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public override O Execute<T, I, O>(I args)
{
var result = base.Execute<T, I, O>(args);
Console.WriteLine("");
Console.WriteLine("---BEGIN TABLE---");
Console.WriteLine(ToTSV());
Console.WriteLine("---END TABLE---");
return result;
}

}
}