Merge pull request #783 from microsoft/xfield/qir

Add support for passing Target Capabilities in QIR generation and job submission
This commit is contained in:
XField 2023-03-17 09:07:03 -07:00 коммит произвёл GitHub
Родитель c4d1f460c9 0c2d7ec931
Коммит 3d8f999d67
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 482 добавлений и 89 удалений

Просмотреть файл

@ -56,7 +56,12 @@ public record AzureExecutionTarget
/// Any other target capability must be subsumed by this maximum
/// in order to be supported by this target.
/// </summary>
public TargetCapability DefaultCapability => GetProvider(TargetId) switch
public TargetCapability DefaultCapability => GetMaximumCapability(TargetId);
/// <summary>
/// Returns the maximum level of capability supported by the target.
/// </summary>
public static TargetCapability GetMaximumCapability(string? targetId) => GetProvider(targetId) switch
{
AzureProvider.IonQ => TargetCapabilityModule.BasicQuantumFunctionality,
AzureProvider.Quantinuum => TargetCapabilityModule.BasicMeasurementFeedback,

Просмотреть файл

@ -4,14 +4,26 @@
#nullable enable
using System.IO;
using System.Reflection.PortableExecutable;
using System.Threading;
using Microsoft.Extensions.Logging;
using Microsoft.Quantum.QsCompiler;
using Microsoft.Quantum.Runtime;
using Microsoft.Quantum.Runtime.Submitters;
using Microsoft.Quantum.Simulation.Core;
namespace Microsoft.Quantum.IQSharp.AzureClient
{
public record EntryPointSubmitEventData
{
public string? MachineName { get; set; }
public string? Target { get; set; }
public string? TargetCapability { get; set; }
public string? JobId { get; set; }
}
public record EntryPointSubmitEvent : Event<EntryPointSubmitEventData>;
/// <inheritdoc/>
internal class EntryPoint : IEntryPoint
{
@ -23,9 +35,12 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
private Type OutputType { get; }
private OperationInfo OperationInfo { get; }
private ILogger? Logger { get; }
private IEventService? EventService { get; }
/// <inheritdoc/>
public Stream? QirStream { get; }
/// <inheritdoc/>
public TargetCapability? TargetCapability { get; }
/// <summary>
/// Creates an object used to submit jobs to Azure Quantum.
@ -38,10 +53,21 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
/// <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="logger">Logger used to report internal diagnostics.</param>
/// <param name="eventService">EventService used to report telemetry.</param>
/// <param name="qirStream">
/// Stream from which QIR bitcode for the entry point can be read.
/// </param>
public EntryPoint(object entryPointInfo, Type inputType, Type outputType, OperationInfo operationInfo, ILogger? logger, Stream? qirStream = null)
/// <param name="targetCapability">
/// The target capability which the QIR was generated for.
/// </param>
public EntryPoint(object entryPointInfo,
Type inputType,
Type outputType,
OperationInfo operationInfo,
ILogger? logger,
IEventService? eventService,
Stream? qirStream = null,
TargetCapability? targetCapability = null)
{
EntryPointInfo = entryPointInfo;
InputType = inputType;
@ -49,6 +75,8 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
OperationInfo = operationInfo;
Logger = logger;
QirStream = qirStream;
TargetCapability = targetCapability;
EventService = eventService;
}
private object GetEntryPointInputObject(AzureSubmissionContext submissionContext)
@ -235,7 +263,19 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
&& method.GetParameters()[2].ParameterType == typeof(IQuantumMachineSubmissionContext))
.MakeGenericMethod(new Type[] { InputType, OutputType });
var submitParameters = new object[] { EntryPointInfo, entryPointInput, submissionContext };
return (Task<IQuantumMachineJob>)submitMethod.Invoke(machine, submitParameters);
return ((Task<IQuantumMachineJob>)submitMethod.Invoke(machine, submitParameters))
.ContinueWith<IQuantumMachineJob>(jobTask =>
{
var job = jobTask.Result;
EventService?.Trigger<EntryPointSubmitEvent, EntryPointSubmitEventData>(new EntryPointSubmitEventData()
{
MachineName = machine.GetType().FullName,
JobId = job.Id,
Target = machine.Target
});
return job;
});
}
/// <inheritdoc/>
@ -252,11 +292,27 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
var entryPointInput = GetEntryPointInputArguments(submissionContext, allowOptionalParameters);
Logger?.LogInformation("Submitting job {FriendlyName} with {NShots} shots.", submissionContext.FriendlyName, submissionContext.Shots);
var options = SubmissionOptions.Default.With(submissionContext.FriendlyName, submissionContext.Shots, submissionContext.InputParams);
var options = SubmissionOptions.Default.With(
friendlyName: submissionContext.FriendlyName,
shots: submissionContext.Shots,
inputParams: submissionContext.InputParams,
targetCapability: TargetCapability?.Name);
// Find and invoke the method on IQirSubmitter that is declared as:
// Task<IQuantumMachineJob> SubmitAsync(Stream qir, string entryPoint, IReadOnlyList<Argument> arguments, SubmissionOptions submissionOptions)
return submitter.SubmitAsync(QirStream, $"{EntryPointNamespaceName}__{submissionContext.OperationName}", entryPointInput, options);
return submitter.SubmitAsync(QirStream, $"{EntryPointNamespaceName}__{submissionContext.OperationName}", entryPointInput, options)
.ContinueWith<IQuantumMachineJob>(jobTask =>
{
var job = jobTask.Result;
EventService?.Trigger<EntryPointSubmitEvent, EntryPointSubmitEventData>(new EntryPointSubmitEventData()
{
MachineName = submitter.GetType().FullName,
JobId = job.Id,
Target = submitter.Target,
TargetCapability = this.TargetCapability?.Name,
});
return job;
});
}
}
}

