From 89282dff3599b744e13c6d97e97d48fb11be56b4 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Thu, 28 May 2020 18:03:10 -0400 Subject: [PATCH 1/9] Updates to IQ# syntax highlighting --- src/Kernel/client/kernel.ts | 140 ++++++++++++++++++++++++------------ 1 file changed, 96 insertions(+), 44 deletions(-) diff --git a/src/Kernel/client/kernel.ts b/src/Kernel/client/kernel.ts index bb001eb7a8..d239a5af47 100644 --- a/src/Kernel/client/kernel.ts +++ b/src/Kernel/client/kernel.ts @@ -11,54 +11,106 @@ import { Telemetry, ClientInfo } from "./telemetry.js"; function defineQSharpMode() { console.log("Loading IQ# kernel-specific extension..."); + + let rules = [ + { + token: "comment", + regex: /(\/\/).*/, + beginWord: false, + }, + { + token: "string", + regex: String.raw`^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)`, + beginWord: false, + }, + { + token: "keyword", + regex: String.raw`(namespace|open|as|operation|function|body|adjoint|newtype|controlled)\b`, + beginWord: true, + }, + { + token: "keyword", + regex: String.raw`(if|elif|else|repeat|until|fixup|for|in|return|fail|within|apply)\b`, + beginWord: true, + }, + { + token: "keyword", + regex: String.raw`(Adjoint|Controlled|Adj|Ctl|is|self|auto|distribute|invert|intrinsic)\b`, + beginWord: true, + }, + { + token: "keyword", + regex: String.raw`(let|set|w\/|new|not|and|or|using|borrowing|newtype|mutable)\b`, + beginWord: true, + }, + { + token: "meta", + regex: String.raw`(Int|BigInt|Double|Bool|Qubit|Pauli|Result|Range|String|Unit)\b`, + beginWord: true, + }, + { + token: "atom", + regex: String.raw`(true|false|Pauli(I|X|Y|Z)|One|Zero)\b`, + beginWord: true, + }, + { + token: "builtin", + regex: String.raw`(X|Y|Z|H|HY|S|T|SWAP|CNOT|CCNOT|MultiX|R|RFrac|Rx|Ry|Rz|R1|R1Frac|Exp|ExpFrac|Measure|M|MultiM)\b`, + beginWord: true, + }, + { + token: "builtin", + regex: String.raw`(Message|Length|Assert|AssertProb|AssertEqual)\b`, + beginWord: true, + }, + { + // built-in magic commands + token: "builtin", + 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", + regex: String.raw`(%chemistry\.(broombridge|encode|fh\.add_terms|fh\.load|inputstate\.load))\b`, + beginWord: true, + }, + { + // katas magic commands + token: "builtin", + regex: String.raw`(%(?:check_kata|kata))\b`, + beginWord: true, + }, + ]; + + let simpleRules = [] + for (let rule of rules) { + simpleRules.push({ + "token": rule.token, + "regex": new RegExp(rule.regex, "g"), + "sol": rule.beginWord + }); + if (rule.beginWord) { + // Need an additional rule due to the fact that CodeMirror simple mode doesn't work with ^ token + simpleRules.push({ + "token": rule.token, + "regex": new RegExp(String.raw`\W` + rule.regex, "g"), + "sol": false + }); + } + } + // NB: The TypeScript definitions for CodeMirror don't currently understand // the simple mode plugin. let codeMirror: any = window.CodeMirror; codeMirror.defineSimpleMode('qsharp', { - start: [ - { - token: "comment", - // include % to support kata special commands - regex: /(\/\/|%kata|%version|%simulate|%package|%workspace|%check_kata).*/ - }, - { - token: "string", - regex: /^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/ - }, - { - // a group of keywords that can typically occur in the beginning of the line but not in the end of a phrase - token: "keyword", - regex: /(^|\W)(?:namespace|open|as|operation|function|body|adjoint|newtype|controlled)\b/ - }, - { - token: "keyword", - regex: /\W(?:if|elif|else|repeat|until|fixup|for|in|return|fail|within|apply)\b/ - }, - { - token: "keyword", - regex: /\W(?:Adjoint|Controlled|Adj|Ctl|is|self|auto|distribute|invert|intrinsic)\b/ - }, - { - token: "keyword", - regex: /\W(?:let|set|w\/|new|not|and|or|using|borrowing|newtype|mutable)\b/ - }, - { - token: "meta", - regex: /[^\w(\s]*(?:Int|BigInt|Double|Bool|Qubit|Pauli|Result|Range|String|Unit)\b/ - }, - { - token: "atom", - regex: /\W(?:true|false|Pauli(I|X|Y|Z)|One|Zero)\b/ - }, - { - token: "builtin", - regex: /(\\n|\W)(?:X|Y|Z|H|HY|S|T|SWAP|CNOT|CCNOT|MultiX|R|RFrac|Rx|Ry|Rz|R1|R1Frac|Exp|ExpFrac|Measure|M|MultiM)\b/ - }, - { - token: "builtin", - regex: /(\\n|\W)(?:Message|Length|Assert|AssertProb|AssertEqual)\b/ - } - ] + start: simpleRules }); codeMirror.defineMIME("text/x-qsharp", "qsharp"); } From f53eb9cbb5a1b8af6b6542e1c7543f13aaa195af Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Thu, 28 May 2020 18:05:22 -0400 Subject: [PATCH 2/9] Validate targets and load provider packages --- src/AzureClient/AzureClient.cs | 110 ++++++++++++++++++------ src/AzureClient/AzureEnvironment.cs | 1 - src/AzureClient/AzureExecutionTarget.cs | 43 +++++++++ src/AzureClient/Extensions.cs | 5 +- src/AzureClient/IAzureClient.cs | 21 +++-- src/AzureClient/Magic/TargetMagic.cs | 14 +-- src/AzureClient/Resources.cs | 3 + src/Tests/AzureClientMagicTests.cs | 11 ++- src/Tests/AzureClientTests.cs | 20 +++-- src/Tool/appsettings.json | 6 +- 10 files changed, 185 insertions(+), 49 deletions(-) create mode 100644 src/AzureClient/AzureExecutionTarget.cs diff --git a/src/AzureClient/AzureClient.cs b/src/AzureClient/AzureClient.cs index c581d8e086..600080e5b9 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,22 @@ 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); MostRecentJobId = job.Id; + channel.Stdout("Job submission successful."); + channel.Stdout($"To check the status, run:\n %azure.status {MostRecentJobId}"); + channel.Stdout($"To see the results, run:\n %azure.output {MostRecentJobId}"); + // TODO: Add encoder for IQuantumMachineJob rather than calling ToJupyterTable() here. return job.ToJupyterTable().ToExecutionResult(); } @@ -210,19 +229,57 @@ 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. + if (!ValidExecutionTargets.Any(target => targetName == target.Id)) + { + 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 = new AzureExecutionTarget(targetName); + await references.AddPackage(ActiveTarget.PackageName); + + return $"Active target is now {ActiveTarget.TargetName}".ToExecutionResult(); } /// @@ -262,13 +319,18 @@ public async Task GetJobResultAsync( } 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(); } /// 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..91de87dcdf --- /dev/null +++ b/src/AzureClient/AzureExecutionTarget.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Quantum.IQSharp.AzureClient +{ + internal enum AzureProvider { IonQ, Honeywell, QCI } + + internal class AzureExecutionTarget + { + public string TargetName { get; private set; } + public AzureProvider? Provider { get => GetProvider(TargetName); } + public string PackageName { get => $"Microsoft.Quantum.Providers.{Provider}"; } + + public static bool IsValid(string targetName) => GetProvider(targetName) != null; + + public AzureExecutionTarget(string targetName) + { + if (!IsValid(targetName)) + { + throw new InvalidOperationException($"{targetName} is not a valid target name."); + } + + TargetName = targetName; + } + + 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..69a0177ade 100644 --- a/src/AzureClient/IAzureClient.cs +++ b/src/AzureClient/IAzureClient.cs @@ -18,43 +18,49 @@ 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, /// /// 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 +124,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/Resources.cs b/src/AzureClient/Resources.cs index 6c3de47cc0..06703703d0 100644 --- a/src/AzureClient/Resources.cs +++ b/src/AzureClient/Resources.cs @@ -16,6 +16,9 @@ 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."; 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..425563f5a4 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,26 @@ 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); } } } diff --git a/src/Tool/appsettings.json b/src/Tool/appsettings.json index 52d295f3e3..f692e0f440 100644 --- a/src/Tool/appsettings.json +++ b/src/Tool/appsettings.json @@ -18,6 +18,10 @@ "Microsoft.Quantum.Chemistry.Jupyter::0.11.2005.1924-beta", "Microsoft.Quantum.Numerics::0.11.2005.1924-beta", - "Microsoft.Quantum.Research::0.11.2005.1924-beta" + "Microsoft.Quantum.Research::0.11.2005.1924-beta", + + "Microsoft.Quantum.Providers.IonQ::0.11.2005.1924-beta", + "Microsoft.Quantum.Providers.Honeywell::0.11.2005.1924-beta", + "Microsoft.Quantum.Providers.QCI::0.11.2005.1924-beta", ] } From 75a8e357d71442aceb77da9755652ccb56f2190b Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Fri, 29 May 2020 09:36:40 -0400 Subject: [PATCH 3/9] Update Python interface for Azure commands --- src/Python/qsharp/azure.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) 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) From 52098e6412cfc5df8aba9948cdfc8f02d6efba7b Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Mon, 1 Jun 2020 10:54:53 -0400 Subject: [PATCH 4/9] Simplify AzureExecutionTarget class --- src/AzureClient/AzureClient.cs | 5 +++-- src/AzureClient/AzureExecutionTarget.cs | 18 +++++------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/AzureClient/AzureClient.cs b/src/AzureClient/AzureClient.cs index 600080e5b9..797a953132 100644 --- a/src/AzureClient/AzureClient.cs +++ b/src/AzureClient/AzureClient.cs @@ -268,7 +268,8 @@ public async Task SetActiveTargetAsync( } // Validate that we know which package to load for this target name. - if (!ValidExecutionTargets.Any(target => targetName == target.Id)) + 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}"); @@ -276,7 +277,7 @@ public async Task SetActiveTargetAsync( } // Set the active target and load the package. - ActiveTarget = new AzureExecutionTarget(targetName); + ActiveTarget = executionTarget; await references.AddPackage(ActiveTarget.PackageName); return $"Active target is now {ActiveTarget.TargetName}".ToExecutionResult(); diff --git a/src/AzureClient/AzureExecutionTarget.cs b/src/AzureClient/AzureExecutionTarget.cs index 91de87dcdf..4881049818 100644 --- a/src/AzureClient/AzureExecutionTarget.cs +++ b/src/AzureClient/AzureExecutionTarget.cs @@ -4,8 +4,6 @@ #nullable enable using System; -using System.Collections.Generic; -using System.Text; namespace Microsoft.Quantum.IQSharp.AzureClient { @@ -14,20 +12,14 @@ internal enum AzureProvider { IonQ, Honeywell, QCI } internal class AzureExecutionTarget { public string TargetName { get; private set; } - public AzureProvider? Provider { get => GetProvider(TargetName); } - public string PackageName { get => $"Microsoft.Quantum.Providers.{Provider}"; } + public string PackageName { get => $"Microsoft.Quantum.Providers.{GetProvider(TargetName)}"; } public static bool IsValid(string targetName) => GetProvider(targetName) != null; - public AzureExecutionTarget(string targetName) - { - if (!IsValid(targetName)) - { - throw new InvalidOperationException($"{targetName} is not a valid target name."); - } - - TargetName = targetName; - } + public static AzureExecutionTarget? Create(string targetName) => + IsValid(targetName) + ? new AzureExecutionTarget() { TargetName = targetName } + : null; private static AzureProvider? GetProvider(string targetName) { From a33a2c9fa36c653b8dc310e39e5074804ff60a12 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Tue, 2 Jun 2020 14:04:48 -0400 Subject: [PATCH 5/9] Changes for JobNotCompleted case --- src/AzureClient/AzureClient.cs | 14 ++++++-------- src/AzureClient/IAzureClient.cs | 6 ++++++ src/AzureClient/Resources.cs | 3 +++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/AzureClient/AzureClient.cs b/src/AzureClient/AzureClient.cs index 797a953132..1f01dc737f 100644 --- a/src/AzureClient/AzureClient.cs +++ b/src/AzureClient/AzureClient.cs @@ -201,10 +201,9 @@ private async Task SubmitOrExecuteJobAsync( { 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; - channel.Stdout("Job submission successful."); - channel.Stdout($"To check the status, run:\n %azure.status {MostRecentJobId}"); - channel.Stdout($"To see the results, run:\n %azure.output {MostRecentJobId}"); // TODO: Add encoder for IQuantumMachineJob rather than calling ToJupyterTable() here. return job.ToJupyterTable().ToExecutionResult(); @@ -314,9 +313,8 @@ 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(); @@ -363,8 +361,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/IAzureClient.cs b/src/AzureClient/IAzureClient.cs index 69a0177ade..f7df675652 100644 --- a/src/AzureClient/IAzureClient.cs +++ b/src/AzureClient/IAzureClient.cs @@ -44,6 +44,12 @@ public enum AzureClientError [Description(Resources.AzureClientErrorJobNotFound)] 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. /// diff --git a/src/AzureClient/Resources.cs b/src/AzureClient/Resources.cs index 06703703d0..1fedce95f2 100644 --- a/src/AzureClient/Resources.cs +++ b/src/AzureClient/Resources.cs @@ -22,6 +22,9 @@ internal static class Resources 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."; From d47fddd9d2295240fab002a676ed562dea3132c3 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Tue, 2 Jun 2020 17:35:11 -0400 Subject: [PATCH 6/9] Simplify property syntax Co-authored-by: Chris Granade --- src/AzureClient/AzureExecutionTarget.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AzureClient/AzureExecutionTarget.cs b/src/AzureClient/AzureExecutionTarget.cs index 4881049818..67803e3ae1 100644 --- a/src/AzureClient/AzureExecutionTarget.cs +++ b/src/AzureClient/AzureExecutionTarget.cs @@ -12,7 +12,7 @@ internal enum AzureProvider { IonQ, Honeywell, QCI } internal class AzureExecutionTarget { public string TargetName { get; private set; } - public string PackageName { get => $"Microsoft.Quantum.Providers.{GetProvider(TargetName)}"; } + public string PackageName => $"Microsoft.Quantum.Providers.{GetProvider(TargetName)}"; public static bool IsValid(string targetName) => GetProvider(targetName) != null; From 521515546c0afcd43c43530d095c71f448469cf0 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Tue, 2 Jun 2020 17:46:24 -0400 Subject: [PATCH 7/9] Add simple tests for AzureExecutionTarget class --- src/AzureClient/Properties/AssemblyInfo.cs | 3 +++ src/Tests/AzureClientTests.cs | 26 ++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 src/AzureClient/Properties/AssemblyInfo.cs 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/Tests/AzureClientTests.cs b/src/Tests/AzureClientTests.cs index 425563f5a4..af75ade1b8 100644 --- a/src/Tests/AzureClientTests.cs +++ b/src/Tests/AzureClientTests.cs @@ -46,5 +46,31 @@ public void TestTargets() result = azureClient.GetActiveTargetAsync(new MockChannel()).GetAwaiter().GetResult(); 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"); + } } } From c63a006bc1264f468bd02d7be2fb9a4a68cfe01b Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Wed, 3 Jun 2020 16:59:20 -0400 Subject: [PATCH 8/9] Add status message while loading provider package --- src/AzureClient/AzureClient.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/AzureClient/AzureClient.cs b/src/AzureClient/AzureClient.cs index 1f01dc737f..ee639ec3e8 100644 --- a/src/AzureClient/AzureClient.cs +++ b/src/AzureClient/AzureClient.cs @@ -277,6 +277,8 @@ public async Task SetActiveTargetAsync( // 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(); From 43c07ae1e4ebf59f6cdd22467b76687748c25dd4 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Thu, 4 Jun 2020 20:35:54 -0400 Subject: [PATCH 9/9] Add documentation to AzureExecutionTarget.GetProvider --- src/AzureClient/AzureExecutionTarget.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/AzureClient/AzureExecutionTarget.cs b/src/AzureClient/AzureExecutionTarget.cs index 67803e3ae1..2f0d5c89d1 100644 --- a/src/AzureClient/AzureExecutionTarget.cs +++ b/src/AzureClient/AzureExecutionTarget.cs @@ -21,6 +21,15 @@ internal class AzureExecutionTarget ? 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);