diff --git a/Simulation.sln b/Simulation.sln index 9a65f34a9db..ac2435996de 100644 --- a/Simulation.sln +++ b/Simulation.sln @@ -113,6 +113,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StandaloneInputReference", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "qir-standalone-input-reference", "src\Qir\Samples\StandaloneInputReference\qsharp\qir-standalone-input-reference.csproj", "{D7D34736-A719-4B45-A33F-2723F59EC29D}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Simulation", "Simulation", "{3CD26906-C7F3-47B8-AF43-FF6BCF1CB3EF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoSubstitution", "src\Simulation\AutoSubstitution\AutoSubstitution.csproj", "{33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.AutoSubstitution", "src\Simulation\AutoSubstitution.Tests\Tests.AutoSubstitution.csproj", "{4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.AutoSubstitution.Integration", "src\Simulation\AutoSubstitution.Integration.Tests\Tests.AutoSubstitution.Integration.csproj", "{D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -749,6 +757,54 @@ Global {D7D34736-A719-4B45-A33F-2723F59EC29D}.RelWithDebInfo|Any CPU.Build.0 = Debug|Any CPU {D7D34736-A719-4B45-A33F-2723F59EC29D}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU {D7D34736-A719-4B45-A33F-2723F59EC29D}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Debug|x64.ActiveCfg = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Debug|x64.Build.0 = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Release|Any CPU.Build.0 = Release|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Release|x64.ActiveCfg = Release|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Release|x64.Build.0 = Release|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.RelWithDebInfo|Any CPU.ActiveCfg = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.RelWithDebInfo|Any CPU.Build.0 = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.Debug|x64.ActiveCfg = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.Debug|x64.Build.0 = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.Release|Any CPU.Build.0 = Release|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.Release|x64.ActiveCfg = Release|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.Release|x64.Build.0 = Release|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.RelWithDebInfo|Any CPU.ActiveCfg = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.RelWithDebInfo|Any CPU.Build.0 = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.Debug|x64.ActiveCfg = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.Debug|x64.Build.0 = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.Release|Any CPU.Build.0 = Release|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.Release|x64.ActiveCfg = Release|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.Release|x64.Build.0 = Release|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.RelWithDebInfo|Any CPU.ActiveCfg = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.RelWithDebInfo|Any CPU.Build.0 = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -802,6 +858,10 @@ Global {AAFB81D3-BC87-404D-BA64-AF40B2D2E45A} = {F6C2D4C0-12DC-40E3-9C86-FA5308D9B567} {A7DB7367-9FD6-4164-8263-A05077BE54AB} = {AAFB81D3-BC87-404D-BA64-AF40B2D2E45A} {D7D34736-A719-4B45-A33F-2723F59EC29D} = {A7DB7367-9FD6-4164-8263-A05077BE54AB} + {3CD26906-C7F3-47B8-AF43-FF6BCF1CB3EF} = {020356B7-C3FC-4100-AE37-97E5D8288D1D} + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F} = {3CD26906-C7F3-47B8-AF43-FF6BCF1CB3EF} + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300} = {3CD26906-C7F3-47B8-AF43-FF6BCF1CB3EF} + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C} = {3CD26906-C7F3-47B8-AF43-FF6BCF1CB3EF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {929C0464-86D8-4F70-8835-0A5EAF930821} diff --git a/build/manifest.ps1 b/build/manifest.ps1 index 4ce93ed25b1..a66e133c80c 100644 --- a/build/manifest.ps1 +++ b/build/manifest.ps1 @@ -20,6 +20,7 @@ param( $artifacts = @{ Packages = @( "Microsoft.Azure.Quantum.Client", + "Microsoft.Quantum.AutoSubstitution", "Microsoft.Quantum.CSharpGeneration", "Microsoft.Quantum.Development.Kit", "Microsoft.Quantum.EntryPointDriver", @@ -35,6 +36,7 @@ $artifacts = @{ Assemblies = @( ".\src\Azure\Azure.Quantum.Client\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Azure.Quantum.Client.dll", + ".\src\Simulation\AutoSubstitution\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.AutoSubstitution.dll", ".\src\Simulation\CSharpGeneration\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.CSharpGeneration.dll", ".\src\Simulation\CSharpGeneration.App\bin\$Env:BUILD_CONFIGURATION\netcoreapp3.1\Microsoft.Quantum.CSharpGeneration.App.dll", ".\src\Simulation\RoslynWrapper\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.RoslynWrapper.dll", diff --git a/build/pack.ps1 b/build/pack.ps1 index 7f6dbe702a4..c786bf8bff9 100644 --- a/build/pack.ps1 +++ b/build/pack.ps1 @@ -90,6 +90,7 @@ function Pack-Dotnet() { Write-Host "##[info]Using nuget to create packages" Pack-Dotnet '../src/Azure/Azure.Quantum.Client/Microsoft.Azure.Quantum.Client.csproj' Pack-One '../src/Simulation/CSharpGeneration/Microsoft.Quantum.CSharpGeneration.fsproj' '-IncludeReferencedProjects' +Pack-One '../src/Simulation/AutoSubstitution/AutoSubstitution.csproj' '-IncludeReferencedProjects' Pack-Dotnet '../src/Simulation/EntryPointDriver/Microsoft.Quantum.EntryPointDriver.csproj' Pack-Dotnet '../src/Simulation/Core/Microsoft.Quantum.Runtime.Core.csproj' Pack-Dotnet '../src/Simulation/TargetDefinitions/Interfaces/Microsoft.Quantum.Targets.Interfaces.csproj' diff --git a/src/Simulation/AutoSubstitution.Integration.Tests/Integration.cs b/src/Simulation/AutoSubstitution.Integration.Tests/Integration.cs new file mode 100644 index 00000000000..c8e9979c4a4 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Integration.Tests/Integration.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Quantum.Simulation.Simulators; +using Xunit; + +namespace Microsoft.Quantum.AutoSubstitution.Testing +{ + public class CodeGenerationTests + { + [Fact] + public void CanSimulateWithAlternativeSimulator() + { + var sim = new ToffoliSimulator(); + TestQuantumSwap.Run(sim).Wait(); + } + } +} diff --git a/src/Simulation/AutoSubstitution.Integration.Tests/Integration.qs b/src/Simulation/AutoSubstitution.Integration.Tests/Integration.qs new file mode 100644 index 00000000000..eb2adb4a726 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Integration.Tests/Integration.qs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.AutoSubstitution.Testing { + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Measurement; + open Microsoft.Quantum.Targeting; + + @SubstitutableOnTarget("Microsoft.Quantum.Intrinsic.SWAP", "ToffoliSimulator") + operation QuantumSwap(a : Qubit, b : Qubit) : Unit { + within { + CNOT(a, b); + H(a); + H(b); + } apply { + CNOT(a, b); + } + } + + operation TestQuantumSwap() : Unit { + use a = Qubit(); + use b = Qubit(); + + X(a); + + QuantumSwap(a, b); + + EqualityFactR(MResetZ(a), Zero, "unexpected value for a after swap"); + EqualityFactR(MResetZ(b), One, "unexpected value for b after swap"); + } +} diff --git a/src/Simulation/AutoSubstitution.Integration.Tests/Tests.AutoSubstitution.Integration.csproj b/src/Simulation/AutoSubstitution.Integration.Tests/Tests.AutoSubstitution.Integration.csproj new file mode 100644 index 00000000000..7260a0b8e2c --- /dev/null +++ b/src/Simulation/AutoSubstitution.Integration.Tests/Tests.AutoSubstitution.Integration.csproj @@ -0,0 +1,22 @@ + + + + Library + netcoreapp3.1 + x64 + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + diff --git a/src/Simulation/AutoSubstitution.Tests/CodeGenerationTests.cs b/src/Simulation/AutoSubstitution.Tests/CodeGenerationTests.cs new file mode 100644 index 00000000000..218482a0069 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/CodeGenerationTests.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using Microsoft.Quantum.QsCompiler.AutoSubstitution; +using Microsoft.Quantum.QsCompiler.CompilationBuilder; +using Microsoft.Quantum.QsCompiler.ReservedKeywords; +using Microsoft.Quantum.QsCompiler.SyntaxTree; +using Xunit; + +namespace Microsoft.Quantum.AutoSubstitution.Testing +{ + public class CodeGenerationTests + { + [Fact] + public void CanGenerateAutoSubstitutionCode() + { + TestOneSuccessfulFile("Success"); + TestOneSuccessfulFile("SuccessA"); + TestOneSuccessfulFile("SuccessC"); + TestOneSuccessfulFile("SuccessCA"); + } + + [Fact] + public void CanFailForVariousReasons() + { + TestOneFailingFile("FailAlternativeDoesNotExist"); + TestOneFailingFile("FailDifferentSignatures"); + TestOneFailingFile("FailDifferentSpecializationKinds"); + TestOneFailingFile("FailNoNamespace"); + } + + private void TestOneSuccessfulFile(string fileName) + { + var step = new RewriteStep(); + var path = CreateNewTemporaryPath(); + step.AssemblyConstants[AssemblyConstants.OutputPath] = path; + + var compilation = CreateCompilation(Path.Combine("TestFiles", "Core.qs"), "Substitution.qs", Path.Combine("TestFiles", $"{fileName}.qs")); + + Assert.True(step.Transformation(compilation, out var transformed)); + var generatedFileName = Path.Combine(path, "__AutoSubstitution__.g.cs"); + Assert.True(File.Exists(generatedFileName)); + + // uncomment this line, when creating new unit tests to + // create files with expected content + //File.Copy(generatedFileName, $"{fileName}.cs_", true); + + Assert.Equal(File.ReadAllText(Path.Combine("TestFiles", $"{fileName}.cs_")).Replace("\r\n", "\n"), File.ReadAllText(generatedFileName).Replace("\r\n", "\n")); + + Directory.Delete(path, true); + } + + private void TestOneFailingFile(string fileName) + { + var step = new RewriteStep(); + var path = CreateNewTemporaryPath(); + step.AssemblyConstants[AssemblyConstants.OutputPath] = path; + + var compilation = CreateCompilation(Path.Combine("TestFiles", "Core.qs"), "Substitution.qs", Path.Combine("TestFiles", $"{fileName}.qs")); + + Assert.False(step.Transformation(compilation, out var transformed)); + Assert.Equal(2, step.GeneratedDiagnostics.Count()); + Assert.Equal(CodeAnalysis.DiagnosticSeverity.Error, step.GeneratedDiagnostics.Last().Severity); + } + + private QsCompilation CreateCompilation(params string[] fileNames) + { + var mgr = new CompilationUnitManager(); + var files = CreateFileManager(fileNames); + mgr.AddOrUpdateSourceFilesAsync(files).Wait(); + return mgr.Build().BuiltCompilation; + } + + private ImmutableHashSet CreateFileManager(params string[] fileNames) => + CompilationUnitManager.InitializeFileManagers( + fileNames.Select(fileName => { + var fileId = new Uri(Path.GetFullPath(fileName)); + return (id: fileId, content: File.ReadAllText(fileName)); + }).ToDictionary(t => t.id, t => t.content) + ); + + private readonly System.Random random = new System.Random(); + private string CreateNewTemporaryPath() => + Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), $"substitution-test-{random.Next(Int32.MaxValue)}")).FullName; + } +} diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/Core.qs b/src/Simulation/AutoSubstitution.Tests/TestFiles/Core.qs new file mode 100644 index 00000000000..2fdd2185eb8 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/Core.qs @@ -0,0 +1,5 @@ +// This file is needed to ensure that the Microsoft.Quantum.Core namespace +// exists for the tests. + +namespace Microsoft.Quantum.Core { +} diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/FailAlternativeDoesNotExist.qs b/src/Simulation/AutoSubstitution.Tests/TestFiles/FailAlternativeDoesNotExist.qs new file mode 100644 index 00000000000..7896578878e --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/FailAlternativeDoesNotExist.qs @@ -0,0 +1,6 @@ +namespace AutoSubstitutionTests { + open Microsoft.Quantum.Targeting; + + @SubstitutableOnTarget("Namespace.NotExisting", "ToffoliSimulator") + operation Fail() : Unit {} +} diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/FailDifferentSignatures.qs b/src/Simulation/AutoSubstitution.Tests/TestFiles/FailDifferentSignatures.qs new file mode 100644 index 00000000000..548496c4a26 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/FailDifferentSignatures.qs @@ -0,0 +1,8 @@ +namespace AutoSubstitutionTests { + open Microsoft.Quantum.Targeting; + + @SubstitutableOnTarget("AutoSubstitutionTests.FailClassical", "ToffoliSimulator") + operation Fail(a : Int) : Unit {} + + operation FailClassical(a : Double) : Unit {} +} diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/FailDifferentSpecializationKinds.qs b/src/Simulation/AutoSubstitution.Tests/TestFiles/FailDifferentSpecializationKinds.qs new file mode 100644 index 00000000000..fcfba7c3866 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/FailDifferentSpecializationKinds.qs @@ -0,0 +1,8 @@ +namespace AutoSubstitutionTests { + open Microsoft.Quantum.Targeting; + + @SubstitutableOnTarget("FailClassical", "ToffoliSimulator") + operation Fail() : Unit is Adj {} + + operation FailClassical() : Unit {} +} diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/FailNoNamespace.qs b/src/Simulation/AutoSubstitution.Tests/TestFiles/FailNoNamespace.qs new file mode 100644 index 00000000000..81ed06e2fa4 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/FailNoNamespace.qs @@ -0,0 +1,8 @@ +namespace AutoSubstitutionTests { + open Microsoft.Quantum.Targeting; + + @SubstitutableOnTarget("FailClassical", "ToffoliSimulator") + operation Fail() : Unit {} + + operation FailClassical() : Unit {} +} diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/Success.cs_ b/src/Simulation/AutoSubstitution.Tests/TestFiles/Success.cs_ new file mode 100644 index 00000000000..1e3cdbfda1a --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/Success.cs_ @@ -0,0 +1,49 @@ +using System; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Simulators; + +namespace AutoSubstitutionTests +{ + public partial class Success + { + public class Native : Success + { + public Native(Microsoft.Quantum.Simulation.Core.IOperationFactory m): base(m) + { + sim0 = ((m) as ToffoliSimulator); + } + + public override void __Init__() + { + base.__Init__(); + if ((sim0) != (null)) + { + alternative0 = (__Factory__.Get(typeof(AutoSubstitutionTests.SuccessClassical))); + } + } + + public override Func __Body__ + { + get + { + return args => + { + if ((sim0) != (null)) + { + return alternative0.__Body__(args); + } + else + { + return base.__Body__(args); + } + } + + ; + } + } + + private AutoSubstitutionTests.SuccessClassical alternative0 = null; + private ToffoliSimulator sim0 = null; + } + } +} \ No newline at end of file diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/Success.qs b/src/Simulation/AutoSubstitution.Tests/TestFiles/Success.qs new file mode 100644 index 00000000000..4e5c8390aec --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/Success.qs @@ -0,0 +1,8 @@ +namespace AutoSubstitutionTests { + open Microsoft.Quantum.Targeting; + + @SubstitutableOnTarget("AutoSubstitutionTests.SuccessClassical", "ToffoliSimulator") + operation Success() : Unit {} + + operation SuccessClassical() : Unit {} +} diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessA.cs_ b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessA.cs_ new file mode 100644 index 00000000000..c8cd88d9fa6 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessA.cs_ @@ -0,0 +1,69 @@ +using System; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Simulators; + +namespace AutoSubstitutionTests +{ + public partial class Success + { + public class Native : Success + { + public Native(Microsoft.Quantum.Simulation.Core.IOperationFactory m): base(m) + { + sim0 = ((m) as ToffoliSimulator); + } + + public override void __Init__() + { + base.__Init__(); + if ((sim0) != (null)) + { + alternative0 = (__Factory__.Get(typeof(AutoSubstitutionTests.SuccessClassical))); + } + } + + public override Func __Body__ + { + get + { + return args => + { + if ((sim0) != (null)) + { + return alternative0.__Body__(args); + } + else + { + return base.__Body__(args); + } + } + + ; + } + } + + public override Func __AdjointBody__ + { + get + { + return args => + { + if ((sim0) != (null)) + { + return alternative0.__AdjointBody__(args); + } + else + { + return base.__AdjointBody__(args); + } + } + + ; + } + } + + private AutoSubstitutionTests.SuccessClassical alternative0 = null; + private ToffoliSimulator sim0 = null; + } + } +} \ No newline at end of file diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessA.qs b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessA.qs new file mode 100644 index 00000000000..99a4407bb37 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessA.qs @@ -0,0 +1,8 @@ +namespace AutoSubstitutionTests { + open Microsoft.Quantum.Targeting; + + @SubstitutableOnTarget("AutoSubstitutionTests.SuccessClassical", "ToffoliSimulator") + operation Success() : Unit is Adj {} + + operation SuccessClassical() : Unit is Adj {} +} diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessC.cs_ b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessC.cs_ new file mode 100644 index 00000000000..73b7cfe76d9 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessC.cs_ @@ -0,0 +1,69 @@ +using System; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Simulators; + +namespace AutoSubstitutionTests +{ + public partial class Success + { + public class Native : Success + { + public Native(Microsoft.Quantum.Simulation.Core.IOperationFactory m): base(m) + { + sim0 = ((m) as ToffoliSimulator); + } + + public override void __Init__() + { + base.__Init__(); + if ((sim0) != (null)) + { + alternative0 = (__Factory__.Get(typeof(AutoSubstitutionTests.SuccessClassical))); + } + } + + public override Func __Body__ + { + get + { + return args => + { + if ((sim0) != (null)) + { + return alternative0.__Body__(args); + } + else + { + return base.__Body__(args); + } + } + + ; + } + } + + public override Func<(IQArray, QVoid), QVoid> __ControlledBody__ + { + get + { + return args => + { + if ((sim0) != (null)) + { + return alternative0.__ControlledBody__(args); + } + else + { + return base.__ControlledBody__(args); + } + } + + ; + } + } + + private AutoSubstitutionTests.SuccessClassical alternative0 = null; + private ToffoliSimulator sim0 = null; + } + } +} \ No newline at end of file diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessC.qs b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessC.qs new file mode 100644 index 00000000000..612e1671ac7 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessC.qs @@ -0,0 +1,8 @@ +namespace AutoSubstitutionTests { + open Microsoft.Quantum.Targeting; + + @SubstitutableOnTarget("AutoSubstitutionTests.SuccessClassical", "ToffoliSimulator") + operation Success() : Unit is Ctl {} + + operation SuccessClassical() : Unit is Ctl {} +} diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessCA.cs_ b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessCA.cs_ new file mode 100644 index 00000000000..eaeac06869c --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessCA.cs_ @@ -0,0 +1,109 @@ +using System; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Simulators; + +namespace AutoSubstitutionTests +{ + public partial class Success + { + public class Native : Success + { + public Native(Microsoft.Quantum.Simulation.Core.IOperationFactory m): base(m) + { + sim0 = ((m) as ToffoliSimulator); + } + + public override void __Init__() + { + base.__Init__(); + if ((sim0) != (null)) + { + alternative0 = (__Factory__.Get(typeof(AutoSubstitutionTests.SuccessClassical))); + } + } + + public override Func __Body__ + { + get + { + return args => + { + if ((sim0) != (null)) + { + return alternative0.__Body__(args); + } + else + { + return base.__Body__(args); + } + } + + ; + } + } + + public override Func __AdjointBody__ + { + get + { + return args => + { + if ((sim0) != (null)) + { + return alternative0.__AdjointBody__(args); + } + else + { + return base.__AdjointBody__(args); + } + } + + ; + } + } + + public override Func<(IQArray, QVoid), QVoid> __ControlledBody__ + { + get + { + return args => + { + if ((sim0) != (null)) + { + return alternative0.__ControlledBody__(args); + } + else + { + return base.__ControlledBody__(args); + } + } + + ; + } + } + + public override Func<(IQArray, QVoid), QVoid> __ControlledAdjointBody__ + { + get + { + return args => + { + if ((sim0) != (null)) + { + return alternative0.__ControlledAdjointBody__(args); + } + else + { + return base.__ControlledAdjointBody__(args); + } + } + + ; + } + } + + private AutoSubstitutionTests.SuccessClassical alternative0 = null; + private ToffoliSimulator sim0 = null; + } + } +} \ No newline at end of file diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessCA.qs b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessCA.qs new file mode 100644 index 00000000000..58b4652ef94 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessCA.qs @@ -0,0 +1,8 @@ +namespace AutoSubstitutionTests { + open Microsoft.Quantum.Targeting; + + @SubstitutableOnTarget("AutoSubstitutionTests.SuccessClassical", "ToffoliSimulator") + operation Success() : Unit is Adj+Ctl {} + + operation SuccessClassical() : Unit is Adj+Ctl {} +} diff --git a/src/Simulation/AutoSubstitution.Tests/Tests.AutoSubstitution.csproj b/src/Simulation/AutoSubstitution.Tests/Tests.AutoSubstitution.csproj new file mode 100644 index 00000000000..d2ce8ad57ba --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/Tests.AutoSubstitution.csproj @@ -0,0 +1,69 @@ + + + + netcoreapp3.1 + Tests.Microsoft.Quantum.AutoSubstitution + false + x64 + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + diff --git a/src/Simulation/AutoSubstitution/AutoSubstitution.csproj b/src/Simulation/AutoSubstitution/AutoSubstitution.csproj new file mode 100644 index 00000000000..4cd398f157c --- /dev/null +++ b/src/Simulation/AutoSubstitution/AutoSubstitution.csproj @@ -0,0 +1,18 @@ + + + + + + Library + netstandard2.1 + Enable + x64 + true + Microsoft.Quantum.AutoSubstitution + + + + + + + diff --git a/src/Simulation/AutoSubstitution/CodeGenerator.cs b/src/Simulation/AutoSubstitution/CodeGenerator.cs new file mode 100644 index 00000000000..50e1ec8e0b4 --- /dev/null +++ b/src/Simulation/AutoSubstitution/CodeGenerator.cs @@ -0,0 +1,297 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.Quantum.QsCompiler.CsharpGeneration; +using Microsoft.Quantum.QsCompiler.SyntaxTree; + +namespace Microsoft.Quantum.QsCompiler.AutoSubstitution +{ + public class CodeGenerator + { + public CodeGenerator(CodegenContext context) + { + ctx = context; + gen = Microsoft.CodeAnalysis.Editing.SyntaxGenerator.GetGenerator(new AdhocWorkspace(), LanguageNames.CSharp); + } + + /// + /// Generates an substitution class for a given callable with a given name + /// + /// + /// + /// In the following we illustrate the syntax that is generated using + /// as an example the `Microsoft.Quantum.Canon.ApplyAnd` operation with + /// `Microsoft.Quantum.Intrinsic.CCNOT` as an alternative when using + /// `ToffoliSimulator`. + /// + /// The generated code looks as follows: + /// + /// + /// namespace Microsoft.Quantum.Canon { + /// public partial class ApplyAnd { + /// public class Native : ApplyAnd { + /// public Native(Microsoft.Quantum.Simulation.Core.IOperationFactory m) : base(m) { + /// sim0 = m as ToffoliSimulator; + /// } + /// + /// public override void __Init__() { + /// base.Init(); + /// if (sim0 != null) alternative0 = __Factory__.Get<Microsoft.Quantum.Intrinsic.CCNOT>(typeof(Microsoft.Quantum.Intrinsic.CCNOT)); + /// } + /// + /// public override Func<(Qubit, Qubit, Qubit), QVoid> __Body__ => args => { + /// if (sim0 != null) return alternative0.__Body__(args); + /// else return base.__Body__(args); + /// } + /// + /// // methods for other specializations ... + /// + /// private ToffoliSimulator sim0 = null; + /// private Microsoft.Quantum.Intrinsic.CCNOT alternative0 = null; + /// } + /// } + /// } + /// + /// + /// + /// Namespace and name of the callable + /// Q# Callable + /// All attribute values from @HasSubstitutionAttribute attributes of that callable + public void AddCallable(QsQualifiedName name, QsCallable callable, IEnumerable<(string AlternativeOperation, string InSimulator)> substitutionAttributes) + { + var attributes = substitutionAttributes.Select((attr, idx) => new SubstitutionAttribute( + SyntaxFactory.ParseTypeName(attr.AlternativeOperation), + gen.IdentifierName($"alternative{idx}"), + SyntaxFactory.ParseTypeName(attr.InSimulator), + gen.IdentifierName($"sim{idx}") + )); + + var operationFields = attributes.Select(CreateOperationField); + var simulatorFields = attributes.Select(CreateSimulatorField); + + var specializationProperties = callable.Specializations.Select(specialization => + CreateSpecializationProperty( + specializationName: specialization.Kind switch + { + var kind when kind.IsQsBody => "__Body__", + var kind when kind.IsQsAdjoint => "__AdjointBody__", + var kind when kind.IsQsControlled => "__ControlledBody__", + var kind when kind.IsQsControlledAdjoint => "__ControlledAdjointBody__", + _ => throw new Exception("unexpected specialization kind") + }, + attributes: attributes, + argumentType: SimulationCode.roslynTypeName(ctx, specialization.Signature.ArgumentType), + returnType: SimulationCode.roslynTypeName(ctx, specialization.Signature.ReturnType)) + ); + + var innerClass = gen.ClassDeclaration( + "Native", + accessibility: Accessibility.Public, + baseType: gen.IdentifierName(name.Name), + members: new[] { + CreateConstructor(attributes.Select(CreateSimulatorCast)), + CreateInitMethod(attributes.Select(CreateOperationAssignment)) + }.Concat(specializationProperties).Concat(operationFields).Concat(simulatorFields)); + + var cls = gen.ClassDeclaration( + name.Name, + accessibility: callable.Access.IsInternal ? Accessibility.Internal : Accessibility.Public, + modifiers: DeclarationModifiers.Partial, + members: new[] { innerClass }); + + InsertClassNode(name.Namespace, cls); + } + + /// + /// Creates a syntax node for the constructor of the inner Native class + /// + private SyntaxNode CreateConstructor(IEnumerable bodyStatements) => + gen.ConstructorDeclaration( + "Native", + parameters: new[] { gen.ParameterDeclaration("m", SyntaxFactory.ParseTypeName("Microsoft.Quantum.Simulation.Core.IOperationFactory")) }, + accessibility: Accessibility.Public, + baseConstructorArguments: new[] { gen.IdentifierName("m") }, + statements: bodyStatements); + + /// + /// Creates a syntax node for the Init() method of the inner Native class + /// + private SyntaxNode CreateInitMethod(IEnumerable bodyStatements) => + gen.MethodDeclaration( + "__Init__", + accessibility: Accessibility.Public, + modifiers: DeclarationModifiers.Override, + statements: new[] { + gen.ExpressionStatement( + gen.InvocationExpression(gen.MemberAccessExpression(gen.BaseExpression(), "__Init__")) + ) + }.Concat(bodyStatements)); + + /// + /// Creates a syntax node for field declarations for the alternative operations + /// + private SyntaxNode CreateOperationField(SubstitutionAttribute attr) => + gen.FieldDeclaration( + attr.OperationName.ToFullString(), + type: attr.OperationType, + accessibility: Accessibility.Private, + initializer: gen.NullLiteralExpression()); + + /// + /// Creates a syntax node for operation field assignments in the Init() method + /// + private SyntaxNode CreateOperationAssignment(SubstitutionAttribute attr) => + gen.IfStatement( + condition: gen.ValueNotEqualsExpression(attr.SimulatorName, gen.NullLiteralExpression()), + trueStatements: new[] + { + gen.AssignmentStatement( + left: attr.OperationName, + right: CreateFactoryGetStatement(attr.OperationType)) + } + ); + + /// + /// Creates a syntax node for the __Factory__.Get statement in operation field assignments + /// + private SyntaxNode CreateFactoryGetStatement(SyntaxNode type) => + gen.InvocationExpression( + gen.MemberAccessExpression( + gen.IdentifierName("__Factory__"), + gen.GenericName("Get", type)), + gen.TypeOfExpression(type)); + + /// + /// Creates a syntax node for field declarations for the simulators + /// + /// + /// + private SyntaxNode CreateSimulatorField(SubstitutionAttribute attr) => + gen.FieldDeclaration( + attr.SimulatorName.ToFullString(), + type: attr.SimulatorType, + accessibility: Accessibility.Private, + initializer: gen.NullLiteralExpression()); + + /// + /// Creates a syntax node for the simulator field cast assignments in the constructor + /// + /// + /// + private SyntaxNode CreateSimulatorCast(SubstitutionAttribute attr) => + gen.AssignmentStatement( + left: attr.SimulatorName, + right: gen.TryCastExpression(gen.IdentifierName("m"), attr.SimulatorType) + ); + + /// + /// Creates a syntax node for one specialization property + /// + private SyntaxNode CreateSpecializationProperty(string specializationName, IEnumerable attributes, string argumentType, string returnType) => + gen.PropertyDeclaration( + specializationName, + type: SyntaxFactory.ParseTypeName($"Func<{argumentType}, {returnType}>"), + accessibility: Accessibility.Public, + modifiers: DeclarationModifiers.Override | DeclarationModifiers.ReadOnly, + getAccessorStatements: new[] { gen.ReturnStatement(CreateSpecializationLambda(specializationName, attributes)) }); + + /// + /// Creates a syntax node for the lambda expression, returned in the + /// `get` accessor of a specialization property + /// + private SyntaxNode CreateSpecializationLambda(string specializationName, IEnumerable attrs) => + gen.ValueReturningLambdaExpression( + "args", + new[] + { + CreateSpecializationBody(specializationName, attrs) + }); + + /// + /// Creates a syntax node for a nested if-statement for all possible + /// operation alternatives + /// + private SyntaxNode CreateSpecializationBody(string specializationName, IEnumerable attrs) => + attrs.Count() switch + { + 0 => gen.ReturnStatement( + gen.InvocationExpression( + gen.MemberAccessExpression(gen.BaseExpression(), specializationName), + gen.IdentifierName("args"))), + _ => gen.IfStatement( + condition: gen.ValueNotEqualsExpression(attrs.First().SimulatorName, gen.NullLiteralExpression()), + trueStatements: new[] { + gen.ReturnStatement( + gen.InvocationExpression( + gen.MemberAccessExpression(attrs.First().OperationName, specializationName), + gen.IdentifierName("args"))) }, + falseStatement: CreateSpecializationBody(specializationName, attrs.Skip(1))) + }; + + /// + /// Inserts a syntax node for a class declaration into a list for its + /// corresponding namespace + /// + /// Namespace name + /// Syntax node for class declaration + private void InsertClassNode(string @namespace, SyntaxNode classNode) + { + if (!classNodes.ContainsKey(@namespace)) + { + classNodes.Add(@namespace, new List()); + } + + classNodes[@namespace].Add(classNode); + } + + /// + /// Group class declarations of the same namespace into a namespace node + /// + private IEnumerable GetNamespaceNodes() => + classNodes.Select(pair => gen.NamespaceDeclaration(pair.Key, pair.Value)); + + /// + /// Write namespaces into a text writer + /// + /// Text writer, e.g., `Console.Out` + public void WriteTo(TextWriter writer) => + gen.CompilationUnit( + new[] { "System", "Microsoft.Quantum.Simulation.Core", "Microsoft.Quantum.Simulation.Simulators" }.Select(gen.NamespaceImportDeclaration) + .Concat(GetNamespaceNodes()) + ).NormalizeWhitespace().WriteTo(writer); + + /// + /// Helper struct to prepare syntax nodes for @SubstitutableOnTarget attribute values + /// + private class SubstitutionAttribute + { + public SubstitutionAttribute(SyntaxNode operationType, SyntaxNode operationName, SyntaxNode simulatorType, SyntaxNode simulatorName) + { + OperationType = operationType; + OperationName = operationName; + SimulatorType = simulatorType; + SimulatorName = simulatorName; + } + + public SyntaxNode OperationType { get; private set; } + public SyntaxNode OperationName { get; private set; } + public SyntaxNode SimulatorType { get; private set; } + public SyntaxNode SimulatorName { get; private set; } + } + + private readonly CodegenContext ctx; + private readonly Microsoft.CodeAnalysis.Editing.SyntaxGenerator gen; + + /// + /// Contains class nodes by namespace names + /// + private readonly Dictionary> classNodes = new Dictionary>(); + } +} diff --git a/src/Simulation/AutoSubstitution/Microsoft.Quantum.AutoSubstitution.nuspec.template b/src/Simulation/AutoSubstitution/Microsoft.Quantum.AutoSubstitution.nuspec.template new file mode 100644 index 00000000000..f9d83f01d9a --- /dev/null +++ b/src/Simulation/AutoSubstitution/Microsoft.Quantum.AutoSubstitution.nuspec.template @@ -0,0 +1,29 @@ + + + + Microsoft.Quantum.AutoSubstitution + $version$ + $title$ + Microsoft + QuantumEngineering, Microsoft + + MIT + https://docs.microsoft.com/azure/quantum + images\qdk-nuget-icon.png + + false + C# code generation for automatic target substitution. + + See: https://docs.microsoft.com/azure/quantum/qdk-relnotes/ + + $copyright$ + Quantum Q# QSharp + + + + + + + + + diff --git a/src/Simulation/AutoSubstitution/RewriteStep.cs b/src/Simulation/AutoSubstitution/RewriteStep.cs new file mode 100644 index 00000000000..2108bd8c93c --- /dev/null +++ b/src/Simulation/AutoSubstitution/RewriteStep.cs @@ -0,0 +1,180 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.Quantum.QsCompiler.CsharpGeneration; +using Microsoft.Quantum.QsCompiler.SyntaxTokens; +using Microsoft.Quantum.QsCompiler.SyntaxTree; + +namespace Microsoft.Quantum.QsCompiler.AutoSubstitution +{ + /// + /// A Q# rewrite step for auto substitution + /// + /// + /// + /// This rewrite step creates custom emulators for operations that have the + /// SubstitutableOnTarget attribute. This attribute holds an alternative operation, + /// with the same signature, as its first argument, and a simulator, for which + /// the alternative operation should be used, as its second argument. + /// + public class RewriteStep : IRewriteStep + { + public string Name => "AutoSubstitution"; + + // This rewrite step needs to be run before C# code generation + public int Priority => -2; + + public IDictionary AssemblyConstants { get; } = new Dictionary(); + + public IEnumerable GeneratedDiagnostics => diagnostics; + + public bool ImplementsPreconditionVerification => false; + + public bool ImplementsTransformation => true; + + public bool ImplementsPostconditionVerification => false; + + public bool PostconditionVerification(QsCompilation compilation) => throw new System.NotImplementedException(); + + public bool PreconditionVerification(QsCompilation compilation) => throw new System.NotImplementedException(); + + public bool Transformation(QsCompilation compilation, [NotNullWhen(true)] out QsCompilation? transformed) + { + // we do not change the Q# syntax tree + transformed = compilation; + + // global callables + var globalCallables = compilation.Namespaces.GlobalCallableResolutions(); + + // collect all callables that have an substitution attribute + var globals = globalCallables.Where(p => p.Value.Source.CodeFile.EndsWith(".qs")) + .Where(p => p.Value.Attributes.Any(HasSubstitutionAttribute)); + + if (!globals.Any()) + { + diagnostics.Add(new IRewriteStep.Diagnostic + { + Severity = DiagnosticSeverity.Info, + Message = "AutoSubstitution: no operations have @SubstitutableOnTarget attribute", + Stage = IRewriteStep.Stage.Transformation + }); + return true; + } + + // no need to generate any C# file, if there is no substitution attribute, or if we cannot retrieve the output path + if (!AssemblyConstants.TryGetValue(Microsoft.Quantum.QsCompiler.ReservedKeywords.AssemblyConstants.OutputPath, out var outputPath)) + { + diagnostics.Add(new IRewriteStep.Diagnostic + { + Severity = DiagnosticSeverity.Error, + Message = "AutoSubstitution: cannot determine output path for generated C# code", + Stage = IRewriteStep.Stage.Transformation + }); + return false; + } + + diagnostics.Add(new IRewriteStep.Diagnostic + { + Severity = DiagnosticSeverity.Info, + Message = $"AutoSubstitution: Generating file __AutoSubstitution__.g.cs in {outputPath}", + Stage = IRewriteStep.Stage.Transformation + }); + + using var writer = new StreamWriter(Path.Combine(outputPath, "__AutoSubstitution__.g.cs")); + var context = CodegenContext.Create(compilation, AssemblyConstants); + + var generator = new CodeGenerator(context); + foreach (var (key, callable) in globals) + { + var attributeArguments = callable.Attributes.Where(HasSubstitutionAttribute).Select(GetSubstitutionAttributeArguments); + foreach (var (alternativeOperation, _) in attributeArguments) + { + var period = alternativeOperation.LastIndexOf('.'); + if (period == -1) + { + diagnostics.Add(new IRewriteStep.Diagnostic + { + Severity = DiagnosticSeverity.Error, + Message = $"AutoSubstitution: name of alternative operation in {key.Namespace}.{key.Name} must be completely specified (including namespace)", + Stage = IRewriteStep.Stage.Transformation + }); + return false; + } + + var qualifiedName = new QsQualifiedName(alternativeOperation.Substring(0, period), alternativeOperation.Substring(period + 1)); + if (!globalCallables.TryGetValue(qualifiedName, out var alternativeCallable)) + { + diagnostics.Add(new IRewriteStep.Diagnostic + { + Severity = DiagnosticSeverity.Error, + Message = $"AutoSubstitution: cannot find alternative operation `{alternativeOperation}`", + Stage = IRewriteStep.Stage.Transformation + }); + return false; + } + + var callableSignature = callable.Signature; + var alternativeSignature = alternativeCallable.Signature; + + if (!callableSignature.ArgumentType.Equals(alternativeSignature.ArgumentType) || !callableSignature.ReturnType.Equals(alternativeSignature.ReturnType)) + { + diagnostics.Add(new IRewriteStep.Diagnostic + { + Severity = DiagnosticSeverity.Error, + Message = $"AutoSubstitution: signature of `{alternativeOperation}` does not match the one of {key.Namespace}.{key.Name}", + Stage = IRewriteStep.Stage.Transformation + }); + return false; + } + + if (!GetSpecializationKinds(callable).IsSubsetOf(GetSpecializationKinds(alternativeCallable))) + { + diagnostics.Add(new IRewriteStep.Diagnostic + { + Severity = DiagnosticSeverity.Error, + Message = $"AutoSubstitution: specializations of `{alternativeOperation}` must be a superset of specializations of {key.Namespace}.{key.Name}", + Stage = IRewriteStep.Stage.Transformation + }); + return false; + } + } + generator.AddCallable(key, callable, attributeArguments); + } + generator.WriteTo(writer); + + + return true; + } + + private static bool HasSubstitutionAttribute(QsDeclarationAttribute attribute) => + attribute.TypeId.IsValue && attribute.TypeId.Item.Namespace == "Microsoft.Quantum.Targeting" && attribute.TypeId.Item.Name == "SubstitutableOnTarget"; + + private static (string AlternativeOperation, string InSimulator) GetSubstitutionAttributeArguments(QsDeclarationAttribute attribute) => + attribute.Argument.Expression switch + { + QsExpressionKind.ValueTuple tuple => + tuple.Item switch + { + var arr when arr.Count() == 2 => + (arr.ElementAt(0).Expression, arr.ElementAt(1).Expression) switch + { + (QsExpressionKind.StringLiteral alternativeOperation, QsExpressionKind.StringLiteral inSimulator) => (alternativeOperation.Item1, inSimulator.Item1), + _ => throw new Exception("Unexpected argument") + }, + _ => throw new Exception("Unexpected argument") + }, + _ => throw new Exception("Unexpected argument") + }; + + private static ISet GetSpecializationKinds(QsCallable callable) => + callable.Specializations.Select(spec => spec.Kind).OrderBy(kind => kind).ToHashSet(); + + private List diagnostics = new List(); + } +} diff --git a/src/Simulation/AutoSubstitution/RewriteStep.props b/src/Simulation/AutoSubstitution/RewriteStep.props new file mode 100644 index 00000000000..ad16b42a1b3 --- /dev/null +++ b/src/Simulation/AutoSubstitution/RewriteStep.props @@ -0,0 +1,8 @@ + + + + + $(MSBuildThisFileDirectory)/../lib/netstandard2.1/Microsoft.Quantum.AutoSubstitution.dll + + + diff --git a/src/Simulation/AutoSubstitution/Substitution.qs b/src/Simulation/AutoSubstitution/Substitution.qs new file mode 100644 index 00000000000..ccaf539b6a0 --- /dev/null +++ b/src/Simulation/AutoSubstitution/Substitution.qs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Targeting { + /// # Summary + /// Enables to substitute an operation with an alternative operation for a given target + /// + /// # Named Items + /// ## AlternativeOperation + /// Fully qualified name of alternative operation to substitute operation with. + /// + /// ## TargetName + /// One of `QuantumSimulator`, `ToffoliSimulator`, or `ResourcesEstimator`, or a fully qualified name + /// of a custom target. + @Attribute() + newtype SubstitutableOnTarget = (AlternativeOperation : String, TargetName : String); +}