Просмотреть файл

@ -23,6 +23,7 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
private IWorkspace Workspace { get; }
private ISnippets Snippets { get; }
private IServiceProvider ServiceProvider { get; }
private IEventService EventService { get; }
public IReferences References { get; }
public AssemblyInfo[] WorkspaceAssemblies { get; set; } = Array.Empty<AssemblyInfo>();
public AssemblyInfo? SnippetsAssemblyInfo { get; set; }
@ -43,10 +44,11 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
References = references;
Logger = logger;
ServiceProvider = serviceProvider;
EventService = eventService;
AssemblyLoadContext.Default.Resolving += Resolve;
eventService?.TriggerServiceInitialized<IEntryPointGenerator>(this);
EventService?.TriggerServiceInitialized<IEntryPointGenerator>(this);
}
/// <summary>
@ -213,8 +215,14 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
.Invoke(new object[] { entryPointOperationInfo.RoslynType });
return new EntryPoint(
entryPointInfo, entryPointInputType, entryPointOutputType, entryPointOperationInfo,
logger: ServiceProvider.GetService<ILogger<EntryPoint>>(), EntryPointAssemblyInfo.QirBitcode
entryPointInfo: entryPointInfo,
inputType: entryPointInputType,
outputType: entryPointOutputType,
operationInfo: entryPointOperationInfo,
logger: ServiceProvider.GetService<ILogger<EntryPoint>>(),
eventService: EventService,
qirStream: EntryPointAssemblyInfo.QirBitcode,
targetCapability: capability
);
}
}

Просмотреть файл

@ -5,7 +5,7 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Quantum.QsCompiler;
using Microsoft.Quantum.Runtime;
using Microsoft.Quantum.Runtime.Submitters;
@ -39,5 +39,10 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
/// The stream from which QIR bitcode for the entry point can be read.
/// </summary>
public Stream? QirStream { get; }
}
/// <summary>
/// The target capability which the QIR was generated for.
/// </summary>
public TargetCapability? TargetCapability { get; }
}
}

Просмотреть файл

