diff --git a/Simulation.sln b/Simulation.sln index dc9f314bfcf..352761e1cfa 100644 --- a/Simulation.sln +++ b/Simulation.sln @@ -63,7 +63,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IonQExe", "src\Simulation\S EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QCIExe", "src\Simulation\Simulators.Tests\TestProjects\QCIExe\QCIExe.csproj", "{C015FF41-9A51-4AF0-AEFC-2547D596B10A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TargetedExe", "src\Simulation\Simulators.Tests\TestProjects\TargetedExe\TargetedExe.csproj", "{D292BF18-3956-4827-820E-254C3F81EF09}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TargetedExe", "src\Simulation\Simulators.Tests\TestProjects\TargetedExe\TargetedExe.csproj", "{D292BF18-3956-4827-820E-254C3F81EF09}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Quantum.QSharp.Foundation", "src\Simulation\QSharpFoundation\Microsoft.Quantum.QSharp.Foundation.csproj", "{DB45AD73-4D91-43F3-85CC-C63614A96FB0}" EndProject @@ -71,7 +71,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Quantum.Type2.Cor EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Microsoft.Quantum.Simulators.Type2", "src\Simulation\Simulators.Type2.Tests\Tests.Microsoft.Quantum.Simulators.Type2.csproj", "{ED3D7040-4B3F-4217-A75E-9DF63DD84707}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntrinsicTests", "src\Simulation\Simulators.Tests\TestProjects\IntrinsicTests\IntrinsicTests.csproj", "{4EF958CA-B4A6-4E5F-924A-100B5615BEC3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntrinsicTests", "src\Simulation\Simulators.Tests\TestProjects\IntrinsicTests\IntrinsicTests.csproj", "{4EF958CA-B4A6-4E5F-924A-100B5615BEC3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Microsoft.Quantum.Simulators.Type1", "src\Simulation\Simulators.Type1.Tests\Tests.Microsoft.Quantum.Simulators.Type1.csproj", "{EB6E3DBD-C884-4241-9BC4-8281191D1F53}" EndProject diff --git a/src/QirRuntime/samples/StandaloneInputReference/qsharp/qir-standalone-input-reference.csproj b/src/QirRuntime/samples/StandaloneInputReference/qsharp/qir-standalone-input-reference.csproj index 74ef21518f7..f5390123881 100644 --- a/src/QirRuntime/samples/StandaloneInputReference/qsharp/qir-standalone-input-reference.csproj +++ b/src/QirRuntime/samples/StandaloneInputReference/qsharp/qir-standalone-input-reference.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/src/QirRuntime/test/FullstateSimulator/qsharp/qir-test-simulator.csproj b/src/QirRuntime/test/FullstateSimulator/qsharp/qir-test-simulator.csproj index 5166db5e375..47093e07fa3 100644 --- a/src/QirRuntime/test/FullstateSimulator/qsharp/qir-test-simulator.csproj +++ b/src/QirRuntime/test/FullstateSimulator/qsharp/qir-test-simulator.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/src/QirRuntime/test/QIR-dynamic/qsharp/qir-test-random.csproj b/src/QirRuntime/test/QIR-dynamic/qsharp/qir-test-random.csproj index 5166db5e375..47093e07fa3 100644 --- a/src/QirRuntime/test/QIR-dynamic/qsharp/qir-test-random.csproj +++ b/src/QirRuntime/test/QIR-dynamic/qsharp/qir-test-random.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/src/QirRuntime/test/QIR-static/qsharp/qir-gen.csproj b/src/QirRuntime/test/QIR-static/qsharp/qir-gen.csproj index b061ce01e44..ee5e2265946 100644 --- a/src/QirRuntime/test/QIR-static/qsharp/qir-gen.csproj +++ b/src/QirRuntime/test/QIR-static/qsharp/qir-gen.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/src/QirRuntime/test/QIR-tracer/qsharp/tracer-qir.csproj b/src/QirRuntime/test/QIR-tracer/qsharp/tracer-qir.csproj index 0167e216edf..eb525775779 100644 --- a/src/QirRuntime/test/QIR-tracer/qsharp/tracer-qir.csproj +++ b/src/QirRuntime/test/QIR-tracer/qsharp/tracer-qir.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/src/Simulation/CSharpGeneration/EntryPoint.fs b/src/Simulation/CSharpGeneration/EntryPoint.fs index 4bd9317a3c9..03349ea29e8 100644 --- a/src/Simulation/CSharpGeneration/EntryPoint.fs +++ b/src/Simulation/CSharpGeneration/EntryPoint.fs @@ -26,24 +26,6 @@ let entryPointClassName = "__QsEntryPoint__" /// The namespace containing the non-generated parts of the entry point driver. let private driverNamespace = "Microsoft.Quantum.EntryPointDriver" -/// The driver settings object. -let private driverSettings = - let newDriverSettings = driverNamespace + ".DriverSettings" |> ``type`` |> SyntaxFactory.ObjectCreationExpression - let namedArg (name : string) expr = SyntaxFactory.NameColon name |> (SyntaxFactory.Argument expr).WithNameColon - let immutableList elements = invoke (ident "System.Collections.Immutable.ImmutableList.Create") ``(`` elements ``)`` - let simulatorOptionAliases = - [ literal <| "--" + fst CommandLineArguments.SimulatorOption - literal <| "-" + snd CommandLineArguments.SimulatorOption ] - |> immutableList - [ namedArg "simulatorOptionAliases" simulatorOptionAliases - namedArg "quantumSimulatorName" <| literal AssemblyConstants.QuantumSimulator - namedArg "toffoliSimulatorName" <| literal AssemblyConstants.ToffoliSimulator - namedArg "resourcesEstimatorName" <| literal AssemblyConstants.ResourcesEstimator ] - |> SyntaxFactory.SeparatedList - |> SyntaxFactory.ArgumentList - |> newDriverSettings.WithArgumentList - :> ExpressionSyntax - /// A sequence of all of the named parameters in the argument tuple and their respective C# and Q# types. let rec private parameters context doc = function | QsTupleItem variable -> @@ -75,7 +57,7 @@ let private parameterOptionsProperty parameters = ``property-arrow_get`` optionsEnumerableTypeName "Options" [``public``] get (``=>`` (``new array`` (Some optionTypeName) options)) -/// A method that creates an instance of the default simulator if it is a custom simulator. +/// A lambda that creates an instance of the default simulator if it is a custom simulator. let private customSimulatorFactory name = let isCustomSimulator = not <| List.contains name [ @@ -87,11 +69,7 @@ let private customSimulatorFactory name = if isCustomSimulator then ``new`` (``type`` name) ``(`` [] ``)`` else upcast SyntaxFactory.ThrowExpression (``new`` (``type`` "InvalidOperationException") ``(`` [] ``)``) - - arrow_method "IOperationFactory" "CreateDefaultCustomSimulator" ``<<`` [] ``>>`` - ``(`` [] ``)`` - [``public``] - (Some (``=>`` factory)) + ``() =>`` [] factory :> ExpressionSyntax /// A method that creates the argument tuple for the entry point, given the command-line parsing result. let private createArgument context entryPoint = @@ -119,65 +97,169 @@ let private callableTypeNames context (callable : QsCallable) = let returnTypeName = SimulationCode.roslynTypeName context callable.Signature.ReturnType callableName, argTypeName, returnTypeName -/// The main method for the standalone executable. -let private mainMethod context entryPoint = +/// Generates the class name for an entry point class. +let private entryPointClassFullName (entryPoint : QsCallable) = + { Namespace = entryPoint.FullName.Namespace; Name = entryPointClassName + entryPoint.FullName.Name } + +/// Generates the Submit method for an entry point class. +let private submitMethod context entryPoint = + let callableName, _, _ = callableTypeNames context entryPoint + let parseResultParamName = "parseResult" + let settingsParamName = "settings" + let args = + [ + ident callableName <|.|> ident "Info" + ident "this" <.> (ident "CreateArgument", [ident parseResultParamName]) + ident settingsParamName :> ExpressionSyntax + ] + arrow_method "System.Threading.Tasks.Task" "Submit" ``<<`` [] ``>>`` + ``(`` + [ + param parseResultParamName ``of`` (``type`` "System.CommandLine.Parsing.ParseResult") + param settingsParamName ``of`` (``type`` (driverNamespace + ".AzureSettings")) + ] + ``)`` + [``public``] + (Some (``=>`` (ident (driverNamespace + ".Azure") <.> (ident "Submit", args)))) + +/// Generates the Simulate method for an entry point class. +let private simulateMethod context entryPoint = let callableName, argTypeName, returnTypeName = callableTypeNames context entryPoint - let driverType = generic (driverNamespace + ".Driver") ``<<`` [callableName; argTypeName; returnTypeName] ``>>`` - let entryPointInstance = ``new`` (``type`` entryPointClassName) ``(`` [] ``)`` - let driver = ``new`` driverType ``(`` [driverSettings; entryPointInstance] ``)`` - let commandLineArgsName = "args" - arrow_method "System.Threading.Tasks.Task" "Main" ``<<`` [] ``>>`` - ``(`` [param commandLineArgsName ``of`` (``type`` "string[]")] ``)`` - [``private``; ``static``; async] - (Some (``=>`` (await (driver <.> (ident "Run", [ident commandLineArgsName]))))) + let simulationType = generic (driverNamespace + ".Simulation") ``<<`` [callableName; argTypeName; returnTypeName] ``>>`` + let parseResultParamName = "parseResult" + let settingsParamName = "settings" + let simulatorParamName = "simulator" + let args = + [ + ident "this" :> ExpressionSyntax + ident "this" <.> (ident "CreateArgument", [ident parseResultParamName]) + ident settingsParamName :> ExpressionSyntax + ident simulatorParamName :> ExpressionSyntax + ] + arrow_method "System.Threading.Tasks.Task" "Simulate" ``<<`` [] ``>>`` + ``(`` + [ + param parseResultParamName ``of`` (``type`` "System.CommandLine.Parsing.ParseResult") + param settingsParamName ``of`` (``type`` (driverNamespace + ".DriverSettings")) + param simulatorParamName ``of`` (``type`` "string") + ] + ``)`` + [``public``] + (Some (``=>`` (simulationType <.> (ident "Simulate", args)))) /// The class that adapts the entry point for use with the command-line parsing library and the driver. -let private entryPointClass context entryPoint = - let callableName, argTypeName, returnTypeName = callableTypeNames context entryPoint +let private entryPointClass context (entryPoint : QsCallable) = let property name typeName value = ``property-arrow_get`` typeName name [``public``] get (``=>`` value) + let nameProperty = + entryPoint.FullName.ToString() + |> literal + |> property "Name" "string" let summaryProperty = (PrintSummary entryPoint.Documentation false).Trim () |> literal |> property "Summary" "string" let parameters = parameters context entryPoint.Documentation entryPoint.ArgumentTuple - let defaultSimulator = - context.assemblyConstants.TryGetValue AssemblyConstants.DefaultSimulator - |> fun (_, value) -> if String.IsNullOrWhiteSpace value then AssemblyConstants.QuantumSimulator else value - let defaultSimulatorNameProperty = literal defaultSimulator |> property "DefaultSimulatorName" "string" - let defaultExecutionTargetProperty = - context.assemblyConstants.TryGetValue AssemblyConstants.ExecutionTarget - |> (fun (_, value) -> if value = null then "" else value) - |> literal - |> property "DefaultExecutionTarget" "string" - let infoProperty = - property "Info" (sprintf "EntryPointInfo<%s, %s>" argTypeName returnTypeName) - (ident callableName <|.|> ident "Info") let members : MemberDeclarationSyntax list = [ + nameProperty summaryProperty parameterOptionsProperty parameters - defaultSimulatorNameProperty - defaultExecutionTargetProperty - infoProperty - customSimulatorFactory defaultSimulator createArgument context entryPoint - mainMethod context entryPoint + submitMethod context entryPoint + simulateMethod context entryPoint ] - let baseName = sprintf "%s.IEntryPoint<%s, %s>" driverNamespace argTypeName returnTypeName - ``class`` entryPointClassName``<<`` [] ``>>`` + let baseName = sprintf "%s.IEntryPoint" driverNamespace + ``class`` ((entryPointClassFullName entryPoint).Name) ``<<`` [] ``>>`` ``:`` (Some (simpleBase baseName)) ``,`` [] [``internal``] ``{`` members ``}`` -/// Generates C# source code for a standalone executable that runs the Q# entry point. -let generate context (entryPoint : QsCallable) = - let ns = - ``namespace`` entryPoint.FullName.Namespace +/// Generates a namespace for a set of entry points that share the namespace +let private entryPointNamespace context name entryPoints = + ``namespace`` name + ``{`` + [] + [for ep in entryPoints -> entryPointClass context ep] + ``}`` + +/// Returns the driver settings object. +let private driverSettings context = + let newDriverSettings = driverNamespace + ".DriverSettings" |> ``type`` |> SyntaxFactory.ObjectCreationExpression + let namedArg (name : string) expr = SyntaxFactory.NameColon name |> (SyntaxFactory.Argument expr).WithNameColon + let immutableList elements = invoke (ident "System.Collections.Immutable.ImmutableList.Create") ``(`` elements ``)`` + let simulatorOptionAliases = + [ literal <| "--" + fst CommandLineArguments.SimulatorOption + literal <| "-" + snd CommandLineArguments.SimulatorOption ] + |> immutableList + let defaultSimulator = + context.assemblyConstants.TryGetValue AssemblyConstants.DefaultSimulator + |> fun (_, value) -> if String.IsNullOrWhiteSpace value then AssemblyConstants.QuantumSimulator else value + let defaultExecutionTarget = + context.assemblyConstants.TryGetValue AssemblyConstants.ExecutionTarget + |> (fun (_, value) -> if value = null then "" else value) + |> literal + [ + namedArg "simulatorOptionAliases" simulatorOptionAliases + namedArg "quantumSimulatorName" <| literal AssemblyConstants.QuantumSimulator + namedArg "toffoliSimulatorName" <| literal AssemblyConstants.ToffoliSimulator + namedArg "resourcesEstimatorName" <| literal AssemblyConstants.ResourcesEstimator + namedArg "defaultSimulatorName" <| literal defaultSimulator + namedArg "defaultExecutionTarget" <| defaultExecutionTarget + namedArg "createDefaultCustomSimulator" <| customSimulatorFactory defaultSimulator + ] + |> SyntaxFactory.SeparatedList + |> SyntaxFactory.ArgumentList + |> newDriverSettings.WithArgumentList + :> ExpressionSyntax + +/// The main method for the standalone executable. +let private mainMethod context entryPoints = + + let entryPointArrayMembers = + [ + for ep in entryPoints do + let name = entryPointClassFullName ep + ``new`` (``type`` (name.ToString())) ``(`` [] ``)`` + ] + + let entryPointArray = + ``new array`` (Some (driverNamespace + ".IEntryPoint")) entryPointArrayMembers + + let driver = ``new`` (``type`` (driverNamespace + ".Driver")) ``(`` [driverSettings context; entryPointArray] ``)`` + let commandLineArgsName = "args" + arrow_method "System.Threading.Tasks.Task" "Main" ``<<`` [] ``>>`` + ``(`` [param commandLineArgsName ``of`` (``type`` "string[]")] ``)`` + [``private``; ``static``; async] + (Some (``=>`` (await (driver <.> (ident "Run", [ident commandLineArgsName]))))) + +/// Generates a namespace for the main function +let private mainNamespace context entryPoints = + let mainClass = + ``class`` entryPointClassName ``<<`` [] ``>>`` + ``:`` None ``,`` [] + [``internal``] ``{`` - (Seq.map using SimulationCode.autoNamespaces) - [entryPointClass context entryPoint] + [mainMethod context entryPoints] ``}`` - ``compilation unit`` [] [] [ns] + + ``namespace`` entryPointClassName + ``{`` + [] + [mainClass] + ``}`` + +/// Generates the C# source code for the file containing the Main function. +let generateMainSource context entryPoints = + let mainNS = mainNamespace context entryPoints + ``compilation unit`` [] (Seq.map using SimulationCode.autoNamespaces) [mainNS :> MemberDeclarationSyntax] + |> ``with leading comments`` SimulationCode.autogenComment + |> SimulationCode.formatSyntaxTree + +/// Generates C# source code for a standalone executable that runs the Q# entry point. +let generateSource context (entryPoints : seq) = + let entryPointNamespaces = entryPoints |> Seq.groupBy (fun ep -> ep.FullName.Namespace) + let namespaces = [for ns, eps in entryPointNamespaces -> entryPointNamespace context ns eps :> MemberDeclarationSyntax] + ``compilation unit`` [] (Seq.map using SimulationCode.autoNamespaces) namespaces |> ``with leading comments`` SimulationCode.autogenComment |> SimulationCode.formatSyntaxTree diff --git a/src/Simulation/CSharpGeneration/Microsoft.Quantum.CSharpGeneration.fsproj b/src/Simulation/CSharpGeneration/Microsoft.Quantum.CSharpGeneration.fsproj index c85fc9bce0b..40ae7fd44c1 100644 --- a/src/Simulation/CSharpGeneration/Microsoft.Quantum.CSharpGeneration.fsproj +++ b/src/Simulation/CSharpGeneration/Microsoft.Quantum.CSharpGeneration.fsproj @@ -1,4 +1,4 @@ - + @@ -22,7 +22,7 @@ - + diff --git a/src/Simulation/CSharpGeneration/RewriteStep.fs b/src/Simulation/CSharpGeneration/RewriteStep.fs index 47e35baf0ca..10ea2242f05 100644 --- a/src/Simulation/CSharpGeneration/RewriteStep.fs +++ b/src/Simulation/CSharpGeneration/RewriteStep.fs @@ -6,11 +6,9 @@ namespace Microsoft.Quantum.QsCompiler.CsharpGeneration open System open System.Collections.Generic open System.IO -open Microsoft.CodeAnalysis open Microsoft.Quantum.QsCompiler +open Microsoft.Quantum.QsCompiler.CompilationBuilder open Microsoft.Quantum.QsCompiler.CsharpGeneration -open Microsoft.Quantum.QsCompiler.DataTypes -open Microsoft.Quantum.QsCompiler.Diagnostics open Microsoft.Quantum.QsCompiler.ReservedKeywords open Microsoft.Quantum.QsCompiler.SyntaxTree open Microsoft.Quantum.QsCompiler.Transformations.BasicTransformations @@ -19,39 +17,29 @@ open Microsoft.Quantum.QsCompiler.Transformations.BasicTransformations type Emitter() = let _AssemblyConstants = new Dictionary<_, _>() - let mutable _Diagnostics = [] interface IRewriteStep with member this.Name = "CSharpGeneration" member this.Priority = -1 // doesn't matter because this rewrite step is the only one in the dll member this.AssemblyConstants = upcast _AssemblyConstants - member this.GeneratedDiagnostics = upcast _Diagnostics - - member this.ImplementsPreconditionVerification = true + member this.GeneratedDiagnostics = Seq.empty + + member this.ImplementsPreconditionVerification = false member this.ImplementsPostconditionVerification = false member this.ImplementsTransformation = true - member this.PreconditionVerification compilation = - if compilation.EntryPoints.Length > 1 then - _Diagnostics <- IRewriteStep.Diagnostic - (Message = DiagnosticItem.Message (ErrorCode.MultipleEntryPoints, []), - Severity = DiagnosticSeverity.Error, - Stage = IRewriteStep.Stage.PreconditionVerification) :: _Diagnostics - false - else - true - + member this.PreconditionVerification _ = NotImplementedException() |> raise member this.PostconditionVerification _ = NotImplementedException() |> raise - - member this.Transformation (compilation, transformed) = + + member this.Transformation (compilation, transformed) = let step = this :> IRewriteStep let dir = step.AssemblyConstants.TryGetValue AssemblyConstants.OutputPath |> function | true, outputFolder when outputFolder <> null -> Path.Combine(outputFolder, "src") | _ -> step.Name let context = CodegenContext.Create (compilation, step.AssemblyConstants) - let allSources = GetSourceFiles.Apply compilation.Namespaces + let allSources = GetSourceFiles.Apply compilation.Namespaces for source in allSources |> Seq.filter context.GenerateCodeForSource do let content = SimulationCode.generate source context @@ -61,9 +49,22 @@ type Emitter() = if content <> null then CompilationLoader.GeneratedFile(source, dir, ".dll.g.cs", content) |> ignore if not compilation.EntryPoints.IsEmpty then - let callable = context.allCallables.[Seq.exactlyOne compilation.EntryPoints] - let content = EntryPoint.generate context callable - CompilationLoader.GeneratedFile(callable.SourceFile, dir, ".EntryPoint.g.cs", content) |> ignore + + let entryPointCallables = + compilation.EntryPoints + |> Seq.map (fun ep -> context.allCallables.[ep]) + + let entryPointSources = + entryPointCallables + |> Seq.groupBy (fun ep -> ep.Source.CodeFile) + + let mainSourceFile = (dir, "EntryPoint") |> Path.Combine |> Path.GetFullPath |> Uri |> CompilationUnitManager.GetFileId + let content = EntryPoint.generateMainSource context entryPointCallables + CompilationLoader.GeneratedFile(mainSourceFile, dir, ".g.Main.cs", content) |> ignore + + for (sourceFile, callables) in entryPointSources do + let content = EntryPoint.generateSource context callables + CompilationLoader.GeneratedFile(sourceFile, dir, ".g.EntryPoint.cs", content) |> ignore transformed <- compilation true diff --git a/src/Simulation/Common/Simulators.Dev.props b/src/Simulation/Common/Simulators.Dev.props index b4eead59d87..62a9ed4319b 100644 --- a/src/Simulation/Common/Simulators.Dev.props +++ b/src/Simulation/Common/Simulators.Dev.props @@ -31,7 +31,7 @@ - + diff --git a/src/Simulation/EntryPointDriver.Tests/Tests.fs b/src/Simulation/EntryPointDriver.Tests/Tests.fs index 3cfb17f8a14..9ca3ad61330 100644 --- a/src/Simulation/EntryPointDriver.Tests/Tests.fs +++ b/src/Simulation/EntryPointDriver.Tests/Tests.fs @@ -70,10 +70,11 @@ let private compileQSharp source = /// Generates C# source code from the compiled Q# syntax tree using the given assembly constants. let private generateCSharp constants (compilation : QsCompilation) = let context = CodegenContext.Create (compilation, constants) - let entryPoint = context.allCallables.[Seq.exactlyOne compilation.EntryPoints] + let entryPoints = seq { for ep in compilation.EntryPoints -> context.allCallables.[ep] } [ SimulationCode.generate testFile context - EntryPoint.generate context entryPoint + EntryPoint.generateSource context entryPoints + EntryPoint.generateMainSource context entryPoints ] /// The full path to a referenced assembly given its short name. @@ -128,7 +129,7 @@ let private testAssembly testName constants = /// Runs the entry point in the assembly with the given command-line arguments, and returns the output, errors, and exit /// code. let private run (assembly : Assembly) (args : string[]) = - let entryPoint = assembly.GetType (sprintf "%s.%s" testNamespace EntryPoint.entryPointClassName) + let entryPoint = assembly.GetType (sprintf "%s.%s" EntryPoint.entryPointClassName EntryPoint.entryPointClassName) let main = entryPoint.GetMethod("Main", BindingFlags.NonPublic ||| BindingFlags.Static) let previousCulture = CultureInfo.DefaultThreadCurrentCulture let previousOut = Console.Out @@ -191,7 +192,7 @@ let private testWithTarget defaultTarget = |> testWithConstants /// Standard command-line arguments for the "submit" command without specifying a target. -let private submitWithoutTarget = +let private submitWithoutTarget = [ "submit" "--subscription" "mySubscription" @@ -420,7 +421,7 @@ let ``Accepts one-tuple`` () = let given = test "Accepts one-tuple" given ["-x"; "7"; "-y"; "8"] |> yields "7 8" -[] +[] let ``Accepts two-tuple`` () = let given = test "Accepts two-tuple" given ["-x"; "7"; "-y"; "8"; "-z"; "9"] |> yields "7 8 9" @@ -451,7 +452,7 @@ let ``Shadows --simulator`` () = |> yields (sprintf "Warning: Option --simulator is overridden by an entry point parameter name. Using default value QuantumSimulator. %s" AssemblyConstants.ResourcesEstimator) - given ["-s"; AssemblyConstants.ResourcesEstimator; "--simulator"; "foo"] |> fails + given ["-s"; AssemblyConstants.ResourcesEstimator; "--simulator"; "foo"] |> fails given ["-s"; "foo"] |> fails [] @@ -836,6 +837,9 @@ let ``Shows help text for submit command`` () = %s submit [options] Options: + -n (REQUIRED) A number. + --pauli (REQUIRED) The name of a Pauli matrix. + --my-cool-bool (REQUIRED) A neat bit. --subscription (REQUIRED) The subscription ID. --resource-group (REQUIRED) The resource group name. --workspace (REQUIRED) The workspace name. @@ -849,9 +853,6 @@ let ``Shows help text for submit command`` () = --output The information to show in the output after the job is submitted. --dry-run Validate the program and options, but do not submit to Azure Quantum. --verbose Show additional information about the submission. - -n (REQUIRED) A number. - --pauli (REQUIRED) The name of a Pauli matrix. - --my-cool-bool (REQUIRED) A neat bit. -?, -h, --help Show help and usage information" let given = test "Help" given ["submit"; "--help"] |> yields message @@ -865,6 +866,9 @@ let ``Shows help text for submit command with default target`` () = %s submit [options] Options: + -n (REQUIRED) A number. + --pauli (REQUIRED) The name of a Pauli matrix. + --my-cool-bool (REQUIRED) A neat bit. --subscription (REQUIRED) The subscription ID. --resource-group (REQUIRED) The resource group name. --workspace (REQUIRED) The workspace name. @@ -878,9 +882,98 @@ let ``Shows help text for submit command with default target`` () = --output The information to show in the output after the job is submitted. --dry-run Validate the program and options, but do not submit to Azure Quantum. --verbose Show additional information about the submission. - -n (REQUIRED) A number. - --pauli (REQUIRED) The name of a Pauli matrix. - --my-cool-bool (REQUIRED) A neat bit. -?, -h, --help Show help and usage information" let given = testWithTarget "foo.target" "Help" given ["submit"; "--help"] |> yields message + +[] +let ``Supports simulating multiple entry points`` () = + let given = test "Multiple entry points" + given ["simulate"; "EntryPointTest.MultipleEntryPoints1"] |> yields "Hello from Entry Point 1!" + given ["simulate"; "EntryPointTest.MultipleEntryPoints2"] |> yields "Hello from Entry Point 2!" + given ["simulate"; "EntryPointTest3.MultipleEntryPoints3"] |> yields "Hello from Entry Point 3!" + given ["simulate"] |> fails + given [] |> fails + +[] +let ``Supports simulating multiple entry points with different parameters`` () = + let given = test "Multiple entry points with different parameters" + let entryPoint1Args = ["-n"; "42.5"] + let entryPoint2Args = ["-s"; "Hello, World!"] + let entryPoint3Args = ["-i"; "3"] + + given (["simulate"; "EntryPointTest.MultipleEntryPoints1"] @ entryPoint1Args) |> yields "42.5" + given (["simulate"; "EntryPointTest.MultipleEntryPoints1"] @ entryPoint2Args) |> fails + given (["simulate"; "EntryPointTest.MultipleEntryPoints1"] @ entryPoint3Args) |> fails + given ["simulate"; "EntryPointTest.MultipleEntryPoints1"] |> fails + + given (["simulate"; "EntryPointTest.MultipleEntryPoints2"] @ entryPoint1Args) |> fails + given (["simulate"; "EntryPointTest.MultipleEntryPoints2"] @ entryPoint2Args) |> yields "Hello, World!" + given (["simulate"; "EntryPointTest.MultipleEntryPoints2"] @ entryPoint3Args) |> fails + given ["simulate"; "EntryPointTest.MultipleEntryPoints2"] |> fails + + given (["simulate"; "EntryPointTest3.MultipleEntryPoints3"] @ entryPoint1Args) |> fails + given (["simulate"; "EntryPointTest3.MultipleEntryPoints3"] @ entryPoint2Args) |> fails + given (["simulate"; "EntryPointTest3.MultipleEntryPoints3"] @ entryPoint3Args) |> yields "3" + given ["simulate"; "EntryPointTest3.MultipleEntryPoints3"] |> fails + + given ["simulate"] |> fails + given [] |> fails + +[] +let ``Supports submitting multiple entry points`` () = + let options = + [ + "--subscription" + "mySubscription" + "--resource-group" + "myResourceGroup" + "--workspace" + "myWorkspace" + "--target" + "test.nothing" + ] + let given = test "Multiple entry points" + let succeeds = yields "https://www.example.com/00000000-0000-0000-0000-0000000000000" + given (["submit"; "EntryPointTest.MultipleEntryPoints1"] @ options) |> succeeds + given (["submit"; "EntryPointTest.MultipleEntryPoints2"] @ options) |> succeeds + given (["submit"; "EntryPointTest3.MultipleEntryPoints3"] @ options) |> succeeds + given (["submit"] @ options) |> fails + given [] |> fails + +[] +let ``Supports submitting multiple entry points with different parameters`` () = + let options = + [ + "--subscription" + "mySubscription" + "--resource-group" + "myResourceGroup" + "--workspace" + "myWorkspace" + "--target" + "test.nothing" + ] + let entryPoint1Args = ["-n"; "42.5"] + let entryPoint2Args = ["-s"; "Hello, World!"] + let entryPoint3Args = ["-i"; "3"] + let given = test "Multiple entry points with different parameters" + let succeeds = yields "https://www.example.com/00000000-0000-0000-0000-0000000000000" + + given (["submit"; "EntryPointTest.MultipleEntryPoints1"] @ entryPoint1Args @ options) |> succeeds + given (["submit"; "EntryPointTest.MultipleEntryPoints1"] @ entryPoint2Args @ options) |> fails + given (["submit"; "EntryPointTest.MultipleEntryPoints1"] @ entryPoint3Args @ options) |> fails + given (["submit"; "EntryPointTest.MultipleEntryPoints1"] @ options) |> fails + + given (["submit"; "EntryPointTest.MultipleEntryPoints2"] @ entryPoint1Args @ options) |> fails + given (["submit"; "EntryPointTest.MultipleEntryPoints2"] @ entryPoint2Args @ options) |> succeeds + given (["submit"; "EntryPointTest.MultipleEntryPoints2"] @ entryPoint3Args @ options) |> fails + given (["submit"; "EntryPointTest.MultipleEntryPoints2"] @ options) |> fails + + given (["submit"; "EntryPointTest3.MultipleEntryPoints3"] @ entryPoint1Args @ options) |> fails + given (["submit"; "EntryPointTest3.MultipleEntryPoints3"] @ entryPoint2Args @ options) |> fails + given (["submit"; "EntryPointTest3.MultipleEntryPoints3"] @ entryPoint3Args @ options) |> succeeds + given (["submit"; "EntryPointTest3.MultipleEntryPoints3"] @ options) |> fails + + given submitWithNothingTarget |> fails + given [] |> fails diff --git a/src/Simulation/EntryPointDriver.Tests/Tests.qs b/src/Simulation/EntryPointDriver.Tests/Tests.qs index 516f8bca50d..bc05e62be83 100644 --- a/src/Simulation/EntryPointDriver.Tests/Tests.qs +++ b/src/Simulation/EntryPointDriver.Tests/Tests.qs @@ -360,3 +360,49 @@ namespace EntryPointTest { @EntryPoint() operation Help(n : Int, pauli : Pauli, myCoolBool : Bool) : Unit { } } + +// +// Multiple Entry Points +// + +// --- Multiple entry points + +namespace EntryPointTest { + @EntryPoint() + operation MultipleEntryPoints1() : String { + return "Hello from Entry Point 1!"; + } + + @EntryPoint() + operation MultipleEntryPoints2() : String { + return "Hello from Entry Point 2!"; + } +} + +namespace EntryPointTest3 { + @EntryPoint() + operation MultipleEntryPoints3() : String { + return "Hello from Entry Point 3!"; + } +} + +// --- Multiple entry points with different parameters + +namespace EntryPointTest { + @EntryPoint() + operation MultipleEntryPoints1(n : Double) : Double { + return n; + } + + @EntryPoint() + operation MultipleEntryPoints2(s : String) : String { + return s; + } +} + +namespace EntryPointTest3 { + @EntryPoint() + operation MultipleEntryPoints3(i : Int) : Int { + return i; + } +} diff --git a/src/Simulation/EntryPointDriver/Azure.cs b/src/Simulation/EntryPointDriver/Azure.cs index e94624f5602..9f33f7baec5 100644 --- a/src/Simulation/EntryPointDriver/Azure.cs +++ b/src/Simulation/EntryPointDriver/Azure.cs @@ -2,13 +2,13 @@ // Licensed under the MIT License. using System; -using System.CommandLine.Parsing; using System.Linq; using System.Threading.Tasks; using Microsoft.Azure.Quantum; using Microsoft.Azure.Quantum.Exceptions; using Microsoft.Quantum.Runtime; using Microsoft.Quantum.Simulation.Common.Exceptions; +using Microsoft.Quantum.Simulation.Core; using static Microsoft.Quantum.EntryPointDriver.Driver; namespace Microsoft.Quantum.EntryPointDriver @@ -16,19 +16,18 @@ namespace Microsoft.Quantum.EntryPointDriver /// /// Provides entry point submission to Azure Quantum. /// - internal static class Azure + public 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. + /// The information about the entry point. + /// The input argument tuple to the entry point. + /// The submission settings. /// The exit code. - internal static async Task Submit( - IEntryPoint entryPoint, ParseResult parseResult, AzureSettings settings) + public static async Task Submit(EntryPointInfo info, TIn input, AzureSettings settings) { if (settings.Verbose) { @@ -44,28 +43,27 @@ internal static async Task Submit( return 1; } - var input = entryPoint.CreateArgument(parseResult); return settings.DryRun - ? Validate(machine, entryPoint, input) - : await SubmitJob(machine, entryPoint, input, settings); + ? Validate(machine, info, input) + : await SubmitJob(machine, info, input, settings); } /// /// Submits a job to Azure Quantum. /// - /// The quantum machine target. - /// The program entry point. - /// The program input. - /// The submission settings. /// The input type. /// The output type. + /// The quantum machine target. + /// The information about the entry point. + /// The input argument tuple to the entry point. + /// The submission settings. /// The exit code. private static async Task SubmitJob( - IQuantumMachine machine, IEntryPoint entryPoint, TIn input, AzureSettings settings) + IQuantumMachine machine, EntryPointInfo info, TIn input, AzureSettings settings) { try { - var job = await machine.SubmitAsync(entryPoint.Info, input, new SubmissionContext + var job = await machine.SubmitAsync(info, input, new SubmissionContext { FriendlyName = settings.JobName, Shots = settings.Shots @@ -93,15 +91,15 @@ private static async Task SubmitJob( /// /// Validates the program for the quantum machine target. /// - /// The quantum machine target. - /// The program entry point. - /// The program input. /// The input type. /// The output type. + /// The quantum machine target. + /// The information about the entry point. + /// The input argument tuple to the entry point. /// The exit code. - private static int Validate(IQuantumMachine machine, IEntryPoint entryPoint, TIn input) + private static int Validate(IQuantumMachine machine, EntryPointInfo info, TIn input) { - var (isValid, message) = machine.Validate(entryPoint.Info, input); + var (isValid, message) = machine.Validate(info, input); Console.WriteLine(isValid ? "✔️ The program is valid!" : "❌ The program is invalid."); if (!string.IsNullOrWhiteSpace(message)) { @@ -186,7 +184,7 @@ private sealed class SubmissionContext : IQuantumMachineSubmissionContext /// /// The information to show in the output after the job is submitted. /// - internal enum OutputFormat + public enum OutputFormat { /// /// Show a friendly message with a URI that can be used to see the job results. @@ -202,7 +200,7 @@ internal enum OutputFormat /// /// Settings for a submission to Azure Quantum. /// - internal sealed class AzureSettings + public sealed class AzureSettings { /// /// The subscription ID. diff --git a/src/Simulation/EntryPointDriver/Driver.cs b/src/Simulation/EntryPointDriver/Driver.cs index fa521c50b80..7997a87db01 100644 --- a/src/Simulation/EntryPointDriver/Driver.cs +++ b/src/Simulation/EntryPointDriver/Driver.cs @@ -13,66 +13,161 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using Microsoft.Quantum.Simulation.Core; -using static Microsoft.Quantum.EntryPointDriver.Driver; namespace Microsoft.Quantum.EntryPointDriver { + using Validators = ImmutableList>; + /// - /// The entry point driver is the entry point for the C# application that executes the Q# entry point. + /// The entry point driver is the entry point for the C# application that executes Q# entry points. /// - /// 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 + public sealed class Driver { /// - /// The driver settings. + /// The subscription option. /// - private readonly DriverSettings settings; + private static readonly OptionInfo SubscriptionOption = new OptionInfo( + ImmutableList.Create("--subscription"), "The subscription ID."); /// - /// The entry point. + /// The resource group option. /// - private readonly IEntryPoint entryPoint; + private static readonly OptionInfo ResourceGroupOption = new OptionInfo( + ImmutableList.Create("--resource-group"), "The resource group name."); /// - /// The simulator option. + /// The workspace option. + /// + private static readonly OptionInfo WorkspaceOption = new OptionInfo( + ImmutableList.Create("--workspace"), "The workspace name."); + + /// + /// The storage option. + /// + private static readonly OptionInfo StorageOption = new OptionInfo( + ImmutableList.Create("--storage"), default, "The storage account connection string."); + + /// + /// The AAD token option. /// - private OptionInfo SimulatorOption { get; } + private static readonly OptionInfo AadTokenOption = new OptionInfo( + ImmutableList.Create("--aad-token"), default, "The Azure Active Directory authentication token."); + + /// + /// The base URI option. + /// + private static readonly OptionInfo BaseUriOption = new OptionInfo( + ImmutableList.Create("--base-uri"), default, "The base URI of the Azure Quantum endpoint."); + + /// + /// The location to use with the default endpoint option. + /// + private static readonly OptionInfo LocationOption = new OptionInfo( + ImmutableList.Create("--location"), + default, + "The location to use with the default endpoint.", + validator: result => + { + var location = result.Tokens.SingleOrDefault()?.Value; + if (location == null) + { + return default; + } + + var normalizedLocation = AzureSettings.NormalizeLocation(location); + return Uri.CheckHostName(normalizedLocation) == UriHostNameType.Unknown ? + $"\"{location}\" is an invalid value for the --location option." : + default; + }); + + /// + /// The job name option. + /// + private static readonly OptionInfo JobNameOption = new OptionInfo( + ImmutableList.Create("--job-name"), default, "The name of the submitted job."); + + /// + /// The shots option. + /// + private static readonly OptionInfo ShotsOption = new OptionInfo( + ImmutableList.Create("--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. + /// + private static readonly OptionInfo OutputOption = new OptionInfo( + ImmutableList.Create("--output"), + OutputFormat.FriendlyUri, + "The information to show in the output after the job is submitted."); + + /// + /// The dry run option. + /// + private static readonly OptionInfo DryRunOption = new OptionInfo( + ImmutableList.Create("--dry-run"), + false, + "Validate the program and options, but do not submit to Azure Quantum."); + + /// + /// The verbose option. + /// + private static readonly OptionInfo VerboseOption = new OptionInfo( + ImmutableList.Create("--verbose"), false, "Show additional information about the submission."); /// /// The target option. /// - private OptionInfo TargetOption { get; } + private readonly OptionInfo TargetOption; + + /// + /// The simulator option. + /// + private readonly OptionInfo SimulatorOption; + + /// + /// The driver settings. + /// + private readonly DriverSettings settings; + + /// + /// All the registered entry points of the program. + /// + private readonly IReadOnlyCollection entryPoints; /// /// Creates a new driver for the entry point. /// /// The driver settings. - /// The entry point. - public Driver(DriverSettings settings, IEntryPoint entryPoint) + /// The entry points. + public Driver(DriverSettings settings, IReadOnlyCollection entryPoints) { this.settings = settings; - this.entryPoint = entryPoint; - SimulatorOption = new OptionInfo( - settings.SimulatorOptionAliases, - entryPoint.DefaultSimulatorName, + this.SimulatorOption = new OptionInfo( + this.settings.SimulatorOptionAliases, + this.settings.DefaultSimulatorName, "The name of the simulator to use.", suggestions: new[] { - settings.QuantumSimulatorName, - settings.ToffoliSimulatorName, - settings.ResourcesEstimatorName, - entryPoint.DefaultSimulatorName + this.settings.QuantumSimulatorName, + this.settings.ToffoliSimulatorName, + this.settings.ResourcesEstimatorName, + this.settings.DefaultSimulatorName }); var targetAliases = ImmutableList.Create("--target"); const string targetDescription = "The target device ID."; - TargetOption = string.IsNullOrWhiteSpace(entryPoint.DefaultExecutionTarget) + this.TargetOption = string.IsNullOrWhiteSpace(settings.DefaultExecutionTarget) ? new OptionInfo(targetAliases, targetDescription) - : new OptionInfo(targetAliases, entryPoint.DefaultExecutionTarget, targetDescription); + : new OptionInfo(targetAliases, this.settings.DefaultExecutionTarget, targetDescription); + + this.entryPoints = entryPoints; } /// @@ -82,46 +177,18 @@ public Driver(DriverSettings settings, IEntryPoint entryPoint) /// The exit code. public async Task Run(string[] args) { - var simulate = new Command("simulate", "(default) Run the program using a local simulator.") - { - Handler = CommandHandler.Create(Simulate) - }; - AddOptionIfAvailable(simulate, SimulatorOption); + var simulateSubCommands = this.entryPoints.Select(this.CreateSimulateEntryPointCommand).ToList(); + var submitSubCommands = this.entryPoints.Select(this.CreateSubmitEntryPointCommand).ToList(); - var submit = new Command("submit", "Submit the program to Azure Quantum.") - { - IsHidden = true, - Handler = CommandHandler.Create(Submit) - }; - AddOptionIfAvailable(submit, SubscriptionOption); - AddOptionIfAvailable(submit, ResourceGroupOption); - AddOptionIfAvailable(submit, WorkspaceOption); - AddOptionIfAvailable(submit, TargetOption); - AddOptionIfAvailable(submit, StorageOption); - AddOptionIfAvailable(submit, AadTokenOption); - AddOptionIfAvailable(submit, BaseUriOption); - AddOptionIfAvailable(submit, LocationOption); - AddOptionIfAvailable(submit, JobNameOption); - AddOptionIfAvailable(submit, ShotsOption); - AddOptionIfAvailable(submit, OutputOption); - AddOptionIfAvailable(submit, DryRunOption); - AddOptionIfAvailable(submit, VerboseOption); - MarkOptionsAsMutuallyExclusive( - submit, - new[] { BaseUriOption.Aliases.First(), LocationOption.Aliases.First() }); - - var root = new RootCommand(entryPoint.Summary) { simulate, submit }; - foreach (var option in entryPoint.Options) - { - root.AddGlobalOption(option); - } + var simulate = CreateSimulateCommand(simulateSubCommands); + var submit = CreateSubmitCommand(submitSubCommands); - // Set the simulate command as the default. - foreach (var option in simulate.Options) + var root = new RootCommand() { simulate.Command, submit.Command }; + if (this.entryPoints.Count() == 1) { - root.AddOption(option); + SetSubCommandAsDefault(root, simulate.Command, simulate.Validators); + root.Description = this.entryPoints.First().Summary; } - root.Handler = simulate.Handler; Console.OutputEncoding = Encoding.UTF8; return await new CommandLineBuilder(root) @@ -132,57 +199,59 @@ public async Task Run(string[] args) } /// - /// Simulates the entry point. + /// Displays a message to the console using the given color and text writer. /// - /// The command-line parsing result. - /// The simulator to use. - /// The exit code. - private async Task Simulate(ParseResult parseResult, string simulator) => - await Simulation.Simulate( - settings, entryPoint, parseResult, DefaultIfShadowed(SimulatorOption, simulator)); + /// The text color. + /// The text writer for the console output stream. + /// The message to display. + internal static void DisplayWithColor(ConsoleColor color, TextWriter writer, string message) + { + var originalForeground = Console.ForegroundColor; + Console.ForegroundColor = color; + writer.WriteLine(message); + Console.ForegroundColor = originalForeground; + } /// - /// Submits the entry point to Azure Quantum. + /// Copies the handle and options from the given sub command to the given command. /// - /// The command-line parsing result. - /// The Azure submission settings. - private async Task Submit(ParseResult parseResult, AzureSettings azureSettings) => - await Azure.Submit(entryPoint, parseResult, new AzureSettings + /// The command whose handle and options will be set. + /// The sub command that will be copied from. + /// The validators associated with the sub command. + private static void SetSubCommandAsDefault(Command root, Command subCommand, Validators validators) + { + root.Handler = subCommand.Handler; + foreach (var option in subCommand.Options) { - Subscription = azureSettings.Subscription, - ResourceGroup = azureSettings.ResourceGroup, - Workspace = azureSettings.Workspace, - Target = DefaultIfShadowed(TargetOption, azureSettings.Target), - Storage = DefaultIfShadowed(StorageOption, azureSettings.Storage), - AadToken = DefaultIfShadowed(AadTokenOption, azureSettings.AadToken), - BaseUri = DefaultIfShadowed(BaseUriOption, azureSettings.BaseUri), - Location = DefaultIfShadowed(LocationOption, azureSettings.Location), - JobName = DefaultIfShadowed(JobNameOption, azureSettings.JobName), - Shots = DefaultIfShadowed(ShotsOption, azureSettings.Shots), - Output = DefaultIfShadowed(OutputOption, azureSettings.Output), - DryRun = DefaultIfShadowed(DryRunOption, azureSettings.DryRun), - Verbose = DefaultIfShadowed(VerboseOption, azureSettings.Verbose) - }); + root.AddOption(option); + } + foreach (var validator in validators) + { + root.AddValidator(validator); + } + } /// - /// Returns true if the alias is not already used by an entry point option. + /// Returns true if the alias is not already used by an existing option. /// /// The alias to check. + /// Existing options to check against. /// True if the alias is available for use by the driver. - private bool IsAliasAvailable(string alias) => - !entryPoint.Options.SelectMany(option => option.RawAliases).Contains(alias); + private static bool IsAliasAvailable(string alias, IEnumerable + /// The type of the option's argument. /// The command to add the option to. /// The option to add. - /// The type of the option's argument. - private void AddOptionIfAvailable(Command command, OptionInfo option) + /// The list of validators added to the command during this function. + private static Validators AddOptionIfAvailable(Command command, OptionInfo option) { - if (IsAliasAvailable(option.Aliases.First())) + if (IsAliasAvailable(option.Aliases.First(), command.Options)) { - command.AddOption(option.Create(option.Aliases.Where(IsAliasAvailable))); + command.AddOption(option.Create(option.Aliases.Where(alias => IsAliasAvailable(alias, command.Options)))); } else if (option.Required) { - command.AddValidator(commandResult => - $"The required option {option.Aliases.First()} conflicts with an entry point parameter name."); + ValidateSymbol validator = commandResult => + $"The required option {option.Aliases.First()} conflicts with an entry point parameter name."; + + command.AddValidator(validator); + return ImmutableList.Create(validator); } + + return Validators.Empty; } /// @@ -220,8 +295,10 @@ private void AddOptionIfAvailable(Command command, OptionInfo option) /// /// The command to add the validator to. /// The primary aliases of the options to be marked as mutually exclusive. - private void MarkOptionsAsMutuallyExclusive(Command command, string[] primaryAliases) => - command.AddValidator(result => + /// The list of validators added to the command during this function. + private static Validators MarkOptionsAsMutuallyExclusive(Command command, string[] primaryAliases) + { + ValidateSymbol validator = result => { var presentAliases = new List(); foreach (var rawAlias in primaryAliases) @@ -241,146 +318,194 @@ private void MarkOptionsAsMutuallyExclusive(Command command, string[] primaryAli } return default; - }); - } - - /// - /// Static members for . - /// - internal static class Driver - { - // TODO: Define the aliases as constants. - - /// - /// The subscription option. - /// - internal static readonly OptionInfo SubscriptionOption = new OptionInfo( - ImmutableList.Create("--subscription"), "The subscription ID."); - - /// - /// The resource group option. - /// - internal static readonly OptionInfo ResourceGroupOption = new OptionInfo( - ImmutableList.Create("--resource-group"), "The resource group name."); - - /// - /// The workspace option. - /// - internal static readonly OptionInfo WorkspaceOption = new OptionInfo( - ImmutableList.Create("--workspace"), "The workspace name."); - - /// - /// The storage option. - /// - internal static readonly OptionInfo StorageOption = new OptionInfo( - ImmutableList.Create("--storage"), default, "The storage account connection string."); + }; - /// - /// The AAD token option. - /// - internal static readonly OptionInfo AadTokenOption = new OptionInfo( - ImmutableList.Create("--aad-token"), default, "The Azure Active Directory authentication token."); + command.AddValidator(validator); + return ImmutableList.Create(validator); + } /// - /// The base URI option. + /// Creates the simulate command. /// - internal static readonly OptionInfo BaseUriOption = new OptionInfo( - ImmutableList.Create("--base-uri"), default, "The base URI of the Azure Quantum endpoint."); + /// The entry point commands that will be the sub commands to the created command. + /// The created simulate command with the validators for that command. + private static CommandWithValidators CreateSimulateCommand( + List entryPointCommands) + { + var simulate = new Command("simulate", "(default) Run the program using a local simulator."); + if (entryPointCommands.Count() == 1) + { + var epCommandWValidators = entryPointCommands.First(); + epCommandWValidators.Command.IsHidden = true; + simulate.AddCommand(epCommandWValidators.Command); + SetSubCommandAsDefault(simulate, epCommandWValidators.Command, epCommandWValidators.Validators); + return new CommandWithValidators(simulate, epCommandWValidators.Validators); + } + else + { + foreach (var epCommandWValidators in entryPointCommands) + { + simulate.AddCommand(epCommandWValidators.Command); + } + return new CommandWithValidators(simulate, Validators.Empty); + } + } /// - /// The location to use with the default endpoint option. + /// Creates the Azure submit command. /// - internal static readonly OptionInfo LocationOption = new OptionInfo( - ImmutableList.Create("--location"), - default, - "The location to use with the default endpoint.", - validator: result => + /// The entry point commands that will be the sub commands to the created command. + /// The created submit command with the validators for that command. + private static CommandWithValidators CreateSubmitCommand(List entryPointCommands) + { + var submit = new Command("submit", "Submit the program to Azure Quantum.") + { + IsHidden = true, + }; + if (entryPointCommands.Count() == 1) + { + var epCommandWValidators = entryPointCommands.First(); + epCommandWValidators.Command.IsHidden = true; + submit.AddCommand(epCommandWValidators.Command); + SetSubCommandAsDefault(submit, epCommandWValidators.Command, epCommandWValidators.Validators); + return new CommandWithValidators(submit, epCommandWValidators.Validators); + } + else + { + foreach (var epCommandWValidators in entryPointCommands) { - var location = result.Tokens.SingleOrDefault()?.Value; - if (location == null) - { - return default; - } - - var normalizedLocation = AzureSettings.NormalizeLocation(location); - return Uri.CheckHostName(normalizedLocation) == UriHostNameType.Unknown ? - $"\"{location}\" is an invalid value for the --location option." : - default; - }); + submit.AddCommand(epCommandWValidators.Command); + } + return new CommandWithValidators(submit, Validators.Empty); + } + } /// - /// The job name option. + /// Creates a sub command specific to the given entry point for the simulate command. /// - internal static readonly OptionInfo JobNameOption = new OptionInfo( - ImmutableList.Create("--job-name"), default, "The name of the submitted job."); + /// The entry point to make a command for. + /// The command corresponding to the given entry point with the validators for that command. + private CommandWithValidators CreateSimulateEntryPointCommand(IEntryPoint entryPoint) + { + var command = new Command(entryPoint.Name, entryPoint.Summary) + { + Handler = CommandHandler.Create((ParseResult parseResult, string simulator) => this.Simulate(parseResult, simulator, entryPoint)) + }; + foreach (var option in entryPoint.Options) + { + command.AddOption(option); + } + return new CommandWithValidators(command, AddOptionIfAvailable(command, this.SimulatorOption)); + } /// - /// The shots option. + /// Creates a sub command specific to the given entry point for the submit command. /// - internal static readonly OptionInfo ShotsOption = new OptionInfo( - ImmutableList.Create("--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 entry point to make a command for. + /// The command corresponding to the given entry point with the validators for that command. + private CommandWithValidators CreateSubmitEntryPointCommand(IEntryPoint entryPoint) + { + var command = new Command(entryPoint.Name, entryPoint.Summary) + { + Handler = CommandHandler.Create((ParseResult parseResult, AzureSettings settings) => this.Submit(parseResult, settings, entryPoint)) + }; + foreach (var option in entryPoint.Options) + { + command.AddOption(option); + } - /// - /// The output option. - /// - internal static readonly OptionInfo OutputOption = new OptionInfo( - ImmutableList.Create("--output"), - OutputFormat.FriendlyUri, - "The information to show in the output after the job is submitted."); + var validators = AddOptionIfAvailable(command, SubscriptionOption) + .Concat(AddOptionIfAvailable(command, ResourceGroupOption)) + .Concat(AddOptionIfAvailable(command, WorkspaceOption)) + .Concat(AddOptionIfAvailable(command, this.TargetOption)) + .Concat(AddOptionIfAvailable(command, StorageOption)) + .Concat(AddOptionIfAvailable(command, AadTokenOption)) + .Concat(AddOptionIfAvailable(command, BaseUriOption)) + .Concat(AddOptionIfAvailable(command, LocationOption)) + .Concat(AddOptionIfAvailable(command, JobNameOption)) + .Concat(AddOptionIfAvailable(command, ShotsOption)) + .Concat(AddOptionIfAvailable(command, OutputOption)) + .Concat(AddOptionIfAvailable(command, DryRunOption)) + .Concat(AddOptionIfAvailable(command, VerboseOption)) + .Concat(MarkOptionsAsMutuallyExclusive( + command, + new[] { BaseUriOption.Aliases.First(), LocationOption.Aliases.First() })); + + return new CommandWithValidators(command, validators.ToImmutableList()); + } /// - /// The dry run option. + /// Simulates the entry point. /// - internal static readonly OptionInfo DryRunOption = new OptionInfo( - ImmutableList.Create("--dry-run"), - false, - "Validate the program and options, but do not submit to Azure Quantum."); + /// The command-line parsing result. + /// The simulator to use. + /// The entry point to simulate. + /// The exit code. + private Task Simulate(ParseResult parseResult, string simulator, IEntryPoint entryPoint) => + entryPoint.Simulate(parseResult, settings, DefaultIfShadowed(entryPoint, this.SimulatorOption, simulator)); /// - /// The verbose option. + /// Submits the entry point to Azure Quantum. /// - internal static readonly OptionInfo VerboseOption = new OptionInfo( - ImmutableList.Create("--verbose"), false, "Show additional information about the submission."); - + /// The command-line parsing result. + /// The Azure submission settings. + /// The entry point to submit. + /// The exit code. + private Task Submit(ParseResult parseResult, AzureSettings azureSettings, IEntryPoint entryPoint) => + entryPoint.Submit(parseResult, new AzureSettings + { + Subscription = azureSettings.Subscription, + ResourceGroup = azureSettings.ResourceGroup, + Workspace = azureSettings.Workspace, + Target = DefaultIfShadowed(entryPoint, this.TargetOption, azureSettings.Target), + Storage = DefaultIfShadowed(entryPoint, StorageOption, azureSettings.Storage), + AadToken = DefaultIfShadowed(entryPoint, AadTokenOption, azureSettings.AadToken), + BaseUri = DefaultIfShadowed(entryPoint, BaseUriOption, azureSettings.BaseUri), + Location = DefaultIfShadowed(entryPoint, LocationOption, azureSettings.Location), + JobName = DefaultIfShadowed(entryPoint, JobNameOption, azureSettings.JobName), + Shots = DefaultIfShadowed(entryPoint, ShotsOption, azureSettings.Shots), + Output = DefaultIfShadowed(entryPoint, OutputOption, azureSettings.Output), + DryRun = DefaultIfShadowed(entryPoint, DryRunOption, azureSettings.DryRun), + Verbose = DefaultIfShadowed(entryPoint, VerboseOption, azureSettings.Verbose) + }); + /// - /// Displays a message to the console using the given color and text writer. + /// A modification of the command-line class. /// - /// The text color. - /// The text writer for the console output stream. - /// The message to display. - internal static void DisplayWithColor(ConsoleColor color, TextWriter writer, string message) + private sealed class QsHelpBuilder : HelpBuilder { - var originalForeground = Console.ForegroundColor; - Console.ForegroundColor = color; - writer.WriteLine(message); - Console.ForegroundColor = originalForeground; + /// + /// Creates a new help builder using the given console. + /// + /// The console to use. + internal QsHelpBuilder(IConsole console) : base(console) + { + } + + protected override string ArgumentDescriptor(IArgument argument) + { + // Hide long argument descriptors. + var descriptor = base.ArgumentDescriptor(argument); + return descriptor.Length > 30 ? argument.Name : descriptor; + } } - } - /// - /// A modification of the command-line class. - /// - internal sealed class QsHelpBuilder : HelpBuilder - { /// - /// Creates a new help builder using the given console. + /// Struct for housing a command with its validators. /// - /// The console to use. - internal QsHelpBuilder(IConsole console) : base(console) + private struct CommandWithValidators { - } + public Command Command; + public Validators Validators; - protected override string ArgumentDescriptor(IArgument argument) - { - // Hide long argument descriptors. - var descriptor = base.ArgumentDescriptor(argument); - return descriptor.Length > 30 ? argument.Name : descriptor; + /// + /// Basic constructor. + /// + public CommandWithValidators(Command command, Validators validators) + { + Command = command; + Validators = validators; + } } } } diff --git a/src/Simulation/EntryPointDriver/DriverSettings.cs b/src/Simulation/EntryPointDriver/DriverSettings.cs index 17ec8e551a3..cd2402b12d8 100644 --- a/src/Simulation/EntryPointDriver/DriverSettings.cs +++ b/src/Simulation/EntryPointDriver/DriverSettings.cs @@ -1,4 +1,6 @@ -using System.Collections.Immutable; +using Microsoft.Quantum.Simulation.Core; +using System; +using System.Collections.Immutable; namespace Microsoft.Quantum.EntryPointDriver { @@ -27,6 +29,25 @@ public sealed class DriverSettings /// internal string ResourcesEstimatorName { get; } + /// + /// The name of the default simulator to use when simulating the entry point. + /// + internal string DefaultSimulatorName { get; } + + /// + /// The default execution target when to use when submitting the entry point to Azure Quantum. + /// + internal string DefaultExecutionTarget { get; } + + /// + /// Creates an instance of the default simulator if it is a custom simulator. + /// + /// An instance of the default custom simulator. + /// + /// Thrown if the default simulator is not a custom simulator. + /// + internal Func CreateDefaultCustomSimulator; + /// /// Creates a new driver settings instance. /// @@ -34,16 +55,25 @@ public sealed class DriverSettings /// The name of the quantum simulator. /// The name of the Toffoli simulator. /// The name of the resources estimator. + /// The name of the default simulator to use. + /// The name of the default execution target to use. + /// The function for creating a new instance of the default simulator if it is a custom simulator. public DriverSettings( IImmutableList simulatorOptionAliases, string quantumSimulatorName, string toffoliSimulatorName, - string resourcesEstimatorName) + string resourcesEstimatorName, + string defaultSimulatorName, + string defaultExecutionTarget, + Func createDefaultCustomSimulator) { SimulatorOptionAliases = simulatorOptionAliases; QuantumSimulatorName = quantumSimulatorName; ToffoliSimulatorName = toffoliSimulatorName; ResourcesEstimatorName = resourcesEstimatorName; + DefaultSimulatorName = defaultSimulatorName; + DefaultExecutionTarget = defaultExecutionTarget; + CreateDefaultCustomSimulator = createDefaultCustomSimulator; } } } diff --git a/src/Simulation/EntryPointDriver/IEntryPoint.cs b/src/Simulation/EntryPointDriver/IEntryPoint.cs index 1018fc10f89..67964abb05d 100644 --- a/src/Simulation/EntryPointDriver/IEntryPoint.cs +++ b/src/Simulation/EntryPointDriver/IEntryPoint.cs @@ -1,11 +1,10 @@ // 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; -using Microsoft.Quantum.Simulation.Core; +using System.Threading.Tasks; namespace Microsoft.Quantum.EntryPointDriver { @@ -16,10 +15,13 @@ namespace Microsoft.Quantum.EntryPointDriver /// Contains entry point properties needed by the command-line interface and allows the entry point to use /// command-line arguments. The implementation of this interface is code-generated. /// - /// The entry point's argument type. - /// The entry point's return type. - public interface IEntryPoint + public interface IEntryPoint { + /// + /// The name of the entry point. + /// + string Name { get; } + /// /// The summary from the entry point's documentation comment. /// @@ -31,34 +33,20 @@ public interface IEntryPoint IEnumerable