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
41 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
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
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
185 changes: 124 additions & 61 deletions src/AzureClient/AzureClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Linq;
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.Simulation.Core;
using Microsoft.Quantum.IQSharp.Common;
using Microsoft.Quantum.Simulation.Common;
using Microsoft.Rest.Azure;
using Microsoft.Azure.Quantum.Client.Models;
using Microsoft.Azure.Quantum.Storage;
using Microsoft.Azure.Quantum;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

Expand All @@ -25,6 +27,9 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
/// <inheritdoc/>
public class AzureClient : IAzureClient
{
private ILogger<AzureClient> Logger { get; }
private IReferences References { get; }
private IEntryPointGenerator EntryPointGenerator { get; }
private string ConnectionString { get; set; } = string.Empty;
private AzureExecutionTarget? ActiveTarget { get; set; }
private AuthenticationResult? AuthenticationResult { get; set; }
Expand All @@ -41,10 +46,20 @@ private string ValidExecutionTargetsDisplayText
: string.Join(", ", ValidExecutionTargets.Select(target => target.Id));
}

public AzureClient(
IReferences references,
IEntryPointGenerator entryPointGenerator,
ILogger<AzureClient> logger,
IEventService eventService)
{
References = references;
EntryPointGenerator = entryPointGenerator;
Logger = logger;
eventService?.TriggerServiceInitialized<IAzureClient>(this);
}