@ -3,13 +3,10 @@
#nullable enable
using System;
using System.Threading.Tasks;
using Microsoft.Quantum.QsCompiler;
using Microsoft.Quantum.Runtime;
using Microsoft.Quantum.Runtime.Submitters;
using System.IO;
using System.Collections.Generic;
using System.Linq;
namespace System.Runtime.CompilerServices
{
@ -19,9 +16,10 @@ namespace System.Runtime.CompilerServices
namespace Microsoft.Quantum.IQSharp.AzureClient
{
/// <param name="ExpectedArguments">The expected entry point arguments to the SubmitAsync method.</param>
internal record MockQirSubmitter(IReadOnlyList<Argument> ExpectedArguments) : IQirSubmitter
/// <param name="ExpectedTargetCapability">The expected target capability to the SubmitAsync method.</param>
internal record MockQirSubmitter(IReadOnlyList<Argument> ExpectedArguments, TargetCapability? ExpectedTargetCapability = null) : IQirSubmitter
{
public string Target => throw new NotImplementedException();
public string Target => "MockQirSubmitter";
private bool IsArgumentValueEqual(ArgumentValue fst, ArgumentValue snd) =>
(fst, snd) switch
@ -73,6 +71,12 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
throw new ArgumentException("The arguments passed to the SubmitAsync did not match the expected arguments to the Mock QIR submitter.");
}
if (ExpectedTargetCapability != null
&& !ExpectedTargetCapability.Name.Equals(options.TargetCapability))
{
throw new ArgumentException($"The options.TargetCapability passed to the SubmitAsync ({options.TargetCapability}) did not match the ExpectedTargetCapability ({ExpectedTargetCapability.Name}) to the Mock QIR submitter.");
}
return Task.FromResult(job as IQuantumMachineJob);
}

Просмотреть файл

@ -3,10 +3,6 @@
#nullable enable
using System;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Quantum.Runtime;
using Microsoft.Quantum.Simulation.Core;
@ -14,9 +10,9 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
{
internal class MockQuantumMachine : IQuantumMachine
{
public string ProviderId => throw new NotImplementedException();
public string ProviderId => "MockQuantumMachine";
public string Target => throw new NotImplementedException();
public string Target => "MockQuantumMachine.Target";
private MockAzureWorkspace? Workspace { get; }

Просмотреть файл

@ -3,14 +3,18 @@
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using LlvmBindings;
using LlvmBindings.Interop;
using Microsoft.Extensions.Logging;
using Microsoft.Quantum.IQSharp.AzureClient;
using Microsoft.Quantum.IQSharp.Common;
using Microsoft.Quantum.QsCompiler;
namespace Microsoft.Quantum.IQSharp.Kernel;
@ -37,6 +41,40 @@ public record LlvmIr(
}
}
public enum QirOutputFormat
{
IR,
Bitcode,
BitcodeBase64,
}
public record QirMagicEventData()
{
static Regex CapabilityInsuficientRegex = new Regex("(requires runtime capabilities).*(not supported by the target)",
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase);
public bool OutputToFile { get; set; }
public string? Target { get; set; }
public string? Capability { get; set; }
public bool InvalidCapability { get; set; }
public bool CapabilityInsufficient { get; set; }
public string? CapabilityName { get; set; }
public long? QirStreamSize { get; set; }
public QirOutputFormat? OutputFormat { get; set; }
public string? Error { get; set; }
public void SetError(string message) => this.Error = message;
public void SetError(Exception exception)
{
this.Error = $"{exception.GetType().FullName}";
var exceptionString = exception.ToString();
this.CapabilityInsufficient = CapabilityInsuficientRegex.IsMatch(exceptionString);
}
}
public record QirMagicEvent : Event<QirMagicEventData>;
/// <summary>
/// A magic command that can be used to generate QIR from a given
/// operation as an entry point.
@ -44,6 +82,10 @@ public record LlvmIr(
public class QirMagic : AbstractMagic
{
private const string ParameterNameOperationName = "__operationName__";
private const string ParameterNameTarget = "target";
private const string ParameterNameTargetCapability = "target_capability";
private const string ParameterNameOutputFile = "output_file";
private const string ParameterNameOutputFormat = "output_format";
/// <summary>
/// Constructs the magic command from DI services.
@ -56,21 +98,22 @@ public class QirMagic : AbstractMagic
ISymbolResolver symbolResolver,
IConfigurationSource configurationSource,
IMetadataController metadataController,
IEventService eventService,
ISnippets snippets
) : base(
"qir",
new Microsoft.Jupyter.Core.Documentation
{
Summary = "Compiles a given Q# entry point to QIR, saving the resulting QIR to a given file.",
Description = @"
Description = $@"
This command takes the full name of a Q# entry point, and compiles the Q# from that entry point
into QIR. The resulting program is then executed, and the output of the program is displayed.
#### Required parameters
- Q# operation or function name. This must be the first parameter, and must be a valid Q# operation
- `{ParameterNameOperationName}=<string>`: Q# operation or function name.
This must be the first parameter, and must be a valid Q# operation
or function name that has been defined either in the notebook or in a Q# file in the same folder.
- The file path for where to save the output QIR to, specified as `output=<file path>`.
".Dedent(),
Examples = new []
{
@ -91,6 +134,7 @@ public class QirMagic : AbstractMagic
this.ConfigurationSource = configurationSource;
this.MetadataController = metadataController;
this.Snippets = snippets;
this.EventService = eventService;
}
private IAzureClient AzureClient { get; }
@ -98,6 +142,7 @@ public class QirMagic : AbstractMagic
private ISymbolResolver SymbolResolver { get; }
private IMetadataController MetadataController { get; }
private ISnippets Snippets { get; }
private IEventService EventService { get; }
private ILogger? Logger { get; }
@ -146,70 +191,134 @@ public class QirMagic : AbstractMagic
}
/// <summary>
/// Simulates an operation given a string with its name and a JSON
/// encoding of its arguments.
/// Runs the QIR magic.
/// In addition to the public parameters documented in the %qir magic command,
/// it also supports some internal parameters used by the `QSharpCallable._repr_qir_`
/// method from the `qsharp` Python Package.
/// #### Optional parameters
/// - `target:string`: The intended execution target for the compiled entrypoint.
/// Defaults to the active Azure Quantum target (which can be set with `%azure.target`).
/// Otherwise, defaults to a generic target, which may not work when running on a specific target.
/// - `target_capability:string`: The capability of the intended execution target.
/// If `target` is specified or there is an active Azure Quantum target,
/// defaults to the target's maximum capability.
/// Otherwise, defaults to `FullComputation`, which may not be supported when running on a specific target.
/// - `output_file:string`: The file path for where to save the output QIR.
/// If empty, a uniquely-named temporary file will be created.
/// - `output_format:QirOutputFormat`: The QIR output format.
/// Defaults to `IR`.
/// Possible options are:
/// * `IR`: Human-readable Intermediate Representation in plain-text
/// * `Bitcode`: LLVM bitcode (only when writing to a output file)
/// * `BitcodeBase64`: LLVM bitcode encoded as Base64
/// </summary>
public async Task<ExecutionResult> RunAsync(string input, IChannel channel)
{
var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameOperationName);
var name = inputParameters.DecodeParameter<string>(ParameterNameOperationName);
if (name == null) throw new InvalidOperationException($"No operation name provided.");
var symbol = SymbolResolver.Resolve(name) as IQSharpSymbol;
if (symbol == null)
{
new CommonMessages.NoSuchOperation(name).Report(channel, ConfigurationSource);
return ExecuteStatus.Error.ToExecutionResult();
}
IEntryPoint entryPoint;
QirMagicEventData qirMagicArgs = new();
try
{
var capability = this.AzureClient.TargetCapability;
var target = this.AzureClient.ActiveTarget?.TargetId;
entryPoint = await EntryPointGenerator.Generate(name, target, capability, generateQir: true);
}
catch (TaskCanceledException tce)
{
throw tce;
}
catch (CompilationErrorsException e)
{
var msg = $"The Q# operation {name} could not be compiled as an entry point for job execution.";
this.Logger?.LogError(e, msg);
channel.Stderr(msg);
channel.Stderr(e.Message);
var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameOperationName);
if (MetadataController.IsPythonUserAgent() || ConfigurationSource.CompilationErrorStyle == CompilationErrorStyle.Basic)
var name = inputParameters.DecodeParameter<string>(ParameterNameOperationName);
if (name == null) throw new InvalidOperationException($"No operation name provided.");
var symbol = SymbolResolver.Resolve(name) as IQSharpSymbol;
if (symbol == null)
{
foreach (var m in e.Errors) channel.Stderr(m);
new CommonMessages.NoSuchOperation(name).Report(channel, ConfigurationSource);
return ExecuteStatus.Error.ToExecutionResult();
}
else
var outputFilePath = inputParameters.DecodeParameter<string>(ParameterNameOutputFile);
qirMagicArgs.OutputToFile = !string.IsNullOrEmpty(outputFilePath);
var outputFormat = inputParameters.DecodeParameter<QirOutputFormat>(ParameterNameOutputFormat,
QirOutputFormat.IR);
qirMagicArgs.OutputFormat = outputFormat;
var target = inputParameters.DecodeParameter<string>(ParameterNameTarget,
this.AzureClient.ActiveTarget?.TargetId);
qirMagicArgs.Target = target;
var capabilityName = inputParameters.DecodeParameter<string>(ParameterNameTargetCapability);
qirMagicArgs.CapabilityName = capabilityName;
TargetCapability? capability = null;
if (!string.IsNullOrEmpty(capabilityName))
{
channel.DisplayFancyDiagnostics(e.Diagnostics, Snippets, input);
var capabilityFromName = TargetCapabilityModule.FromName(capabilityName);
if (FSharp.Core.OptionModule.IsNone(capabilityFromName))
{
qirMagicArgs.InvalidCapability = true;
return $"The capability {capabilityName} is not a valid target capability."
.ToExecutionResult(ExecuteStatus.Error);
}
capability = capabilityFromName.Value;
}
return AzureClientError.InvalidEntryPoint.ToExecutionResult();
}
else if (!string.IsNullOrEmpty(target))
{
capability = AzureExecutionTarget.GetMaximumCapability(target);
}
qirMagicArgs.Capability = capability?.ToString();
if (entryPoint is null)
IEntryPoint entryPoint;
try
{
entryPoint = await EntryPointGenerator.Generate(name, target, capability, generateQir: true);
}
catch (CompilationErrorsException exception)
{
qirMagicArgs.SetError(exception);
return ReturnCompilationError(input, channel, name, exception);
}
if (entryPoint is null)
{
var message = "Internal error: generated entry point was null, but no compilation errors were returned.";
qirMagicArgs.SetError(message);
return message.ToExecutionResult(ExecuteStatus.Error);
}
if (entryPoint.QirStream is null)
{
var message = "Internal error: generated entry point does not contain a QIR bitcode stream, but no compilation errors were returned.";
return message.ToExecutionResult(ExecuteStatus.Error);
}
Stream qriStream = entryPoint.QirStream;
qirMagicArgs.QirStreamSize = qriStream?.Length;
return outputFormat switch
{
QirOutputFormat.Bitcode =>
ReturnQirBitcode(outputFilePath, qriStream),
QirOutputFormat.BitcodeBase64 =>
ReturnQirBitcodeBase64(outputFilePath, qriStream),
QirOutputFormat.IR or _ =>
ReturnQIR_IR(channel, outputFilePath, qriStream),
};
}
catch (Exception exception)
{
return "Internal error: generated entry point was null, but no compilation errors were returned."
.ToExecutionResult(ExecuteStatus.Error);
qirMagicArgs.SetError(exception);
throw;
}
if (entryPoint.QirStream is null)
finally
{
return "Internal error: generated entry point does not contain a QIR bitcode stream, but no compilation errors were returned."
.ToExecutionResult(ExecuteStatus.Error);
this.EventService?.Trigger<QirMagicEvent, QirMagicEventData>(qirMagicArgs);
}
}
var bitcodeFile = Path.ChangeExtension(Path.GetTempFileName(), ".bc");
using (var outStream = File.OpenWrite(bitcodeFile))
private ExecutionResult ReturnQIR_IR(IChannel channel, string? outputFilePath, Stream qirStream)
{
string bitcodeFilePath = Path.ChangeExtension(Path.GetTempFileName(), ".bc")!;
string irFilePath = string.IsNullOrEmpty(outputFilePath)
? Path.ChangeExtension(bitcodeFilePath, ".ll")
: outputFilePath;
using (var outStream = File.OpenWrite(bitcodeFilePath))
{
entryPoint.QirStream.CopyTo(outStream);
qirStream.CopyTo(outStream);
}
if (!TryParseBitcode(bitcodeFile, out var moduleRef, out var parseErr))
if (!TryParseBitcode(bitcodeFilePath, out var moduleRef, out var parseErr))
{
var msg = $"Internal error: Could not parse generated QIR bitcode.\nLLVM returned error message: {parseErr}";
channel.Stderr(msg);
@ -217,10 +326,72 @@ public class QirMagic : AbstractMagic
return ExecuteStatus.Error.ToExecutionResult();
}
var llFile = Path.ChangeExtension(bitcodeFile, ".ll");
moduleRef.TryPrintToFile(llFile, out var writeErr);
var llvmIR = File.ReadAllText(llFile);
if (!moduleRef.TryPrintToFile(irFilePath, out var writeErr))
{
return $"Error generating IR from bitcode: {writeErr}"
.ToExecutionResult(ExecuteStatus.Error);
}
if (!string.IsNullOrEmpty(outputFilePath))
{
return new LlvmIr($"QIR {QirOutputFormat.IR} written to {outputFilePath}").ToExecutionResult();
}
var llvmIR = File.ReadAllText(irFilePath);
return new LlvmIr(llvmIR).ToExecutionResult();
}
private static ExecutionResult ReturnQirBitcodeBase64(string? outputFilePath, Stream qirStream)
{
byte[] bytes;
using (var memoryStream = new MemoryStream())
{
qirStream.CopyTo(memoryStream);
bytes = memoryStream.ToArray();
}
string bitchedBase64 = System.Convert.ToBase64String(bytes);
if (!string.IsNullOrEmpty(outputFilePath))
{
File.WriteAllText(outputFilePath, bitchedBase64);
return new LlvmIr($"QIR {QirOutputFormat.BitcodeBase64} written to {outputFilePath}").ToExecutionResult();
}
return new LlvmIr(bitchedBase64).ToExecutionResult();
}
private static ExecutionResult ReturnQirBitcode(string? outputFilePath, Stream qirStream)
{
if (string.IsNullOrEmpty(outputFilePath))
{
return $"Bitcode format can only be written to a file. You must pass the `{ParameterNameOutputFile}` parameter."
.ToExecutionResult(ExecuteStatus.Error);
}
using (var outStream = File.OpenWrite(outputFilePath))
{
qirStream.CopyTo(outStream);
}
return new LlvmIr($"QIR {QirOutputFormat.Bitcode} written to {outputFilePath}").ToExecutionResult();
}
private ExecutionResult ReturnCompilationError(string input, IChannel channel, string? name, CompilationErrorsException exception)
{
StringBuilder message = new($"The Q# operation {name} could not be compiled as an entry point for job execution.");
this.Logger?.LogError(exception, message.ToString());
message.AppendLine(exception.Message);
if (MetadataController.IsPythonUserAgent() || ConfigurationSource.CompilationErrorStyle == CompilationErrorStyle.Basic)
{
foreach (var m in exception.Errors) message.AppendLine(m);
}
else
{
channel.DisplayFancyDiagnostics(exception.Diagnostics, Snippets, input);
}
channel.Stderr(message.ToString());
return message.ToString().ToExecutionResult(ExecuteStatus.Error);
}
}

