Skip to content
This repository was archived by the owner on Jan 12, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
89282df
Updates to IQ# syntax highlighting
rmshaffer May 28, 2020
f53eb9c
Validate targets and load provider packages
rmshaffer May 28, 2020
75a8e35
Update Python interface for Azure commands
rmshaffer May 29, 2020
1787ea4
Merge branch 'feature/azure-client' into rmshaffer/azure-targets
rmshaffer Jun 1, 2020
52098e6
Simplify AzureExecutionTarget class
rmshaffer Jun 1, 2020
53fd8e9
Generate EntryPoint and compile into new assembly
rmshaffer Jun 1, 2020
a33a2c9
Changes for JobNotCompleted case
rmshaffer Jun 2, 2020
e47c9df
Merge branch 'rmshaffer/azure-targets' into rmshaffer/azure-entrypoint
rmshaffer Jun 2, 2020
4449040
Refactor entry point code into new classes
rmshaffer Jun 2, 2020
6f3a6fe
Use correct input and output types
rmshaffer Jun 2, 2020
d47fddd
Simplify property syntax
Jun 2, 2020
5215155
Add simple tests for AzureExecutionTarget class
rmshaffer Jun 2, 2020
eeb4b11
Merge branch 'rmshaffer/azure-targets' into rmshaffer/azure-entrypoint
rmshaffer Jun 2, 2020
5e62413
Recompile everything for specified execution target
rmshaffer Jun 3, 2020
26a79ea
Add tests and error handling
rmshaffer Jun 3, 2020
69a4fe6
Improve variable names
rmshaffer Jun 3, 2020
c63a006
Add status message while loading provider package
rmshaffer Jun 3, 2020
1c7d83f
Merge branch 'feature/azure-client' into rmshaffer/azure-targets
rmshaffer Jun 3, 2020
cbdd00e
Merge branch 'rmshaffer/azure-targets' into rmshaffer/azure-entrypoint
rmshaffer Jun 3, 2020
944cec0
Merge branch 'feature/azure-client' into rmshaffer/azure-targets
rmshaffer Jun 3, 2020
12c7e12
Merge branch 'rmshaffer/azure-targets' into rmshaffer/azure-entrypoint
rmshaffer Jun 3, 2020
b8e680b
Merge branch 'feature/azure-client' into rmshaffer/azure-targets
rmshaffer Jun 3, 2020
126f43b
Merge branch 'rmshaffer/azure-targets' into rmshaffer/azure-entrypoint
rmshaffer Jun 3, 2020
43c07ae
Add documentation to AzureExecutionTarget.GetProvider
rmshaffer Jun 5, 2020
2552632
Merge branch 'feature/azure-client' into rmshaffer/azure-targets
rmshaffer Jun 5, 2020
4bf71db
Merge branch 'rmshaffer/azure-targets' into rmshaffer/azure-entrypoint
rmshaffer Jun 5, 2020
52c481d
Extend tests to call EntryPoint.SubmitAsync
rmshaffer Jun 5, 2020
a470662
Wait for job completion on %azure.execute
rmshaffer Jun 5, 2020
354d826
Update comment
rmshaffer Jun 5, 2020
9041701
Add timeout for %azure.execute
rmshaffer Jun 5, 2020
0300ecd
Minor fixes in AzureClient
rmshaffer Jun 5, 2020
8ce9c63
Minor formatting and comment tweaks
rmshaffer Jun 5, 2020
e3e7bcc
Style improvements in test code
rmshaffer Jun 5, 2020
f807723
More consistent handling of job objects
rmshaffer Jun 5, 2020
1409213
Consistent error handling for IWorkspace calls
rmshaffer Jun 5, 2020
78196ff
Update to latest QDK released version
rmshaffer Jun 5, 2020
fce176e
Merge branch 'feature/azure-client' into rmshaffer/azure-entrypoint
rmshaffer Jun 8, 2020
0164134
Merge branch 'feature/azure-client' into rmshaffer/azure-entrypoint
rmshaffer Jun 8, 2020
edbe96e
Add encoders for CloudJob and TargetStatus
rmshaffer Jun 8, 2020
1b27c4b
Move extension methods into encoder files
rmshaffer Jun 8, 2020
3dabab4
Change signature of CloudJob.ToDictionary
rmshaffer Jun 8, 2020
7c69473
Update histogram deserialization and add encoders
rmshaffer Jun 8, 2020
a9bc3d3
Use JsonConverter classes directly
rmshaffer Jun 8, 2020
c9dec08
Small cleanup
rmshaffer Jun 8, 2020
b92db91
Register single JsonEncoder
rmshaffer Jun 8, 2020
bb82ac4
Add a simple HTML histogram display based on StateVectorToHtmlResultE…
rmshaffer Jun 9, 2020
78886f7
Various improvements from PR suggestions
rmshaffer Jun 9, 2020
274ebde
Use UTC for dates returned to Python
rmshaffer Jun 9, 2020
9b3b74b
Use switch syntax for entryPointInput
Jun 10, 2020
eb8de48
Remove 'All rights reserved.'
Jun 10, 2020
0429635
Addressing PR feedback and compiler warnings
rmshaffer Jun 11, 2020
7e1146e
Merge branch 'rmshaffer/azure-entrypoint' into rmshaffer/azure-encoders
rmshaffer Jun 11, 2020
0737ebc
Merge branch 'feature/azure-client' into rmshaffer/azure-encoders
rmshaffer Jun 12, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build/test.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function Test-Python {
Write-Host "##[info]Testing Python inside $testFolder"
Push-Location (Join-Path $PSScriptRoot $testFolder)
python --version
pytest --log-level=DEBUG
pytest -v --log-level=DEBUG
Pop-Location

if ($LastExitCode -ne 0) {
Expand Down
78 changes: 41 additions & 37 deletions src/AzureClient/AzureClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,18 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.Azure.Quantum;
using Microsoft.Azure.Quantum.Client;
using Microsoft.Azure.Quantum.Client.Models;
using Microsoft.Azure.Quantum.Storage;
using Microsoft.Extensions.Logging;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Extensions.Msal;
using Microsoft.Jupyter.Core;
using Microsoft.Quantum.IQSharp.Common;
using Microsoft.Quantum.Simulation.Common;
using Microsoft.Rest.Azure;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.Quantum.IQSharp.AzureClient
{
Expand All @@ -47,6 +45,7 @@ private string ValidExecutionTargetsDisplayText
}

public AzureClient(
IExecutionEngine engine,
IReferences references,
IEntryPointGenerator entryPointGenerator,
ILogger<AzureClient> logger,
Expand All @@ -56,6 +55,16 @@ public AzureClient(
EntryPointGenerator = entryPointGenerator;
Logger = logger;
eventService?.TriggerServiceInitialized<IAzureClient>(this);

if (engine is BaseEngine baseEngine)
{
baseEngine.RegisterDisplayEncoder(new CloudJobToHtmlEncoder());
baseEngine.RegisterDisplayEncoder(new CloudJobToTextEncoder());
baseEngine.RegisterDisplayEncoder(new TargetStatusToHtmlEncoder());
baseEngine.RegisterDisplayEncoder(new TargetStatusToTextEncoder());
baseEngine.RegisterDisplayEncoder(new HistogramToHtmlEncoder());
baseEngine.RegisterDisplayEncoder(new HistogramToTextEncoder());
}
}

/// <inheritdoc/>
Expand Down Expand Up @@ -148,8 +157,7 @@ public async Task<ExecutionResult> ConnectAsync(IChannel channel,

channel.Stdout($"Connected to Azure Quantum workspace {QuantumClient.WorkspaceName}.");

// TODO: Add encoder for IEnumerable<TargetStatus> rather than calling ToJupyterTable() here directly.
return ValidExecutionTargets.ToJupyterTable().ToExecutionResult();
return ValidExecutionTargets.ToExecutionResult();
}

/// <inheritdoc/>
Expand All @@ -162,8 +170,7 @@ public async Task<ExecutionResult> GetConnectionStatusAsync(IChannel channel)

channel.Stdout($"Connected to Azure Quantum workspace {QuantumClient.WorkspaceName}.");

// TODO: Add encoder for IEnumerable<TargetStatus> rather than calling ToJupyterTable() here directly.
return ValidExecutionTargets.ToJupyterTable().ToExecutionResult();
return ValidExecutionTargets.ToExecutionResult();
}

private async Task<ExecutionResult> SubmitOrExecuteJobAsync(IChannel channel, string operationName, Dictionary<string, string> inputParameters, bool execute)
Expand All @@ -187,20 +194,20 @@ private async Task<ExecutionResult> SubmitOrExecuteJobAsync(IChannel channel, st
return AzureClientError.NoOperationName.ToExecutionResult();
}

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

channel.Stdout($"Submitting {operationName} to target {ActiveTarget.TargetName}...");
channel.Stdout($"Submitting {operationName} to target {ActiveTarget.TargetId}...");

IEntryPoint? entryPoint = null;
try
{
entryPoint = EntryPointGenerator.Generate(operationName, ActiveTarget.TargetName);
entryPoint = EntryPointGenerator.Generate(operationName, ActiveTarget.TargetId);
}
catch (UnsupportedOperationException e)
{
Expand Down Expand Up @@ -279,38 +286,38 @@ public async Task<ExecutionResult> GetActiveTargetAsync(IChannel channel)

if (ActiveTarget == null)
{
channel.Stderr("No execution target has been specified. To specify one, run:\n%azure.target <target name>");
channel.Stderr("No execution target has been specified. To specify one, run:\n%azure.target <target ID>");
channel.Stdout($"Available execution targets: {ValidExecutionTargetsDisplayText}");
return AzureClientError.NoTarget.ToExecutionResult();
}

channel.Stdout($"Current execution target: {ActiveTarget.TargetName}");
channel.Stdout($"Current execution target: {ActiveTarget.TargetId}");
channel.Stdout($"Available execution targets: {ValidExecutionTargetsDisplayText}");
return ActiveTarget.TargetName.ToExecutionResult();
return ActiveTarget.TargetId.ToExecutionResult();
}

/// <inheritdoc/>
public async Task<ExecutionResult> SetActiveTargetAsync(IChannel channel, string targetName)
public async Task<ExecutionResult> SetActiveTargetAsync(IChannel channel, string targetId)
{
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))
// Validate that this target is valid in the workspace.
if (!AvailableTargets.Any(target => targetId == target.Id))
{
channel.Stderr($"Target name {targetName} is not available in the current Azure Quantum workspace.");
channel.Stderr($"Target {targetId} 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);
// Validate that we know which package to load for this target.
var executionTarget = AzureExecutionTarget.Create(targetId);
if (executionTarget == null)
{
channel.Stderr($"Target name {targetName} does not support executing Q# jobs.");
channel.Stderr($"Target {targetId} does not support executing Q# jobs.");
channel.Stdout($"Available execution targets: {ValidExecutionTargetsDisplayText}");
return AzureClientError.InvalidTarget.ToExecutionResult();
}
Expand All @@ -321,7 +328,7 @@ public async Task<ExecutionResult> SetActiveTargetAsync(IChannel channel, string
channel.Stdout($"Loading package {ActiveTarget.PackageName} and dependencies...");
await References.AddPackage(ActiveTarget.PackageName);

return $"Active target is now {ActiveTarget.TargetName}".ToExecutionResult();
return $"Active target is now {ActiveTarget.TargetId}".ToExecutionResult();
}

/// <inheritdoc/>
Expand Down Expand Up @@ -357,19 +364,18 @@ public async Task<ExecutionResult> GetJobResultAsync(IChannel channel, string jo
return AzureClientError.JobNotCompleted.ToExecutionResult();
}

var stream = new MemoryStream();
await new JobStorageHelper(ConnectionString).DownloadJobOutputAsync(jobId, stream);
stream.Seek(0, SeekOrigin.Begin);
var output = new StreamReader(stream).ReadToEnd();
var deserializedOutput = JsonConvert.DeserializeObject<Dictionary<string, JToken>>(output);
var histogram = new Dictionary<string, double>();
foreach (var entry in deserializedOutput["histogram"] as JObject)
try
{
histogram[entry.Key] = entry.Value.ToObject<double>();
var request = WebRequest.Create(job.Details.OutputDataUri);
using var responseStream = request.GetResponse().GetResponseStream();
return responseStream.ToHistogram().ToExecutionResult();
}
catch (Exception e)
{
channel.Stderr($"Failed to retrieve results for job ID {jobId}.");
Logger?.LogError(e, $"Failed to download the job output for the specified Azure Quantum job: {e.Message}");
return AzureClientError.JobOutputDownloadFailed.ToExecutionResult();
}

// TODO: Add encoder to visualize IEnumerable<KeyValuePair<string, double>>
return histogram.ToExecutionResult();
}

/// <inheritdoc/>
Expand Down Expand Up @@ -399,8 +405,7 @@ public async Task<ExecutionResult> GetJobStatusAsync(IChannel channel, string jo
return AzureClientError.JobNotFound.ToExecutionResult();
}

// TODO: Add encoder for CloudJob rather than calling ToJupyterTable() here directly.
return job.ToJupyterTable().ToExecutionResult();
return job.ToExecutionResult();
}

/// <inheritdoc/>
Expand All @@ -419,8 +424,7 @@ public async Task<ExecutionResult> GetJobListAsync(IChannel channel)
return AzureClientError.JobNotFound.ToExecutionResult();
}