/// <inheritdoc/>
public async Task<ExecutionResult> ConnectAsync(
IChannel channel,
public async Task<ExecutionResult> ConnectAsync(IChannel channel,
string subscriptionId,
string resourceGroupName,
string workspaceName,
Expand Down Expand Up @@ -127,7 +142,7 @@ public async Task<ExecutionResult> ConnectAsync(
}
catch (Exception e)
{
channel.Stderr(e.ToString());
Logger?.LogError(e, $"Failed to download providers list from Azure Quantum workspace: {e.Message}");
return AzureClientError.WorkspaceNotFound.ToExecutionResult();
}

Expand All @@ -151,11 +166,7 @@ public async Task<ExecutionResult> GetConnectionStatusAsync(IChannel channel)
return ValidExecutionTargets.ToJupyterTable().ToExecutionResult();
}

private async Task<ExecutionResult> SubmitOrExecuteJobAsync(
IChannel channel,
IOperationResolver operationResolver,
string operationName,
bool execute)
private async Task<ExecutionResult> SubmitOrExecuteJobAsync(IChannel channel, string operationName, Dictionary<string, string> inputParameters, bool execute)
{
if (ActiveWorkspace == null)
{
Expand All @@ -176,57 +187,89 @@ private async Task<ExecutionResult> SubmitOrExecuteJobAsync(
return AzureClientError.NoOperationName.ToExecutionResult();
}

var machine = Azure.Quantum.QuantumMachineFactory.CreateMachine(ActiveWorkspace, ActiveTarget.TargetName, ConnectionString);
var machine = QuantumMachineFactory.CreateMachine(ActiveWorkspace, ActiveTarget.TargetName, 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}.");
return AzureClientError.InvalidTarget.ToExecutionResult();
}

var operationInfo = operationResolver.Resolve(operationName);
var entryPointInfo = new EntryPointInfo<QVoid, Result>(operationInfo.RoslynType);
var entryPointInput = QVoid.Instance;
channel.Stdout($"Submitting {operationName} to target {ActiveTarget.TargetName}...");

if (execute)
IEntryPoint? entryPoint = null;
try
{
channel.Stdout($"Executing {operationName} on target {ActiveTarget.TargetName}...");
var output = await machine.ExecuteAsync(entryPointInfo, entryPointInput);
MostRecentJobId = output.Job.Id;

// TODO: Add encoder to visualize IEnumerable<KeyValuePair<string, double>>
return output.Histogram.ToExecutionResult();
entryPoint = EntryPointGenerator.Generate(operationName, ActiveTarget.TargetName);
}
else
catch (UnsupportedOperationException e)
{
channel.Stdout($"Submitting {operationName} to target {ActiveTarget.TargetName}...");
var job = await machine.SubmitAsync(entryPointInfo, entryPointInput);
channel.Stdout($"Job {job.Id} submitted successfully.");
channel.Stderr($"{operationName} is not a recognized Q# operation name.");
return AzureClientError.UnrecognizedOperationName.ToExecutionResult();
}
catch (CompilationErrorsException e)
{
channel.Stderr($"The Q# operation {operationName} could not be compiled as an entry point for job execution.");
foreach (var message in e.Errors) channel.Stderr(message);
return AzureClientError.InvalidEntryPoint.ToExecutionResult();
}

try
{
var job = await entryPoint.SubmitAsync(machine, inputParameters);
channel.Stdout($"Job {job.Id} submitted successfully.");
MostRecentJobId = job.Id;
}
catch (ArgumentException e)
{
channel.Stderr($"Failed to parse all expected parameters for Q# operation {operationName}.");
channel.Stderr(e.Message);
return AzureClientError.JobSubmissionFailed.ToExecutionResult();
}
catch (Exception e)
{
channel.Stderr($"Failed to submit Q# operation {operationName} for execution.");
channel.Stderr(e.InnerException?.Message ?? e.Message);
return AzureClientError.JobSubmissionFailed.ToExecutionResult();
}

// TODO: Add encoder for IQuantumMachineJob rather than calling ToJupyterTable() here.
return job.ToJupyterTable().ToExecutionResult();
if (!execute)
{
return await GetJobStatusAsync(channel, MostRecentJobId);
}

var timeoutInSeconds = 30;
channel.Stdout($"Waiting up to {timeoutInSeconds} seconds for Azure Quantum job to complete...");

using (var cts = new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(30)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this timeout be configurable? That may be doable with the %config magic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! In fact I have made it configurable in #161.

{
CloudJob? cloudJob = null;
do
{
// TODO: Once jupyter-core supports interrupt requests (https://github.com/microsoft/jupyter-core/issues/55),
// handle Jupyter kernel interrupt here and break out of this loop
var pollingIntervalInSeconds = 5;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, this is configurable in #161. Reading my mind...

await Task.Delay(TimeSpan.FromSeconds(pollingIntervalInSeconds));
if (cts.IsCancellationRequested) break;
cloudJob = await GetCloudJob(MostRecentJobId);
channel.Stdout($"[{DateTime.Now.ToLongTimeString()}] Current job status: {cloudJob?.Status ?? "Unknown"}");
}
while (cloudJob == null || cloudJob.InProgress);
}

return await GetJobResultAsync(channel, MostRecentJobId);
}

/// <inheritdoc/>
public async Task<ExecutionResult> SubmitJobAsync(
IChannel channel,
IOperationResolver operationResolver,
string operationName) =>
await SubmitOrExecuteJobAsync(channel, operationResolver, operationName, execute: false);
public async Task<ExecutionResult> SubmitJobAsync(IChannel channel, string operationName, Dictionary<string, string> inputParameters) =>
await SubmitOrExecuteJobAsync(channel, operationName, inputParameters, execute: false);

/// <inheritdoc/>
public async Task<ExecutionResult> ExecuteJobAsync(
IChannel channel,
IOperationResolver operationResolver,
string operationName) =>
await SubmitOrExecuteJobAsync(channel, operationResolver, operationName, execute: true);
public async Task<ExecutionResult> ExecuteJobAsync(IChannel channel, string operationName, Dictionary<string, string> inputParameters) =>
await SubmitOrExecuteJobAsync(channel, operationName, inputParameters, execute: true);

/// <inheritdoc/>
public async Task<ExecutionResult> GetActiveTargetAsync(
IChannel channel)
public async Task<ExecutionResult> GetActiveTargetAsync(IChannel channel)
{
if (AvailableProviders == null)
{
Expand All @@ -247,10 +290,7 @@ public async Task<ExecutionResult> GetActiveTargetAsync(
}

/// <inheritdoc/>
public async Task<ExecutionResult> SetActiveTargetAsync(
IChannel channel,
IReferences references,
string targetName)
public async Task<ExecutionResult> SetActiveTargetAsync(IChannel channel, string targetName)
{
if (AvailableProviders == null)
{
Expand Down Expand Up @@ -279,15 +319,13 @@ public async Task<ExecutionResult> SetActiveTargetAsync(
ActiveTarget = executionTarget;

channel.Stdout($"Loading package {ActiveTarget.PackageName} and dependencies...");
await references.AddPackage(ActiveTarget.PackageName);
await References.AddPackage(ActiveTarget.PackageName);

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

/// <inheritdoc/>
public async Task<ExecutionResult> GetJobResultAsync(
IChannel channel,
string jobId)
public async Task<ExecutionResult> GetJobResultAsync(IChannel channel, string jobId)
{
if (ActiveWorkspace == null)
{
Expand All @@ -306,7 +344,7 @@ public async Task<ExecutionResult> GetJobResultAsync(
jobId = MostRecentJobId;
}

var job = ActiveWorkspace.GetJob(jobId);
var job = await GetCloudJob(jobId);
if (job == null)
{
channel.Stderr($"Job ID {jobId} not found in current Azure Quantum workspace.");
Expand Down Expand Up @@ -335,9 +373,7 @@ public async Task<ExecutionResult> GetJobResultAsync(
}

/// <inheritdoc/>
public async Task<ExecutionResult> GetJobStatusAsync(
IChannel channel,
string jobId)
public async Task<ExecutionResult> GetJobStatusAsync(IChannel channel, string jobId)
{
if (ActiveWorkspace == null)
{
Expand All @@ -356,36 +392,63 @@ public async Task<ExecutionResult> GetJobStatusAsync(
jobId = MostRecentJobId;
}

var job = ActiveWorkspace.GetJob(jobId);
var job = await GetCloudJob(jobId);
if (job == null)
{
channel.Stderr($"Job ID {jobId} not found in current Azure Quantum workspace.");
return AzureClientError.JobNotFound.ToExecutionResult();
}

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

/// <inheritdoc/>
public async Task<ExecutionResult> GetJobListAsync(
IChannel channel)
public async Task<ExecutionResult> GetJobListAsync(IChannel channel)
{
if (ActiveWorkspace == null)
{
channel.Stderr("Please call %azure.connect before listing jobs.");
return AzureClientError.NotConnected.ToExecutionResult();
}

var jobs = ActiveWorkspace.ListJobs();
var jobs = await GetCloudJobs();
if (jobs == null || jobs.Count() == 0)
{
channel.Stderr("No jobs found in current Azure Quantum workspace.");
return AzureClientError.JobNotFound.ToExecutionResult();
}

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

private async Task<CloudJob?> GetCloudJob(string jobId)
{
try
{
return await ActiveWorkspace.GetJobAsync(jobId);
}
catch (Exception e)
{
Logger?.LogError(e, $"Failed to retrieve the specified Azure Quantum job: {e.Message}");
}

return null;
}

private async Task<IEnumerable<CloudJob>?> GetCloudJobs()
{
try
{
return await ActiveWorkspace.ListJobsAsync();
}
catch (Exception e)
{
Logger?.LogError(e, $"Failed to retrieve the list of jobs from the Azure Quantum workspace: {e.Message}");
}

return null;
}
}
}
2 changes: 1 addition & 1 deletion src/AzureClient/AzureClient.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Azure.Quantum.Client" Version="0.11.2006.207" />
<PackageReference Include="Microsoft.Azure.Quantum.Client" Version="0.11.2006.403" />
<PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.21" />
<PackageReference Include="Microsoft.Rest.ClientRuntime.Azure" Version="3.3.19" />
<PackageReference Include="System.Reactive" Version="4.3.2" />
Expand Down
Loading