Просмотреть файл

@ -188,8 +188,8 @@ class IQSharpClient(object):
def trace(self, op, **kwargs) -> Any:
return self._execute_callable_magic('trace', op, _quiet_ = True, **kwargs)
def compile_to_qir(self, op) -> None:
return self._execute_callable_magic('qir', op)
def compile_to_qir(self, op, **kwargs) -> None:
return self._execute_callable_magic('qir', op, **kwargs)
def component_versions(self, **kwargs) -> Dict[str, LooseVersion]:
"""

Просмотреть файл

@ -24,7 +24,7 @@ def register_magics():
callables = qs.compile(cell)
if isinstance(callables, qs.QSharpCallable):
local_ns[callables._name] = callables
else:
elif callables:
for qs_callable in callables:
local_ns[qs_callable._name] = qs_callable

Просмотреть файл

@ -7,20 +7,18 @@
# Licensed under the MIT License.
##
import os
import sys
import logging
from types import ModuleType, new_class
import importlib
from importlib.abc import MetaPathFinder, Loader
import tempfile as tf
from typing import Iterable, Optional, Any, Dict, Tuple
import qsharp
from qsharp.clients.iqsharp import IQSharpError
from typing import Iterable, List, Optional, Any, Dict, Tuple
import logging
logger = logging.getLogger(__name__)
class QSharpModuleFinder(MetaPathFinder):
def find_module(self, full_name : str, path : Optional[str] = None) -> Loader:
# We expose Q# namespaces as their own root-level packages.
@ -122,13 +120,58 @@ class QSharpCallable(object):
"""
return qsharp.client.simulate_noise(self, **kwargs)
def as_qir(self) -> bytes:
def as_qir(self, **kwargs) -> str:
"""
Returns the QIR representation of the callable,
assuming the callable is an entry point.
This method uses `%qir` magic command, which accepts the
following (optional) kwargs parameters:
- `target`: The intended execution target for the compiled entrypoint.
Defaults to the active Azure Quantum target (which can be set with `%azure.target`).
Otherwise, defaults to a generic target, which may not work when running on a specific target.
- `target_capability`: The capability of the intended execution target.
If `target` is specified or there is an active Azure Quantum target, defaults to the target's maximum capability.
Otherwise, defaults to `FullComputation`, which may not be supported when running on a specific target.
- `output_format`: the QIR output format. Defaults to `IR`.
Possible options are:
* `IR`: Human-readable Intermediate Representation in plain-text
* `Bitcode`: LLVM bitcode (only when writing to a output file)
* `BitcodeBase64`: LLVM bitcode encoded as Base64
- `output_file`: The file path for where to save the output QIR.
:rtype: bytes
"""
data = qsharp.client.compile_to_qir(self)
return data['Text']
data = qsharp.client.compile_to_qir(self,
**kwargs)
if data and ("Text" in data):
return data['Text']
raise IQSharpError([f'Error in generating QIR. {data}'])
def _repr_qir_(self, **kwargs: Any) -> bytes:
"""
Returns the QIR representation of the callable,
assuming the callable is an entry point.
This method uses `%qir` magic command, which accepts the
following (optional) kwargs parameters:
- `target`: The intended execution target for the compiled entrypoint.
Defaults to the active Azure Quantum target (which can be set with `%azure.target`).
Otherwise, defaults to a generic target, which may not work when running on a specific target.
- `target_capability`: The capability of the intended execution target.
If `target` is specified or there is an active Azure Quantum target, defaults to the target's maximum capability.
Otherwise, defaults to `FullComputation`, which may not be supported when running on a specific target.
:rtype: bytes
"""
# We need to use the Base64 encoding to be able to transfer
# the bitcode via the Jupyter protocol
qir_bitcodeBase64 = self.as_qir(output_format="BitcodeBase64",
kwargs=kwargs)
import base64
qir_bitcode = base64.b64decode(qir_bitcodeBase64)
return qir_bitcode
class QSharpModule(ModuleType):
_qs_name : str

