diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs index b9f1afdec73..e138a12d6a1 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs @@ -481,6 +481,9 @@ let ``Supports default custom simulator`` () = given ["--simulator"; typeof.FullName; "--use-h"; "false"] |> fails +// TODO: Add tests for the "submit" command. + + // Help [] @@ -501,8 +504,7 @@ Options: -?, -h, --help Show help and usage information Commands: - simulate (default) Run the program using a local simulator. - submit Submit the program to Azure Quantum." + simulate (default) Run the program using a local simulator." let given = test 31 given ["--help"] |> yields message diff --git a/src/Simulation/EntryPointDriver/Azure.cs b/src/Simulation/EntryPointDriver/Azure.cs new file mode 100644 index 00000000000..b9edb7c5477 --- /dev/null +++ b/src/Simulation/EntryPointDriver/Azure.cs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.CommandLine.Parsing; +using System.Threading.Tasks; +using Microsoft.Azure.Quantum; +using Microsoft.Quantum.Runtime; + +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver +{ + /// + /// Provides entry point submission to Azure Quantum. + /// + internal static class Azure + { + /// + /// Submits the entry point to Azure Quantum. + /// + /// The entry point. + /// The command-line parsing result. + /// The submission settings. + /// The entry point's argument type. + /// The entry point's return type. + internal static async Task Submit( + IEntryPoint entryPoint, ParseResult parseResult, AzureSettings settings) + { + var machine = CreateMachine(settings); + if (machine is null) + { + DisplayUnknownTargetError(settings.Target); + return 1; + } + + // TODO: Specify the number of shots. The IQuantumMachine interface should be updated. + var job = await machine.SubmitAsync(entryPoint.Info, entryPoint.CreateArgument(parseResult)); + switch (settings.Output) + { + case OutputFormat.FriendlyUri: + Console.WriteLine("Job submitted. To track your job status and see the results use:"); + Console.WriteLine(); + // TODO: Show the friendly URI. The friendly URI is not yet available from the job. + Console.WriteLine(job.Id); + break; + case OutputFormat.Id: + Console.WriteLine(job.Id); + break; + default: + throw new ArgumentOutOfRangeException($"Invalid output format '{settings.Output}'."); + } + return 0; + } + + /// + /// Creates a quantum machine based on the Azure Quantum submission settings. + /// + /// The Azure Quantum submission settings. + /// A quantum machine. + private static IQuantumMachine? CreateMachine(AzureSettings settings) => + settings.Target == "nothing" + ? new NothingMachine() + : QuantumMachineFactory.CreateMachine(settings.CreateWorkspace(), settings.Target, settings.Storage); + + /// + /// Displays an error message for attempting to use an unknown target machine. + /// + /// The target machine. + private static void DisplayUnknownTargetError(string? target) + { + var originalForeground = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.WriteLine($"The target '{target}' was not recognized."); + Console.ForegroundColor = originalForeground; + } + } + + /// + /// The information to show in the output after the job is submitted. + /// + internal enum OutputFormat + { + /// + /// Show a friendly message with a URI that can be used to see the job results. + /// + FriendlyUri, + + /// + /// Show only the job ID. + /// + Id + } + + /// + /// Settings for a submission to Azure Quantum. + /// + internal sealed class AzureSettings + { + /// + /// The target device ID. + /// + public string? Target { get; set; } + + /// + /// The storage account connection string. + /// + public string? Storage { get; set; } + + /// + /// The subscription ID. + /// + public string? Subscription { get; set; } + + /// + /// The resource group name. + /// + public string? ResourceGroup { get; set; } + + /// + /// The workspace name. + /// + public string? Workspace { get; set; } + + /// + /// The Azure Active Directory authentication token. + /// + public string? AadToken { get; set; } + + /// + /// The base URI of the Azure Quantum endpoint. + /// + public Uri? BaseUri { get; set; } + + /// + /// The number of times the program is executed on the target machine. + /// + public int Shots { get; set; } + + /// + /// The information to show in the output after the job is submitted. + /// + public OutputFormat Output { get; set; } + + /// + /// Creates a based on the settings. + /// + /// The based on the settings. + internal Workspace CreateWorkspace() => + AadToken is null + ? new Workspace(Subscription, ResourceGroup, Workspace, baseUri: BaseUri) + : new Workspace(Subscription, ResourceGroup, Workspace, AadToken, BaseUri); + } +} diff --git a/src/Simulation/EntryPointDriver/Driver.cs b/src/Simulation/EntryPointDriver/Driver.cs index 118d7031dbf..13ea4c1ee61 100644 --- a/src/Simulation/EntryPointDriver/Driver.cs +++ b/src/Simulation/EntryPointDriver/Driver.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; using System.CommandLine; using System.CommandLine.Builder; using System.CommandLine.Help; @@ -12,7 +11,6 @@ using System.Threading.Tasks; using Microsoft.Quantum.QsCompiler.ReservedKeywords; using Microsoft.Quantum.Simulation.Core; -using Microsoft.Quantum.Simulation.Simulators; using static Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Driver; namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver @@ -20,6 +18,9 @@ namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver /// /// The entry point driver is the entry point for the C# application that executes the Q# entry point. /// + /// The entry point's callable type. + /// The entry point's argument type. + /// The entry point's return type. public sealed class Driver where TCallable : AbstractCallable, ICallable { /// @@ -27,11 +28,34 @@ public sealed class Driver where TCallable : AbstractCalla /// private readonly IEntryPoint entryPoint; + /// + /// The simulator option. + /// + private OptionInfo SimulatorOption { get; } + /// /// Creates a new driver for the entry point. /// /// The entry point. - public Driver(IEntryPoint entryPoint) => this.entryPoint = entryPoint; + public Driver(IEntryPoint entryPoint) + { + this.entryPoint = entryPoint; + SimulatorOption = new OptionInfo( + new[] + { + "--" + CommandLineArguments.SimulatorOption.Item1, + "-" + CommandLineArguments.SimulatorOption.Item2 + }, + entryPoint.DefaultSimulator, + "The name of the simulator to use.", + suggestions: new[] + { + AssemblyConstants.QuantumSimulator, + AssemblyConstants.ToffoliSimulator, + AssemblyConstants.ResourcesEstimator, + entryPoint.DefaultSimulator + }); + } /// /// Runs the entry point using the command-line arguments. @@ -44,26 +68,34 @@ public async Task Run(string[] args) { Handler = CommandHandler.Create(Simulate) }; - TryCreateOption( - SimulatorOptions, - () => entryPoint.DefaultSimulator, - "The name of the simulator to use.") - .Then(simulator => simulate.AddOption(simulator.WithSuggestions( - AssemblyConstants.QuantumSimulator, - AssemblyConstants.ToffoliSimulator, - AssemblyConstants.ResourcesEstimator, - entryPoint.DefaultSimulator))); + AddOptionIfAvailable(simulate, SimulatorOption); var submit = new Command("submit", "Submit the program to Azure Quantum.") { - Handler = CommandHandler.Create(Submit) + IsHidden = true, + Handler = CommandHandler.Create(Submit) }; + AddOptionIfAvailable(submit, TargetOption); + AddOptionIfAvailable(submit, StorageOption); + AddOptionIfAvailable(submit, SubscriptionOption); + AddOptionIfAvailable(submit, ResourceGroupOption); + AddOptionIfAvailable(submit, WorkspaceOption); + AddOptionIfAvailable(submit, AadTokenOption); + AddOptionIfAvailable(submit, BaseUriOption); + AddOptionIfAvailable(submit, OutputOption); + AddOptionIfAvailable(submit, ShotsOption); var root = new RootCommand(entryPoint.Summary) { simulate, submit }; - foreach (var option in entryPoint.Options) { root.AddGlobalOption(option); } + foreach (var option in entryPoint.Options) + { + root.AddGlobalOption(option); + } // Set the simulate command as the default. - foreach (var option in simulate.Options) { root.AddOption(option); } + foreach (var option in simulate.Options) + { + root.AddOption(option); + } root.Handler = simulate.Handler; return await new CommandLineBuilder(root) @@ -79,74 +111,29 @@ public async Task Run(string[] args) /// The command-line parsing result. /// The simulator to use. /// The exit code. - private async Task Simulate(ParseResult parseResult, string simulator) - { - simulator = DefaultIfShadowed(SimulatorOptions.First(), simulator, entryPoint.DefaultSimulator); - if (simulator == AssemblyConstants.ResourcesEstimator) - { - var resourcesEstimator = new ResourcesEstimator(); - await resourcesEstimator.Run(entryPoint.CreateArgument(parseResult)); - Console.WriteLine(resourcesEstimator.ToTSV()); - } - else - { - var (isCustom, createSimulator) = - simulator == AssemblyConstants.QuantumSimulator - ? (false, () => new QuantumSimulator()) - : simulator == AssemblyConstants.ToffoliSimulator - ? (false, new Func(() => new ToffoliSimulator())) - : (true, entryPoint.CreateDefaultCustomSimulator); - if (isCustom && simulator != entryPoint.DefaultSimulator) - { - DisplayCustomSimulatorError(simulator); - return 1; - } - await RunSimulator(parseResult, createSimulator); - } - return 0; - } + private async Task Simulate(ParseResult parseResult, string simulator) => + await Simulation.Simulate( + entryPoint, parseResult, DefaultIfShadowed(SimulatorOption, simulator)); /// /// Submits the entry point to Azure Quantum. /// /// The command-line parsing result. - private async Task Submit(ParseResult parseResult) - { - // TODO: Use an actual quantum machine. - var machine = new SimulatorMachine(); - var output = await machine.ExecuteAsync(entryPoint.Info, entryPoint.CreateArgument(parseResult)); - // TODO: Provide output options and show the most frequent output by default. - foreach (var (result, frequency) in output.Histogram) + /// The submission settings. + private async Task Submit(ParseResult parseResult, AzureSettings settings) => + await Azure.Submit(entryPoint, parseResult, new AzureSettings { - Console.WriteLine($"{result} (frequency = {frequency})"); - } - } - - /// - /// Runs the entry point on a simulator and displays its return value. - /// - /// The command-line parsing result. - /// A function that creates an instance of the simulator to use. - private async Task RunSimulator(ParseResult parseResult, Func createSimulator) - { - var simulator = createSimulator(); - try - { - var value = await simulator.Run(entryPoint.CreateArgument(parseResult)); - if (!(value is QVoid)) - { - Console.WriteLine(value); - } - } - finally - { - if (simulator is IDisposable disposable) - { - disposable.Dispose(); - } - } - } - + Target = settings.Target, + Storage = settings.Storage, + Subscription = settings.Subscription, + ResourceGroup = settings.ResourceGroup, + Workspace = settings.Workspace, + AadToken = DefaultIfShadowed(AadTokenOption, settings.AadToken), + BaseUri = DefaultIfShadowed(BaseUriOption, settings.BaseUri), + Shots = DefaultIfShadowed(ShotsOption, settings.Shots), + Output = DefaultIfShadowed(OutputOption, settings.Output) + }); + /// /// Returns true if the alias is not already used by an entry point option. /// @@ -156,17 +143,16 @@ private bool IsAliasAvailable(string alias) => !entryPoint.Options.SelectMany(option => option.RawAliases).Contains(alias); /// - /// Returns the default value and displays a warning if the alias is shadowed by an entry point option, and - /// returns the original value otherwise. + /// Returns the default value and displays a warning if the primary (first) alias is shadowed by an entry point + /// option, and returns the original value otherwise. /// /// The type of the option values. - /// The primary option alias corresponding to the value. + /// The option. /// The value of the option given on the command line. - /// The default value for the option. /// The default value or the original value. - private T DefaultIfShadowed(string alias, T value, T defaultValue) + private T DefaultIfShadowed(OptionInfo option, T value) { - if (IsAliasAvailable(alias)) + if (IsAliasAvailable(option.Aliases.First())) { return value; } @@ -175,67 +161,104 @@ private T DefaultIfShadowed(string alias, T value, T defaultValue) var originalForeground = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Yellow; Console.Error.WriteLine( - $"Warning: Option {alias} is overridden by an entry point parameter name. " + - $"Using default value {defaultValue}."); + $"Warning: Option {option.Aliases.First()} is overridden by an entry point parameter name. " + + $"Using default value {option.DefaultValue}."); Console.ForegroundColor = originalForeground; - return defaultValue; + return option.DefaultValue; } } /// - /// Tries to create an option by removing aliases that are already in use by the entry point. If the first - /// alias, which is considered the primary alias, is in use, then the option is not created. + /// Adds the option to the command using only the aliases that are available, and only if the primary (first) + /// alias is available. If a required option is not available, the command is disabled. /// + /// The command to add the option to. + /// The option to add. /// The type of the option's argument. - /// The option's aliases. - /// A function that returns the option's default value. - /// The option's description. - /// A validation of the option. - private Validation> TryCreateOption( - IReadOnlyCollection aliases, Func getDefaultValue, string? description = null) => - IsAliasAvailable(aliases.First()) - ? Validation>.Success( - new Option( - aliases.Where(IsAliasAvailable).ToArray(), - getDefaultValue, - description)) - : Validation>.Failure(); - - /// - /// Displays an error message for using a non-default custom simulator. - /// - /// The name of the custom simulator. - private static void DisplayCustomSimulatorError(string name) + private void AddOptionIfAvailable(Command command, OptionInfo option) { - var originalForeground = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Red; - Console.Error.WriteLine($"The simulator '{name}' could not be found."); - Console.ForegroundColor = originalForeground; - Console.Error.WriteLine(); - Console.Error.WriteLine( - $"If '{name}' is a custom simulator, it must be set in the DefaultSimulator project property:"); - Console.Error.WriteLine(); - Console.Error.WriteLine(""); - Console.Error.WriteLine($" {name}"); - Console.Error.WriteLine(""); + if (IsAliasAvailable(option.Aliases.First())) + { + command.AddOption(option.Create(option.Aliases.Where(IsAliasAvailable))); + } + else if (option.Required) + { + command.AddValidator(commandResult => + $"The required option {option.Aliases.First()} conflicts with an entry point parameter name."); + } } } /// - /// Static members for that do not depend on its type parameters. + /// Static members for . /// internal static class Driver { + // TODO: Define the aliases as constants. + /// - /// The option aliases for the simulator option. + /// The target option. /// - internal static readonly IReadOnlyCollection SimulatorOptions = Array.AsReadOnly(new[] - { - "--" + CommandLineArguments.SimulatorOption.Item1, - "-" + CommandLineArguments.SimulatorOption.Item2 - }); + internal static readonly OptionInfo TargetOption = new OptionInfo( + new[] { "--target" }, "The target device ID."); + + /// + /// The storage option. + /// + internal static readonly OptionInfo StorageOption = new OptionInfo( + new[] { "--storage" }, "The storage account connection string."); + + /// + /// The subscription option. + /// + internal static readonly OptionInfo SubscriptionOption = new OptionInfo( + new[] { "--subscription" }, "The subscription ID."); + + /// + /// The resource group option. + /// + internal static readonly OptionInfo ResourceGroupOption = new OptionInfo( + new[] { "--resource-group" }, "The resource group name."); + + /// + /// The workspace option. + /// + internal static readonly OptionInfo WorkspaceOption = new OptionInfo( + new[] { "--workspace" }, "The workspace name."); + + /// + /// The AAD token option. + /// + internal static readonly OptionInfo AadTokenOption = new OptionInfo( + new[] { "--aad-token" }, default, "The Azure Active Directory authentication token."); + + /// + /// The base URI option. + /// + internal static readonly OptionInfo BaseUriOption = new OptionInfo( + new[] { "--base-uri" }, default, "The base URI of the Azure Quantum endpoint."); + + /// + /// The shots option. + /// + internal static readonly OptionInfo ShotsOption = new OptionInfo( + new[] { "--shots" }, + 500, + "The number of times the program is executed on the target machine.", + validator: result => + int.TryParse(result.Tokens.SingleOrDefault()?.Value, out var value) && value <= 0 + ? "The number of shots must be a positive number." + : default); + + /// + /// The output option. + /// + internal static readonly OptionInfo OutputOption = new OptionInfo( + new[] { "--output" }, + OutputFormat.FriendlyUri, + "The information to show in the output after the job is submitted."); } - + /// /// A modification of the command-line class. /// diff --git a/src/Simulation/EntryPointDriver/EntryPointDriver.csproj b/src/Simulation/EntryPointDriver/EntryPointDriver.csproj index 7760104f92a..6125aa21b4d 100644 --- a/src/Simulation/EntryPointDriver/EntryPointDriver.csproj +++ b/src/Simulation/EntryPointDriver/EntryPointDriver.csproj @@ -15,6 +15,7 @@ + diff --git a/src/Simulation/EntryPointDriver/IEntryPoint.cs b/src/Simulation/EntryPointDriver/IEntryPoint.cs index 8ab574bd0dc..65250d086d3 100644 --- a/src/Simulation/EntryPointDriver/IEntryPoint.cs +++ b/src/Simulation/EntryPointDriver/IEntryPoint.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; using System.Collections.Generic; using System.CommandLine; using System.CommandLine.Parsing; diff --git a/src/Simulation/EntryPointDriver/NothingMachine.cs b/src/Simulation/EntryPointDriver/NothingMachine.cs new file mode 100644 index 00000000000..193150b7a64 --- /dev/null +++ b/src/Simulation/EntryPointDriver/NothingMachine.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Quantum.Runtime; +using Microsoft.Quantum.Simulation.Core; + +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver +{ + /// + /// A quantum machine that does nothing. + /// + internal class NothingMachine : IQuantumMachine + { + public string ProviderId => nameof(NothingMachine); + + public string Target => "Nothing"; + + public Task> ExecuteAsync(EntryPointInfo info, TIn input) => + throw new NotSupportedException(); + + public Task SubmitAsync(EntryPointInfo info, TIn input) => + Task.FromResult(new DefaultJob()); + + /// + /// A quantum machine job with default properties. + /// + private class DefaultJob : IQuantumMachineJob + { + public string Id { get; } = "00000000-0000-0000-0000-0000000000000"; + + public string Status { get; } = "NotImplemented"; + + public bool InProgress { get; } = false; + + public bool Succeeded { get; } = false; + + public bool Failed { get; } = true; + + public Uri Uri => new Uri("https://example.com/" + Id); + + public Task CancelAsync(CancellationToken cancellationToken = default) => throw new NotSupportedException(); + + public Task RefreshAsync(CancellationToken cancellationToken = default) => + throw new NotSupportedException(); + } + } +} diff --git a/src/Simulation/EntryPointDriver/OptionInfo.cs b/src/Simulation/EntryPointDriver/OptionInfo.cs new file mode 100644 index 00000000000..7f96ec423de --- /dev/null +++ b/src/Simulation/EntryPointDriver/OptionInfo.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.Parsing; +using System.Linq; + +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver +{ + /// + /// Information about a command-line option. + /// + /// The type of the option's argument. + internal sealed class OptionInfo + { + /// + /// The option aliases. + /// + internal IReadOnlyCollection Aliases { get; } + + /// + /// The option's default value if the option has one. + /// + /// Thrown if the option does not have a default value. + internal T DefaultValue => defaultValue(); + + /// + /// True if the option is required. + /// + internal bool Required { get; } + + /// + /// A function that returns the default value or throws a . + /// + private readonly Func defaultValue; + + /// + /// The option description. + /// + private readonly string description; + + /// + /// The option suggestions. + /// + private readonly IEnumerable? suggestions; + + /// + /// The option validator. + /// + private readonly ValidateSymbol? validator; + + /// + /// Creates an for a non-required option. + /// + /// The option aliases. + /// The option's default value. + /// The option description. + /// The option suggestions. + /// The option validator. + internal OptionInfo( + IReadOnlyCollection aliases, + T defaultValue, + string description, + IEnumerable? suggestions = default, + ValidateSymbol? validator = default) + { + Aliases = aliases; + Required = false; + this.defaultValue = () => defaultValue; + this.description = description; + this.suggestions = suggestions; + this.validator = validator; + } + + /// + /// Creates an for a required option. + /// + /// The option aliases. + /// The option description. + /// The option suggestions. + /// The option validator. + internal OptionInfo( + IReadOnlyCollection aliases, + string description, + IEnumerable? suggestions = default, + ValidateSymbol? validator = default) + { + Aliases = aliases; + Required = true; + defaultValue = () => throw new NotSupportedException("The option does not have a default value."); + this.description = description; + this.suggestions = suggestions; + this.validator = validator; + } + + /// + /// Creates an option based on the information in the . + /// + /// The option aliases. + /// The option. + internal Option Create(IEnumerable aliases) + { + var option = Required + ? new Option(aliases.ToArray(), description) { Required = true } + : new Option(aliases.ToArray(), defaultValue, description); + if (!(validator is null)) + { + option.AddValidator(validator); + } + return suggestions is null ? option : option.WithSuggestions(suggestions.ToArray()); + } + } +} diff --git a/src/Simulation/EntryPointDriver/Options.cs b/src/Simulation/EntryPointDriver/Options.cs index 75a6149388e..fb64d2dce5c 100644 --- a/src/Simulation/EntryPointDriver/Options.cs +++ b/src/Simulation/EntryPointDriver/Options.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; using System.Collections.Generic; using System.CommandLine; using System.Linq; diff --git a/src/Simulation/EntryPointDriver/Simulation.cs b/src/Simulation/EntryPointDriver/Simulation.cs new file mode 100644 index 00000000000..f9d533ae0e7 --- /dev/null +++ b/src/Simulation/EntryPointDriver/Simulation.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.CommandLine.Parsing; +using System.Threading.Tasks; +using Microsoft.Quantum.QsCompiler.ReservedKeywords; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Simulators; + +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver +{ + /// + /// Provides entry point simulation. + /// + /// The entry point's callable type. + /// The entry point's argument type. + /// The entry point's return type. + internal static class Simulation where TCallable : AbstractCallable, ICallable + { + /// + /// Simulates the entry point. + /// + /// The entry point. + /// The command-line parsing result. + /// The simulator to use. + /// The exit code. + internal static async Task Simulate( + IEntryPoint entryPoint, ParseResult parseResult, string simulator) + { + if (simulator == AssemblyConstants.ResourcesEstimator) + { + var resourcesEstimator = new ResourcesEstimator(); + await resourcesEstimator.Run(entryPoint.CreateArgument(parseResult)); + Console.WriteLine(resourcesEstimator.ToTSV()); + } + else + { + var (isCustom, createSimulator) = + simulator == AssemblyConstants.QuantumSimulator + ? (false, () => new QuantumSimulator()) + : simulator == AssemblyConstants.ToffoliSimulator + ? (false, new Func(() => new ToffoliSimulator())) + : (true, entryPoint.CreateDefaultCustomSimulator); + if (isCustom && simulator != entryPoint.DefaultSimulator) + { + DisplayCustomSimulatorError(simulator); + return 1; + } + await RunSimulator(entryPoint, parseResult, createSimulator); + } + return 0; + } + + /// + /// Runs the entry point on a simulator and displays its return value. + /// + /// The entry point. + /// The command-line parsing result. + /// A function that creates an instance of the simulator to use. + private static async Task RunSimulator( + IEntryPoint entryPoint, ParseResult parseResult, Func createSimulator) + { + var simulator = createSimulator(); + try + { + var value = await simulator.Run(entryPoint.CreateArgument(parseResult)); + if (!(value is QVoid)) + { + Console.WriteLine(value); + } + } + finally + { + if (simulator is IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + /// + /// Displays an error message for using a non-default custom simulator. + /// + /// The name of the custom simulator. + private static void DisplayCustomSimulatorError(string name) + { + var originalForeground = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.WriteLine($"The simulator '{name}' could not be found."); + Console.ForegroundColor = originalForeground; + Console.Error.WriteLine(); + Console.Error.WriteLine( + $"If '{name}' is a custom simulator, it must be set in the DefaultSimulator project property:"); + Console.Error.WriteLine(); + Console.Error.WriteLine(""); + Console.Error.WriteLine($" {name}"); + Console.Error.WriteLine(""); + } + } +} diff --git a/src/Simulation/EntryPointDriver/SimulatorMachine.cs b/src/Simulation/EntryPointDriver/SimulatorMachine.cs deleted file mode 100644 index 8c7b5909bfe..00000000000 --- a/src/Simulation/EntryPointDriver/SimulatorMachine.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Threading.Tasks; -using Microsoft.Quantum.Runtime; -using Microsoft.Quantum.Simulation.Core; -using Microsoft.Quantum.Simulation.Simulators; - -namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver -{ - /// - /// A quantum machine that runs an operation using a local quantum simulator. - /// - /// - /// TODO: This class is only for testing the interface. It should be removed once an - /// actual quantum machine implementation can be used. - /// - internal class SimulatorMachine : IQuantumMachine - { - public string ProviderId => nameof(SimulatorMachine); - - public string Target => "QuantumSimulator"; - - public async Task> ExecuteAsync( - EntryPointInfo info, TIn input) - { - var result = await (Task)typeof(QuantumSimulator) - .GetMethod(nameof(QuantumSimulator.Run)) - .MakeGenericMethod(info.Operation, info.InType, info.OutType) - .Invoke(new QuantumSimulator(), new object?[] { input }); - return new SimulatorMachineOutput(result); - } - - public Task SubmitAsync(EntryPointInfo info, TIn input) => - throw new NotImplementedException(); - } - - internal class SimulatorMachineOutput : IQuantumMachineOutput - { - public IReadOnlyDictionary Histogram { get; } - - public IQuantumMachineJob Job => throw new NotImplementedException(); - - public int Shots => 1; - - internal SimulatorMachineOutput(T result) => Histogram = ImmutableDictionary.Empty.Add(result, 1); - } -}