diff --git a/src/AzureClient/AzureClient.cs b/src/AzureClient/AzureClient.cs index c581d8e086..ee639ec3e8 100644 --- a/src/AzureClient/AzureClient.cs +++ b/src/AzureClient/AzureClient.cs @@ -17,6 +17,8 @@ using Microsoft.Azure.Quantum.Client.Models; using Microsoft.Azure.Quantum.Storage; using Microsoft.Azure.Quantum; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Microsoft.Quantum.IQSharp.AzureClient { @@ -24,12 +26,21 @@ namespace Microsoft.Quantum.IQSharp.AzureClient public class AzureClient : IAzureClient { private string ConnectionString { get; set; } = string.Empty; - private string ActiveTargetName { get; set; } = string.Empty; + private AzureExecutionTarget? ActiveTarget { get; set; } private AuthenticationResult? AuthenticationResult { get; set; } private IQuantumClient? QuantumClient { get; set; } - private IPage? ProviderStatusList { get; set; } private Azure.Quantum.IWorkspace? ActiveWorkspace { get; set; } private string MostRecentJobId { get; set; } = string.Empty; + private IPage? AvailableProviders { get; set; } + private IEnumerable? AvailableTargets { get => AvailableProviders?.SelectMany(provider => provider.Targets); } + private IEnumerable? ValidExecutionTargets { get => AvailableTargets?.Where(target => AzureExecutionTarget.IsValid(target.Id)); } + private string ValidExecutionTargetsDisplayText + { + get => ValidExecutionTargets == null + ? "(no execution targets available)" + : string.Join(", ", ValidExecutionTargets.Select(target => target.Id)); + } + /// public async Task ConnectAsync( @@ -112,7 +123,7 @@ public async Task ConnectAsync( try { - ProviderStatusList = await QuantumClient.Providers.GetStatusAsync(); + AvailableProviders = await QuantumClient.Providers.GetStatusAsync(); } catch (Exception e) { @@ -122,22 +133,22 @@ public async Task ConnectAsync( channel.Stdout($"Connected to Azure Quantum workspace {QuantumClient.WorkspaceName}."); - // TODO: Add encoder for IPage rather than calling ToJupyterTable() here directly. - return ProviderStatusList.ToJupyterTable().ToExecutionResult(); + // TODO: Add encoder for IEnumerable rather than calling ToJupyterTable() here directly. + return ValidExecutionTargets.ToJupyterTable().ToExecutionResult(); } /// public async Task GetConnectionStatusAsync(IChannel channel) { - if (QuantumClient == null || ProviderStatusList == null) + if (QuantumClient == null || AvailableProviders == null) { return AzureClientError.NotConnected.ToExecutionResult(); } channel.Stdout($"Connected to Azure Quantum workspace {QuantumClient.WorkspaceName}."); - // TODO: Add encoder for IPage rather than calling ToJupyterTable() here directly. - return ProviderStatusList.ToJupyterTable().ToExecutionResult(); + // TODO: Add encoder for IEnumerable rather than calling ToJupyterTable() here directly. + return ValidExecutionTargets.ToJupyterTable().ToExecutionResult(); } private async Task SubmitOrExecuteJobAsync( @@ -152,7 +163,7 @@ private async Task SubmitOrExecuteJobAsync( return AzureClientError.NotConnected.ToExecutionResult(); } - if (ActiveTargetName == null) + if (ActiveTarget == null) { channel.Stderr("Please call %azure.target before submitting a job."); return AzureClientError.NoTarget.ToExecutionResult(); @@ -165,11 +176,12 @@ private async Task SubmitOrExecuteJobAsync( return AzureClientError.NoOperationName.ToExecutionResult(); } - var machine = Azure.Quantum.QuantumMachineFactory.CreateMachine(ActiveWorkspace, ActiveTargetName, ConnectionString); + var machine = Azure.Quantum.QuantumMachineFactory.CreateMachine(ActiveWorkspace, ActiveTarget.TargetName, ConnectionString); if (machine == null) { - channel.Stderr($"Could not find an execution target for target {ActiveTargetName}."); - return AzureClientError.NoTarget.ToExecutionResult(); + // 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.TargetName}."); + return AzureClientError.InvalidTarget.ToExecutionResult(); } var operationInfo = operationResolver.Resolve(operationName); @@ -178,15 +190,21 @@ private async Task SubmitOrExecuteJobAsync( if (execute) { + channel.Stdout($"Executing {operationName} on target {ActiveTarget.TargetName}..."); var output = await machine.ExecuteAsync(entryPointInfo, entryPointInput); MostRecentJobId = output.Job.Id; - // TODO: Add encoder for IQuantumMachineOutput rather than returning the Histogram directly + + // TODO: Add encoder to visualize IEnumerable> return output.Histogram.ToExecutionResult(); } else { + channel.Stdout($"Submitting {operationName} to target {ActiveTarget.TargetName}..."); var job = await machine.SubmitAsync(entryPointInfo, entryPointInput); + channel.Stdout($"Job {job.Id} submitted successfully."); + MostRecentJobId = job.Id; + // TODO: Add encoder for IQuantumMachineJob rather than calling ToJupyterTable() here. return job.ToJupyterTable().ToExecutionResult(); } @@ -210,19 +228,60 @@ public async Task ExecuteJobAsync( public async Task GetActiveTargetAsync( IChannel channel) { - // TODO: This should also print the list of available targets to the IChannel. - return ActiveTargetName.ToExecutionResult(); + if (AvailableProviders == null) + { + channel.Stderr("Please call %azure.connect before getting the execution target."); + return AzureClientError.NotConnected.ToExecutionResult(); + } + + if (ActiveTarget == null) + { + channel.Stderr("No execution target has been specified. To specify one, run:\n%azure.target "); + channel.Stdout($"Available execution targets: {ValidExecutionTargetsDisplayText}"); + return AzureClientError.NoTarget.ToExecutionResult(); + } + + channel.Stdout($"Current execution target: {ActiveTarget.TargetName}"); + channel.Stdout($"Available execution targets: {ValidExecutionTargetsDisplayText}"); + return ActiveTarget.TargetName.ToExecutionResult(); } /// public async Task SetActiveTargetAsync( IChannel channel, + IReferences references, string targetName) { - // TODO: Validate that this target name is valid in the workspace. - // TODO: Load the associated provider package. - ActiveTargetName = targetName; - return $"Active target is now {ActiveTargetName}".ToExecutionResult(); + if (AvailableProviders == null) + { + channel.Stderr("Please call %azure.connect before setting an execution target."); + return AzureClientError.NotConnected.ToExecutionResult(); + } + + // Validate that this target name is valid in the workspace. + if (!AvailableTargets.Any(target => targetName == target.Id)) + { + channel.Stderr($"Target name {targetName} is not available in the current Azure Quantum workspace."); + channel.Stdout($"Available execution targets: {ValidExecutionTargetsDisplayText}"); + return AzureClientError.InvalidTarget.ToExecutionResult(); + } + + // Validate that we know which package to load for this target name. + var executionTarget = AzureExecutionTarget.Create(targetName); + if (executionTarget == null) + { + channel.Stderr($"Target name {targetName} does not support executing Q# jobs."); + channel.Stdout($"Available execution targets: {ValidExecutionTargetsDisplayText}"); + return AzureClientError.InvalidTarget.ToExecutionResult(); + } + + // Set the active target and load the package. + ActiveTarget = executionTarget; + + channel.Stdout($"Loading package {ActiveTarget.PackageName} and dependencies..."); + await references.AddPackage(ActiveTarget.PackageName); + + return $"Active target is now {ActiveTarget.TargetName}".ToExecutionResult(); } /// @@ -256,19 +315,23 @@ public async Task GetJobResultAsync( if (!job.Succeeded || string.IsNullOrEmpty(job.Details.OutputDataUri)) { - channel.Stderr($"Job ID {jobId} has not completed. Displaying the status instead."); - // TODO: Add encoder for CloudJob rather than calling ToJupyterTable() here directly. - return job.Details.ToJupyterTable().ToExecutionResult(); + channel.Stderr($"Job ID {jobId} has not completed. To check the status, use:\n %azure.status {jobId}"); + return AzureClientError.JobNotCompleted.ToExecutionResult(); } var stream = new MemoryStream(); - var protocol = await new JobStorageHelper(ConnectionString).DownloadJobOutputAsync(jobId, stream); + await new JobStorageHelper(ConnectionString).DownloadJobOutputAsync(jobId, stream); stream.Seek(0, SeekOrigin.Begin); - var outputJson = new StreamReader(stream).ReadToEnd(); + var output = new StreamReader(stream).ReadToEnd(); + var deserializedOutput = JsonConvert.DeserializeObject>(output); + var histogram = new Dictionary(); + foreach (var entry in deserializedOutput["histogram"] as JObject) + { + histogram[entry.Key] = entry.Value.ToObject(); + } - // TODO: Deserialize this once we have a way of getting the output type - // TODO: Add encoder for job output - return outputJson.ToExecutionResult(); + // TODO: Add encoder to visualize IEnumerable> + return histogram.ToExecutionResult(); } /// @@ -300,8 +363,8 @@ public async Task GetJobStatusAsync( return AzureClientError.JobNotFound.ToExecutionResult(); } - // TODO: Add encoder for CloudJob rather than calling ToJupyterTable() here directly. - return job.Details.ToJupyterTable().ToExecutionResult(); + // TODO: Add encoder for CloudJob which calls ToJupyterTable() for display. + return job.Details.ToExecutionResult(); } /// diff --git a/src/AzureClient/AzureEnvironment.cs b/src/AzureClient/AzureEnvironment.cs index d5c6ac2a2c..20db5920ae 100644 --- a/src/AzureClient/AzureEnvironment.cs +++ b/src/AzureClient/AzureEnvironment.cs @@ -3,7 +3,6 @@ #nullable enable -using Microsoft.Quantum.Simulation.Common; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/AzureClient/AzureExecutionTarget.cs b/src/AzureClient/AzureExecutionTarget.cs new file mode 100644 index 0000000000..2f0d5c89d1 --- /dev/null +++ b/src/AzureClient/AzureExecutionTarget.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable enable + +using System; + +namespace Microsoft.Quantum.IQSharp.AzureClient +{ + internal enum AzureProvider { IonQ, Honeywell, QCI } + + internal class AzureExecutionTarget + { + public string TargetName { get; private set; } + public string PackageName => $"Microsoft.Quantum.Providers.{GetProvider(TargetName)}"; + + public static bool IsValid(string targetName) => GetProvider(targetName) != null; + + public static AzureExecutionTarget? Create(string targetName) => + IsValid(targetName) + ? new AzureExecutionTarget() { TargetName = targetName } + : null; + + /// + /// Gets the Azure Quantum provider corresponding to the given execution target. + /// + /// The Azure Quantum execution target name. + /// The enum value representing the provider. + /// + /// Valid target names are structured as "provider.target". + /// For example, "ionq.simulator" or "honeywell.qpu". + /// + private static AzureProvider? GetProvider(string targetName) + { + var parts = targetName.Split('.', 2); + if (Enum.TryParse(parts[0], true, out AzureProvider provider)) + { + return provider; + } + + return null; + } + } +} diff --git a/src/AzureClient/Extensions.cs b/src/AzureClient/Extensions.cs index 7a8e950951..116643480f 100644 --- a/src/AzureClient/Extensions.cs +++ b/src/AzureClient/Extensions.cs @@ -90,7 +90,6 @@ internal static Table ToJupyterTable(this IQuantumMachineJob { ("JobId", job => job.Id), ("JobStatus", job => job.Status), - ("JobUri", job => job.Uri.ToString()), }, Rows = new List() { job } }; @@ -107,7 +106,7 @@ internal static Table ToJupyterTable(this IQuantumClient quantum Rows = new List() { quantumClient } }; - internal static Table ToJupyterTable(this IEnumerable providerStatusList) => + internal static Table ToJupyterTable(this IEnumerable targets) => new Table { Columns = new List<(string, Func)> @@ -117,7 +116,7 @@ internal static Table ToJupyterTable(this IEnumerable target.AverageQueueTime.ToString()), ("StatusPage", target => target.StatusPage), }, - Rows = providerStatusList.SelectMany(provider => provider.Targets).ToList() + Rows = targets.ToList() }; } } diff --git a/src/AzureClient/IAzureClient.cs b/src/AzureClient/IAzureClient.cs index 82bb3cb756..f7df675652 100644 --- a/src/AzureClient/IAzureClient.cs +++ b/src/AzureClient/IAzureClient.cs @@ -18,43 +18,55 @@ public enum AzureClientError /// Method completed with an unknown error. /// [Description(Resources.AzureClientErrorUnknownError)] - UnknownError = 0, + UnknownError, /// /// No connection has been made to any Azure Quantum workspace. /// [Description(Resources.AzureClientErrorNotConnected)] - NotConnected = 1, + NotConnected, /// /// A target has not yet been configured for job submission. /// [Description(Resources.AzureClientErrorNoTarget)] - NoTarget = 2, + NoTarget, + + /// + /// The specified target is not valid for job submission. + /// + [Description(Resources.AzureClientErrorInvalidTarget)] + InvalidTarget, /// /// A job meeting the specified criteria was not found. /// [Description(Resources.AzureClientErrorJobNotFound)] - JobNotFound = 3, + JobNotFound, + + /// + /// The result of a job was requested, but the job has not yet completed. + /// + [Description(Resources.AzureClientErrorJobNotCompleted)] + JobNotCompleted, /// /// No Q# operation name was provided where one was required. /// [Description(Resources.AzureClientErrorNoOperationName)] - NoOperationName = 4, + NoOperationName, /// /// Authentication with the Azure service failed. /// [Description(Resources.AzureClientErrorAuthenticationFailed)] - AuthenticationFailed = 5, + AuthenticationFailed, /// /// A workspace meeting the specified criteria was not found. /// [Description(Resources.AzureClientErrorWorkspaceNotFound)] - WorkspaceNotFound = 6, + WorkspaceNotFound, } /// @@ -118,6 +130,7 @@ public Task ExecuteJobAsync( /// public Task SetActiveTargetAsync( IChannel channel, + IReferences references, string targetName); /// diff --git a/src/AzureClient/Magic/TargetMagic.cs b/src/AzureClient/Magic/TargetMagic.cs index b17eb0ab3e..7e5e2a4151 100644 --- a/src/AzureClient/Magic/TargetMagic.cs +++ b/src/AzureClient/Magic/TargetMagic.cs @@ -19,13 +19,18 @@ public class TargetMagic : AzureClientMagicBase { private const string ParameterNameTargetName = "name"; + private IReferences? References { get; set; } + /// /// Initializes a new instance of the class. /// /// /// The object to use for Azure functionality. /// - public TargetMagic(IAzureClient azureClient) + /// + /// The object to use for loading target-specific packages. + /// + public TargetMagic(IAzureClient azureClient, IReferences references) : base( azureClient, "azure.target", @@ -57,9 +62,8 @@ available in the workspace. ``` ".Dedent(), }, - }) - { - } + }) => + References = references; /// /// Sets or views the target for job submission to the current Azure Quantum workspace. @@ -70,7 +74,7 @@ public override async Task RunAsync(string input, IChannel chan if (inputParameters.ContainsKey(ParameterNameTargetName)) { string targetName = inputParameters.DecodeParameter(ParameterNameTargetName); - return await AzureClient.SetActiveTargetAsync(channel, targetName); + return await AzureClient.SetActiveTargetAsync(channel, References, targetName); } return await AzureClient.GetActiveTargetAsync(channel); diff --git a/src/AzureClient/Properties/AssemblyInfo.cs b/src/AzureClient/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..bb672ac9dd --- /dev/null +++ b/src/AzureClient/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Tests.IQsharp" + SigningConstants.PUBLIC_KEY)] diff --git a/src/AzureClient/Resources.cs b/src/AzureClient/Resources.cs index 6c3de47cc0..1fedce95f2 100644 --- a/src/AzureClient/Resources.cs +++ b/src/AzureClient/Resources.cs @@ -16,9 +16,15 @@ internal static class Resources public const string AzureClientErrorNoTarget = "No execution target has been configured for Azure Quantum job submission."; + public const string AzureClientErrorInvalidTarget = + "The specified execution target is not valid for Q# job submission in the current Azure Quantum workspace."; + public const string AzureClientErrorJobNotFound = "No job with the given ID was found in the current Azure Quantum workspace."; + public const string AzureClientErrorJobNotCompleted = + "The specified Azure Quantum job has not yet completed."; + public const string AzureClientErrorNoOperationName = "No Q# operation name was specified for Azure Quantum job submission."; diff --git a/src/Kernel/client/kernel.ts b/src/Kernel/client/kernel.ts index 3be307a020..7590ddc03b 100644 --- a/src/Kernel/client/kernel.ts +++ b/src/Kernel/client/kernel.ts @@ -69,6 +69,12 @@ function defineQSharpMode() { regex: String.raw`(%(config|estimate|lsmagic|package|performance|simulate|toffoli|version|who|workspace))\b`, beginWord: true, }, + { + // Azure magic commands + token: "builtin", + regex: String.raw`(%azure\.(connect|execute|jobs|output|status|submit|target))\b`, + beginWord: true, + }, { // chemistry magic commands token: "builtin", diff --git a/src/Python/qsharp/azure.py b/src/Python/qsharp/azure.py index 2182a20b30..f69fb83a00 100644 --- a/src/Python/qsharp/azure.py +++ b/src/Python/qsharp/azure.py @@ -29,19 +29,31 @@ 'connect', 'target', 'submit', - 'status' + 'execute', + 'status', + 'output', + 'jobs' ] ## FUNCTIONS ## def connect(**params) -> Any: - return qsharp.client._execute_magic(f"connect", raise_on_stderr=False, **params) + return qsharp.client._execute_magic(f"azure.connect", raise_on_stderr=False, **params) def target(name : str = '', **params) -> Any: - return qsharp.client._execute_magic(f"target {name}", raise_on_stderr=False, **params) + return qsharp.client._execute_magic(f"azure.target {name}", raise_on_stderr=False, **params) def submit(op, **params) -> Any: - return qsharp.client._execute_callable_magic("submit", op, raise_on_stderr=False, **params) + return qsharp.client._execute_callable_magic("azure.submit", op, raise_on_stderr=False, **params) + +def execute(op, **params) -> Any: + return qsharp.client._execute_callable_magic("azure.execute", op, raise_on_stderr=False, **params) def status(jobId : str = '', **params) -> Any: - return qsharp.client._execute_magic(f"status {jobId}", raise_on_stderr=False, **params) + return qsharp.client._execute_magic(f"azure.status {jobId}", raise_on_stderr=False, **params) + +def output(jobId : str = '', **params) -> Any: + return qsharp.client._execute_magic(f"azure.output {jobId}", raise_on_stderr=False, **params) + +def jobs(**params) -> Any: + return qsharp.client._execute_magic(f"azure.jobs", raise_on_stderr=False, **params) diff --git a/src/Tests/AzureClientMagicTests.cs b/src/Tests/AzureClientMagicTests.cs index 0eddabee2d..fbbc880253 100644 --- a/src/Tests/AzureClientMagicTests.cs +++ b/src/Tests/AzureClientMagicTests.cs @@ -10,6 +10,7 @@ using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp; using Microsoft.Quantum.IQSharp.AzureClient; +using Microsoft.Extensions.DependencyInjection; namespace Tests.IQSharp { @@ -140,15 +141,19 @@ public void TestJobsMagic() [TestMethod] public void TestTargetMagic() { + var workspace = "Workspace"; + var services = Startup.CreateServiceProvider(workspace); + var references = services.GetService(); + // single argument - should set active target var azureClient = new MockAzureClient(); - var targetMagic = new TargetMagic(azureClient); + var targetMagic = new TargetMagic(azureClient, references); targetMagic.Test(targetName); Assert.AreEqual(azureClient.LastAction, AzureClientAction.SetActiveTarget); // no arguments - should print active target azureClient = new MockAzureClient(); - targetMagic = new TargetMagic(azureClient); + targetMagic = new TargetMagic(azureClient, references); targetMagic.Test(string.Empty); Assert.AreEqual(azureClient.LastAction, AzureClientAction.GetActiveTarget); } @@ -177,7 +182,7 @@ public class MockAzureClient : IAzureClient internal List SubmittedJobs = new List(); internal List ExecutedJobs = new List(); - public async Task SetActiveTargetAsync(IChannel channel, string targetName) + public async Task SetActiveTargetAsync(IChannel channel, IReferences references, string targetName) { LastAction = AzureClientAction.SetActiveTarget; ActiveTargetName = targetName; diff --git a/src/Tests/AzureClientTests.cs b/src/Tests/AzureClientTests.cs index c9258af1af..af75ade1b8 100644 --- a/src/Tests/AzureClientTests.cs +++ b/src/Tests/AzureClientTests.cs @@ -6,7 +6,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Linq; using Microsoft.Jupyter.Core; +using Microsoft.Quantum.IQSharp; using Microsoft.Quantum.IQSharp.AzureClient; +using Microsoft.Extensions.DependencyInjection; namespace Tests.IQSharp { @@ -23,18 +25,52 @@ public class AzureClientTests private readonly string storageAccountConnectionString = "TEST_CONNECTION_STRING"; private readonly string jobId = "TEST_JOB_ID"; private readonly string operationName = "TEST_OPERATION_NAME"; - private readonly string targetName = "TEST_TARGET_NAME"; [TestMethod] public void TestTargets() { - var azureClient = new AzureClient(); + var workspace = "Workspace"; + var services = Startup.CreateServiceProvider(workspace); + var references = services.GetService(); + var azureClient = services.GetService(); - var result = azureClient.SetActiveTargetAsync(new MockChannel(), targetName).GetAwaiter().GetResult(); - Assert.IsTrue(result.Status == ExecuteStatus.Ok); + // SetActiveTargetAsync with recognized target name, but not yet connected + var result = azureClient.SetActiveTargetAsync(new MockChannel(), references, "ionq.simulator").GetAwaiter().GetResult(); + Assert.IsTrue(result.Status == ExecuteStatus.Error); + // SetActiveTargetAsync with unrecognized target name + result = azureClient.SetActiveTargetAsync(new MockChannel(), references, "contoso.qpu").GetAwaiter().GetResult(); + Assert.IsTrue(result.Status == ExecuteStatus.Error); + + // GetActiveTargetAsync, but not yet connected result = azureClient.GetActiveTargetAsync(new MockChannel()).GetAwaiter().GetResult(); - Assert.IsTrue(result.Status == ExecuteStatus.Ok); + Assert.IsTrue(result.Status == ExecuteStatus.Error); + } + + [TestMethod] + public void TestAzureExecutionTarget() + { + var targetName = "invalidname"; + var executionTarget = AzureExecutionTarget.Create(targetName); + Assert.IsNull(executionTarget); + + targetName = "ionq.targetname"; + executionTarget = AzureExecutionTarget.Create(targetName); + Assert.IsNotNull(executionTarget); + Assert.AreEqual(executionTarget.TargetName, targetName); + Assert.AreEqual(executionTarget.PackageName, "Microsoft.Quantum.Providers.IonQ"); + + targetName = "HonEYWEll.targetname"; + executionTarget = AzureExecutionTarget.Create(targetName); + Assert.IsNotNull(executionTarget); + Assert.AreEqual(executionTarget.TargetName, targetName); + Assert.AreEqual(executionTarget.PackageName, "Microsoft.Quantum.Providers.Honeywell"); + + targetName = "qci.target.name.qpu"; + executionTarget = AzureExecutionTarget.Create(targetName); + Assert.IsNotNull(executionTarget); + Assert.AreEqual(executionTarget.TargetName, targetName); + Assert.AreEqual(executionTarget.PackageName, "Microsoft.Quantum.Providers.QCI"); } } } diff --git a/src/Tool/appsettings.json b/src/Tool/appsettings.json index 31834fd153..3edf302854 100644 --- a/src/Tool/appsettings.json +++ b/src/Tool/appsettings.json @@ -20,6 +20,10 @@ "Microsoft.Quantum.Katas::0.11.2006.207", - "Microsoft.Quantum.Research::0.11.2006.207" + "Microsoft.Quantum.Research::0.11.2006.207", + + "Microsoft.Quantum.Providers.IonQ::0.11.2006.207", + "Microsoft.Quantum.Providers.Honeywell::0.11.2006.207", + "Microsoft.Quantum.Providers.QCI::0.11.2006.207", ] }