Просмотреть файл

@ -0,0 +1,61 @@
##
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
##
import unittest
import qsharp
import pytest
class TestQir(unittest.TestCase):
@classmethod
def setUpClass(cls):
# A Q# callable that should work in all target capabilities
cls.qsharp_callable_basic = qsharp.compile("""
open Microsoft.Quantum.Intrinsic;
operation GenerateRandomBitBasic() : Result {
use qubit = Qubit();
H(qubit);
return M(qubit);
}
""")
# A Q# callable with a conditional branch that is not
# supported in all target capabilities
cls.qsharp_callable_advanced = qsharp.compile("""
open Microsoft.Quantum.Intrinsic;
operation GenerateRandomBitAdvanced() : Result {
use qubits = Qubit[2];
H(qubits[0]);
let r1 = M(qubits[0]);
if r1 == One {
H(qubits[1]);
}
return M(qubits[1]);
}
""")
def test_as_qir(self):
qir_text = self.qsharp_callable_basic.as_qir()
self.assertIn("@ENTRYPOINT__GenerateRandomBitBasic", qir_text)
@pytest.mark.skip(reason="Skipping the tests due to `Unable to find package 'Microsoft.Quantum.Type4.Core'` error.")
def test_as_qir_kwargs(self):
qir_text = self.qsharp_callable_basic \
.as_qir(target="rigetti.simulator")
self.assertIn("@ENTRYPOINT__GenerateRandomBitBasic", qir_text)
qir_text = self.qsharp_callable_basic \
.as_qir(target="rigetti.simulator",
target_capability="BasicExecution")
self.assertIn("@ENTRYPOINT__GenerateRandomBitBasic", qir_text)
qir_text = self.qsharp_callable_advanced \
.as_qir(target="rigetti.simulator",
target_capability="FullComputation")
self.assertIn("@ENTRYPOINT__GenerateRandomBitAdvanced", qir_text)
@pytest.mark.skip(reason="Skipping the tests due to `Unable to find package 'Microsoft.Quantum.Type4.Core'` error.")
def test_repr_qir_(self):
qir_bitcode = self.qsharp_callable_basic._repr_qir_(target="rigetti.simulator",
target_capability="BasicExecution")
self.assertGreater(len(qir_bitcode), 4)

