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

Commit

Permalink
Merge pull request #601 from microsoft/sccarda/implement-qsharp-submi…
Browse files Browse the repository at this point in the history
…tter

Initial Support for microsoft.simulator Targets on Jupyter Notebooks
  • Loading branch information
ScottCarda-MS authored Mar 22, 2022
2 parents 98e26ec + 0e0c655 commit d188446
Show file tree
Hide file tree
Showing 28 changed files with 1,140 additions and 107 deletions.
60 changes: 46 additions & 14 deletions src/AzureClient/AzureClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,27 @@
using Microsoft.Jupyter.Core;
using Microsoft.Quantum.IQSharp.Common;
using Microsoft.Quantum.IQSharp.Jupyter;
using Microsoft.Quantum.Runtime;
using Microsoft.Quantum.Runtime.Submitters;
using Microsoft.Quantum.Simulation.Common;

namespace Microsoft.Quantum.IQSharp.AzureClient
{
/// <inheritdoc/>
public class AzureClient : IAzureClient
{
private const string MicrosoftSimulator = "microsoft.simulator";

/// <summary>
/// Returns whether a target ID is meant for quantum execution since not all targets
/// exposed by providers are meant for that. More specifically, the Microsoft provider exposes
/// targets that are not meant for quantum execution and the only ones meant for that start
/// with "microsoft.simulator"
/// </summary>
private static bool IsQuantumExecutionTarget(string targetId) =>
AzureExecutionTarget.GetProvider(targetId) != AzureProvider.Microsoft
|| targetId.StartsWith(MicrosoftSimulator);

/// <inheritdoc />
public Microsoft.Azure.Quantum.IWorkspace? ActiveWorkspace { get; private set; }
private TokenCredential? Credential { get; set; }
Expand All @@ -39,7 +53,10 @@ public class AzureClient : IAzureClient
private AzureExecutionTarget? ActiveTarget { get; set; }
private string MostRecentJobId { get; set; } = string.Empty;
private IEnumerable<ProviderStatusInfo>? AvailableProviders { get; set; }
private IEnumerable<TargetStatusInfo>? AvailableTargets => AvailableProviders?.SelectMany(provider => provider.Targets);
private IEnumerable<TargetStatusInfo>? AvailableTargets =>
AvailableProviders
?.SelectMany(provider => provider.Targets)
?.Where(t => t.TargetId != null && IsQuantumExecutionTarget(t.TargetId));
private IEnumerable<TargetStatusInfo>? ValidExecutionTargets => AvailableTargets?.Where(AzureExecutionTarget.IsValid);
private string ValidExecutionTargetsDisplayText =>
(ValidExecutionTargets == null || ValidExecutionTargets.Count() == 0)
Expand Down Expand Up @@ -315,16 +332,6 @@ private async Task<ExecutionResult> SubmitOrExecuteJobAsync(
return connectionResult;
}

var machine = AzureFactory.CreateMachine(this.ActiveWorkspace, this.ActiveTarget.TargetId, this.StorageConnectionString);
if (machine == null)
{
// We should never get here, since ActiveTarget should have already been validated at the time it was set.
channel?.Stderr($"Unexpected error while preparing job for execution on target {ActiveTarget.TargetId}.");
return AzureClientError.InvalidTarget.ToExecutionResult();
}

channel?.Stdout($"Submitting {submissionContext.OperationName} to target {ActiveTarget.TargetId}...");

IEntryPoint? entryPoint;
try
{
Expand All @@ -346,11 +353,33 @@ private async Task<ExecutionResult> SubmitOrExecuteJobAsync(
return AzureClientError.InvalidEntryPoint.ToExecutionResult();
}

channel?.Stdout($"Submitting {submissionContext.OperationName} to target {ActiveTarget.TargetId}...");

try
{
// QirSubmitter and CreateMachine have return types with different base types
// but both have a SubmitAsync method that returns an IQuantumMachineJob.
// Thus, we can branch on whether we need a QIR submitter or a translator,
// but can use the same task object to represent both return values.
Task<IQuantumMachineJob>? jobTask = null;
if (SubmitterFactory.QirSubmitter(this.ActiveTarget.TargetId, this.ActiveWorkspace, this.StorageConnectionString) is IQirSubmitter submitter)
{
jobTask = entryPoint.SubmitAsync(submitter, submissionContext);
}
else if (AzureFactory.CreateMachine(this.ActiveWorkspace, this.ActiveTarget.TargetId, this.StorageConnectionString) is IQuantumMachine machine)
{
jobTask = entryPoint.SubmitAsync(machine, submissionContext);
}
else
{
// We should never get here, since ActiveTarget should have already been validated at the time it was set.
channel?.Stderr($"Unexpected error while preparing job for execution on target {ActiveTarget.TargetId}.");
return AzureClientError.InvalidTarget.ToExecutionResult();
}

Logger.LogDebug("About to submit entry point for {OperationName}.", submissionContext.OperationName);
var job = await entryPoint.SubmitAsync(machine, submissionContext);
channel?.Stdout($"Job successfully submitted for {submissionContext.Shots} shots.");
var job = await jobTask;
channel?.Stdout($"Job successfully submitted.");
channel?.Stdout($" Job name: {submissionContext.FriendlyName}");
channel?.Stdout($" Job ID: {job.Id}");
MostRecentJobId = job.Id;
Expand Down Expand Up @@ -543,7 +572,10 @@ public async Task<ExecutionResult> GetJobResultAsync(IChannel? channel, string j
// cancellation token support.
var request = WebRequest.Create(job.OutputDataUri);
using var responseStream = (await request.GetResponseAsync()).GetResponseStream();
return responseStream.ToHistogram(Logger).ToExecutionResult();
return responseStream.ToHistogram(
Logger,
isSimulatorOutput: this.ActiveTarget?.TargetId?.StartsWith(MicrosoftSimulator) ?? false)
.ToExecutionResult();
}
catch (Exception e)
{
Expand Down
5 changes: 4 additions & 1 deletion src/AzureClient/AzureExecutionTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ internal enum AzureProvider
// workspaces and should still be supported.
Honeywell,
QCI,
Microsoft,
Mock
}

Expand All @@ -37,6 +38,7 @@ protected AzureExecutionTarget(string? targetId)
AzureProvider.Quantinuum => "Microsoft.Quantum.Providers.Honeywell",
AzureProvider.Honeywell => "Microsoft.Quantum.Providers.Honeywell",
AzureProvider.QCI => "Microsoft.Quantum.Providers.QCI",
AzureProvider.Microsoft => "Microsoft.Quantum.Providers.Core",
_ => $"Microsoft.Quantum.Providers.{GetProvider(TargetId)}"
};

Expand All @@ -46,6 +48,7 @@ protected AzureExecutionTarget(string? targetId)
AzureProvider.Quantinuum => RuntimeCapability.BasicMeasurementFeedback,
AzureProvider.Honeywell => RuntimeCapability.BasicMeasurementFeedback,
AzureProvider.QCI => RuntimeCapability.BasicMeasurementFeedback,
AzureProvider.Microsoft => RuntimeCapability.FullComputation,
_ => RuntimeCapability.FullComputation
};

Expand Down Expand Up @@ -92,7 +95,7 @@ protected AzureExecutionTarget(string? targetId)
/// Valid target IDs are structured as "provider.target".
/// For example, "ionq.simulator" or "quantinuum.qpu".
/// </remarks>
protected static AzureProvider? GetProvider(string? targetId)
protected internal static AzureProvider? GetProvider(string? targetId)
{
if (targetId == null)
{
Expand Down
111 changes: 106 additions & 5 deletions src/AzureClient/EntryPoint/EntryPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Quantum.IQSharp.Jupyter;
using Microsoft.Quantum.Runtime;
using Microsoft.Quantum.Runtime.Submitters;
using Microsoft.Quantum.Simulation.Core;

namespace Microsoft.Quantum.IQSharp.AzureClient
Expand All @@ -24,6 +26,9 @@ internal class EntryPoint : IEntryPoint
private OperationInfo OperationInfo { get; }
private ILogger? Logger { get; }

/// <inheritdoc/>
public Stream? QirStream { get; }

/// <summary>
/// Creates an object used to submit jobs to Azure Quantum.
/// </summary>
Expand All @@ -34,18 +39,21 @@ internal class EntryPoint : IEntryPoint
/// <param name="outputType">Specifies the output parameter type for the
/// <see cref="EntryPointInfo{I,O}"/> object provided as the <c>entryPointInfo</c> argument.</param>
/// <param name="operationInfo">Information about the Q# operation to be used as the entry point.</param>
/// <param name="qirStream">
/// Stream from which QIR bitcode for the entry point can be read.
/// </param>
/// <param name="logger">Logger used to report internal diagnostics.</param>
public EntryPoint(object entryPointInfo, Type inputType, Type outputType, OperationInfo operationInfo, ILogger? logger)
public EntryPoint(object entryPointInfo, Type inputType, Type outputType, OperationInfo operationInfo, Stream? qirStream, ILogger? logger)
{
EntryPointInfo = entryPointInfo;
InputType = inputType;
OutputType = outputType;
OperationInfo = operationInfo;
Logger = logger;
QirStream = qirStream;
}

/// <inheritdoc/>
public Task<IQuantumMachineJob> SubmitAsync(IQuantumMachine machine, AzureSubmissionContext submissionContext, CancellationToken cancellationToken = default)
private object GetEntryPointInputObject(AzureSubmissionContext submissionContext)
{
var parameterTypes = new List<Type>();
var parameterValues = new List<object>();
Expand All @@ -56,7 +64,7 @@ public Task<IQuantumMachineJob> SubmitAsync(IQuantumMachine machine, AzureSubmis
throw new ArgumentException($"Required parameter {parameter.Name} was not specified.");
}

string rawParameterValue = submissionContext.InputParameters[parameter.Name];
var rawParameterValue = submissionContext.InputParameters[parameter.Name];
try
{
var parameterValue = submissionContext.InputParameters.DecodeParameter(parameter.Name, type: parameter.ParameterType);
Expand All @@ -73,12 +81,78 @@ public Task<IQuantumMachineJob> SubmitAsync(IQuantumMachine machine, AzureSubmis
}
}

var entryPointInput = parameterValues.Count switch
return parameterValues.Count switch
{
0 => QVoid.Instance,
1 => parameterValues.Single(),
_ => InputType.GetConstructor(parameterTypes.ToArray()).Invoke(parameterValues.ToArray())
};
}

private ArgumentValue GetArgumentValue(string parameterValue, System.Reflection.ParameterInfo parameter)
{
var parameterType = parameter.ParameterType;

if (parameterType == typeof(bool))
{
return new ArgumentValue.Bool(Newtonsoft.Json.JsonConvert.DeserializeObject<bool>(parameterValue));
}
else if (parameterType == typeof(double))
{
return new ArgumentValue.Double(Newtonsoft.Json.JsonConvert.DeserializeObject<double>(parameterValue));
}
else if (parameterType == typeof(long))
{
return new ArgumentValue.Int(Newtonsoft.Json.JsonConvert.DeserializeObject<long>(parameterValue));
}
else if (parameterType == typeof(string))
{
return new ArgumentValue.String(parameterValue);
}
else if (parameterType == typeof(Pauli))
{
return new ArgumentValue.Pauli(Newtonsoft.Json.JsonConvert.DeserializeObject<Pauli>(parameterValue));
}
else if (parameterType == typeof(Result))
{
return new ArgumentValue.Result(Newtonsoft.Json.JsonConvert.DeserializeObject<Result>(parameterValue)!);
}
else
{
throw new ArgumentException($"The given type of {parameterType.Name} is not supported."); ;
}
}

private IReadOnlyList<Argument> GetEntryPointInputArguments(AzureSubmissionContext submissionContext)
{
var argumentList = new List<Argument>();
foreach (var parameter in OperationInfo.RoslynParameters)
{
if (!submissionContext.InputParameters.ContainsKey(parameter.Name))
{
throw new ArgumentException($"Required parameter {parameter.Name} was not specified.");
}

string rawParameterValue = submissionContext.InputParameters[parameter.Name];

try
{
var argument = new Argument(parameter.Name, GetArgumentValue(rawParameterValue, parameter));
argumentList.Add(argument);
}
catch (Exception e)
{
throw new ArgumentException($"The value {rawParameterValue} provided for parameter {parameter.Name} could not be converted to the expected type: {e.Message}");
}
}

return argumentList;
}

/// <inheritdoc/>
public Task<IQuantumMachineJob> SubmitAsync(IQuantumMachine machine, AzureSubmissionContext submissionContext, CancellationToken cancellationToken = default)
{
var entryPointInput = GetEntryPointInputObject(submissionContext);

try
{
Expand All @@ -104,5 +178,32 @@ public Task<IQuantumMachineJob> SubmitAsync(IQuantumMachine machine, AzureSubmis
var submitParameters = new object[] { EntryPointInfo, entryPointInput, submissionContext };
return (Task<IQuantumMachineJob>)submitMethod.Invoke(machine, submitParameters);
}

/// <inheritdoc/>
public Task<IQuantumMachineJob> SubmitAsync(IQirSubmitter submitter, AzureSubmissionContext submissionContext, CancellationToken cancellationToken = default)
{
var entryPointInput = GetEntryPointInputArguments(submissionContext);

var options = SubmissionOptions.Default;
options = options.With(submissionContext.FriendlyName, submissionContext.Shots, submissionContext.InputParams);

// The namespace must match the one found in the in CompilerService.cs in the Core project.
var entryPointNamespaceName = "ENTRYPOINT";

// Find and invoke the method on IQirSubmitter that is declared as:
// Task<IQuantumMachineJob> SubmitAsync(Stream qir, string entryPoint, IReadOnlyList<Argument> arguments, SubmissionOptions submissionOptions)
var submitMethod = typeof(IQirSubmitter)
.GetMethods()
.Single(method =>
method.Name == "SubmitAsync"
&& method.GetParameters().Length == 4
&& method.GetParameters()[0].ParameterType == typeof(Stream)
&& method.GetParameters()[1].ParameterType == typeof(string)
&& method.GetParameters()[2].ParameterType == typeof(IReadOnlyList<Argument>)
&& method.GetParameters()[3].ParameterType == typeof(SubmissionOptions)
);
var submitParameters = new object[] { QirStream!, $"{entryPointNamespaceName}__{submissionContext.OperationName}", entryPointInput, options };
return (Task<IQuantumMachineJob>)submitMethod.Invoke(submitter, submitParameters);
}
}
}
8 changes: 5 additions & 3 deletions src/AzureClient/EntryPoint/EntryPointGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ public EntryPointGenerator(
return null;
}

/// <inheritdoc/>
public IEntryPoint Generate(string operationName, string? executionTarget,
RuntimeCapability? runtimeCapability = null)
RuntimeCapability? runtimeCapability = null, bool generateQir = false)
{
Logger?.LogDebug($"Generating entry point: operationName={operationName}, executionTarget={executionTarget}");

Expand Down Expand Up @@ -152,7 +153,8 @@ public IEntryPoint Generate(string operationName, string? executionTarget,
}

EntryPointAssemblyInfo = Compiler.BuildEntryPoint(
operationInfo, compilerMetadata, logger, Path.Combine(Workspace.CacheFolder, "__entrypoint__.dll"), executionTarget, runtimeCapability);
operationInfo, compilerMetadata, logger, Path.Combine(Workspace.CacheFolder, "__entrypoint__.dll"), executionTarget, runtimeCapability,
generateQir: generateQir);
if (EntryPointAssemblyInfo == null || logger.HasErrors)
{
Logger?.LogError($"Error compiling entry point for operation {operationName}.");
Expand Down Expand Up @@ -206,7 +208,7 @@ public IEntryPoint Generate(string operationName, string? executionTarget,
.Invoke(new object[] { entryPointOperationInfo.RoslynType });

return new EntryPoint(
entryPointInfo, entryPointInputType, entryPointOutputType, entryPointOperationInfo,
entryPointInfo, entryPointInputType, entryPointOutputType, entryPointOperationInfo, EntryPointAssemblyInfo.QirBitcode,
logger: ServiceProvider.GetService<ILogger<EntryPoint>>()
);
}
Expand Down
19 changes: 16 additions & 3 deletions src/AzureClient/EntryPoint/IEntryPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@

#nullable enable

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Quantum.Runtime;
using Microsoft.Quantum.Runtime.Submitters;

namespace Microsoft.Quantum.IQSharp.AzureClient
{
Expand All @@ -26,5 +25,19 @@ public interface IEntryPoint
/// <param name="cancellationToken">Cancellation token used to interrupt this submission.</param>
/// <returns>The details of the submitted job.</returns>
public Task<IQuantumMachineJob> SubmitAsync(IQuantumMachine machine, AzureSubmissionContext submissionContext, CancellationToken cancellationToken = default);

/// <summary>
/// Submits the entry point for execution to Azure Quantum.
/// </summary>
/// <param name="submitter">The <see cref="IQirSubmitter"/> object representing the job submission target.</param>
/// <param name="submissionContext">The <see cref="AzureSubmissionContext"/> object representing the submission context for the job.</param>
/// /// <param name="cancellationToken">Cancellation token used to interrupt this submission.</param>
/// <returns>The details of the submitted job.</returns>
public Task<IQuantumMachineJob> SubmitAsync(IQirSubmitter submitter, AzureSubmissionContext submissionContext, CancellationToken cancellationToken = default);

/// <summary>
/// The stream from which QIR bitcode for the entry point can be read.
/// </summary>
public Stream? QirStream { get; }
}
}
3 changes: 2 additions & 1 deletion src/AzureClient/EntryPoint/IEntryPointGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ public interface IEntryPointGenerator
/// <param name="operationName">The name of the operation to wrap in an entry point.</param>
/// <param name="executionTarget">The intended execution target for the compiled entry point.</param>
/// <param name="runtimeCapabilities">The runtime capabilities of the intended execution target.</param>
/// <param name="generateQir">When <c>true</c>, uses QIR to generate the entry point.</param>
/// <returns>The generated entry point.</returns>
public IEntryPoint Generate(string operationName, string? executionTarget,
RuntimeCapability? runtimeCapabilities = null);
RuntimeCapability? runtimeCapabilities = null, bool generateQir = false);
}
}
Loading

0 comments on commit d188446

Please sign in to comment.