// TODO: Add encoder for IEnumerable<CloudJob> rather than calling ToJupyterTable() here directly.
return jobs.ToJupyterTable().ToExecutionResult();
return jobs.ToExecutionResult();
}

private async Task<CloudJob?> GetCloudJob(string jobId)
Expand Down
20 changes: 10 additions & 10 deletions src/AzureClient/AzureExecutionTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,28 @@ internal enum AzureProvider { IonQ, Honeywell, QCI }

internal class AzureExecutionTarget
{
public string TargetName { get; private set; }
public string PackageName => $"Microsoft.Quantum.Providers.{GetProvider(TargetName)}";
public string TargetId { get; private set; }
public string PackageName => $"Microsoft.Quantum.Providers.{GetProvider(TargetId)}";

public static bool IsValid(string targetName) => GetProvider(targetName) != null;
public static bool IsValid(string targetId) => GetProvider(targetId) != null;

public static AzureExecutionTarget? Create(string targetName) =>
IsValid(targetName)
? new AzureExecutionTarget() { TargetName = targetName }
public static AzureExecutionTarget? Create(string targetId) =>
IsValid(targetId)
? new AzureExecutionTarget() { TargetId = targetId }
: null;

/// <summary>
/// Gets the Azure Quantum provider corresponding to the given execution target.
/// </summary>
/// <param name="targetName">The Azure Quantum execution target name.</param>
/// <param name="targetId">The Azure Quantum execution target ID.</param>
/// <returns>The <see cref="AzureProvider"/> enum value representing the provider.</returns>
/// <remarks>
/// Valid target names are structured as "provider.target".
/// Valid target IDs are structured as "provider.target".
/// For example, "ionq.simulator" or "honeywell.qpu".
/// </remarks>
private static AzureProvider? GetProvider(string targetName)
private static AzureProvider? GetProvider(string targetId)
{
var parts = targetName.Split('.', 2);
var parts = targetId.Split('.', 2);
if (Enum.TryParse(parts[0], true, out AzureProvider provider))
{
return provider;
Expand Down
77 changes: 17 additions & 60 deletions src/AzureClient/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,12 @@
#nullable enable

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Quantum;
using Microsoft.Azure.Quantum.Client;
using Microsoft.Azure.Quantum.Client.Models;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Jupyter.Core;
using Microsoft.Quantum.Runtime;

namespace Microsoft.Quantum.IQSharp.AzureClient
{
Expand All @@ -37,7 +33,7 @@ public static void AddAzureClient(this IServiceCollection services)
/// <param name="azureClientError">
/// The result of an IAzureClient API call.
/// </param>
public static ExecutionResult ToExecutionResult(this AzureClientError azureClientError) =>
internal static ExecutionResult ToExecutionResult(this AzureClientError azureClientError) =>
new ExecutionResult
{
Status = ExecuteStatus.Error,
Expand All @@ -50,7 +46,7 @@ public static ExecutionResult ToExecutionResult(this AzureClientError azureClien
/// </summary>
/// <param name="azureClientError"></param>
/// <returns></returns>
public static string ToDescription(this AzureClientError azureClientError)
internal static string ToDescription(this AzureClientError azureClientError)
{
var attributes = azureClientError
.GetType()
Expand All @@ -65,60 +61,21 @@ public static string ToDescription(this AzureClientError azureClientError)
/// <param name="task">
/// A task which will return the result of an IAzureClient API call.
/// </param>
public static async Task<ExecutionResult> ToExecutionResult(this Task<AzureClientError> task) =>
internal static async Task<ExecutionResult> ToExecutionResult(this Task<AzureClientError> task) =>
(await task).ToExecutionResult();

internal static Table<CloudJob> ToJupyterTable(this CloudJob cloudJob) =>
new List<CloudJob> { cloudJob }.ToJupyterTable();

internal static Table<CloudJob> ToJupyterTable(this IEnumerable<CloudJob> jobsList) =>
new Table<CloudJob>
{
Columns = new List<(string, Func<CloudJob, string>)>
{
("JobId", cloudJob => cloudJob.Id),
("JobName", cloudJob => cloudJob.Details.Name),
("JobStatus", cloudJob => cloudJob.Status),
("Provider", cloudJob => cloudJob.Details.ProviderId),
("Target", cloudJob => cloudJob.Details.Target),
},
Rows = jobsList.ToList()
};

internal static Table<IQuantumMachineJob> ToJupyterTable(this IQuantumMachineJob job) =>
new Table<IQuantumMachineJob>
{
Columns = new List<(string, Func<IQuantumMachineJob, string>)>
{
("JobId", job => job.Id),
("JobStatus", job => job.Status),
},
Rows = new List<IQuantumMachineJob>() { job }
};

internal static Table<IQuantumClient> ToJupyterTable(this IQuantumClient quantumClient) =>
new Table<IQuantumClient>
{
Columns = new List<(string, Func<IQuantumClient, string>)>
{
("SubscriptionId", quantumClient => quantumClient.SubscriptionId),
("ResourceGroupName", quantumClient => quantumClient.ResourceGroupName),
("WorkspaceName", quantumClient => quantumClient.WorkspaceName),
},
Rows = new List<IQuantumClient>() { quantumClient }
};

internal static Table<TargetStatus> ToJupyterTable(this IEnumerable<TargetStatus> targets) =>
new Table<TargetStatus>
{
Columns = new List<(string, Func<TargetStatus, string>)>
{
("TargetId", target => target.Id),
("CurrentAvailability", target => target.CurrentAvailability),
("AverageQueueTime", target => target.AverageQueueTime.ToString()),
("StatusPage", target => target.StatusPage),
},
Rows = targets.ToList()
};
/// <summary>
/// Returns the provided argument as an enumeration of the specified type.
/// </summary>
/// <returns>
/// If the argument is already an <see cref="IEnumerable{T}"/> of the specified type,
/// the argument is returned. If the argument is of type <c>T</c>, then an
/// enumeration is returned with this argument as the only element.
/// Otherwise, null is returned.
/// </returns>
internal static IEnumerable<T>? AsEnumerableOf<T>(this object? source) =>
source is T singleton ? new List<T> { singleton } :
source is IEnumerable<T> collection ? collection :
null;
}
}
10 changes: 8 additions & 2 deletions src/AzureClient/IAzureClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ public enum AzureClientError
[Description(Resources.AzureClientErrorJobNotCompleted)]
JobNotCompleted,

/// <summary>
/// The job output failed to be downloaded from the Azure storage location.
/// </summary>
[Description(Resources.AzureClientErrorJobOutputDownloadFailed)]
JobOutputDownloadFailed,

/// <summary>
/// No Q# operation name was provided where one was required.
/// </summary>
Expand Down Expand Up @@ -139,13 +145,13 @@ public Task<ExecutionResult> ConnectAsync(IChannel channel,
/// <returns>
/// Success if the target is valid, or an error if the target cannot be set.
/// </returns>
public Task<ExecutionResult> SetActiveTargetAsync(IChannel channel, string targetName);
public Task<ExecutionResult> SetActiveTargetAsync(IChannel channel, string targetId);

/// <summary>
/// Gets the currently specified target for job submission.
/// </summary>
/// <returns>
/// The target name.
/// The target ID.
/// </returns>
public Task<ExecutionResult> GetActiveTargetAsync(IChannel channel);

Expand Down
Loading