Просмотреть файл

@ -15,7 +15,11 @@ import sys
def set_environment_variables():
'''
Sets environment variables for test execution and restarts the IQ# kernel.
Also changes the working directory to the qsharp/tests folder
so that the `Operations.qs` file will be correctly imported/loaded when
the `qsharp` module reloads.
'''
os.chdir(os.path.dirname(os.path.abspath(__file__)))
os.environ["AZURE_QUANTUM_ENV"] = "mock"
os.environ["IQSHARP_AUTO_LOAD_PACKAGES"] = "$null"
importlib.reload(qsharp)

Просмотреть файл

@ -106,11 +106,15 @@ namespace Tests.IQSharp
public async Task QIRSubmission()
{
var entryPointGenerator = Init("Workspace", new string[] { SNIPPETS.HelloQ });
var entryPoint = await entryPointGenerator.Generate("HelloQ", null, generateQir: true);
var entryPoint = await entryPointGenerator.Generate(operationName: "HelloQ",
executionTarget: "MyTarget",
capability: TargetCapabilityModule.AdaptiveExecution,
generateQir: true);
Assert.IsNotNull(entryPoint);
Assert.AreEqual(TargetCapabilityModule.AdaptiveExecution, entryPoint.TargetCapability);
var job = await entryPoint.SubmitAsync(
new MockQirSubmitter(new List<Argument>()),
new MockQirSubmitter(new List<Argument>(), ExpectedTargetCapability: TargetCapabilityModule.AdaptiveExecution),
new AzureSubmissionContext());
Assert.IsNotNull(job);
}

