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);
+}