Просмотреть файл

@ -82,8 +82,7 @@ namespace Microsoft.Quantum.IQSharp
this.OnServiceInitialized<IWorkspace>();
eventService.Events<WorkspaceReadyEvent, IWorkspace>().On += (workspace) =>
{
var evt = "WorkspaceReady"
.AsTelemetryEvent();
var evt = "WorkspaceReady".AsTelemetryEvent();
TelemetryLogger.LogEvent(evt);
};
this.OnServiceInitialized<IReferences>();
@ -123,6 +122,16 @@ namespace Microsoft.Quantum.IQSharp
evt.SetProperty("TotalMemoryInGiB".WithTelemetryNamespace(), args.TotalMemoryInGiB?.ToString() ?? "");
TelemetryLogger.LogEvent(evt);
};
eventService.Events<QirMagicEvent, QirMagicEventData>().On += (qirMagicArgs) =>
{
var evt = qirMagicArgs.AsTelemetryEvent();
TelemetryLogger.LogEvent(evt);
};
eventService.Events<EntryPointSubmitEvent, EntryPointSubmitEventData>().On += (entryPointSubmitEventData) =>
{
var evt = entryPointSubmitEventData.AsTelemetryEvent();
TelemetryLogger.LogEvent(evt);
};
// As each different service starts up, we can subscribe to their
// events as well.
@ -272,6 +281,33 @@ namespace Microsoft.Quantum.IQSharp
return evt;
}
public static EventProperties AsTelemetryEvent(this QirMagicEventData qirMagicEventData)
{
var evt = new EventProperties() { Name = "QirMagic".WithTelemetryNamespace() };
evt.SetProperty("CapabilityName".WithTelemetryNamespace(), qirMagicEventData.CapabilityName);
evt.SetProperty("CapabilityInsufficient".WithTelemetryNamespace(), qirMagicEventData.CapabilityInsufficient);
evt.SetProperty("QirStreamSize".WithTelemetryNamespace(), qirMagicEventData.QirStreamSize?.ToString());
evt.SetProperty("Capability".WithTelemetryNamespace(), qirMagicEventData.Capability);
evt.SetProperty("CapabilityInsufficient".WithTelemetryNamespace(), qirMagicEventData.CapabilityInsufficient);
evt.SetProperty("Error".WithTelemetryNamespace(), qirMagicEventData.Error);
evt.SetProperty("InvalidCapability".WithTelemetryNamespace(), qirMagicEventData.InvalidCapability);
evt.SetProperty("OutputFormat".WithTelemetryNamespace(), qirMagicEventData.OutputFormat?.ToString());
evt.SetProperty("Target".WithTelemetryNamespace(), qirMagicEventData.Target);
evt.SetCommonProperties();
return evt;
}
public static EventProperties AsTelemetryEvent(this EntryPointSubmitEventData entryPointSubmitEventData)
{
var evt = new EventProperties() { Name = "EntryPointSubmit".WithTelemetryNamespace() };
evt.SetProperty("MachineName".WithTelemetryNamespace(), entryPointSubmitEventData.MachineName);
evt.SetProperty("Target".WithTelemetryNamespace(), entryPointSubmitEventData.Target);
evt.SetProperty("TargetCapability".WithTelemetryNamespace(), entryPointSubmitEventData.TargetCapability);
evt.SetProperty("JobId".WithTelemetryNamespace(), entryPointSubmitEventData.JobId);
evt.SetCommonProperties();
return evt;
}
public static EventProperties AsTelemetryEvent(this ReloadedEventArgs info)
{
var evt = new EventProperties() { Name = "WorkspaceReload".WithTelemetryNamespace() };