diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index c5f1c5ba27f..74971e5da41 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -4,15 +4,6 @@ FROM mcr.microsoft.com/vscode/devcontainers/universal:1-linux
USER root
-RUN apt update \
- && apt-get install -y --no-install-recommends \
- ninja-build \
- clang-11 \
- clang-tidy-11 \
- build-essential \
- && apt-get clean \
- && rm -rf /var/lib/apt/lists/
-
# Install CMake 3.20 (required since apt-get uses 3.16 and repo requires 3.20)
RUN curl -SsL https://github.com/Kitware/CMake/releases/download/v3.20.5/cmake-3.20.5-linux-x86_64.sh -o cmakeinstall.sh \
&& echo "f582e02696ceee81818dc3378531804b2213ed41c2a8bc566253d16d894cefab cmakeinstall.sh" | sha256sum -c --strict - \
@@ -20,5 +11,4 @@ RUN curl -SsL https://github.com/Kitware/CMake/releases/download/v3.20.5/cmake-3
&& ./cmakeinstall.sh --prefix=/usr/local --exclude-subdir \
&& rm cmakeinstall.sh
-
USER codespace
diff --git a/.gitignore b/.gitignore
index 962637a6fc6..80eefeea221 100644
--- a/.gitignore
+++ b/.gitignore
@@ -232,7 +232,7 @@ ClientBin/
*.publishsettings
orleans.codegen.cs
-# Including strong name files can present a security risk
+# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
@@ -328,7 +328,7 @@ __pycache__/
# OpenCover UI analysis results
OpenCover/
-# Azure Stream Analytics local run output
+# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
@@ -337,7 +337,7 @@ ASALocalRun/
# NVidia Nsight GPU debugger configuration file
*.nvuser
-# MFractors (Xamarin productivity tool) working folder
+# MFractors (Xamarin productivity tool) working folder
.mfractor/
/src/Simulation/Simulators.Tests/TestProjects/QSharpExe/built
@@ -357,6 +357,13 @@ xplat
src/Simulation/Native/win10/Microsoft.Quantum.Simulator.Runtime.dll
src/Simulation/Native/linux/libMicrosoft.Quantum.Simulator.Runtime.so
src/Simulation/Native/osx/libMicrosoft.Quantum.Simulator.Runtime.dylib
+src/Simulation/Native/win10/Microsoft.Quantum.SparseSimulator.Runtime.dll
+src/Simulation/Native/linux/libMicrosoft.Quantum.SparseSimulator.Runtime.so
+src/Simulation/Native/osx/libMicrosoft.Quantum.SparseSimulator.Runtime.dylib
src/Simulation/Native/win10/Microsoft.Quantum.Experimental.Simulators.Runtime.dll
src/Simulation/Native/linux/Microsoft.Quantum.Experimental.Simulators.Runtime.dll
src/Simulation/Native/osx/Microsoft.Quantum.Experimental.Simulators.Runtime.dll
+
+# Temporary Rust files/dirs.
+Cargo.lock
+target/
diff --git a/AdvantageBenchmark/privateBuild/host.csproj b/AdvantageBenchmark/privateBuild/host.csproj
index 7abf2e91bcc..ee51caa5bd8 100644
--- a/AdvantageBenchmark/privateBuild/host.csproj
+++ b/AdvantageBenchmark/privateBuild/host.csproj
@@ -4,7 +4,7 @@
Exe
- netcoreapp3.1
+ net6.0
false
diff --git a/AdvantageBenchmark/privateBuild/runTest.ps1 b/AdvantageBenchmark/privateBuild/runTest.ps1
index 12fe3870497..7f4bc003581 100644
--- a/AdvantageBenchmark/privateBuild/runTest.ps1
+++ b/AdvantageBenchmark/privateBuild/runTest.ps1
@@ -3,7 +3,7 @@ for ($tst=1; $tst -le 2; $tst++) {
for ($span=4; $span -ge 0; $span--) {
$env:OMP_NUM_THREADS = $thrd
$env:QDK_SIM_FUSESPAN = $span
- .\bin\Release\netcoreapp3.1\host.exe $tst $tst 5
+ .\bin\Release\net6.0\host.exe $tst $tst 5
}
}
}
diff --git a/AdvantageBenchmark/privateBuild/runTest.sh b/AdvantageBenchmark/privateBuild/runTest.sh
index c8dc2155faa..6d25553c88d 100755
--- a/AdvantageBenchmark/privateBuild/runTest.sh
+++ b/AdvantageBenchmark/privateBuild/runTest.sh
@@ -8,7 +8,7 @@ do
do
export OMP_NUM_THREADS=$thrd
export QDK_SIM_FUSESPAN=$span
- ./bin/Release/netcoreapp3.1/host $tst $tst 5
+ ./bin/Release/net6.0/host $tst $tst 5
done
done
done
diff --git a/AdvantageBenchmark/readme.md b/AdvantageBenchmark/readme.md
index 33242bd4fec..df6017ab1bf 100644
--- a/AdvantageBenchmark/readme.md
+++ b/AdvantageBenchmark/readme.md
@@ -6,7 +6,7 @@ This benchmark is intended to provide an easy way to verify the performance char
## Executing the benchmark
-To execute the benchmark, compile each version of advantage.sln using `dotnet build .\advantage.sln -c Release` from their respective folders. Then the executable to run will be either `bin\Release\netcoreapp3.1\host.exe` in the privateBuild folder or `host\bin\Release\netcoreapp3.1\host.exe` in the releaseBuild folder. This executable takes parameters describing which test circuits to execute and how many loops to perform as integer arguments, such that `host.exe 1 1 5` will run 5 loops of test 1 and `host.exe 0 3 100` will run 100 loops of tests 0 through 3. Check the contents of `privateBuild\Program.cs` to see the tests that correspond to each identifier; for most machines, test 1 aka advantage 4x4 circuit is the best choice for benchmarking.
+To execute the benchmark, compile each version of advantage.sln using `dotnet build .\advantage.sln -c Release` from their respective folders. Then the executable to run will be either `bin\Release\net6.0\host.exe` in the privateBuild folder or `host\bin\Release\net6.0\host.exe` in the releaseBuild folder. This executable takes parameters describing which test circuits to execute and how many loops to perform as integer arguments, such that `host.exe 1 1 5` will run 5 loops of test 1 and `host.exe 0 3 100` will run 100 loops of tests 0 through 3. Check the contents of `privateBuild\Program.cs` to see the tests that correspond to each identifier; for most machines, test 1 aka advantage 4x4 circuit is the best choice for benchmarking.
The benchmark can also be run via runTest.ps1 or runTest.sh, which performs a sweep across configured environment variables that adjust the number of threads used and gates fused in simulating the circuit. See the definition of the script used on your platform to understand how it configures the `OMP_NUM_THREADS` and `QDK_SIM_FUSESPAN` environment variables.
diff --git a/AdvantageBenchmark/releasedBuild/host/host.csproj b/AdvantageBenchmark/releasedBuild/host/host.csproj
index 8b145ec5eef..54fddb1b9be 100644
--- a/AdvantageBenchmark/releasedBuild/host/host.csproj
+++ b/AdvantageBenchmark/releasedBuild/host/host.csproj
@@ -10,7 +10,7 @@
Exe
- netcoreapp3.1
+ net6.0
diff --git a/Simulation.sln b/Simulation.sln
index 3e5df4df1bb..2dc1c691154 100644
--- a/Simulation.sln
+++ b/Simulation.sln
@@ -855,7 +855,7 @@ Global
{EB6E3DBD-C884-4241-9BC4-8281191D1F53} = {34D419E9-CCF1-4E48-9FA4-3AD4B86BEEB4}
{E1A463D7-2E23-4134-BE04-1EFF7A546813} = {93409CC3-8DF9-45FA-AE21-16A19FDEF650}
{789C86D9-CE77-40DA-BDDD-979436952512} = {93409CC3-8DF9-45FA-AE21-16A19FDEF650}
- {7E24885B-D86D-477E-A840-06FA53C33FE1} = {34D419E9-CCF1-4E48-9FA4-3AD4B86BEEB4}
+ {7E24885B-D86D-477E-A840-06FA53C33FE1} = {93409CC3-8DF9-45FA-AE21-16A19FDEF650}
{7F80466B-A6B5-4EF1-A9E9-22ABAE3C20C1} = {34D419E9-CCF1-4E48-9FA4-3AD4B86BEEB4}
{7F7BB60A-5DCB-469E-8546-1BE9E3CAC833} = {F6C2D4C0-12DC-40E3-9C86-FA5308D9B567}
{EAC5EAE7-D1B3-4726-AFDB-73000E62176A} = {7F7BB60A-5DCB-469E-8546-1BE9E3CAC833}
diff --git a/bootstrap.ps1 b/bootstrap.ps1
index 44db7e6c17a..302106c3be3 100644
--- a/bootstrap.ps1
+++ b/bootstrap.ps1
@@ -7,25 +7,22 @@ Push-Location (Join-Path $PSScriptRoot "build")
.\prerequisites.ps1
Pop-Location
+cargo install cargo-edit
Push-Location (Join-Path $PSScriptRoot "./src/Simulation/qdk_sim_rs")
- # We use dotnet-script to inject the version number into Cargo.toml,
- # so we go on ahead here and restore any missing tools.
- # Since that Cargo.toml is referenced by CMake lists in the QIR
- # runtime, this injection has to be the first thing we do.
- dotnet tool restore
- dotnet script inject-version.csx -- `
- --template Cargo.toml.template `
- --out-path Cargo.toml `
- --version $Env:NUGET_VERSION;
+ cargo set-version $Env:NUGET_VERSION;
Pop-Location
if (-not (Test-Path Env:/AGENT_OS)) { # If not CI build, i.e. local build (if AGENT_OS envvar is not defined)
if ($Env:ENABLE_NATIVE -ne "false") {
- Write-Host "Build release flavor of the native simulator"
$Env:BUILD_CONFIGURATION = "Release"
+ Write-Host "Build release flavor of the full state simulator"
Push-Location (Join-Path $PSScriptRoot "src/Simulation/Native")
.\build-native-simulator.ps1
Pop-Location
+
+ Write-Host "Build release flavor of the Sparse Simulator"
+ Invoke-Expression (Join-Path $PSScriptRoot "src" "Simulation" "NativeSparseSimulator" "build.ps1")
+
Push-Location (Join-Path $PSScriptRoot "src/Simulation/qdk_sim_rs")
# Don't run the experimental simulator build if we're local
# and prerequisites are missing.
diff --git a/build/build.ps1 b/build/build.ps1
index 10a19ee503c..23e0fbd47a7 100644
--- a/build/build.ps1
+++ b/build/build.ps1
@@ -22,6 +22,8 @@ if ($Env:ENABLE_NATIVE -ne "false") {
if ($LastExitCode -ne 0) {
$script:all_ok = $False
}
+
+ ( & (Join-Path $PSScriptRoot .. src Simulation NativeSparseSimulator build.ps1) ) || ( $script:all_ok = $False )
} else {
Write-Host "Skipping build of native simulator because ENABLE_NATIVE variable is set to: $Env:ENABLE_NATIVE."
}
diff --git a/build/ci.yml b/build/ci.yml
index 4e60039fd26..9b41c7cf18f 100644
--- a/build/ci.yml
+++ b/build/ci.yml
@@ -1,4 +1,4 @@
-name: $(Build.Major).$(Build.Minor).$(date:yyMM).$(DayOfMonth)$(rev:rr)
+name: $(Build.Major).$(Build.Minor).$(DayOfMonth)$(rev:rr)
trigger: none
diff --git a/build/pack.ps1 b/build/pack.ps1
index 37882ee7581..7b8df946aad 100644
--- a/build/pack.ps1
+++ b/build/pack.ps1
@@ -26,6 +26,17 @@ Push-Location (Join-Path $PSScriptRoot ../src/Simulation/Native)
Copy-Item -Verbose "$DROP/Microsoft.Quantum.Simulator.Runtime.dll" "win10/Microsoft.Quantum.Simulator.Runtime.dll"
}
+ $DROP = "$Env:DROP_NATIVE/src/Simulation/NativeSparseSimulator/build"
+ Write-Host "##[info]Copying NativeSparseSimulator files from $DROP...";
+ If (Test-Path "$DROP/libMicrosoft.Quantum.SparseSimulator.Runtime.dylib") {
+ Copy-Item -Verbose "$DROP/libMicrosoft.Quantum.SparseSimulator.Runtime.dylib" "osx/libMicrosoft.Quantum.SparseSimulator.Runtime.dylib"
+ }
+ If (Test-Path "$DROP/libMicrosoft.Quantum.SparseSimulator.Runtime.so") {
+ Copy-Item -Verbose "$DROP/libMicrosoft.Quantum.SparseSimulator.Runtime.so" "linux/libMicrosoft.Quantum.SparseSimulator.Runtime.so"
+ }
+ If (Test-Path "$DROP/Microsoft.Quantum.SparseSimulator.Runtime.dll") {
+ Copy-Item -Verbose "$DROP/Microsoft.Quantum.SparseSimulator.Runtime.dll" "win10/Microsoft.Quantum.SparseSimulator.Runtime.dll"
+ }
$DROP = "$Env:DROP_NATIVE/src/Simulation/qdk_sim_rs/drop";
Write-Host "##[info]Copying qdk_sim_rs files from $DROP...";
@@ -43,7 +54,7 @@ Pop-Location
function Pack-One() {
Param(
- $project,
+ $project,
$option1 = "",
$option2 = "",
$option3 = "",
@@ -74,7 +85,7 @@ function Pack-One() {
function Pack-Dotnet() {
Param(
- $project,
+ $project,
$option1 = "",
$option2 = "",
$option3 = "",
@@ -129,7 +140,7 @@ function Pack-Crate() {
$OutPath = Resolve-Path (Join-Path $PSScriptRoot $OutPath);
}
Push-Location (Join-Path $PSScriptRoot $PackageDirectory)
- cargo package;
+ cargo package --allow-dirty;
# Copy only the .crate file, since we don't need all the intermediate
# artifacts brought in by the full folder under target/package.
Copy-Item -Force (Join-Path $PSScriptRoot .. "target" "package" "*.crate") $OutPath;
diff --git a/build/steps-codecheck.yml b/build/steps-codecheck.yml
index fe474e3ccbf..e19bf254ff1 100644
--- a/build/steps-codecheck.yml
+++ b/build/steps-codecheck.yml
@@ -8,6 +8,12 @@ steps:
inputs:
versionSpec: '5.6.0'
+- task: UseDotNet@2
+ displayName: 'Use .NET Core SDK 6.0.x'
+ inputs:
+ packageType: sdk
+ version: '6.0.x'
+
# QIR Runtime:
- pwsh: src/Qir/Runtime/prerequisites.ps1
displayName: "Install QIR Runtime Prerequisites"
diff --git a/build/steps-init.yml b/build/steps-init.yml
index 42d9df112aa..feb3e4cc351 100644
--- a/build/steps-init.yml
+++ b/build/steps-init.yml
@@ -9,10 +9,10 @@ steps:
versionSpec: '5.6.0'
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 3.1.300'
+ displayName: 'Use .NET Core SDK 6.0.x'
inputs:
packageType: sdk
- version: '3.1.300'
+ version: '6.0.x'
- script: |
curl https://sh.rustup.rs -sSf | sh -s -- -y
diff --git a/build/test.ps1 b/build/test.ps1
index 8beec26e232..20be9e2a76e 100644
--- a/build/test.ps1
+++ b/build/test.ps1
@@ -5,6 +5,8 @@
$all_ok = $True
if ($Env:ENABLE_NATIVE -ne "false") {
+ ( & (Join-Path $PSScriptRoot .. src Simulation NativeSparseSimulator test.ps1 ) ) || ( $script:all_ok = $False )
+
$nativeSimulator = (Join-Path $PSScriptRoot "../src/Simulation/Native")
& "$nativeSimulator/test-native-simulator.ps1"
if ($LastExitCode -ne 0) {
diff --git a/global.json b/global.json
index 86178bfb451..d8117534bd5 100644
--- a/global.json
+++ b/global.json
@@ -1,5 +1,5 @@
{
"msbuild-sdks": {
- "Microsoft.Quantum.Sdk": "0.18.2109163417-beta"
+ "Microsoft.Quantum.Sdk": "0.24.201332"
}
}
diff --git a/src/Azure/Azure.Quantum.Client.Test/Authentication/TokenFileCredentialTests.cs b/src/Azure/Azure.Quantum.Client.Test/Authentication/TokenFileCredentialTests.cs
index 8a3d8a95a6c..6d2e5ae8976 100644
--- a/src/Azure/Azure.Quantum.Client.Test/Authentication/TokenFileCredentialTests.cs
+++ b/src/Azure/Azure.Quantum.Client.Test/Authentication/TokenFileCredentialTests.cs
@@ -36,7 +36,7 @@ public T GetFileContent(string path)
public async Task GetFileContentAsync(string path, CancellationToken cancellationToken)
{
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(_fileContent));
- return await JsonSerializer.DeserializeAsync(stream, null, cancellationToken);
+ return await JsonSerializer.DeserializeAsync(stream, options: null, cancellationToken);
}
}
diff --git a/src/Azure/Azure.Quantum.Client.Test/Tests.Microsoft.Azure.Quantum.Client.csproj b/src/Azure/Azure.Quantum.Client.Test/Tests.Microsoft.Azure.Quantum.Client.csproj
index 7ff14f03a27..ff8ffac582e 100644
--- a/src/Azure/Azure.Quantum.Client.Test/Tests.Microsoft.Azure.Quantum.Client.csproj
+++ b/src/Azure/Azure.Quantum.Client.Test/Tests.Microsoft.Azure.Quantum.Client.csproj
@@ -1,7 +1,7 @@
- netcoreapp3.1
+ net6.0
x64
false
diff --git a/src/Azure/Azure.Quantum.Client/Machine/QuantumMachineFactory.cs b/src/Azure/Azure.Quantum.Client/Machine/QuantumMachineFactory.cs
index be9c1b2b0e2..41b45ed095e 100644
--- a/src/Azure/Azure.Quantum.Client/Machine/QuantumMachineFactory.cs
+++ b/src/Azure/Azure.Quantum.Client/Machine/QuantumMachineFactory.cs
@@ -29,6 +29,8 @@ public static class QuantumMachineFactory
var machineName =
targetNameNormalized is null
? null
+ : targetNameNormalized.StartsWith("quantinuum.")
+ ? "Microsoft.Quantum.Providers.Quantinuum.Targets.QuantinuumQuantumMachine, Microsoft.Quantum.Providers.Honeywell"
: targetNameNormalized.StartsWith("qci.")
? "Microsoft.Quantum.Providers.QCI.Targets.QCIQuantumMachine, Microsoft.Quantum.Providers.QCI"
: targetNameNormalized.StartsWith("ionq.")
diff --git a/src/Azure/Azure.Quantum.Client/Utility/FileSystem.cs b/src/Azure/Azure.Quantum.Client/Utility/FileSystem.cs
index 7cb16b3a772..78ad50eb711 100644
--- a/src/Azure/Azure.Quantum.Client/Utility/FileSystem.cs
+++ b/src/Azure/Azure.Quantum.Client/Utility/FileSystem.cs
@@ -26,7 +26,7 @@ public T GetFileContent(string path)
public async Task GetFileContentAsync(string path, CancellationToken cancellationToken)
{
using FileStream stream = File.OpenRead(path);
- return await JsonSerializer.DeserializeAsync(stream, null, cancellationToken);
+ return await JsonSerializer.DeserializeAsync(stream, options: null, cancellationToken);
}
}
}
diff --git a/src/Qir/.clang-tidy b/src/Qir/.clang-tidy
index a306ababc4c..115b29be7c0 100644
--- a/src/Qir/.clang-tidy
+++ b/src/Qir/.clang-tidy
@@ -2,7 +2,8 @@
# https://clang.llvm.org/extra/clang-tidy/checks/list.html
Checks:
- 'bugprone-*,readability-identifier-*,readability-braces-around-statements,cert*,\
+ 'bugprone-*,-bugprone-easily-swappable-parameters,\
+ readability-identifier-*,readability-braces-around-statements,cert*,\
-llvmlibc-callee-namespace,-llvmlibc-implementation-in-namespace,\
-llvmlibc-restrict-system-libc-headers,-modernize-use-trailing-return-type,\
-fuchsia-default-arguments-calls,-fuchsia-default-arguments-declarations,
diff --git a/src/Qir/CommandLineTool/Microsoft.Quantum.Qir.CommandLineTool.csproj b/src/Qir/CommandLineTool/Microsoft.Quantum.Qir.CommandLineTool.csproj
index af69227323b..99fe6c63943 100644
--- a/src/Qir/CommandLineTool/Microsoft.Quantum.Qir.CommandLineTool.csproj
+++ b/src/Qir/CommandLineTool/Microsoft.Quantum.Qir.CommandLineTool.csproj
@@ -3,7 +3,7 @@
Exe
x64
- netcoreapp3.1
+ net6.0
diff --git a/src/Qir/Common/cmake/qir_cmake_include.cmake b/src/Qir/Common/cmake/qir_cmake_include.cmake
index 9ad60d716ea..3117f4eb896 100644
--- a/src/Qir/Common/cmake/qir_cmake_include.cmake
+++ b/src/Qir/Common/cmake/qir_cmake_include.cmake
@@ -81,6 +81,13 @@ set(WARNING_FLAGS "${WARNING_FLAGS} -Weverything")
# -Wpre-c++20-compat.
# -Wpre-c++2b-compat-pedantic (= -Wpre-c++2b-compat).
+include(CheckCCompilerFlag)
+check_c_compiler_flag(-Wreserved-identifier HAVE_RESERVED_IDENTIFIER_WARNING)
+if(HAVE_RESERVED_IDENTIFIER_WARNING)
+ # We need to be able to use `__` prefix for QIR names like `__quantum__rt__*` and `__quantum__qis__*`.
+ set(WARNING_FLAGS "${WARNING_FLAGS} -Wno-reserved-identifier")
+endif()
+
# https://clang.llvm.org/docs/DiagnosticsReference.html#wc-98-compat-pedantic
set(WARNING_FLAGS "${WARNING_FLAGS} -Wno-c++98-compat-pedantic")
diff --git a/src/Qir/Runtime/README.md b/src/Qir/Runtime/README.md
index ca78e33afa0..66db0a7d061 100644
--- a/src/Qir/Runtime/README.md
+++ b/src/Qir/Runtime/README.md
@@ -26,7 +26,7 @@ while on macOS, `prerequisites.ps1` relies on the [`brew` package manager](https
#### Windows pre-reqs
-1. Install Clang 11, Ninja and CMake from the public distros.
+1. Install Clang 13, Ninja and CMake from the public distros.
1. Add all three to your/system `%PATH%`.
1. Install VS 2019 and enable "Desktop development with C++" component (Clang uses MSVC's standard library on Windows).
1. Install clang-tidy and clang-format if your Clang/LLVM packages didn't include the tools.
@@ -42,11 +42,11 @@ Running cmake from the editors will likely default to MSVC or clang-cl and fail.
1. In the Ubuntu's terminal:
1. `$ sudo apt install cmake` (`$ cmake --version` should return 3.16.3)
1. `$ sudo apt-get install ninja-build` (`$ ninja --version` should return 1.10.0)
- 1. `$ sudo apt install clang-11` (`$ clang++-11 --version` should return 11.0.0)
+ 1. `$ sudo apt install clang-13` (`$ clang++-13 --version` should return 13.0.0)
1. Set Clang as the preferred C/C++ compiler:
- - $ export CC=/usr/bin/clang-11
- - $ export CXX=/usr/bin/clang++-11
- 1. `$ sudo apt install clang-tidy-11` (`$ clang-tidy-11 --version` should return 'LLVM version 11.0.0')
+ - $ export CC=/usr/bin/clang-13
+ - $ export CXX=/usr/bin/clang++-13
+ 1. `$ sudo apt install clang-tidy-13` (`$ clang-tidy-13 --version` should return 'LLVM version 13.0.0')
1. Install the same version of dotnet as specified by qsharp-runtime [README](../../../README.md)
See [https://code.visualstudio.com/docs/remote/wsl] on how to use VS Code with WSL.
diff --git a/src/Qir/Runtime/lib/QIR/QubitManager.cpp b/src/Qir/Runtime/lib/QIR/QubitManager.cpp
index 329fbab32b1..8a506827e79 100644
--- a/src/Qir/Runtime/lib/QIR/QubitManager.cpp
+++ b/src/Qir/Runtime/lib/QIR/QubitManager.cpp
@@ -423,7 +423,7 @@ namespace Quantum
{
areaIndex = freeQubitsInAreas[(size_t)areaIndex].prevAreaWithFreeQubits;
id = freeQubitsInAreas[(size_t)areaIndex].FreeQubitsReuseAllowed.TakeQubitFromFront(
- sharedQubitStatusArray);
+ sharedQubitStatusArray);
} while ((areaIndex != 0) && (id == NoneMarker));
// We remember previous area where a free qubit was found or 0 if none were found.
diff --git a/src/Qir/Runtime/lib/QIR/arrays.cpp b/src/Qir/Runtime/lib/QIR/arrays.cpp
index b9f1b78ee16..5c980a9fe26 100644
--- a/src/Qir/Runtime/lib/QIR/arrays.cpp
+++ b/src/Qir/Runtime/lib/QIR/arrays.cpp
@@ -118,7 +118,7 @@ QirArray::QirArray(TItemCount countItems, TItemSize itemSizeBytes, TDimCount dim
assert(this->count * (TBufSize)itemSizeInBytes < std::numeric_limits::max());
// Using `<` rather than `<=` to calm down the compiler on 32-bit arch.
- const TBufSize bufferSize = this->count * itemSizeInBytes;
+ const TBufSize bufferSize = (TBufSize)this->count * (TBufSize)itemSizeInBytes;
if (bufferSize > 0)
{
this->buffer = new char[bufferSize];
@@ -146,7 +146,7 @@ QirArray::QirArray(const QirArray& other)
assert((TBufSize)(this->count) * this->itemSizeInBytes < std::numeric_limits::max());
// Using `<` rather than `<=` to calm down the compiler on 32-bit arch.
- const TBufSize size = this->count * this->itemSizeInBytes;
+ const TBufSize size = (TBufSize)this->count * (TBufSize)this->itemSizeInBytes;
if (this->count > 0)
{
this->buffer = new char[size];
@@ -167,7 +167,7 @@ QirArray::~QirArray()
char* QirArray::GetItemPointer(TItemCount index) const
{
assert(index < this->count);
- return &this->buffer[index * this->itemSizeInBytes];
+ return &this->buffer[static_cast(index * this->itemSizeInBytes)];
}
void QirArray::Append(const QirArray* other)
@@ -178,7 +178,7 @@ void QirArray::Append(const QirArray* other)
assert((TBufSize)(other->count) * other->itemSizeInBytes < std::numeric_limits::max());
// Using `<` rather than `<=` to calm down the compiler on 32-bit arch.
- const TBufSize otherSize = other->count * other->itemSizeInBytes;
+ const TBufSize otherSize = (TBufSize)other->count * (TBufSize)other->itemSizeInBytes;
if (otherSize == 0)
{
@@ -187,7 +187,7 @@ void QirArray::Append(const QirArray* other)
assert((TBufSize)(this->count) * this->itemSizeInBytes < std::numeric_limits::max());
// Using `<` rather than `<=` to calm down the compiler on 32-bit arch.
- const TBufSize thisSize = this->count * this->itemSizeInBytes;
+ const TBufSize thisSize = (TBufSize)this->count * (TBufSize)this->itemSizeInBytes;
char* newBuffer = new char[thisSize + otherSize];
if (thisSize)
@@ -585,14 +585,16 @@ extern "C"
assert((QirArray::TBufSize)rangeRunCount * itemSizeInBytes <
std::numeric_limits::max());
// Using `<` rather than `<=` to calm down the compiler on 32-bit arch.
- const QirArray::TBufSize rangeChunkSize = rangeRunCount * itemSizeInBytes;
+ const QirArray::TBufSize rangeChunkSize =
+ (QirArray::TBufSize)rangeRunCount * (QirArray::TBufSize)itemSizeInBytes;
QirArray::TItemCount dst = 0;
QirArray::TItemCount src = (QirArray::TItemCount)(singleIndexRunCount * range.start);
while (src < array->count)
{
assert(dst < slice->count);
- memcpy(&slice->buffer[dst * itemSizeInBytes], &array->buffer[src * itemSizeInBytes], rangeChunkSize);
+ memcpy(&slice->buffer[static_cast(dst * itemSizeInBytes)],
+ &array->buffer[static_cast(src * itemSizeInBytes)], rangeChunkSize);
src += rowCount;
dst += rangeRunCount;
}
@@ -603,9 +605,10 @@ extern "C"
assert((QirArray::TBufSize)singleIndexRunCount * itemSizeInBytes <
std::numeric_limits::max());
// Using `<` rather than `<=` to calm down the compiler on 32-bit arch.
- const QirArray::TBufSize chunkSize = singleIndexRunCount * itemSizeInBytes;
- QirArray::TItemCount dst = 0;
- QirArray::TItemCount src = (QirArray::TItemCount)(singleIndexRunCount * range.start);
+ const QirArray::TBufSize chunkSize =
+ (QirArray::TBufSize)singleIndexRunCount * (QirArray::TBufSize)itemSizeInBytes;
+ QirArray::TItemCount dst = 0;
+ QirArray::TItemCount src = (QirArray::TItemCount)(singleIndexRunCount * range.start);
while (src < array->count)
{
assert(dst < slice->count);
@@ -613,12 +616,14 @@ extern "C"
int64_t srcInner = src; // The `srcInner` can go negative in the end of the last iteration.
for (int64_t index = range.start; index != range.end; index += range.step)
{
- assert((dst * itemSizeInBytes + chunkSize) <= (slice->count * slice->itemSizeInBytes));
+ assert(((QirArray::TItemSize)dst * itemSizeInBytes + (QirArray::TItemSize)chunkSize) <=
+ (QirArray::TItemSize)slice->count * slice->itemSizeInBytes);
assert((srcInner * (int64_t)itemSizeInBytes + (int64_t)chunkSize) <=
- (array->count * array->itemSizeInBytes));
+ ((int64_t)array->count * (int64_t)array->itemSizeInBytes));
assert(srcInner >= 0);
- memcpy(&slice->buffer[dst * itemSizeInBytes], &array->buffer[srcInner * itemSizeInBytes], chunkSize);
+ memcpy(&slice->buffer[static_cast(dst * itemSizeInBytes)],
+ &array->buffer[static_cast(srcInner * itemSizeInBytes)], chunkSize);
srcInner += (singleIndexRunCount * range.step);
dst += singleIndexRunCount;
}
@@ -660,14 +665,16 @@ extern "C"
std::numeric_limits::max());
// Using `<` rather than `<=` to calm down the compiler on 32-bit arch.
- const QirArray::TBufSize chunkSize = singleIndexRunCount * itemSizeInBytes;
+ const QirArray::TBufSize chunkSize =
+ (QirArray::TBufSize)singleIndexRunCount * (QirArray::TBufSize)itemSizeInBytes;
QirArray::TItemCount dst = 0;
QirArray::TItemCount src = (QirArray::TItemCount)(singleIndexRunCount * index);
while (src < array->count)
{
assert(dst < project->count);
- memcpy(&project->buffer[dst * itemSizeInBytes], &array->buffer[src * itemSizeInBytes], chunkSize);
+ memcpy(&project->buffer[static_cast(dst * itemSizeInBytes)],
+ &array->buffer[static_cast(src * itemSizeInBytes)], chunkSize);
src += rowCount;
dst += singleIndexRunCount;
}
diff --git a/src/Qir/Runtime/lib/QIR/callables.cpp b/src/Qir/Runtime/lib/QIR/callables.cpp
index 93cd1dee466..2108d574e54 100644
--- a/src/Qir/Runtime/lib/QIR/callables.cpp
+++ b/src/Qir/Runtime/lib/QIR/callables.cpp
@@ -371,7 +371,7 @@ QirTupleHeader* FlattenControlArrays(QirTupleHeader* tuple, int depth)
// Copy the controls into the new array. This array doesn't own the qubits so must use the generic constructor.
QirArray* combinedControls = new QirArray(cControls, qubitSize);
char* dst = combinedControls->buffer;
- [[maybe_unused]] const char* dstEnd = dst + qubitSize * cControls;
+ [[maybe_unused]] const char* dstEnd = dst + static_cast(qubitSize * cControls);
current = outer;
QirTupleHeader* last = nullptr;
for (int i = 0; i < depth; i++)
@@ -383,7 +383,7 @@ QirTupleHeader* FlattenControlArrays(QirTupleHeader* tuple, int depth)
QirArray* controls = current->controls;
- const QirArray::TBufSize blockSize = qubitSize * controls->count;
+ const QirArray::TBufSize blockSize = (QirArray::TBufSize)qubitSize * (QirArray::TBufSize)controls->count;
// Make sure we don't overflow `TBufSize` on 32-bit arch:
assert((blockSize >= qubitSize) && (blockSize >= controls->count));
diff --git a/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp
index 93836936317..99250f57bb0 100644
--- a/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp
+++ b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp
@@ -158,13 +158,15 @@ namespace Quantum
void DumpState()
{
std::cout << "*********************" << std::endl;
- this->GetState([](size_t idx, double re, double im) {
- if (!Close(re, 0.0) || !Close(im, 0.0))
+ this->GetState(
+ [](const char* idx, double re, double im)
{
- std::cout << "|" << std::bitset<8>(idx) << ">: " << re << "+" << im << "i" << std::endl;
- }
- return true;
- });
+ if (!Close(re, 0.0) || !Close(im, 0.0))
+ {
+ std::cout << "|" << idx << ">: " << re << "+" << im << "i" << std::endl;
+ }
+ return true;
+ });
std::cout << "*********************" << std::endl;
}
@@ -541,7 +543,8 @@ namespace Quantum
private:
TDumpToLocationCallback const dumpToLocationCallback = [](size_t idx, double re, double im,
- TDumpLocation location) -> bool {
+ TDumpLocation location) -> bool
+ {
std::ostream& outStream = *reinterpret_cast(location);
if (!Close(re, 0.0) || !Close(im, 0.0))
diff --git a/src/Qir/Runtime/prerequisites.ps1 b/src/Qir/Runtime/prerequisites.ps1
index 0452c936612..e1b06c24f6d 100644
--- a/src/Qir/Runtime/prerequisites.ps1
+++ b/src/Qir/Runtime/prerequisites.ps1
@@ -8,7 +8,7 @@ if ($Env:ENABLE_QIRRUNTIME -ne "false") {
if (!(Get-Command clang -ErrorAction SilentlyContinue) -or `
!(Get-Command clang-format -ErrorAction SilentlyContinue) -or `
(Test-Path Env:/AGENT_OS)) {
- choco install llvm --version=11.1.0 --allow-downgrade
+ choco install llvm --version=13.0.0 --allow-downgrade
Write-Host "##vso[task.setvariable variable=PATH;]$($env:SystemDrive)\Program Files\LLVM\bin;$Env:PATH"
}
if (!(Get-Command ninja -ErrorAction SilentlyContinue)) {
@@ -28,12 +28,21 @@ if ($Env:ENABLE_QIRRUNTIME -ne "false") {
brew install clang-format
}
} else {
+ $needClang = !(Get-Command clang-13 -ErrorAction SilentlyContinue)
if (Get-Command sudo -ErrorAction SilentlyContinue) {
+ if ($needClang) {
+ wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
+ sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-13 main"
+ }
sudo apt update
- sudo apt-get install -y ninja-build clang-11 clang-tidy-11 clang-format-11
+ sudo apt-get install -y ninja-build clang-13 clang-tidy-13 clang-format-13
} else {
+ if ($needClang) {
+ wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|apt-key add -
+ add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-13 main"
+ }
apt update
- apt-get install -y ninja-build clang-11 clang-tidy-11 clang-format-11
+ apt-get install -y ninja-build clang-13 clang-tidy-13 clang-format-13
}
}
}
diff --git a/src/Qir/Runtime/public/QSharpSimApi_I.hpp b/src/Qir/Runtime/public/QSharpSimApi_I.hpp
index 45754fc68a9..11d0473cde3 100644
--- a/src/Qir/Runtime/public/QSharpSimApi_I.hpp
+++ b/src/Qir/Runtime/public/QSharpSimApi_I.hpp
@@ -64,7 +64,8 @@ namespace Quantum
// The callback should be invoked on each basis vector (in the standard computational basis) in little-endian
// order until it returns `false` or the state is fully dumped.
- typedef bool (*TGetStateCallback)(size_t /*basis vector*/, double /* amplitude Re*/, double /* amplitude Im*/);
+ typedef bool (*TGetStateCallback)(const char* /*basis vector*/, double /* amplitude Re*/,
+ double /* amplitude Im*/);
// Deprecated, use `DumpMachine()` and `DumpRegister()` instead.
virtual void GetState(TGetStateCallback callback) = 0;
diff --git a/src/Qir/Runtime/unittests/QirRuntimeTests.cpp b/src/Qir/Runtime/unittests/QirRuntimeTests.cpp
index 09a0892d770..6b29ac9e5cf 100644
--- a/src/Qir/Runtime/unittests/QirRuntimeTests.cpp
+++ b/src/Qir/Runtime/unittests/QirRuntimeTests.cpp
@@ -126,7 +126,7 @@ TEST_CASE("Arrays: one dimensional", "[qir_support]")
TEST_CASE("Arrays: multiple dimensions", "[qir_support]")
{
- const size_t count = 5 * 3 * 4; // 60
+ const size_t count = (size_t)(5 * 3 * 4); // 60
QirArray* a = __quantum__rt__array_create(sizeof(int), 3, (int64_t)5, (int64_t)3, (int64_t)4);
REQUIRE(__quantum__rt__array_get_dim(a) == 3);
REQUIRE(__quantum__rt__array_get_size(a, 0) == 5);
diff --git a/src/Qir/Samples/StandaloneInputReference/qsharp/qir-standalone-input-reference.csproj b/src/Qir/Samples/StandaloneInputReference/qsharp/qir-standalone-input-reference.csproj
index 07baf1044f1..69284631606 100644
--- a/src/Qir/Samples/StandaloneInputReference/qsharp/qir-standalone-input-reference.csproj
+++ b/src/Qir/Samples/StandaloneInputReference/qsharp/qir-standalone-input-reference.csproj
@@ -2,7 +2,7 @@
Exe
- netcoreapp3.1
+ net6.0
True
false
diff --git a/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp b/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp
index 65ec0e740e5..563a1601f85 100644
--- a/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp
+++ b/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp
@@ -3,6 +3,7 @@
#include
#include
+#include
#include
#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
@@ -346,45 +347,59 @@ TEST_CASE("Fullstate simulator: get qubit state of Bell state", "[fullstate_simu
iqa->ControlledX(1, &qs[0], qs[1]);
// 1/sqrt(2)(|00> + |11>)x|0>
- dynamic_cast(sim.get())->GetState([](size_t idx, double re, double im) {
- norm += re * re + im * im;
- REQUIRE(idx < 4);
- switch (idx)
+ dynamic_cast(sim.get())->GetState(
+ [](const char* idxStr, double re, double im)
{
- case 0:
- case 3:
- REQUIRE((1 / sqrt(2.0) == Approx(re).epsilon(0.0001)));
- REQUIRE(im == 0.0);
- break;
- default:
- REQUIRE(re == 0.0);
- REQUIRE(im == 0.0);
- break;
- }
- return idx < 3; // the last qubit is in separable |0> state
- });
+ norm += re * re + im * im;
+ size_t idx = 0;
+ for (size_t i = 0; idxStr[i] != '\0'; ++i)
+ {
+ idx |= (idxStr[i] == '1' ? 1u : 0u) << i;
+ }
+ REQUIRE(idx < 4);
+ switch (idx)
+ {
+ case 0:
+ case 3:
+ REQUIRE((1 / sqrt(2.0) == Approx(re).epsilon(0.0001)));
+ REQUIRE(im == 0.0);
+ break;
+ default:
+ REQUIRE(re == 0.0);
+ REQUIRE(im == 0.0);
+ break;
+ }
+ return idx < 3; // the last qubit is in separable |0> state
+ });
REQUIRE(1.0 == Approx(norm).epsilon(0.0001));
norm = 0.0;
iqa->Y(qs[2]);
// 1/sqrt(2)(|00> + |11>)xi|1>
- dynamic_cast(sim.get())->GetState([](size_t idx, double re, double im) {
- norm += re * re + im * im;
- switch (idx)
+ dynamic_cast(sim.get())->GetState(
+ [](const char* idxStr, double re, double im)
{
- case 4:
- case 7:
- REQUIRE(re == 0.0);
- REQUIRE(1 / sqrt(2.0) == Approx(im).epsilon(0.0001));
- break;
- default:
- REQUIRE(re == 0.0);
- REQUIRE(im == 0.0);
- break;
- }
- return true; // get full state
- });
+ norm += re * re + im * im;
+ size_t idx = 0;
+ for (size_t i = 0; idxStr[i] != '\0'; ++i)
+ {
+ idx |= (idxStr[i] == '1' ? 1u : 0u) << i;
+ }
+ switch (idx)
+ {
+ case 4:
+ case 7:
+ REQUIRE(re == 0.0);
+ REQUIRE(1 / sqrt(2.0) == Approx(im).epsilon(0.0001));
+ break;
+ default:
+ REQUIRE(re == 0.0);
+ REQUIRE(im == 0.0);
+ break;
+ }
+ return true; // get full state
+ });
REQUIRE(1.0 == Approx(norm).epsilon(0.0001));
norm = 0.0;
diff --git a/src/Qir/Tests/FullstateSimulator/qsharp/qir-test-simulator.csproj b/src/Qir/Tests/FullstateSimulator/qsharp/qir-test-simulator.csproj
index 0ac37846280..689f0f2fa22 100644
--- a/src/Qir/Tests/FullstateSimulator/qsharp/qir-test-simulator.csproj
+++ b/src/Qir/Tests/FullstateSimulator/qsharp/qir-test-simulator.csproj
@@ -2,7 +2,7 @@
Exe
- netcoreapp3.1
+ net6.0
True
false
false
diff --git a/src/Qir/Tests/QIR-dynamic/qsharp/qir-test-random.csproj b/src/Qir/Tests/QIR-dynamic/qsharp/qir-test-random.csproj
index 0ac37846280..689f0f2fa22 100644
--- a/src/Qir/Tests/QIR-dynamic/qsharp/qir-test-random.csproj
+++ b/src/Qir/Tests/QIR-dynamic/qsharp/qir-test-random.csproj
@@ -2,7 +2,7 @@
Exe
- netcoreapp3.1
+ net6.0
True
false
false
diff --git a/src/Qir/Tests/QIR-static/qir-driver.cpp b/src/Qir/Tests/QIR-static/qir-driver.cpp
index b49187deeaa..ac8d24ca0e9 100644
--- a/src/Qir/Tests/QIR-static/qir-driver.cpp
+++ b/src/Qir/Tests/QIR-static/qir-driver.cpp
@@ -43,9 +43,9 @@ To update the *.ll files to a newer version:
- the generated file name.ll will be placed into `s` folder
*/
-struct Array
+struct Array // TODO(rokuzmin, #969): Document the mechanism of passing an array to the QIR generated from Q#.
{
- int64_t itemSize;
+ int64_t itemCount;
void* buffer;
};
@@ -60,7 +60,7 @@ TEST_CASE("QIR: Using 1D arrays", "[qir][qir.arr1d]")
constexpr int64_t n = 5;
int64_t values[n] = {0, 1, 2, 3, 4};
- auto array = Array{5, values};
+ auto array = Array{n, values};
int64_t res = Microsoft__Quantum__Testing__QIR__Test_Arrays__Interop(&array, 2, 42);
REQUIRE(res == (0 + 42) + (42 + 3 + 4));
diff --git a/src/Qir/Tests/QIR-static/qsharp/qir-gen.csproj b/src/Qir/Tests/QIR-static/qsharp/qir-gen.csproj
index 0ac37846280..689f0f2fa22 100644
--- a/src/Qir/Tests/QIR-static/qsharp/qir-gen.csproj
+++ b/src/Qir/Tests/QIR-static/qsharp/qir-gen.csproj
@@ -2,7 +2,7 @@
Exe
- netcoreapp3.1
+ net6.0
True
false
false
diff --git a/src/Qir/Tests/QIR-tracer/qsharp/tracer-qir.csproj b/src/Qir/Tests/QIR-tracer/qsharp/tracer-qir.csproj
index 0daacfeede4..f8677e7b9a4 100644
--- a/src/Qir/Tests/QIR-tracer/qsharp/tracer-qir.csproj
+++ b/src/Qir/Tests/QIR-tracer/qsharp/tracer-qir.csproj
@@ -2,7 +2,7 @@
Exe
- netcoreapp3.1
+ net6.0
True
false
false
diff --git a/src/Qir/Tests/Tools/Tests.Microsoft.Quantum.Qir.Runtime.Tools.csproj b/src/Qir/Tests/Tools/Tests.Microsoft.Quantum.Qir.Runtime.Tools.csproj
index 23f2d9c6982..b920292cdd9 100644
--- a/src/Qir/Tests/Tools/Tests.Microsoft.Quantum.Qir.Runtime.Tools.csproj
+++ b/src/Qir/Tests/Tools/Tests.Microsoft.Quantum.Qir.Runtime.Tools.csproj
@@ -2,7 +2,7 @@
x64
- netcoreapp3.1
+ net6.0
false
diff --git a/src/Qir/Tools/EntryPointOperationLoader.cs b/src/Qir/Tools/EntryPointOperationLoader.cs
index 941a3f87105..eb4e37e1dca 100644
--- a/src/Qir/Tools/EntryPointOperationLoader.cs
+++ b/src/Qir/Tools/EntryPointOperationLoader.cs
@@ -56,7 +56,7 @@ private static List GetParams(QsCallable callable)
.ToList();
}
- private static Parameter MakeParameter(LocalVariableDeclaration parameter)
+ private static Parameter MakeParameter(LocalVariableDeclaration parameter)
{
var type = MapResolvedTypeToDataType(parameter.Type);
var arrayType = parameter.Type.Resolution is QsTypeKind.ArrayType innerType
diff --git a/src/Qir/Tools/Microsoft.Quantum.Qir.Runtime.Tools.csproj b/src/Qir/Tools/Microsoft.Quantum.Qir.Runtime.Tools.csproj
index 4c7849ae217..ee0c7825f33 100644
--- a/src/Qir/Tools/Microsoft.Quantum.Qir.Runtime.Tools.csproj
+++ b/src/Qir/Tools/Microsoft.Quantum.Qir.Runtime.Tools.csproj
@@ -36,7 +36,7 @@
-
+
diff --git a/src/Qir/check-sources-formatted.ps1 b/src/Qir/check-sources-formatted.ps1
index 39a573c44c0..015ca2a64ef 100644
--- a/src/Qir/check-sources-formatted.ps1
+++ b/src/Qir/check-sources-formatted.ps1
@@ -14,7 +14,7 @@ if (-not $IsMacOS) { # We do not control the clang-format version on MacOS, an
$clangFormatCommand = "clang-format"
if(($IsLinux) -or ((Test-Path Env:/AGENT_OS) -and ($Env:AGENT_OS.StartsWith("Lin")))) {
- $script:clangFormatCommand = "clang-format-11"
+ $script:clangFormatCommand = "clang-format-13"
}
$OldErrorActionPreference = $ErrorActionPreference
diff --git a/src/Qir/microsoft-quantum-qir-runtime-sys/build.rs b/src/Qir/microsoft-quantum-qir-runtime-sys/build.rs
index 287da25af7c..2b8b169a2d1 100644
--- a/src/Qir/microsoft-quantum-qir-runtime-sys/build.rs
+++ b/src/Qir/microsoft-quantum-qir-runtime-sys/build.rs
@@ -45,12 +45,12 @@ fn compile_runtime_libraries(path_to_runtime_src: &str) -> Result<(), Box Result<(), Box>{
if cfg!(target_os = "linux") {
let mut c_cfg = cc::Build::new();
- let clang_11 = which::which("clang-11")?;
+ let clang_11 = which::which("clang-13")?;
c_cfg.compiler(clang_11);
config.init_c_cfg(c_cfg);
let mut cxx_cfg = cc::Build::new();
- let clangpp_11 = which::which("clang++-11")?;
+ let clangpp_11 = which::which("clang++-13")?;
cxx_cfg.compiler(clangpp_11);
config.init_cxx_cfg(cxx_cfg);
} else if cfg!(target_os = "windows") {
diff --git a/src/Qir/qir-utils.ps1 b/src/Qir/qir-utils.ps1
index 612c19b9246..16988d97a86 100644
--- a/src/Qir/qir-utils.ps1
+++ b/src/Qir/qir-utils.ps1
@@ -44,9 +44,9 @@ function Build-CMakeProject {
}
elseif (($IsLinux) -or ((Test-Path Env:AGENT_OS) -and ($Env:AGENT_OS.StartsWith("Lin")))) {
Write-Host "On Linux build $Name using Clang"
- $CMAKE_C_COMPILER = "-DCMAKE_C_COMPILER=clang-11"
- $CMAKE_CXX_COMPILER = "-DCMAKE_CXX_COMPILER=clang++-11"
- $clangTidy = "-DCMAKE_CXX_CLANG_TIDY=clang-tidy-11"
+ $CMAKE_C_COMPILER = "-DCMAKE_C_COMPILER=clang-13"
+ $CMAKE_CXX_COMPILER = "-DCMAKE_CXX_COMPILER=clang++-13"
+ $clangTidy = "-DCMAKE_CXX_CLANG_TIDY=clang-tidy-13"
}
elseif (($IsWindows) -or ((Test-Path Env:AGENT_OS) -and ($Env:AGENT_OS.StartsWith("Win")))) {
Write-Host "On Windows build $Name using Clang"
diff --git a/src/Simulation/AutoSubstitution.Integration.Tests/Tests.Microsoft.Quantum.AutoSubstitution.Integration.csproj b/src/Simulation/AutoSubstitution.Integration.Tests/Tests.Microsoft.Quantum.AutoSubstitution.Integration.csproj
index 2dbeb310c32..b6dc129a612 100644
--- a/src/Simulation/AutoSubstitution.Integration.Tests/Tests.Microsoft.Quantum.AutoSubstitution.Integration.csproj
+++ b/src/Simulation/AutoSubstitution.Integration.Tests/Tests.Microsoft.Quantum.AutoSubstitution.Integration.csproj
@@ -2,7 +2,7 @@
Library
- netcoreapp3.1
+ net6.0
x64
d
diff --git a/src/Simulation/AutoSubstitution.Tests/Tests.Microsoft.Quantum.AutoSubstitution.csproj b/src/Simulation/AutoSubstitution.Tests/Tests.Microsoft.Quantum.AutoSubstitution.csproj
index c0638219bd4..c43ac4f0c01 100644
--- a/src/Simulation/AutoSubstitution.Tests/Tests.Microsoft.Quantum.AutoSubstitution.csproj
+++ b/src/Simulation/AutoSubstitution.Tests/Tests.Microsoft.Quantum.AutoSubstitution.csproj
@@ -1,7 +1,7 @@
- netcoreapp3.1
+ net6.0
Tests.Microsoft.Quantum.AutoSubstitution
false
x64
diff --git a/src/Simulation/AutoSubstitution/Microsoft.Quantum.AutoSubstitution.csproj b/src/Simulation/AutoSubstitution/Microsoft.Quantum.AutoSubstitution.csproj
index 917706b04be..7708efa7180 100644
--- a/src/Simulation/AutoSubstitution/Microsoft.Quantum.AutoSubstitution.csproj
+++ b/src/Simulation/AutoSubstitution/Microsoft.Quantum.AutoSubstitution.csproj
@@ -15,11 +15,11 @@
false
false
true
- false
+ false
-
+
-
+
diff --git a/src/Simulation/Common/Simulators.Dev.props b/src/Simulation/Common/Simulators.Dev.props
index 8e10e59a7b9..93018cbef21 100644
--- a/src/Simulation/Common/Simulators.Dev.props
+++ b/src/Simulation/Common/Simulators.Dev.props
@@ -7,6 +7,7 @@
$([MSBuild]::NormalizePath($(EnlistmentRoot)src/Simulation/Native))
$([MSBuild]::NormalizePath($(NativeRootPath)/build/drop))
$([MSBuild]::NormalizePath($(EnlistmentRoot)src/Simulation/qdk_sim_rs/drop))
+ $([MSBuild]::NormalizePath($(EnlistmentRoot)src/Simulation/NativeSparseSimulator/build))
@@ -26,6 +27,11 @@
+
+
+
+
+
- netcoreapp3.1
+ net6.0
false
false
diff --git a/src/Simulation/Core/EntryPointInfo.cs b/src/Simulation/Core/EntryPointInfo.cs
index 7061be98855..3c188fa2efa 100644
--- a/src/Simulation/Core/EntryPointInfo.cs
+++ b/src/Simulation/Core/EntryPointInfo.cs
@@ -53,4 +53,16 @@ public QCIEntryPointInfo(Type operation)
: base(operation)
{ }
}
+
+ ///
+ /// Base class containing information about an entry point
+ /// for a Q# executable targeted for a Quantinuum quantum processor.
+ ///
+ public class QuantinuumEntryPointInfo
+ : EntryPointInfo
+ {
+ public QuantinuumEntryPointInfo(Type operation)
+ : base(operation)
+ { }
+ }
}
diff --git a/src/Simulation/Core/Microsoft.Quantum.Runtime.Core.csproj b/src/Simulation/Core/Microsoft.Quantum.Runtime.Core.csproj
index 3256cf8bf56..0156e6f8c64 100644
--- a/src/Simulation/Core/Microsoft.Quantum.Runtime.Core.csproj
+++ b/src/Simulation/Core/Microsoft.Quantum.Runtime.Core.csproj
@@ -18,7 +18,7 @@
-
+
diff --git a/src/Simulation/EntryPointDriver.Tests/Tests.Microsoft.Quantum.EntryPointDriver.fsproj b/src/Simulation/EntryPointDriver.Tests/Tests.Microsoft.Quantum.EntryPointDriver.fsproj
index 125157d80e8..abbb9599d00 100644
--- a/src/Simulation/EntryPointDriver.Tests/Tests.Microsoft.Quantum.EntryPointDriver.fsproj
+++ b/src/Simulation/EntryPointDriver.Tests/Tests.Microsoft.Quantum.EntryPointDriver.fsproj
@@ -1,6 +1,6 @@
-
+
- netcoreapp3.1
+ net6.0
false
Microsoft.Quantum.EntryPointDriver.Tests
x64
@@ -23,7 +23,7 @@
-
+
diff --git a/src/Simulation/EntryPointDriver.Tests/Tests.fs b/src/Simulation/EntryPointDriver.Tests/Tests.fs
index 1e00e8bd15b..fdf5a447922 100644
--- a/src/Simulation/EntryPointDriver.Tests/Tests.fs
+++ b/src/Simulation/EntryPointDriver.Tests/Tests.fs
@@ -929,7 +929,7 @@ let ``Submit has required options`` () =
let ``Submit catches exceptions`` () =
let given = test "Returns Unit"
given submitWithErrorTarget
- |> failsWith "Something went wrong when submitting the program to the Azure Quantum service.
+ |> failsWith "An error occurred when submitting to the Azure Quantum service.
This machine always has an error."
diff --git a/src/Simulation/EntryPointDriver/Azure/Azure.cs b/src/Simulation/EntryPointDriver/Azure/Azure.cs
index 0965e78f212..21b0f931aa6 100644
--- a/src/Simulation/EntryPointDriver/Azure/Azure.cs
+++ b/src/Simulation/EntryPointDriver/Azure/Azure.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Immutable;
+using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Azure.Quantum;
using Microsoft.Azure.Quantum.Exceptions;
@@ -204,6 +205,32 @@ private static async Task SubmitQir(
/// The exit code.
private static async Task DisplayJobOrError(AzureSettings settings, Task job)
{
+ void DisplayAzureQuantumException(AzureQuantumException ex) =>
+ DisplayError(
+ "An error occurred when submitting to the Azure Quantum service.",
+ ex.Message);
+
+ void DisplayQuantumProcessorTranslationException(QuantumProcessorTranslationException ex) =>
+ DisplayError(
+ "Unable to translate the program for execution on the target quantum machine.",
+ ex.Message);
+
+ bool HandleTargetInvocationException(TargetInvocationException ex)
+ {
+ if (ex.InnerException is AzureQuantumException azureQuantumEx)
+ {
+ DisplayAzureQuantumException(azureQuantumEx);
+ return true;
+ }
+ else if (ex.InnerException is QuantumProcessorTranslationException quantumProcessorTranslationEx)
+ {
+ DisplayQuantumProcessorTranslationException(quantumProcessorTranslationEx);
+ return true;
+ }
+
+ return false;
+ }
+
try
{
DisplayJob(await job, settings.Output);
@@ -211,16 +238,20 @@ private static async Task DisplayJobOrError(AzureSettings settings, Task entryPoi
suggestions: new[]
{
this.settings.QuantumSimulatorName,
+ this.settings.SparseSimulatorName,
this.settings.ToffoliSimulatorName,
this.settings.ResourcesEstimatorName,
this.settings.DefaultSimulatorName
diff --git a/src/Simulation/EntryPointDriver/DriverSettings.cs b/src/Simulation/EntryPointDriver/DriverSettings.cs
index e39abd341d9..3c944aa3677 100644
--- a/src/Simulation/EntryPointDriver/DriverSettings.cs
+++ b/src/Simulation/EntryPointDriver/DriverSettings.cs
@@ -19,6 +19,11 @@ public sealed class DriverSettings
///
internal string QuantumSimulatorName { get; }
+ ///
+ /// The name of the sparse simulator.
+ ///
+ internal string SparseSimulatorName { get; }
+
///
/// The name of the Toffoli simulator.
///
@@ -53,6 +58,7 @@ public sealed class DriverSettings
///
/// The aliases for the simulator command-line option.
/// The name of the quantum simulator.
+ /// The name of the sparse simulator.
/// The name of the Toffoli simulator.
/// The name of the resources estimator.
/// The name of the default simulator to use.
@@ -61,6 +67,7 @@ public sealed class DriverSettings
public DriverSettings(
ImmutableList simulatorOptionAliases,
string quantumSimulatorName,
+ string sparseSimulatorName,
string toffoliSimulatorName,
string resourcesEstimatorName,
string defaultSimulatorName,
@@ -69,6 +76,7 @@ public DriverSettings(
{
SimulatorOptionAliases = simulatorOptionAliases;
QuantumSimulatorName = quantumSimulatorName;
+ SparseSimulatorName = sparseSimulatorName;
ToffoliSimulatorName = toffoliSimulatorName;
ResourcesEstimatorName = resourcesEstimatorName;
DefaultSimulatorName = defaultSimulatorName;
diff --git a/src/Simulation/EntryPointDriver/Simulation.cs b/src/Simulation/EntryPointDriver/Simulation.cs
index e2599163191..250860414d3 100644
--- a/src/Simulation/EntryPointDriver/Simulation.cs
+++ b/src/Simulation/EntryPointDriver/Simulation.cs
@@ -51,6 +51,8 @@ public static async Task Simulate(
var (isCustom, createSimulator) =
simulator == settings.QuantumSimulatorName
? (false, () => new QuantumSimulator())
+ : simulator == settings.SparseSimulatorName
+ ? (false, () => new SparseSimulator())
: simulator == settings.ToffoliSimulatorName
? (false, new Func(() => new ToffoliSimulator()))
: (true, settings.CreateDefaultCustomSimulator);
diff --git a/src/Simulation/Native/build-native-simulator.ps1 b/src/Simulation/Native/build-native-simulator.ps1
index 75f7043fc68..d71228e4ce5 100644
--- a/src/Simulation/Native/build-native-simulator.ps1
+++ b/src/Simulation/Native/build-native-simulator.ps1
@@ -62,7 +62,7 @@ if (($IsWindows) -or ((Test-Path Env:AGENT_OS) -and ($Env:AGENT_OS.StartsWith("W
}
elseif (($IsLinux) -or ((Test-Path Env:AGENT_OS) -and ($Env:AGENT_OS.StartsWith("Lin"))))
{
- cmake -D BUILD_SHARED_LIBS:BOOL="1" -D CMAKE_C_COMPILER=clang-11 -D CMAKE_CXX_COMPILER=clang++-11 `
+ cmake -D BUILD_SHARED_LIBS:BOOL="1" -D CMAKE_C_COMPILER=clang -D CMAKE_CXX_COMPILER=clang++ `
-D CMAKE_C_FLAGS_DEBUG="$SANITIZE_FLAGS" `
-D CMAKE_CXX_FLAGS_DEBUG="$SANITIZE_FLAGS" `
-D CMAKE_BUILD_TYPE="$Env:BUILD_CONFIGURATION" ..
diff --git a/src/Simulation/Native/prerequisites.ps1 b/src/Simulation/Native/prerequisites.ps1
index 935e7d4caaf..1e00fed0e00 100644
--- a/src/Simulation/Native/prerequisites.ps1
+++ b/src/Simulation/Native/prerequisites.ps1
@@ -7,7 +7,7 @@ if (($IsMacOS) -or ((Test-Path Env:AGENT_OS) -and ($Env:AGENT_OS.StartsWith("Dar
} elseif (($IsWindows) -or ((Test-Path Env:/AGENT_OS) -and ($Env:AGENT_OS.StartsWith("Win")))) {
if (!(Get-Command clang -ErrorAction SilentlyContinue) -or `
(Test-Path Env:/AGENT_OS)) {
- choco install llvm --version=11.1.0 --allow-downgrade
+ choco install llvm --version=13.0.0 --allow-downgrade
Write-Host "##vso[task.setvariable variable=PATH;]$($env:SystemDrive)\Program Files\LLVM\bin;$Env:PATH"
}
if (!(Get-Command ninja -ErrorAction SilentlyContinue)) {
@@ -19,12 +19,21 @@ if (($IsMacOS) -or ((Test-Path Env:AGENT_OS) -and ($Env:AGENT_OS.StartsWith("Dar
refreshenv
}
else {
+ $needClang = !(Get-Command clang-13 -ErrorAction SilentlyContinue)
if (Get-Command sudo -ErrorAction SilentlyContinue) {
+ if ($needClang) {
+ wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
+ sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-13 main"
+ }
sudo apt update
- sudo apt-get install -y clang-11
+ sudo apt-get install -y clang-13
} else {
+ if ($needClang) {
+ wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|apt-key add -
+ add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-13 main"
+ }
apt update
- apt-get install -y clang-11
+ apt-get install -y clang-13
}
}
diff --git a/src/Simulation/Native/src/simulator/capi.cpp b/src/Simulation/Native/src/simulator/capi.cpp
index 827351f2ee1..b4cbc0870bd 100644
--- a/src/Simulation/Native/src/simulator/capi.cpp
+++ b/src/Simulation/Native/src/simulator/capi.cpp
@@ -202,7 +202,7 @@ extern "C"
}
// dump wavefunction to given callback until callback returns false
- MICROSOFT_QUANTUM_DECL void Dump(_In_ unsigned id, _In_ bool (*callback)(size_t, double, double))
+ MICROSOFT_QUANTUM_DECL void Dump(_In_ unsigned id, _In_ bool (*callback)(const char*, double, double))
{
Microsoft::Quantum::Simulator::get(id)->dump(callback);
}
@@ -217,7 +217,7 @@ extern "C"
_In_ unsigned id,
_In_ unsigned n,
_In_reads_(n) unsigned* q,
- _In_ bool (*callback)(size_t, double, double))
+ _In_ bool (*callback)(const char*, double, double))
{
std::vector qs(q, q + n);
return Microsoft::Quantum::Simulator::get(id)->dumpQubits(qs, callback);
diff --git a/src/Simulation/Native/src/simulator/capi.hpp b/src/Simulation/Native/src/simulator/capi.hpp
index 2c33478b56b..2d73e2b5589 100644
--- a/src/Simulation/Native/src/simulator/capi.hpp
+++ b/src/Simulation/Native/src/simulator/capi.hpp
@@ -21,12 +21,12 @@ extern "C"
MICROSOFT_QUANTUM_DECL unsigned init(); // NOLINT
MICROSOFT_QUANTUM_DECL void destroy(_In_ unsigned sid); // NOLINT
MICROSOFT_QUANTUM_DECL void seed(_In_ unsigned sid, _In_ unsigned s); // NOLINT
- MICROSOFT_QUANTUM_DECL void Dump(_In_ unsigned sid, _In_ bool (*callback)(size_t, double, double));
+ MICROSOFT_QUANTUM_DECL void Dump(_In_ unsigned sid, _In_ bool (*callback)(const char*, double, double));
MICROSOFT_QUANTUM_DECL bool DumpQubits(
_In_ unsigned sid,
_In_ unsigned n,
_In_reads_(n) unsigned* q,
- _In_ bool (*callback)(size_t, double, double));
+ _In_ bool (*callback)(const char*, double, double));
typedef void* TDumpLocation;
typedef bool (*TDumpToLocationCallback)(size_t, double, double, TDumpLocation);
diff --git a/src/Simulation/Native/src/simulator/capi_test.cpp b/src/Simulation/Native/src/simulator/capi_test.cpp
index d92bd38f138..37eefe4df6e 100644
--- a/src/Simulation/Native/src/simulator/capi_test.cpp
+++ b/src/Simulation/Native/src/simulator/capi_test.cpp
@@ -38,7 +38,7 @@ void CRx(unsigned sim_id, double phi, unsigned c, unsigned q)
void dump(unsigned sim_id, const char* label)
{
- auto dump_callback = [](size_t idx, double r, double i) {
+ auto dump_callback = [](const char* idx, double r, double i) {
std::cout << idx << ":\t" << r << '\t' << i << '\n';
return true;
};
diff --git a/src/Simulation/Native/src/simulator/dbw_test.cpp b/src/Simulation/Native/src/simulator/dbw_test.cpp
index 7c00d7d2cef..5770a3a50e7 100644
--- a/src/Simulation/Native/src/simulator/dbw_test.cpp
+++ b/src/Simulation/Native/src/simulator/dbw_test.cpp
@@ -49,7 +49,7 @@ void CRx(unsigned sim_id, double phi, unsigned c, unsigned q)
void dump(unsigned sim_id, const char* label)
{
- auto dump_callback = [](size_t idx, double r, double i) {
+ auto dump_callback = [](char const* idx, double r, double i) {
std::cout << idx << ":\t" << r << '\t' << i << '\n';
return true;
};
diff --git a/src/Simulation/Native/src/simulator/simulator.hpp b/src/Simulation/Native/src/simulator/simulator.hpp
index ff5fc48e77b..b5edc702475 100644
--- a/src/Simulation/Native/src/simulator/simulator.hpp
+++ b/src/Simulation/Native/src/simulator/simulator.hpp
@@ -290,15 +290,19 @@ class Simulator : public Microsoft::Quantum::Simulator::SimulatorInterface
return psi.data().data();
}
- void dump(bool (*callback)(size_t, double, double))
+ void dump(bool (*callback)(const char*, double, double))
{
recursive_lock_type l(getmutex());
flush();
auto wfn = psi.data();
+ auto nq = num_qubits();
+ std::string label_str(nq, '0');
for (std::size_t i = 0; i < wfn.size(); i++)
{
- if (!callback(i, wfn[i].real(), wfn[i].imag())) return;
+ for (std::size_t j = 0; j < nq; ++j)
+ label_str[j] = ((i >> j)&1) ? '1' : '0';
+ if (!callback(label_str.c_str(), wfn[i].real(), wfn[i].imag())) return;
}
}
@@ -345,17 +349,20 @@ class Simulator : public Microsoft::Quantum::Simulator::SimulatorInterface
}
}
- bool dumpQubits(std::vector const& qs, bool (*callback)(size_t, double, double))
+ bool dumpQubits(std::vector const& qs, bool (*callback)(const char*, double, double))
{
assert(qs.size() <= num_qubits());
WavefunctionStorage wfn(1ull << qs.size());
-
+ auto nq = num_qubits();
+ std::string label_str(nq, '0');
if (subsytemwavefunction(qs, wfn, 1e-10))
{
for (std::size_t i = 0; i < wfn.size(); i++)
{
- if (!callback(i, wfn[i].real(), wfn[i].imag())) break;
+ for (std::size_t j = 0; j < nq; ++j)
+ label_str[j] = ((i >> j)&1) ? '1' : '0';
+ if (!callback(label_str.c_str(), wfn[i].real(), wfn[i].imag())) break;
}
return true;
}
diff --git a/src/Simulation/Native/src/simulator/simulatorinterface.hpp b/src/Simulation/Native/src/simulator/simulatorinterface.hpp
index 7a85d65aef6..0d075951c68 100644
--- a/src/Simulation/Native/src/simulator/simulatorinterface.hpp
+++ b/src/Simulation/Native/src/simulator/simulatorinterface.hpp
@@ -91,7 +91,7 @@ class SimulatorInterface
return false;
};
- virtual void dump(bool (*callback)(size_t, double, double))
+ virtual void dump(bool (*callback)(const char*, double, double))
{
assert(false);
}
@@ -99,7 +99,7 @@ class SimulatorInterface
{
assert(false);
}
- virtual bool dumpQubits(std::vector const& qs, bool (*callback)(size_t, double, double))
+ virtual bool dumpQubits(std::vector const& qs, bool (*callback)(const char*, double, double))
{
assert(false);
return false;
diff --git a/src/Simulation/NativeSparseSimulator/.gitignore b/src/Simulation/NativeSparseSimulator/.gitignore
new file mode 100644
index 00000000000..378eac25d31
--- /dev/null
+++ b/src/Simulation/NativeSparseSimulator/.gitignore
@@ -0,0 +1 @@
+build
diff --git a/src/Simulation/NativeSparseSimulator/CMakeLists.txt b/src/Simulation/NativeSparseSimulator/CMakeLists.txt
new file mode 100644
index 00000000000..9159c0fefc4
--- /dev/null
+++ b/src/Simulation/NativeSparseSimulator/CMakeLists.txt
@@ -0,0 +1,32 @@
+cmake_minimum_required(VERSION 3.10)
+project(Microsoft.Quantum.SparseSimulator.Runtime)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
+
+set(CMAKE_MACOSX_RPATH 1)
+# Main build files
+add_library(Microsoft.Quantum.SparseSimulator.Runtime SHARED factory.cpp capi.cpp)
+
+# Windows adds a special dllexport command which must be defined
+if (WIN32)
+ target_compile_options(Microsoft.Quantum.SparseSimulator.Runtime PUBLIC -fdeclspec)
+ target_compile_definitions(Microsoft.Quantum.SparseSimulator.Runtime PRIVATE BUILD_DLL=1)
+endif()
+# Try to optimize with gcc
+if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ target_compile_options(Microsoft.Quantum.SparseSimulator.Runtime PUBLIC -O3 -ftree-vectorize -mavx2 -mfma)
+endif()
+
+message("Compiler flags: ${CMAKE_CXX_FLAGS_RELEASE}")
+
+
+include(CTest)
+enable_testing()
+
+foreach(TEST SparseSimulatorTests CSharpIntegrationTests)
+ add_executable(${TEST} ${TEST}.cpp TestHelpers.cpp)
+ target_include_directories(${TEST} PRIVATE ../../Qir/Common/Externals/catch2)
+ add_test(${TEST} ${TEST})
+endforeach()
diff --git a/src/Simulation/NativeSparseSimulator/CSharpIntegrationTests.cpp b/src/Simulation/NativeSparseSimulator/CSharpIntegrationTests.cpp
new file mode 100644
index 00000000000..d6ebb8186d2
--- /dev/null
+++ b/src/Simulation/NativeSparseSimulator/CSharpIntegrationTests.cpp
@@ -0,0 +1,304 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#define CATCH_CONFIG_MAIN
+#include
+
+#include "SparseSimulator.h"
+#include "capi.hpp"
+#include "capi.cpp" // yes really
+#include "factory.hpp"
+#include "factory.cpp"
+#include "TestHelpers.hpp"
+
+#include
+#include
+#include
+
+using namespace Microsoft::Quantum::SPARSESIMULATOR;
+using namespace SparseSimulatorTestHelpers;
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+
+template
+void MultiExpReferenceTest(
+ std::function qubit_prep,
+ std::function qubit_clear
+) {
+ const qubit_label_type zero(0);
+ logical_qubit_id* qubits = new logical_qubit_id[3];
+ qubits[0] = 0;
+ qubits[1] = 1;
+ qubits[2] = 2;
+ int* Paulis = new int[3];
+ for (int intPaulis = 0; intPaulis < 4 * 4 * 4; intPaulis++) {
+ Paulis[0] = intPaulis % 4;
+ Paulis[1] = (intPaulis / 4 ) % 4;
+ Paulis[2] = intPaulis / 16;
+
+ for (double angle = 0.0; angle < M_PI / 2.0; angle += 0.1) {
+ simulator_id_type sim = init_cpp(32);
+ qubit_prep(sim);
+
+ std::vector vector_rep(8, 0.0);
+ for (size_t i = 0; i < 8; i++) {
+ vector_rep[i] = getSimulator(sim)->probe(std::bitset<3>(i).to_string());
+ }
+ // New simulator Exp
+ Exp_cpp(sim, 3, Paulis, angle, qubits);
+ // Old simulator Exp
+ std::vector actualPaulis = { (Gates::Basis)Paulis[0],(Gates::Basis)Paulis[1], (Gates::Basis)Paulis[2] };
+ apply_exp(vector_rep, actualPaulis, angle, std::vector{ (logical_qubit_id)0, (logical_qubit_id)1, (logical_qubit_id)2 });
+ for (size_t i = 0; i < 8; i++) {
+ amplitude result = getSimulator(sim)->probe(std::bitset<3>(i).to_string());
+ assert_amplitude_equality(vector_rep[i], result);
+ }
+ Exp_cpp(sim, 3, Paulis, -angle, qubits);
+ qubit_clear(sim);
+ }
+ }
+}
+
+TEST_CASE("initializationTest") {
+ simulator_id_type sim = init_cpp(32);
+}
+
+TEST_CASE("AllocationTest") {
+ simulator_id_type sim = init_cpp(32);
+ allocateQubit_cpp(sim, 0);
+ releaseQubit_cpp(sim, 0);
+}
+TEST_CASE("AllocateRebuildTest") {
+ simulator_id_type sim = init_cpp(64);
+ for (int i = 0; i < 1024; i++) {
+ allocateQubit_cpp(sim, i);
+ getSimulator(sim)->X(i);
+ getSimulator(sim)->update_state();
+ }
+ for (int i = 0; i < 1024; i++) {
+ getSimulator(sim)->X(i);
+ releaseQubit_cpp(sim, i);
+ }
+}
+
+TEST_CASE("XTest") {
+ simulator_id_type sim = init_cpp(32);
+ allocateQubit_cpp(sim, 0);
+ X_cpp(sim, 0);
+ assert_amplitude_equality(getSimulator(sim)->probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(getSimulator(sim)->probe("1"), 1.0, 0.0);
+ X_cpp(sim, 0);
+ releaseQubit_cpp(sim, 0);
+}
+TEST_CASE("ZTest") {
+ simulator_id_type sim = init_cpp(32);
+ allocateQubit_cpp(sim, 0);
+ Z_cpp(sim, 0);
+ assert_amplitude_equality(getSimulator(sim)->probe("0"), 1.0, 0.0);
+ assert_amplitude_equality(getSimulator(sim)->probe("1"), 0.0, 0.0);
+ Z_cpp(sim, 0);
+ assert_amplitude_equality(getSimulator(sim)->probe("0"), 1.0, 0.0);
+ assert_amplitude_equality(getSimulator(sim)->probe("1"), 0.0, 0.0);
+ X_cpp(sim, 0);
+ Z_cpp(sim, 0);
+ assert_amplitude_equality(getSimulator(sim)->probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(getSimulator(sim)->probe("1"), -1.0, 0.0);
+ Z_cpp(sim, 0);
+ assert_amplitude_equality(getSimulator(sim)->probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(getSimulator(sim)->probe("1"), 1.0, 0.0);
+ X_cpp(sim, 0);
+ releaseQubit_cpp(sim, 0);
+}
+TEST_CASE("HTest") {
+ simulator_id_type sim = init_cpp(32);
+ allocateQubit_cpp(sim, 0);
+ H_cpp(sim, 0);
+ assert_amplitude_equality(getSimulator(sim)->probe("0"), 1.0 / sqrt(2.0), 0.0);
+ assert_amplitude_equality(getSimulator(sim)->probe("1"), 1.0 / sqrt(2.0), 0.0);
+ H_cpp(sim, 0);
+ assert_amplitude_equality(getSimulator(sim)->probe("0"), 1.0, 0.0);
+ assert_amplitude_equality(getSimulator(sim)->probe("1"), 0.0, 0.0);
+ X_cpp(sim, 0);
+ H_cpp(sim, 0);
+ assert_amplitude_equality(getSimulator(sim)->probe("0"), 1.0 / sqrt(2.0), 0.0);
+ assert_amplitude_equality(getSimulator(sim)->probe("1"), -1.0 / sqrt(2.0), 0.0);
+ H_cpp(sim, 0);
+ assert_amplitude_equality(getSimulator(sim)->probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(getSimulator(sim)->probe("1"), 1.0, 0.0);
+ X_cpp(sim, 0);
+ releaseQubit_cpp(sim, 0);
+}
+
+TEST_CASE("TGateTest") {
+ simulator_id_type sim = init_cpp(32);
+ allocateQubit_cpp(sim, 0);
+ T_cpp(sim, 0);
+ assert_amplitude_equality(getSimulator(sim)->probe("0"), 1.0, 0.0);
+ assert_amplitude_equality(getSimulator(sim)->probe("1"), 0.0, 0.0);
+ X_cpp(sim, 0);
+ T_cpp(sim, 0);
+ assert_amplitude_equality(getSimulator(sim)->probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(getSimulator(sim)->probe("1"), 1.0 / sqrt(2.0), 1.0 / sqrt(2.0));
+ T_cpp(sim, 0);
+ assert_amplitude_equality(getSimulator(sim)->probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(getSimulator(sim)->probe("1"), 0.0, 1.0);
+ T_cpp(sim, 0);
+ assert_amplitude_equality(getSimulator(sim)->probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(getSimulator(sim)->probe("1"), -1.0 / sqrt(2.0), 1.0 / sqrt(2.0));
+ T_cpp(sim, 0);
+ assert_amplitude_equality(getSimulator(sim)->probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(getSimulator(sim)->probe("1"), -1.0, 0.0);
+ T_cpp(sim, 0);
+ assert_amplitude_equality(getSimulator(sim)->probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(getSimulator(sim)->probe("1"), -1.0 / sqrt(2.0), -1.0 / sqrt(2.0));
+ T_cpp(sim, 0);
+ assert_amplitude_equality(getSimulator(sim)->probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(getSimulator(sim)->probe("1"), 0.0, -1.0);
+ T_cpp(sim, 0);
+ assert_amplitude_equality(getSimulator(sim)->probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(getSimulator(sim)->probe("1"), 1.0 / sqrt(2.0), -1.0 / sqrt(2.0));
+ T_cpp(sim, 0);
+ assert_amplitude_equality(getSimulator(sim)->probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(getSimulator(sim)->probe("1"), 1.0, 0.0);
+ X_cpp(sim, 0);
+ releaseQubit_cpp(sim, 0);
+}
+
+TEST_CASE("HCancellationTest")
+{
+ int n_qubits = 16;
+ simulator_id_type sim = init_cpp(n_qubits);
+ size_t buckets = 0;
+ for (int i = 0; i < n_qubits; i++) {
+ allocateQubit_cpp(sim, i);
+ H_cpp(sim, i);
+ }
+ for (int i = n_qubits - 1; i >= 0; i--) {
+ H_cpp(sim, i);
+ // If the H do not cancel out, release will fail in an opaque way
+ releaseQubit_cpp(sim, i);
+ }
+}
+
+TEST_CASE("HXZCommutationTest")
+{
+ const int n_qubits = 16;
+ simulator_id_type sim = init_cpp(n_qubits);
+ for (int i = 0; i < n_qubits; i++) {
+ allocateQubit_cpp(sim, i);
+ H_cpp(sim, i);
+ }
+ std::bitset one_state = 0;
+ for (int i = 0; i < n_qubits - 1; i += 2) {
+ Z_cpp(sim, i);
+ X_cpp(sim, i+1);
+ one_state.set(i);
+ }
+ for (int i = n_qubits - 1; i >= 0; i--) {
+ H_cpp(sim, i);
+ }
+ for (std::uint64_t i = 0; i < (std::uint64_t{1} << n_qubits); i++) {
+ amplitude state = getSimulator(sim)->probe(std::bitset(i).to_string());
+ if (i == one_state.to_ulong()) {
+ assert_amplitude_equality(state, 1.0, 0.0);
+ }
+ else {
+ assert_amplitude_equality(state, 0.0, 0.0);
+ }
+ }
+}
+
+TEST_CASE("ResetTest")
+{
+ const int n_qubits = 16;
+ simulator_id_type sim = init_cpp(n_qubits);
+ allocateQubit_cpp(sim, 0);
+ Reset_cpp(sim, 0);
+ amplitude state = getSimulator(sim)->probe("0");
+ assert_amplitude_equality(state, 1.0, 0.0);
+ X_cpp(sim, 0);
+ Reset_cpp(sim, 0);
+ state = getSimulator(sim)->probe("0");
+ // No qubit exists; should have amplitude 0
+ assert_amplitude_equality(state, 1.0, 0.0);
+ allocateQubit_cpp(sim, 1);
+ X_cpp(sim, 0);
+ logical_qubit_id* controls = new logical_qubit_id{ 0 };
+ MCX_cpp(sim, 1, controls, 1);
+ Reset_cpp(sim, 0);
+ state = getSimulator(sim)->probe("00");
+ assert_amplitude_equality(state, 0.0, 0.0);
+ state = getSimulator(sim)->probe("10");
+ assert_amplitude_equality(state, 1.0, 0.0);
+ Reset_cpp(sim, 1);
+ state = getSimulator(sim)->probe("00");
+ assert_amplitude_equality(state, 1.0, 0.0);
+ state = getSimulator(sim)->probe("10");
+ assert_amplitude_equality(state, 0.0, 0.0);
+ releaseQubit_cpp(sim, 1);
+ releaseQubit_cpp(sim, 0);
+}
+
+TEST_CASE("MultiExpWithHTest") {
+ const int num_qubits = 32;
+ auto qubit_prep = [](simulator_id_type sim ) {
+ H_cpp(sim, 0);
+ H_cpp(sim, 1);
+ H_cpp(sim, 2);
+ };
+ auto qubit_clear = [](simulator_id_type sim) {
+ H_cpp(sim, 2);
+ releaseQubit_cpp(sim, 2);
+ H_cpp(sim, 1);
+ releaseQubit_cpp(sim, 1);
+ H_cpp(sim, 0);
+ releaseQubit_cpp(sim, 0);
+ };
+ MultiExpReferenceTest(qubit_prep, qubit_clear);
+}
+
+TEST_CASE("MultiExpBasisTest") {
+ const int num_qubits = 32;
+ auto qubit_prep = [](simulator_id_type sim, int index) {
+ if ((index & 1) == 0) { X_cpp(sim, 0); }
+ if ((index & 2) == 0) { X_cpp(sim, 1); }
+ if ((index & 4) == 0) { X_cpp(sim, 2); }
+ };
+ auto qubit_clear = [](simulator_id_type sim, int index) {
+ if ((index & 1) == 0) { X_cpp(sim, 0); }
+ releaseQubit_cpp(sim, 0);
+ if ((index & 2) == 0) { X_cpp(sim, 1); }
+ releaseQubit_cpp(sim, 1);
+ if ((index & 4) == 0) { X_cpp(sim, 2); }
+ releaseQubit_cpp(sim, 2);
+ };
+ for (int i = 0; i < 8; i++) {
+ MultiExpReferenceTest([=](simulator_id_type sim) {qubit_prep(sim, i); }, [=](simulator_id_type sim) {qubit_clear(sim, i); });
+ }
+}
+
+TEST_CASE("R1Test") {
+ const int num_qubits = 32;
+ amplitude result0;
+ amplitude result1;
+ for (double angle = 0.0; angle < M_PI / 2.0; angle += 0.1) {
+ simulator_id_type sim = init_cpp(num_qubits);
+ H_cpp(sim, 0);
+ R1_cpp(sim, angle, 0);
+ result0 = getSimulator(sim)->probe("0");
+ result1 = getSimulator(sim)->probe("1");
+ assert_amplitude_equality(result0, 1.0 / sqrt(2.0));
+ assert_amplitude_equality(result1, amplitude(cos(angle), sin(angle))/sqrt(2.0));
+ R1_cpp(sim, -angle, 0);
+ result0 = getSimulator(sim)->probe("0");
+ result1 = getSimulator(sim)->probe("1");
+ assert_amplitude_equality(result0, 1.0 / sqrt(2.0));
+ assert_amplitude_equality(result1, 1.0 / sqrt(2.0));
+ H_cpp(sim, 0);
+ releaseQubit_cpp(sim, 0);
+ destroy_cpp(sim);
+ }
+}
\ No newline at end of file
diff --git a/src/Simulation/NativeSparseSimulator/SparseSimulator.h b/src/Simulation/NativeSparseSimulator/SparseSimulator.h
new file mode 100644
index 00000000000..5bdea45e5b2
--- /dev/null
+++ b/src/Simulation/NativeSparseSimulator/SparseSimulator.h
@@ -0,0 +1,894 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "quantum_state.hpp"
+#include "basic_quantum_state.hpp"
+#include "types.h"
+#include "gates.h"
+
+using namespace std::literals::complex_literals;
+
+namespace Microsoft::Quantum::SPARSESIMULATOR
+{
+
+constexpr logical_qubit_id MAX_QUBITS = 1024;
+constexpr logical_qubit_id MIN_QUBITS = 64;
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+// Recrusively compiles sizes of QuantumState types between MIN_QUBITS and MAX_QUBITS
+// qubits large, growing by powers of 2
+template
+std::shared_ptr construct_wfn_helper(logical_qubit_id nqubits) {
+ return (nqubits > max_num_bits / 2) ?
+ std::shared_ptr(new QuantumState())
+ : (nqubits > MIN_QUBITS ? construct_wfn_helper(nqubits) :
+ std::shared_ptr(new QuantumState()));
+}
+
+// Constructs a new quantum state, templated to use enough qubits to hold `nqubits`,
+// with the same state as `old_sim`
+template
+std::shared_ptr expand_wfn_helper(std::shared_ptr old_sim, logical_qubit_id nqubits) {
+ return (nqubits > max_num_bits / 2) ? std::shared_ptr(new QuantumState(old_sim)): expand_wfn_helper(old_sim, nqubits);
+}
+
+// Sparse simulator only stores non-zero coefficients of the quantum state.
+// It has good performance only when the number of non-zero coefficients is low.
+// If the number of non-zero coefficients is low, the number of qubits may be fairly large.
+// Sparse simulator employs std::unordered_map hashtable.
+// Keys are basis vectors represented by std::bitset<>.
+// Values are non-zero amplitudes represented by std::complex.
+// Zero amplitudes are simply not stored.
+// Hashtable is reallocated and reconstructed on almost every gate.
+// Reallocation is saved for some gates that can be performed in one round.
+class SparseSimulator
+{
+public:
+
+ std::set operations_done;
+
+ SparseSimulator(logical_qubit_id num_qubits) {
+ // Constructs a quantum state templated to the right number of qubits
+ // and returns a pointer to it as a basic_quantum_state
+ _quantum_state = construct_wfn_helper(num_qubits);
+ // Return the number of qubits this actually produces
+ num_qubits = _quantum_state->get_num_qubits();
+ // Initialize with no qubits occupied
+ _occupied_qubits = std::vector(num_qubits, 0);
+ _max_num_qubits_used = 0;
+ _current_number_qubits_used = 0;
+
+ _queue_Ry = std::vector(num_qubits, 0);
+ _queue_Rx = std::vector(num_qubits, 0);
+ _queue_H = std::vector(num_qubits, 0);
+ _angles_Rx = std::vector(num_qubits, 0.0);
+ _angles_Ry = std::vector(num_qubits, 0.0);
+
+ }
+
+
+ ~SparseSimulator() {
+ _execute_queued_ops();
+ }
+
+ void dump_ids(void (*callback)(logical_qubit_id))
+ {
+ for(size_t qid = 0; qid < _occupied_qubits.size(); ++qid)
+ {
+ if(_occupied_qubits[qid])
+ {
+ callback((logical_qubit_id)qid);
+ }
+ }
+ }
+
+ // Outputs the wavefunction to the console, after
+ // executing any queued operations
+ void DumpWavefunction(size_t indent = 0){
+ _execute_queued_ops();
+ _quantum_state->DumpWavefunction(indent);
+ }
+
+ // Outputs the wavefunction as it is currently,
+ // without executing any operations
+ void DumpWavefunctionQuietly(size_t indent = 0) {
+ _quantum_state->DumpWavefunction(indent);
+ }
+
+ void set_random_seed(std::mt19937::result_type seed = std::mt19937::default_seed) {
+ _quantum_state->set_random_seed(seed);
+ }
+
+ // Returns the number of qubits currently available
+ // to the simulator, including those already used
+ logical_qubit_id get_num_qubits() {
+ return _quantum_state->get_num_qubits();
+ }
+
+ // Allocates a qubit at a specific location
+ // Implies that the caller of this function is tracking
+ // free qubits
+ void allocate_specific_qubit(logical_qubit_id qubit) {
+ logical_qubit_id num_qubits = _quantum_state->get_num_qubits();
+ // Checks that there are enough qubits
+ if (qubit >= num_qubits){
+ // We create a new wavefunction and reallocate
+ std::shared_ptr old_state = _quantum_state;
+ _quantum_state = expand_wfn_helper(old_state, qubit+1);
+
+ num_qubits = _quantum_state->get_num_qubits();
+ _occupied_qubits.resize(num_qubits, 0);
+ _queue_Ry.resize(num_qubits, 0);
+ _queue_Rx.resize(num_qubits, 0);
+ _queue_H.resize(num_qubits, 0);
+ _angles_Rx.resize(num_qubits, 0.0);
+ _angles_Ry.resize(num_qubits, 0.0);
+ }
+ // The external qubit manager should prevent this, but this checks anyway
+ if (_occupied_qubits[qubit]) {
+ throw std::runtime_error("Qubit " + std::to_string(qubit) + " is already occupied");
+ }
+ // There is actually nothing to do to "allocate" a qubit, as every qubit
+ // is already available for use with this data structure
+ }
+
+
+
+ // Removes a qubit in the zero state from the list
+ // of occupied qubits
+ bool release(logical_qubit_id qubit_id) {
+ // Quick check if it's zero
+ if (_occupied_qubits[qubit_id]) {
+ // If not zero here, we must execute any remaining operations
+ // Then check if the result is all zero
+ _execute_queued_ops(qubit_id);
+ auto is_classical = _quantum_state->is_qubit_classical(qubit_id);
+ if (!is_classical.first){ // qubit isn't classical
+ _quantum_state->Reset(qubit_id);
+ _set_qubit_to_zero(qubit_id);
+ return false;
+ }
+ else if (is_classical.second) {// qubit is in |1>
+ X(qubit_id); // reset to |0> and release
+ _execute_queued_ops(qubit_id);
+ }
+ }
+ _set_qubit_to_zero(qubit_id);
+ return true;
+ }
+
+
+ void X(logical_qubit_id index) {
+ // XY = - YX
+ if (_queue_Ry[index]){
+ _angles_Ry[index] *= -1.0;
+ }
+ // Rx trivially commutes
+ if (_queue_H[index]) {
+ _queued_operations.push_back(operation(OP::Z, index));
+ return;
+ }
+ _queued_operations.push_back(operation(OP::X, index));
+ _set_qubit_to_nonzero(index);
+ }
+
+
+ // For both CNOT and all types of C*NOT
+ // If a control index is repeated, it just treats it as one control
+ // (Q# will throw an error in that condition)
+ void MCX(std::vector const& controls, logical_qubit_id target) {
+ if (controls.size() == 0) {
+ X(target);
+ return;
+ }
+ // Check for anything on the controls
+ if (controls.size() > 1){
+ _execute_if(controls);
+ } else {
+ // An H on the control but not the target forces execution
+ if (_queue_Ry[controls[0]] || _queue_Rx[controls[0]] || (_queue_H[controls[0]] && !_queue_H[target])){
+ _execute_queued_ops(controls, OP::Ry);
+ }
+ }
+ // Ry on the target causes issues
+ if (_queue_Ry[target]){
+ _execute_queued_ops(target, OP::Ry);
+ }
+ // Rx on the target trivially commutes
+
+ // An H on the target flips the operation
+ if (_queue_H[target]){
+ // If it is a CNOT and there is also an H on the control, we swap control and target
+ if (controls.size() == 1 && _queue_H[controls[0]]){
+ _queued_operations.push_back(operation(OP::MCX, controls[0], std::vector{target}));
+ _set_qubit_to_nonzero(controls[0]);
+ } else {
+ _queued_operations.push_back(operation(OP::MCZ, target, controls));
+ }
+ return;
+ }
+ // Queue the operation at this point
+ _queued_operations.push_back(operation(OP::MCX, target, controls));
+ _set_qubit_to_nonzero(target);
+ }
+
+ // Same as MCX, but we assert that the target is 0 before execution
+ void MCApplyAnd(std::vector const& controls, logical_qubit_id target) {
+ Assert(std::vector{Gates::Basis::PauliZ}, std::vector{target}, 0);
+ MCX(controls, target);
+ }
+ // Same as MCX, but we assert that the target is 0 after execution
+ void MCApplyAndAdj(std::vector const& controls, logical_qubit_id target) {
+ MCX(controls, target);
+ Assert(std::vector{Gates::Basis::PauliZ}, std::vector{target}, 0);
+ _set_qubit_to_zero(target);
+ }
+
+ void Y(logical_qubit_id index) {
+ // XY = -YX
+ if (_queue_Rx[index]){
+ _angles_Rx[index] *= -1.0;
+ }
+ // commutes with H up to phase, so we ignore the H queue
+ _queued_operations.push_back(operation(OP::Y, index));
+ _set_qubit_to_nonzero(index);
+ }
+
+ void MCY(std::vector const& controls, logical_qubit_id target) {
+ if (controls.size() == 0) {
+ Y(target);
+ return;
+ }
+ _execute_if(controls);
+ // Commutes with Ry on the target, not Rx
+ if (_queue_Rx[target]){
+ _execute_queued_ops(target, OP::Rx);
+ }
+ // HY = -YH, so we add a phase to track this
+ if (_queue_H[target]){
+ // The phase added does not depend on the target
+ // Thus we use one of the controls as a target
+ if (controls.size() == 1)
+ _queued_operations.push_back(operation(OP::Z, controls[0]));
+ else if (controls.size() > 1)
+ _queued_operations.push_back(operation(OP::MCZ, controls[0], controls));
+ }
+ _queued_operations.push_back(operation(OP::MCY, target, controls));
+ _set_qubit_to_nonzero(target);
+ }
+
+
+ void Z(logical_qubit_id index) {
+ // ZY = -YZ
+ if (_queue_Ry[index]){
+ _angles_Ry[index] *= -1;
+ }
+ // XZ = -ZX
+ if (_queue_Rx[index]){
+ _angles_Rx[index] *= -1;
+ }
+ // HZ = XH
+ if (_queue_H[index]) {
+ _queued_operations.push_back(operation(OP::X, index));
+ _set_qubit_to_nonzero(index);
+ return;
+ }
+ // No need to modified _occupied_qubits, since if a qubit is 0
+ // a Z will not change that
+ _queued_operations.push_back(operation( OP::Z, index ));
+ }
+
+ void MCZ(std::vector const& controls, logical_qubit_id target) {
+ if (controls.size() == 0) {
+ Z(target);
+ return;
+ }
+ // If the only thing on the controls is one H, we can switch
+ // this to an MCX. Any Rx or Ry, or more than 1 H, means we
+ // must execute.
+ size_t count = 0;
+ for (auto control : controls) {
+ if (_queue_Ry[control] || _queue_Rx[control]){
+ count += 2;
+ }
+ if (_queue_H[control]){
+ count++;
+ }
+ }
+ if (_queue_Ry[target] || _queue_Rx[target]){
+ count +=2;
+ }
+ if (_queue_H[target]) {count++;}
+ if (count > 1) {
+ _execute_queued_ops(controls, OP::Ry);
+ _execute_queued_ops(target, OP::Ry);
+ } else if (count == 1) {
+ // Transform to an MCX, but we need to swap one of the controls
+ // with the target if the Hadamard is on one of the control qubits
+ std::vector new_controls(controls);
+ for (std::size_t i = 0; i < new_controls.size(); ++i){
+ if (_queue_H[new_controls[i]]){
+ std::swap(new_controls[i], target);
+ break;
+ }
+ }
+ _queued_operations.push_back(operation(OP::MCX, target, new_controls));
+ _set_qubit_to_nonzero(target);
+ return;
+ }
+ _queued_operations.push_back(operation(OP::MCZ, target, controls));
+ }
+
+
+ // Any phase gate
+ void Phase(amplitude const& phase, logical_qubit_id index) {
+ // Rx, Ry, and H do not commute well with arbitrary phase gates
+ if (_queue_Ry[index] || _queue_Rx[index] || _queue_H[index]){
+ _execute_queued_ops(index, OP::Ry);
+ }
+ _queued_operations.push_back(operation(OP::Phase, index, phase));
+ }
+
+ void MCPhase(std::vector const& controls, amplitude const& phase, logical_qubit_id target){
+ if (controls.size() == 0) {
+ Phase(phase, target);
+ return;
+ }
+ _execute_if(controls);
+ _execute_if(target);
+ _queued_operations.push_back(operation(OP::MCPhase, target, controls, phase));
+ }
+
+ void T(logical_qubit_id index) {
+ Phase(amplitude(_normalizer_double, _normalizer_double), index);
+ }
+
+ void AdjT(logical_qubit_id index) {
+ Phase(amplitude(_normalizer_double, -_normalizer_double), index);
+ }
+
+
+ void R1(double const& angle, logical_qubit_id index) {
+ Phase(std::polar(1.0, angle), index);
+ }
+
+ void MCR1(std::vector const& controls, double const& angle, logical_qubit_id target){
+ if (controls.size() > 0)
+ MCPhase(controls, std::polar(1.0, angle), target);
+ else
+ R1(angle, target);
+ }
+
+ void R1Frac(std::int64_t numerator, std::int64_t power, logical_qubit_id index) {
+ R1((double)numerator * pow(0.5, power)*M_PI, index);
+ }
+
+ void MCR1Frac(std::vector const& controls, std::int64_t numerator, std::int64_t power, logical_qubit_id target){
+ if (controls.size() > 0)
+ MCR1(controls, (double)numerator * pow(0.5, power) * M_PI, target);
+ else
+ R1Frac(numerator, power, target);
+ }
+
+ void S(logical_qubit_id index) {
+ Phase(1i, index);
+ }
+
+ void AdjS(logical_qubit_id index) {
+ Phase(-1i, index);
+ }
+
+
+
+ void R(Gates::Basis b, double phi, logical_qubit_id index)
+ {
+ if (b == Gates::Basis::PauliI){
+ return;
+ }
+
+ // Tries to absorb the rotation into the existing queue,
+ // if it hits a different kind of rotation, the queue executes
+ if (b == Gates::Basis::PauliY){
+ _queue_Ry[index] = true;
+ _angles_Ry[index] += phi;
+ _set_qubit_to_nonzero(index);
+ return;
+ } else if (_queue_Ry[index]) {
+ _execute_queued_ops(index, OP::Ry);
+ }
+
+ if (b == Gates::Basis::PauliX){
+ _queue_Rx[index] = true;
+ _angles_Rx[index] += phi;
+ _set_qubit_to_nonzero(index);
+ return;
+ } else if (_queue_Rx[index]){
+ _execute_queued_ops(index, OP::Rz);
+ }
+
+ // An Rz is just a phase
+ if (b == Gates::Basis::PauliZ){
+ // HRz = RxH, but that's the wrong order for this structure
+ // Thus we must execute the H queue
+ if (_queue_H[index]){
+ _execute_queued_ops(index, OP::H);
+ }
+ // Rz(phi) = RI(phi)*R1(-2*phi)
+ // Global phase from RI is ignored
+ R1(phi, index);
+ }
+ }
+
+ void MCR (std::vector const& controls, Gates::Basis b, double phi, logical_qubit_id target) {
+ if (controls.size() == 0) {
+ R(b, phi, target);
+ return;
+ }
+ if (b == Gates::Basis::PauliI){
+ // Controlled I rotations are equivalent to controlled phase gates
+ if (controls.size() > 1){
+ MCPhase(controls, std::polar(1.0, -0.5*phi), controls[0]);
+ } else {
+ Phase(std::polar(1.0, -0.5*phi), controls[0]);
+ }
+ return;
+ }
+
+ _execute_if(controls);
+ // The target can commute with rotations of the same type
+ if (_queue_Ry[target] && b != Gates::Basis::PauliY){
+ _execute_queued_ops(target, OP::Ry);
+ }
+ if (_queue_Rx[target] && b != Gates::Basis::PauliX){
+ _execute_queued_ops(target, OP::Rx);
+ }
+ if (_queue_H[target]){
+ _execute_queued_ops(target, OP::H);
+ }
+ // Execute any phase and permutation gates
+ // These are not indexed by qubit so it does
+ // not matter what the qubit argument is
+ _execute_queued_ops(0, OP::PermuteLarge);
+ _quantum_state->MCR(controls, b, phi, target);
+ _set_qubit_to_nonzero(target);
+ }
+
+ void RFrac(Gates::Basis axis, std::int64_t numerator, std::int64_t power, logical_qubit_id index) {
+ // Opposite sign convention
+ R(axis, -(double)numerator * std::pow(0.5, power-1 )*M_PI, index);
+ }
+
+ void MCRFrac(std::vector const& controls, Gates::Basis axis, std::int64_t numerator, std::int64_t power, logical_qubit_id target) {
+ // Opposite sign convention
+ MCR(controls, axis, -(double)numerator * std::pow(0.5, power - 1) * M_PI, target);
+ }
+
+ void Exp(std::vector const& axes, double angle, std::vector const& qubits){
+ amplitude cosAngle = std::cos(angle);
+ amplitude sinAngle = 1i*std::sin(angle);
+ // This does not commute nicely with anything, so we execute everything
+ _execute_queued_ops(qubits);
+ _quantum_state->PauliCombination(axes, qubits, cosAngle, sinAngle);
+ for (auto qubit : qubits){
+ _set_qubit_to_nonzero(qubit);
+ }
+ }
+
+ void MCExp(std::vector const& controls, std::vector const& axes, double angle, std::vector const& qubits){
+ if (controls.size() == 0) {
+ Exp(axes, angle, qubits);
+ return;
+ }
+ amplitude cosAngle = std::cos(angle);
+ amplitude sinAngle = 1i*std::sin(angle);
+ // This does not commute nicely with anything, so we execute everything
+ _execute_queued_ops(qubits);
+ _execute_queued_ops(controls);
+ _quantum_state->MCPauliCombination(controls, axes, qubits, cosAngle, sinAngle);
+ for (auto qubit : qubits){
+ _set_qubit_to_nonzero(qubit);
+ }
+ }
+
+
+
+ void H(logical_qubit_id index) {
+ // YH = -HY
+ _angles_Ry[index] *= (_queue_Ry[index] ? -1.0 : 1.0);
+ // Commuting with Rx creates a phase, but on the wrong side
+ // So we execute any Rx immediately
+ if (_queue_Rx[index]){
+ _execute_queued_ops(index, OP::Rx);
+ }
+ _queue_H[index] = !_queue_H[index];
+ _set_qubit_to_nonzero(index);
+ }
+
+ void MCH(std::vector const& controls, logical_qubit_id target) {
+ if (controls.size() == 0) {
+ H(target);
+ return;
+ }
+ // No commutation on controls
+ _execute_if(controls);
+ // No Ry or Rx commutation on target
+ if (_queue_Ry[target] || _queue_Rx[target]){
+ _execute_queued_ops(target, OP::Ry);
+ }
+ // Commutes through H gates on the target, so it does not check
+ _execute_phase_and_permute();
+ _quantum_state->MCH(controls, target);
+ _set_qubit_to_nonzero(target);
+ }
+
+
+
+
+ void SWAP(logical_qubit_id index_1, logical_qubit_id index_2){
+ // This is necessary for the "shift" to make sense
+ if (index_1 > index_2){
+ std::swap(index_2, index_1);
+ }
+ // Everything commutes nicely with a swap
+ _queue_Ry.swap(_queue_Ry[index_1], _queue_Ry[index_2]);
+ std::swap(_angles_Ry[index_1], _angles_Ry[index_2]);
+ _queue_Rx.swap(_queue_Rx[index_1], _queue_Rx[index_2]);
+ std::swap(_angles_Rx[index_1], _angles_Rx[index_2]);
+ _queue_H.swap(_queue_H[index_1], _queue_H[index_2]);
+ _occupied_qubits.swap(_occupied_qubits[index_1], _occupied_qubits[index_2]);
+ logical_qubit_id shift = index_2 - index_1;
+ _queued_operations.push_back(operation(OP::SWAP, index_1, shift, index_2));
+ }
+
+ void CSWAP(std::vector const& controls, logical_qubit_id index_1, logical_qubit_id index_2){
+ if (controls.size() == 0) {
+ SWAP(index_1, index_2);
+ return;
+ }
+ if (index_1 > index_2){
+ std::swap(index_2, index_1);
+ }
+ // Nothing commutes nicely with a controlled swap
+ _execute_if(controls);
+ _execute_if(index_1);
+ _execute_if(index_2);
+
+ logical_qubit_id shift = index_2 - index_1;
+ _queued_operations.push_back(operation(OP::MCSWAP, index_1, shift, controls, index_2));
+ // If either qubit is occupied, then set them both to occupied
+ if(_occupied_qubits[index_1] || _occupied_qubits[index_2]){
+ _set_qubit_to_nonzero(index_1);
+ _set_qubit_to_nonzero(index_2);
+ }
+ }
+
+ unsigned M(logical_qubit_id target) {
+ // Do nothing if the qubit is known to be 0
+ if (!_occupied_qubits[target]){
+ return 0;
+ }
+ // If we get a measurement, we take it as soon as we can
+ _execute_queued_ops(target, OP::Ry);
+ // If we measure 0, then this resets the occupied qubit register
+ unsigned res = _quantum_state->M(target);
+ if (res == 0)
+ _set_qubit_to_zero(target);
+ return res;
+ }
+
+ void Reset(logical_qubit_id target) {
+ if (!_occupied_qubits[target]){ return; }
+ _execute_queued_ops(target, OP::Ry);
+ _quantum_state->Reset(target);
+ _set_qubit_to_zero(target);
+ }
+
+ void Assert(std::vector axes, std::vector const& qubits, bool result) {
+ // Assertions will not commute well with Rx or Ry
+ for (auto qubit : qubits) {
+ if (_queue_Rx[qubit] || _queue_Ry[qubit])
+ _execute_queued_ops(qubits, OP::Ry);
+ }
+ bool isEmpty = true;
+ // Process each assertion by H commutation
+ for (int i = 0; i < qubits.size(); i++) {
+ switch (axes[i]){
+ case Gates::Basis::PauliY:
+ // HY=-YH, so we switch the eigenvalue
+ if (_queue_H[qubits[i]])
+ result ^= 1;
+ isEmpty = false;
+ break;
+ case Gates::Basis::PauliX:
+ // HX = ZH
+ if (_queue_H[qubits[i]])
+ axes[i] = Gates::Basis::PauliZ;
+ isEmpty = false;
+ break;
+ case Gates::Basis::PauliZ:
+ // HZ = XH
+ if (_queue_H[qubits[i]])
+ axes[i] = Gates::Basis::PauliX;
+ isEmpty = false;
+ break;
+ default:
+ break;
+ }
+ }
+ if (isEmpty) {
+ return;
+ }
+ _execute_queued_ops(qubits, OP::PermuteLarge);
+ _quantum_state->Assert(axes, qubits, result);
+ }
+
+ // Returns the probability of a given measurement in a Pauli basis
+ // by decomposing each pair of computational basis states into eigenvectors
+ // and adding the coefficients of the respective components
+ double MeasurementProbability(std::vector const& axes, std::vector const& qubits) {
+ _execute_queued_ops(qubits, OP::Ry);
+ return _quantum_state->MeasurementProbability(axes, qubits);
+ }
+
+
+
+ unsigned Measure(std::vector const& axes, std::vector const& qubits){
+ _execute_queued_ops(qubits, OP::Ry);
+ unsigned result = _quantum_state->Measure(axes, qubits);
+ // Switch basis to save space
+ // Idea being that, e.g., HH = I, but if we know
+ // that the qubit is in the X-basis, we can apply H
+ // and execute, and this will send that qubit to all ones
+ // or all zeros; then we leave the second H in the queue
+ // Ideally we would also do that with Y, but HS would force execution,
+ // rendering it pointless
+ std::vector measurements;
+ for (int i =0; i < axes.size(); i++){
+ if (axes[i]==Gates::Basis::PauliX){
+ H(qubits[i]);
+ measurements.push_back(qubits[i]);
+ }
+ }
+ _execute_queued_ops(measurements, OP::H);
+ // These operations undo the previous operations, but they will be
+ // queued
+ for (int i =0; i < axes.size(); i++){
+ if (axes[i]==Gates::Basis::PauliX){
+ H(qubits[i]);
+ }
+ }
+ return result;
+ }
+
+ // Returns the amplitude of a given bitstring
+ amplitude probe(std::string const& label) {
+ _execute_queued_ops();
+ return _quantum_state->probe(label);
+ }
+
+ std::string Sample() {
+ _execute_queued_ops();
+ return _quantum_state->Sample();
+ }
+
+ using callback_t = std::function;
+ using extended_callback_t = std::function;
+ // Dumps the state of a subspace of particular qubits, if they are not entangled
+ // This requires it to detect if the subspace is entangled, construct a new
+ // projected wavefunction, then call the `callback` function on each state.
+ bool dump_qubits(std::vector const& qubits, callback_t const& callback) {
+ _execute_queued_ops(qubits, OP::Ry);
+ return _quantum_state->dump_qubits(qubits, callback);
+ }
+ bool dump_qubits_ext(std::vector const& qubits, extended_callback_t const& callback, void* arg) {
+ return dump_qubits(qubits, [arg,&callback](const char* c, double re, double im) -> bool { return callback(c, re, im, arg); });
+ }
+
+ // Dumps all the states in superposition via a callback function
+ void dump_all(callback_t const& callback) {
+ _execute_queued_ops();
+ logical_qubit_id max_qubit_id = 0;
+ for (std::size_t i = 0; i < _occupied_qubits.size(); ++i) {
+ if (_occupied_qubits[i])
+ max_qubit_id = i;
+ }
+ _quantum_state->dump_all(max_qubit_id, callback);
+ }
+ void dump_all_ext(extended_callback_t const& callback, void* arg) {
+ dump_all([arg,&callback](const char* c, double re, double im) -> bool { return callback(c, re, im, arg); });
+ }
+
+ // Updates state to all queued gates
+ void update_state() {
+ _execute_queued_ops();
+ }
+
+
+private:
+
+ // These indicate whether there are any H, Rx, or Ry gates
+ // that have yet to be applied to the wavefunction.
+ // Since HH=I and Rx(theta_1)Rx(theta_2) = Rx(theta_1+theta_2)
+ // it only needs a boolean to track them.
+ std::vector _queue_H;
+ std::vector _queue_Rx;
+ std::vector _queue_Ry;
+
+ std::vector _angles_Rx;
+ std::vector _angles_Ry;
+
+ // Store which qubits are non-zero as a bitstring
+ std::vector _occupied_qubits;
+ logical_qubit_id _max_num_qubits_used = 0;
+ logical_qubit_id _current_number_qubits_used;
+
+ // In a situation where we know a qubit is zero,
+ // this sets the occupied qubit vector and decrements
+ // the current number of qubits if necessary
+ void _set_qubit_to_zero(logical_qubit_id index){
+ if (_occupied_qubits[index]){
+ --_current_number_qubits_used;
+ }
+ _occupied_qubits[index] = false;
+ }
+
+ // In a situation where a qubit may be non-zero,
+ // we increment which qubits are used, and update the current
+ // and maximum number of qubits
+ void _set_qubit_to_nonzero(logical_qubit_id index){
+ if (!_occupied_qubits[index]){
+ ++_current_number_qubits_used;
+ _max_num_qubits_used = std::max(_max_num_qubits_used, _current_number_qubits_used);
+ }
+ _occupied_qubits[index] = true;
+ }
+
+ // Normalizer for T gates: 1/sqrt(2)
+ const double _normalizer_double = 1.0 / std::sqrt(2.0);
+
+ // Internal quantum state
+ std::shared_ptr _quantum_state;
+
+ // Queued phase and permutation operations
+ std::list _queued_operations;
+
+ // The next three functions execute the H, and/or Rx, and/or Ry
+ // queues on a single qubit
+ void _execute_RyRxH_single_qubit(logical_qubit_id const &index){
+ if (_queue_H[index]){
+ _quantum_state->H(index);
+ _queue_H[index] = false;
+ }
+ if (_queue_Rx[index]){
+ _quantum_state->R(Gates::Basis::PauliX, _angles_Rx[index], index);
+ _angles_Rx[index] = 0.0;
+ _queue_Rx[index] = false;
+ }
+ if (_queue_Ry[index]){
+ _quantum_state->R(Gates::Basis::PauliY, _angles_Ry[index], index);
+ _angles_Ry[index] = 0.0;
+ _queue_Ry[index] = false;
+ }
+ }
+
+ void _execute_RxH_single_qubit(logical_qubit_id const &index){
+ if (_queue_H[index]){
+ _quantum_state->H(index);
+ _queue_H[index] = false;
+ }
+ if (_queue_Rx[index]){
+ _quantum_state->R(Gates::Basis::PauliX, _angles_Rx[index], index);
+ _angles_Rx[index] = 0.0;
+ _queue_Rx[index] = false;
+ }
+ }
+
+ void _execute_H_single_qubit(logical_qubit_id const &index){
+ if (_queue_H[index]){
+ _quantum_state->H(index);
+ _queue_H[index] = false;
+ }
+ }
+
+ // Executes all phase and permutation operations, if any exist
+ void _execute_phase_and_permute(){
+ if (_queued_operations.size() != 0){
+ _quantum_state->phase_and_permute(_queued_operations);
+ _queued_operations.clear();
+ }
+ }
+
+ // Executes all queued operations (including H and rotations)
+ // on all qubits
+ void _execute_queued_ops() {
+ _execute_phase_and_permute();
+ logical_qubit_id num_qubits = _quantum_state->get_num_qubits();
+ for (logical_qubit_id index =0; index < num_qubits; index++){
+ _execute_RyRxH_single_qubit(index);
+ }
+ }
+
+ // Executes all phase and permutation operations,
+ // then any H, Rx, or Ry gates queued on the qubit index,
+ // up to the level specified (where H < Rx < Ry)
+ void _execute_queued_ops(logical_qubit_id index, OP level = OP::Ry){
+ _execute_phase_and_permute();
+ switch (level){
+ case OP::Ry:
+ _execute_RyRxH_single_qubit(index);
+ break;
+ case OP::Rx:
+ _execute_RxH_single_qubit(index);
+ break;
+ case OP::H:
+ _execute_H_single_qubit(index);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Executes all phase and permutation operations,
+ // then any H, Rx, or Ry gates queued on any of the qubit indices,
+ // up to the level specified (where H < Rx < Ry)
+ void _execute_queued_ops(std::vector const& indices, OP level = OP::Ry){
+ _execute_phase_and_permute();
+ switch (level){
+ case OP::Ry:
+ for (auto index : indices){
+ _execute_RyRxH_single_qubit(index);
+ }
+ break;
+ case OP::Rx:
+ for (auto index : indices){
+ _execute_RxH_single_qubit(index);
+ }
+ break;
+ case OP::H:
+ for (auto index : indices){
+ _execute_H_single_qubit(index);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+
+ // Executes if there is anything already queued on the qubit target
+ // Used when queuing gates that do not commute well
+ void _execute_if(logical_qubit_id target){
+ if (_queue_Ry[target] || _queue_Rx[target] || _queue_H[target]){
+ _execute_queued_ops(target, OP::Ry);
+ }
+ }
+
+ // Executes if there is anything already queued on the qubits in controls
+ // Used when queuing gates that do not commute well
+ void _execute_if(std::vector const &controls) {
+ for (auto control : controls){
+ if (_queue_Ry[control] || _queue_Rx[control] || _queue_H[control]){
+ _execute_queued_ops(controls, OP::Ry);
+ return;
+ }
+ }
+ }
+
+};
+
+} // namespace Microsoft::Quantum::SPARSESIMULATOR
diff --git a/src/Simulation/NativeSparseSimulator/SparseSimulatorTests.cpp b/src/Simulation/NativeSparseSimulator/SparseSimulatorTests.cpp
new file mode 100644
index 00000000000..aead77adc9d
--- /dev/null
+++ b/src/Simulation/NativeSparseSimulator/SparseSimulatorTests.cpp
@@ -0,0 +1,886 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#define CATCH_CONFIG_MAIN
+#include
+
+#include "SparseSimulator.h"
+#include "TestHelpers.hpp"
+#include
+#include
+#include
+
+using namespace Microsoft::Quantum::SPARSESIMULATOR;
+using namespace SparseSimulatorTestHelpers;
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+
+
+template
+void MultiExpTest(
+ std::function qubit_prep,
+ std::function qubit_clear
+) {
+ for (int intPaulis = 0; intPaulis < 4 * 4 * 4; intPaulis++) {
+ std::vector Paulis{
+ (Gates::Basis)(intPaulis % 4),
+ (Gates::Basis)((intPaulis / 4) % 4),
+ (Gates::Basis)(intPaulis / 16)
+ };
+ for (double angle = 0.0; angle < M_PI / 2.0; angle += 0.1) {
+ SparseSimulator sim = SparseSimulator(num_qubits);
+ std::vector qubits{ 0,1,2 };
+ qubit_prep(sim);
+ std::vector vector_rep(8, 0.0);
+ for (size_t i = 0; i < 8; i++) {
+ vector_rep[i] = sim.probe(std::bitset<3>(i).to_string());
+ }
+ // New simulator Exp
+ sim.Exp(Paulis, angle, qubits);
+ // Old simulator Exp
+ apply_exp(vector_rep, Paulis, angle, std::vector{ 0, 1, 2 });
+ for (size_t i = 0; i < 8; i++) {
+ amplitude result = sim.probe(std::bitset<3>(i).to_string());
+ assert_amplitude_equality(vector_rep[i], result);
+ }
+ sim.Exp(Paulis, -angle, qubits);
+ qubit_clear(sim);
+ }
+ }
+}
+
+// Tests comparisons of bitstrings
+TEST_CASE("LabelComparisonTest") {
+ const logical_qubit_id num_qubits = 1024;
+ SparseSimulator sim = SparseSimulator(num_qubits);
+ uint64_t i = 0;
+ uint64_t j;
+ uint64_t k = 0;
+ qubit_label_type label1(0);
+ qubit_label_type label2(1);
+
+ for (i = 0; i < 500; i++){
+ k += i * i * i * i;
+ uint64_t m = 0;
+ label1 = qubit_label_type(k);
+ for (j = 0; j < 500; j++){
+ m += j * j * j * j;
+ label2 = qubit_label_type(m);
+ REQUIRE((k < m) == (label1 < label2));
+ }
+ }
+}
+// Tests that the X gate flips the computational basis states
+TEST_CASE("XGateTest") {
+ const logical_qubit_id num_qubits = 32;
+ SparseSimulator sim = SparseSimulator(num_qubits);
+ sim.X(0);
+ assert_amplitude_equality(sim.probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 1.0, 0.0);
+ sim.X(0);
+ assert_amplitude_equality(sim.probe("0"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 0.0, 0.0);
+}
+
+// Tests Z on computational basis states
+TEST_CASE("ZGateTest") {
+ const logical_qubit_id num_qubits = 32;
+ SparseSimulator sim = SparseSimulator(num_qubits);
+ sim.Z(0);
+ assert_amplitude_equality(sim.probe("0"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 0.0, 0.0);
+ sim.Z(0);
+ assert_amplitude_equality(sim.probe("0"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 0.0, 0.0);
+ sim.X(0);
+ sim.Z(0);
+ assert_amplitude_equality(sim.probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), -1.0, 0.0);
+ sim.Z(0);
+ assert_amplitude_equality(sim.probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 1.0, 0.0);
+}
+
+// Tests H on computational basis states
+TEST_CASE("HGateTest") {
+ const logical_qubit_id num_qubits = 32;
+ const qubit_label_type zero(0);
+ SparseSimulator sim = SparseSimulator(num_qubits);
+ sim.H(0);
+ assert_amplitude_equality(sim.probe("0"), 1.0 / sqrt(2.0), 0.0);
+ assert_amplitude_equality(sim.probe("1"), 1.0 / sqrt(2.0), 0.0);
+ sim.H(0);
+ assert_amplitude_equality(sim.probe("0"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 0.0, 0.0);
+ sim.X(0);
+ sim.H(0);
+ assert_amplitude_equality(sim.probe("0"), 1.0 / sqrt(2.0), 0.0);
+ assert_amplitude_equality(sim.probe("1"), -1.0 / sqrt(2.0), 0.0);
+ sim.H(0);
+ assert_amplitude_equality(sim.probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 1.0, 0.0);
+}
+
+// Tests powers of T on computational basis states
+TEST_CASE("TGateTest") {
+ const logical_qubit_id num_qubits = 32;
+ const qubit_label_type zero(0);
+ SparseSimulator sim = SparseSimulator(num_qubits);
+ sim.T(0);
+ assert_amplitude_equality(sim.probe("0"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 0.0, 0.0);
+ sim.X(0);
+ sim.T(0);
+ assert_amplitude_equality(sim.probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 1.0 / sqrt(2.0), 1.0 / sqrt(2.0));
+ sim.T(0);
+ assert_amplitude_equality(sim.probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 0.0, 1.0);
+ sim.T(0);
+ assert_amplitude_equality(sim.probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), -1.0 / sqrt(2.0), 1.0 / sqrt(2.0));
+ sim.T(0);
+ assert_amplitude_equality(sim.probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), -1.0, 0.0);
+ sim.T(0);
+ assert_amplitude_equality(sim.probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), -1.0 / sqrt(2.0), -1.0 / sqrt(2.0));
+ sim.T(0);
+ assert_amplitude_equality(sim.probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 0.0, -1.0);
+ sim.T(0);
+ assert_amplitude_equality(sim.probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 1.0 / sqrt(2.0), -1.0 / sqrt(2.0));
+ sim.T(0);
+ assert_amplitude_equality(sim.probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 1.0, 0.0);
+}
+
+// Tests Rx on computational basis states, for angles between 0 and pi/2
+TEST_CASE("RxGateTest") {
+ const logical_qubit_id num_qubits = 32;
+ const qubit_label_type zero(0);
+ for (double angle = 0.0; angle < M_PI / 2.0; angle += 0.1) {
+ SparseSimulator sim = SparseSimulator(num_qubits);
+ sim.R(Gates::Basis::PauliX, angle, 0);
+ assert_amplitude_equality(sim.probe("0"), cos(angle / 2.0), 0.0);
+ assert_amplitude_equality(sim.probe("1"), 0.0, -sin(angle / 2.0));
+ sim.R(Gates::Basis::PauliX, -angle, 0);
+ assert_amplitude_equality(sim.probe("0"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 0.0, 0.0);
+ sim.X(0);
+ sim.R(Gates::Basis::PauliX, angle, 0);
+ assert_amplitude_equality(sim.probe("0"), 0.0, -sin(angle / 2.0));
+ assert_amplitude_equality(sim.probe("1"), cos(angle / 2.0), 0.0);
+ sim.R(Gates::Basis::PauliX, -angle, 0);
+ assert_amplitude_equality(sim.probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 1.0, 0.0);
+ }
+}
+
+// Tests Ry on computational basis states, for angles between 0 and pi/2
+TEST_CASE("RyGateTest") {
+ const logical_qubit_id num_qubits = 32;
+ const qubit_label_type zero(0);
+ for (double angle = 0.0; angle < M_PI / 2.0; angle += 0.1) {
+ SparseSimulator sim = SparseSimulator(num_qubits);
+ sim.R(Gates::Basis::PauliY, angle, 0);
+ assert_amplitude_equality(sim.probe("0"), cos(angle / 2.0), 0.0);
+ assert_amplitude_equality(sim.probe("1"), sin(angle / 2.0), 0.0);
+ sim.R(Gates::Basis::PauliY, -angle, 0);
+ assert_amplitude_equality(sim.probe("0"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 0.0, 0.0);
+ sim.X(0);
+ sim.R(Gates::Basis::PauliY, angle, 0);
+ assert_amplitude_equality(sim.probe("0"), -sin(angle / 2.0), 0.0);
+ assert_amplitude_equality(sim.probe("1"), cos(angle / 2.0), 0.0);
+ sim.R(Gates::Basis::PauliY, -angle, 0);
+ assert_amplitude_equality(sim.probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 1.0, 0.0);
+ }
+}
+
+// Tests Rz on computational basis states, for angles between 0 and pi/2
+TEST_CASE("RzGateTest") {
+ const logical_qubit_id num_qubits = 32;
+ const qubit_label_type zero(0);
+ for (double angle = 0.0; angle < M_PI / 2.0; angle += 0.1) {
+ SparseSimulator sim = SparseSimulator(num_qubits);
+ logical_qubit_id qubit = 0;
+ sim.R(Gates::Basis::PauliZ, angle, qubit);
+ assert_amplitude_equality(sim.probe("0"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 0.0, 0.0);
+ sim.R(Gates::Basis::PauliZ, -angle, qubit);
+ assert_amplitude_equality(sim.probe("0"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 0.0, 0.0);
+ sim.X(qubit);
+ assert_amplitude_equality(sim.probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 1.0, 0.0);
+ sim.R(Gates::Basis::PauliZ, angle, qubit);
+ assert_amplitude_equality(sim.probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), cos(angle), sin(angle));
+ sim.R(Gates::Basis::PauliZ, -angle, qubit);
+ assert_amplitude_equality(sim.probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 1.0, 0.0);
+ }
+}
+
+// Tests CNOT on all 2-qubit computational basis stats
+TEST_CASE("CNOTGateTest") {
+ const logical_qubit_id num_qubits = 32;
+ const qubit_label_type zero(0);
+ SparseSimulator sim = SparseSimulator(num_qubits);
+ logical_qubit_id qubits[2]{ 0, 1 };
+ sim.MCX({ qubits[0] }, qubits[1]);
+ assert_amplitude_equality(sim.probe("00"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe("01"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("10"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("11"), 0.0, 0.0);
+ sim.X(qubits[0]);
+ sim.MCX({ qubits[0] }, qubits[1]);
+ assert_amplitude_equality(sim.probe("00"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("01"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("10"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("11"), 1.0, 0.0);
+ sim.MCX({ qubits[0] }, qubits[1]);
+ assert_amplitude_equality(sim.probe("00"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("01"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe("10"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("11"), 0.0, 0.0);
+ sim.X(qubits[0]);
+ sim.X(qubits[1]);
+ sim.MCX({ qubits[0] }, qubits[1]);
+ assert_amplitude_equality(sim.probe("00"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("01"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("10"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe("11"), 0.0, 0.0);
+}
+
+
+
+// Tests all possible computational basis states
+// for some number of controls and one target
+TEST_CASE("MCXGateTest") {
+ const size_t n_qubits = 7;
+ const qubit_label_type zero(0);
+ SparseSimulator sim = SparseSimulator(n_qubits);
+ std::vector qubits(n_qubits);
+ std::generate(qubits.begin(), qubits.end(), [] { static int i{ 0 }; return i++; });
+ std::vector controls(qubits.begin() + 1, qubits.end());
+ logical_qubit_id target = qubits[0];
+ for (logical_qubit_id i = 0; i < pow(2, n_qubits - 1); i++) { // the bitstring of the controls
+ sim.MCX(controls, target);
+ for (logical_qubit_id j = 0; j < pow(2, n_qubits - 1); j++) { // bitstring to test
+ std::bitset j_bits = j; // a bitset for the string to test, with the target as 0
+ j_bits = j_bits << 1;
+ std::bitset j_odd_bits = j_bits; // same as j, but with the target as 1
+ j_odd_bits.set(0);
+ if (j != i) { // The controls are not in this state, so the amplitude should be 0
+ assert_amplitude_equality(sim.probe(j_bits.to_string()), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe(j_odd_bits.to_string()), 0.0, 0.0);
+ }
+ else if (i == pow(2, n_qubits - 1) - 1) { // All controls are 1, so this should flip the output
+ assert_amplitude_equality(sim.probe(j_bits.to_string()), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe(j_odd_bits.to_string()), 1.0, 0.0);
+ }
+ else { // This is the state of the controls, but they are not all 1, so nothing should have happened
+ assert_amplitude_equality(sim.probe(j_bits.to_string()), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe(j_odd_bits.to_string()), 0.0, 0.0);
+ }
+ }
+ // Since MCX^2 = I, this should undo anything previously
+ sim.MCX(controls, target);
+ for (logical_qubit_id j = 0; j < pow(2, n_qubits - 1); j++) {
+ std::bitset j_bits = j;
+ j_bits = j_bits << 1;
+ std::bitset j_odd_bits = j_bits;
+ j_odd_bits.set(0);
+ if (j != i) { // The controls are not in this state, so the amplitude should be 0
+ assert_amplitude_equality(sim.probe(j_bits.to_string()), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe(j_odd_bits.to_string()), 0.0, 0.0);
+ }
+ else { // This is the state of the controls, but the final qubit should be 0
+ assert_amplitude_equality(sim.probe(j_bits.to_string()), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe(j_odd_bits.to_string()), 0.0, 0.0);
+ }
+ }
+ // Update the controls
+ std::bitset diff = i ^ (i + 1);
+ for (logical_qubit_id j = 0; j < n_qubits - 1; j++) {
+ if (diff[j]) sim.X(controls[j]);
+ }
+ }
+}
+
+
+// Tests a controlled Y
+// Same logic as the MCXGateTest
+TEST_CASE("MCYGateTest") {
+ const size_t n_qubits = 7;
+ const qubit_label_type zero(0);
+ SparseSimulator sim = SparseSimulator(n_qubits);
+ std::vector qubits(n_qubits);
+ std::generate(qubits.begin(), qubits.end(), [] { static int i{ 0 }; return i++; });
+ std::vector controls(qubits.begin() + 1, qubits.end());
+ logical_qubit_id target = qubits[0];
+ for (logical_qubit_id i = 0; i < pow(2, n_qubits - 1); i++) {
+ sim.MCY(controls, target);
+ for (logical_qubit_id j = 0; j < pow(2, n_qubits - 1); j++) {
+ std::bitset j_bits = j;
+ j_bits = j_bits << 1;
+ std::bitset j_odd_bits = j_bits;
+ j_odd_bits.set(0);
+ if (j != i) {
+ assert_amplitude_equality(sim.probe(j_bits.to_string()), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe(j_odd_bits.to_string()), 0.0, 0.0);
+ }
+ else if (i == pow(2, n_qubits - 1) - 1) {
+ assert_amplitude_equality(sim.probe(j_bits.to_string()), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe(j_odd_bits.to_string()), 0.0, 1.0);
+ }
+ else {
+ assert_amplitude_equality(sim.probe(j_bits.to_string()), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe(j_odd_bits.to_string()), 0.0, 0.0);
+ }
+ }
+ sim.MCY(controls, target);
+ for (logical_qubit_id j = 0; j < pow(2, n_qubits - 1); j++) {
+ std::bitset j_bits = j;
+ j_bits = j_bits << 1;
+ std::bitset j_odd_bits = j_bits;
+ j_odd_bits.set(0);
+ if (j != i) {
+ assert_amplitude_equality(sim.probe(j_bits.to_string()), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe(j_odd_bits.to_string()), 0.0, 0.0);
+ }
+ else {
+ assert_amplitude_equality(sim.probe(j_bits.to_string()), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe(j_odd_bits.to_string()), 0.0, 0.0);
+ }
+ }
+ std::bitset diff = i ^ (i + 1);
+ for (logical_qubit_id j = 0; j < n_qubits - 1; j++) {
+ if (diff[j]) sim.X(controls[j]);
+ }
+ }
+}
+// Tests a controlled Z
+// Same logic as the MCXGateTest
+TEST_CASE("MCZGateTest") {
+ const size_t n_qubits = 7;
+ const qubit_label_type zero(0);
+ SparseSimulator sim = SparseSimulator(n_qubits);
+ std::vector qubits(n_qubits);
+ std::generate(qubits.begin(), qubits.end(), [] { static int i{ 0 }; return i++; });
+ std::vector controls(qubits.begin() + 1, qubits.end());
+ logical_qubit_id target = qubits[0];
+ sim.H(target);
+ for (logical_qubit_id i = 0; i < pow(2, n_qubits - 1); i++) {
+ sim.MCZ(controls, target);
+ for (logical_qubit_id j = 0; j < pow(2, n_qubits - 1); j++) {
+ std::bitset j_bits = j;
+ j_bits = j_bits << 1;
+ std::bitset j_odd_bits = j_bits;
+ j_odd_bits.set(0);
+ if (j != i) {
+ assert_amplitude_equality(sim.probe(j_bits.to_string()), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe(j_odd_bits.to_string()), 0.0, 0.0);
+ }
+ else if (i == pow(2, n_qubits - 1) - 1) {
+ assert_amplitude_equality(sim.probe(j_bits.to_string()), 1.0/sqrt(2.0), 0.0);
+ assert_amplitude_equality(sim.probe(j_odd_bits.to_string()), -1.0 / sqrt(2.0), 0.0);
+ }
+ else {
+ assert_amplitude_equality(sim.probe(j_bits.to_string()), 1.0 / sqrt(2.0), 0.0);
+ assert_amplitude_equality(sim.probe(j_odd_bits.to_string()), 1.0 / sqrt(2.0), 0.0);
+ }
+ }
+ sim.MCZ(controls, target);
+ for (logical_qubit_id j = 0; j < pow(2, n_qubits - 1); j++) {
+ std::bitset j_bits = j;
+ j_bits = j_bits << 1;
+ std::bitset j_odd_bits = j_bits;
+ j_odd_bits.set(0);
+ if (j != i) {
+ assert_amplitude_equality(sim.probe(j_bits.to_string()), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe(j_odd_bits.to_string()), 0.0, 0.0);
+ }
+ else {
+ assert_amplitude_equality(sim.probe(j_bits.to_string()), 1.0 / sqrt(2.0), 0.0);
+ assert_amplitude_equality(sim.probe(j_odd_bits.to_string()), 1.0 / sqrt(2.0), 0.0);
+ }
+ }
+ std::bitset diff = i ^ (i + 1);
+ for (logical_qubit_id j = 0; j < n_qubits - 1; j++) {
+ if (diff[j]) sim.X(controls[j]);
+ }
+ }
+}
+
+
+
+// Tests the multi-exp on a uniform superposition
+TEST_CASE("MultiExpWithHTest") {
+ const int num_qubits = 32;
+ auto qubit_prep = [](SparseSimulator& sim) {
+ sim.H(0);
+ sim.H(1);
+ sim.H(2);
+ };
+ auto qubit_clear = [](SparseSimulator& sim) {
+ sim.H(2);
+ sim.release(2);
+ sim.H(1);
+ sim.release(1);
+ sim.H(0);
+ sim.release(0);
+ };
+ MultiExpTest(qubit_prep, qubit_clear);
+}
+
+// Tests the MultiExp on all computational basis states of 3 qubits
+TEST_CASE("MultiExpBasisTest") {
+ const int num_qubits = 32;
+ auto qubit_prep = [](SparseSimulator& sim, int index) {
+ if ((index & 1) == 0) { sim.X(0); }
+ if ((index & 2) == 0) { sim.X(1); }
+ if ((index & 4) == 0) { sim.X(2); }
+ };
+ auto qubit_clear = [](SparseSimulator& sim, int index) {
+ if ((index & 1) == 0) { sim.X(0); }
+ sim.release(0);
+ if ((index & 2) == 0) { sim.X(1); }
+ sim.release(1);
+ if ((index & 4) == 0) { sim.X(2); }
+ sim.release(2);
+ };
+ for (int i = 0; i < 8; i++) {
+ MultiExpTest([=](SparseSimulator& sim) {qubit_prep(sim, i); }, [=](SparseSimulator& sim) {qubit_clear(sim, i); });
+ }
+}
+
+// Tests a SWAP gate on all 2-qubit computational basis states
+TEST_CASE("SWAPGateTest") {
+ const logical_qubit_id num_qubits = 32;
+ const qubit_label_type zero(0);
+ SparseSimulator sim = SparseSimulator(num_qubits);
+ std::vector qubits{ 0,1 };
+ sim.SWAP({ qubits[0] }, qubits[1]); // 00 -> 00
+ assert_amplitude_equality(sim.probe("00"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe("01"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("10"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("11"), 0.0, 0.0);
+ sim.X(qubits[0]);
+ sim.SWAP( qubits[0] , qubits[1]); // 10 -> 01
+ assert_amplitude_equality(sim.probe("00"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("01"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("10"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe("11"), 0.0, 0.0);
+ sim.SWAP( qubits[0] , qubits[1]); // 01 -> 10
+ assert_amplitude_equality(sim.probe("0"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe("10"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("11"), 0.0, 0.0);
+ sim.X(qubits[1]);
+ sim.SWAP( qubits[0] , qubits[1]); // 11 -> 11
+ assert_amplitude_equality(sim.probe("00"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("01"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("10"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("11"), 1.0, 0.0);
+}
+
+// Tests multi-controlled swap on all computational basis states of 4 qubits
+// (2 controls, 2 targets)
+TEST_CASE("CSWAPGateTest") {
+ const logical_qubit_id num_qubits = 32;
+ const qubit_label_type zero(0);
+ SparseSimulator sim = SparseSimulator(num_qubits);
+ std::vector target_qubits{ 0,1 };
+ std::vector control_qubits{ 2,3 };
+ // Lambda to test when controls should cause no swap
+ auto no_swap_test = [&](std::string controls) {
+ sim.CSWAP(control_qubits, target_qubits[0], target_qubits[1]); // 00 -> 00
+ assert_amplitude_equality(sim.probe(controls+"00"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe(controls + "01"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe(controls + "10"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe(controls + "11"), 0.0, 0.0);
+ sim.X(target_qubits[0]);
+ sim.CSWAP(control_qubits, target_qubits[0], target_qubits[1]); // 01 -> 01
+ assert_amplitude_equality(sim.probe(controls + "00"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe(controls + "01"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe(controls + "10"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe(controls + "11"), 0.0, 0.0);
+ sim.X(target_qubits[1]);
+ sim.CSWAP(control_qubits, target_qubits[0], target_qubits[1]); // 11 -> 11
+ assert_amplitude_equality(sim.probe(controls + "00"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe(controls + "01"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe(controls + "10"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe(controls + "11"), 1.0, 0.0);
+ sim.X(target_qubits[0]);
+ sim.CSWAP(control_qubits, target_qubits[0], target_qubits[1]); // 10 -> 10
+ assert_amplitude_equality(sim.probe(controls + "00"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe(controls + "01"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe(controls + "10"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe(controls + "11"), 0.0, 0.0);
+ sim.X(target_qubits[1]);
+ };
+ // Controls are 00, no swap
+ no_swap_test("00");
+ sim.X(control_qubits[0]);
+ // Controls are 01, no swap
+ no_swap_test("01");
+ sim.X(control_qubits[1]);
+ // Controls are 11, test for swap
+ sim.CSWAP(control_qubits, target_qubits[0], target_qubits[1]); // 00 -> 00
+ assert_amplitude_equality(sim.probe("1100"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe("1101"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1110"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1111"), 0.0, 0.0);
+ sim.X(target_qubits[0]);
+ sim.CSWAP(control_qubits, target_qubits[0], target_qubits[1]); // 10 -> 01
+ assert_amplitude_equality(sim.probe("1100"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1101"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1110"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe("1111"), 0.0, 0.0);
+ sim.CSWAP(control_qubits, target_qubits[0], target_qubits[1]);// 01 -> 10
+ assert_amplitude_equality(sim.probe("1100"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1101"), 1.0, 0.0);
+ assert_amplitude_equality(sim.probe("1110"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1111"), 0.0, 0.0);
+ sim.X(target_qubits[1]);
+ sim.CSWAP(control_qubits, target_qubits[0], target_qubits[1]); // 11 -> 11
+ assert_amplitude_equality(sim.probe("1100"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1101"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1110"), 0.0, 0.0);
+ assert_amplitude_equality(sim.probe("1111"), 1.0, 0.0);
+ sim.X(target_qubits[1]);
+ sim.X(target_qubits[0]);
+ sim.X(control_qubits[0]);
+ // Controls are 10, test for no swap
+ no_swap_test("10");
+}
+
+// Tests measurement probabilistically
+// Based on the expected measurement probabilities for a Pauli Y
+// rotation
+// It samples a lot of measurements, and based on the
+// current variance (of a binomial distrbution):
+// - if it's very close to the expected distribution,
+// it considers this a success
+// - if it's very far from the expected distribution,
+// it throws an exception
+// - if it's in between, it runs more samples
+// While this run-time is undetermined, the threshold
+// for an exception shrinks with the number of tests
+TEST_CASE("MTest") {
+ const logical_qubit_id num_qubits = 32;
+ const qubit_label_type zero(0);
+ const size_t n_tests = 5000;
+ const double log_false_positive_threshold = 0.1;
+ const double log_false_negative_threshold = 100.0;
+ for (double angle = 0.0; angle < M_PI / 2.0; angle += 0.1) {
+ SparseSimulator sim = SparseSimulator(num_qubits);
+ sim.set_random_seed(12345);
+ double expected_ratio = sin(angle / 2.0) * sin(angle / 2.0);
+ double ratio = 0.0;
+ size_t total_tests = 0;
+ size_t ones = 0;
+ double std_dev = 0.0;
+ double log_prob = 0.0;
+ logical_qubit_id qubit = 0;
+ do {
+ for (int i = 0; i < n_tests; i++) {
+ sim.R(Gates::Basis::PauliY, angle, qubit);
+ if (sim.M(qubit)) {
+ ones++;
+ sim.X(qubit);
+ }
+ }
+ total_tests += n_tests;
+ ratio = (double)ones / (double)total_tests;
+ double abs_diff = std::abs(expected_ratio - ratio);
+ // Based on Chernoff bounds
+ log_prob = abs_diff * abs_diff * expected_ratio * (double)total_tests;
+ std_dev = sqrt(expected_ratio * (1.0 - expected_ratio)) / (double)total_tests;
+ // Using variance of the binomial distribution
+ if (log_false_positive_threshold >= log_prob) {
+ break;
+ }
+ } while (log_false_negative_threshold >= log_prob);
+ if (log_false_negative_threshold < log_prob) {
+ throw std::runtime_error("Statistically improbable measurement results");
+ }
+ }
+
+}
+
+// Tests an assortment of assertions to both pass and to throw exceptions
+TEST_CASE("AssertTest") {
+ const logical_qubit_id num_qubits = 32;
+ const qubit_label_type zero(0);
+ SparseSimulator sim = SparseSimulator(num_qubits);
+ using namespace Gates;
+ std::vector basis{ Basis::PauliZ, Basis::PauliZ, Basis::PauliZ };
+ std::vector qubits{ 0,1,2 };
+ sim.Assert(basis, qubits, false);
+ sim.update_state();
+ // These require forcing the simulator to update the state for it to actually throw the exception
+
+ auto sim_assert = [&](std::vector const& basis, bool val) {
+ sim.Assert(basis, qubits, val);
+ sim.update_state();
+ };
+ basis = { Basis::PauliZ, Basis::PauliZ, Basis::PauliI };
+ sim_assert(basis, false);
+ sim.X(0);
+ sim_assert(basis, true);
+
+ basis = { Basis::PauliX, Basis::PauliI, Basis::PauliI };
+ REQUIRE_THROWS_AS(sim_assert(basis, false), std::exception);
+}
+
+// Tests an assortment of assertions to both pass and to throw exceptions
+TEST_CASE("AssertTest2") {
+ const logical_qubit_id num_qubits = 32;
+ const qubit_label_type zero(0);
+ SparseSimulator sim = SparseSimulator(num_qubits);
+ using namespace Gates;
+ std::vector basis{ Basis::PauliZ, Basis::PauliZ };
+ std::vector qubits{ 0,1 };
+ sim.Assert(basis, qubits, false);
+ sim.update_state();
+ // These require forcing the simulator to update the state for it to actually throw the exception
+
+ auto sim_assert = [&](std::vector const& basis, bool val) {
+ sim.Assert(basis, qubits, val);
+ sim.update_state();
+ };
+ sim.X(0);
+ sim.X(1);
+ sim_assert(basis, false);
+ REQUIRE(sim.Measure(basis, {0,1}) == false);
+
+ sim.H(0);
+ sim.H(1);
+ basis = {Basis::PauliX, Basis::PauliX};
+ sim_assert(basis, false);
+ REQUIRE(sim.Measure(basis, {0,1}) == false);
+
+ sim.S(0);
+ sim.S(1);
+ basis = {Basis::PauliY, Basis::PauliY};
+ sim_assert(basis, false);
+ REQUIRE(sim.Measure(basis, {0,1}) == false);
+
+
+ sim.X(0);
+ sim_assert(basis, true);
+ REQUIRE(sim.Measure(basis, {0,1}));
+ sim.AdjS(0);
+ sim.AdjS(1);
+
+ basis = {Basis::PauliX, Basis::PauliX};
+ sim_assert(basis, true);
+ REQUIRE(sim.Measure(basis, {0,1}));
+ sim.H(0);
+ sim.H(1);
+
+ basis = {Basis::PauliZ, Basis::PauliZ};
+ sim_assert(basis, true);
+ REQUIRE(sim.Measure(basis, {0,1}));
+ sim.X(1);
+ sim.X(0);
+
+ sim_assert(basis, true);
+ sim.X(0);
+ sim_assert(basis, false);
+}
+
+// Tests an assortment of assertions to both pass with decomposed measurements
+TEST_CASE("AssertTest3") {
+ const logical_qubit_id num_qubits = 32;
+ const qubit_label_type zero(0);
+ SparseSimulator sim = SparseSimulator(num_qubits);
+ using namespace Gates;
+ std::vector basis{ Basis::PauliZ, Basis::PauliZ };
+ std::vector qubits{ 0,1,2 };
+ sim.Assert(basis, qubits, false);
+ sim.update_state();
+ // These require forcing the simulator to update the state for it to actually throw the exception
+
+ auto sim_assert = [&](std::vector const& basis, bool val) {
+ sim.Assert(basis, qubits, val);
+ sim.update_state();
+ };
+ sim.X(0);
+ sim.X(1);
+ sim_assert(basis, false);
+ REQUIRE(sim.Measure(basis, {0,1}) == false);
+
+ sim.H(0);
+ sim.H(1);
+ basis = {Basis::PauliX, Basis::PauliX};
+ sim_assert(basis, false);
+ REQUIRE(sim.Measure(basis, {0,1}) == false);
+
+ sim.S(0);
+ sim.S(1);
+ basis = {Basis::PauliY, Basis::PauliY};
+ sim_assert(basis, false);
+ REQUIRE(sim.Measure(basis, {0,1}) == false);
+
+
+ sim.X(0);
+ sim_assert(basis, true);
+ REQUIRE(sim.Measure(basis, {0,1}));
+ sim.AdjS(0);
+ sim.AdjS(1);
+
+ basis = {Basis::PauliX, Basis::PauliX};
+ sim_assert(basis, true);
+ REQUIRE(sim.Measure(basis, {0,1}));
+ sim.H(0);
+ sim.H(1);
+
+ basis = {Basis::PauliZ, Basis::PauliZ};
+ sim_assert(basis, true);
+ REQUIRE(sim.Measure(basis, {0,1}));
+ sim.H(2);
+ sim.MCZ({2}, 0);
+ sim.MCZ({2}, 1);
+ sim.H(2);
+ REQUIRE(sim.M(2));
+ sim.X(2);
+ sim.MCX({0}, 2);
+ sim.MCX({1}, 2);
+ REQUIRE(sim.M(2));
+ sim.X(2);
+ sim.X(1);
+ sim.X(0);
+
+ sim_assert(basis, true);
+ sim.X(0);
+ sim_assert(basis, false);
+}
+
+// Tests an assortment of assertions on GHZ states
+TEST_CASE("AssertGHZTest") {
+ const logical_qubit_id num_qubits = 32;
+ const qubit_label_type zero(0);
+ SparseSimulator sim = SparseSimulator(num_qubits);
+ using namespace Gates;
+ std::vector basis(3, Basis::PauliX);
+ std::vector qubits{ 0,1,2 };
+ sim.H(0);
+ sim.MCX({ 0 }, 1);
+ sim.MCX({ 0 }, 2);
+
+ sim.Assert(basis, qubits, false);
+ REQUIRE_THROWS_AS(sim.Assert(basis, qubits, true), std::exception);
+ sim.Z(0);
+ sim.Assert(basis, qubits, true);
+ REQUIRE_THROWS_AS(sim.Assert(basis, qubits, false), std::exception);
+ sim.S(0);
+ basis = { Basis::PauliY, Basis::PauliY, Basis::PauliY };
+ sim.Assert(basis, qubits, false);
+ REQUIRE_THROWS_AS(sim.Assert(basis, qubits, true), std::exception);
+ sim.Z(0);
+ sim.Assert(basis, qubits, true);
+ REQUIRE_THROWS_AS(sim.Assert(basis, qubits, false), std::exception);
+ sim.probe("0");
+ sim.update_state();
+}
+
+// Basic test of quantum teleportation
+TEST_CASE("TeleportationTest")
+{
+ const logical_qubit_id num_qubits = 32;
+ const qubit_label_type zero(0);
+ for (double test_angle = 0; test_angle < 1.0; test_angle += 0.34) {
+ SparseSimulator sim = SparseSimulator(num_qubits);
+ sim.set_random_seed(12345);
+ std::vector qubits{ 0,1,2 };
+ sim.H(qubits[1]);
+ sim.MCX({ qubits[1] }, qubits[2]);
+
+ sim.R(Gates::Basis::PauliY, test_angle, 0);
+
+ sim.MCX({ qubits[0] }, qubits[1]);
+ sim.H(qubits[0]);
+ bool result0 = sim.M(qubits[0]);
+ bool result1 = sim.M(qubits[1]);
+ if (result1) {
+ sim.X(qubits[2]);
+ sim.X(qubits[1]);
+ }
+ if (result0) {
+ sim.Z(qubits[2]);
+ sim.X(qubits[0]);
+ }
+
+ amplitude teleported_qubit_0 = sim.probe("000");
+ amplitude teleported_qubit_1 = sim.probe("100");
+ REQUIRE((float)cos(test_angle / 2.0) == (float)teleported_qubit_0.real());
+ REQUIRE((float)0.0 == (float)teleported_qubit_0.imag());
+ REQUIRE((float)sin(test_angle / 2.0) == (float)teleported_qubit_1.real());
+ REQUIRE((float)0.0 == (float)teleported_qubit_1.imag());
+ }
+}
+
+
+// Tests that H gates properly cancel when executed
+TEST_CASE("HCancellationTest") {
+ const int n_qubits = 128;
+ SparseSimulator sim = SparseSimulator(n_qubits);
+ sim.set_random_seed(12345);
+ std::vector qubits(n_qubits);
+ std::generate(qubits.begin(), qubits.end(), [] { static int i{ 0 }; return i++; });
+ size_t buckets = 0;
+ // Will cause a huge memory problem if there is no cancellation
+ const int n_samples = 16;
+ for (int i = 0; i < n_qubits; i += n_samples) {
+ for (int ii = 0; ii < n_samples; ii++) {
+ sim.H(qubits[i + ii]);
+ }
+ sim.update_state();
+ for (int ii = n_samples - 1; ii >= 0; ii--) {
+ sim.H(qubits[i + ii]);
+ }
+ sim.update_state();
+ }
+}
+
+// Checks that X and Z gates commute with H
+TEST_CASE("HXZCommutationTest") {
+ const int n_qubits = 16;
+ SparseSimulator sim = SparseSimulator(n_qubits);
+ sim.set_random_seed(12345);
+ std::vector qubits(n_qubits);
+ std::generate(qubits.begin(), qubits.end(), [] { static int i{ 0 }; return i++; });
+ for (int i = 0; i < n_qubits; i++) {
+ sim.H(qubits[i]);
+ }
+ // Here it will actually just commute the X and Z through the H in the queue
+ // without actually executing anything
+ std::bitset one_state = 0;
+ for (int i = 0; i < n_qubits - 1; i += 2) {
+ sim.Z(qubits[i]);
+ sim.X(qubits[i + 1]);
+ one_state.set(i);
+ }
+ for (int i = n_qubits - 1; i >= 0; i--) {
+ sim.H(qubits[i]);
+ }
+ for (std::uint64_t i = 0; i < (std::uint64_t{1} << n_qubits); i++) {
+ amplitude state = sim.probe(std::bitset(i).to_string());
+ if (i == one_state.to_ulong()) {
+ assert_amplitude_equality(state, 1.0, 0.0);
+ }
+ else {
+ assert_amplitude_equality(state, 0.0, 0.0);
+ }
+ }
+}
diff --git a/src/Simulation/NativeSparseSimulator/TestHelpers.cpp b/src/Simulation/NativeSparseSimulator/TestHelpers.cpp
new file mode 100644
index 00000000000..cd74572231a
--- /dev/null
+++ b/src/Simulation/NativeSparseSimulator/TestHelpers.cpp
@@ -0,0 +1,149 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#include "SparseSimulator.h"
+#include
+#include
+#include
+
+using namespace Microsoft::Quantum::SPARSESIMULATOR;
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#define TEST_TOLERANCE 1.e-10
+
+
+namespace SparseSimulatorTestHelpers
+{
+
+ inline std::size_t make_mask(std::vector const& qs)
+ {
+ std::size_t mask = 0;
+ for (std::size_t q : qs)
+ mask = mask | (1ull << q);
+ return mask;
+ }
+ // power of square root of -1
+ inline amplitude iExp(int power)
+ {
+ using namespace std::literals::complex_literals;
+ int p = ((power % 4) + 8) % 4;
+ switch (p)
+ {
+ case 0:
+ return 1;
+ case 1:
+ return 1i;
+ case 2:
+ return -1;
+ case 3:
+ return -1i;
+ default:
+ return 0;
+ }
+ return 0;
+ }
+
+ inline bool isDiagonal(std::vector const& b)
+ {
+ for (auto x : b)
+ if (x == Gates::Basis::PauliX || x == Gates::Basis::PauliY) return false;
+ return true;
+ }
+
+ inline static void removeIdentities(std::vector& b, std::vector& qs)
+ {
+ size_t i = 0;
+ while (i != b.size())
+ {
+ if (b[i] == Gates::Basis::PauliI)
+ {
+ b.erase(b.begin() + i);
+ qs.erase(qs.begin() + i);
+ }
+ else
+ ++i;
+ }
+ }
+
+ // Taken from qsharp-runtime
+ void apply_exp(
+ std::vector& wfn,
+ std::vector b,
+ double phi,
+ std::vector qs)
+ {
+ removeIdentities(b, qs);
+ if (qs.size() == 0) { return; }
+ logical_qubit_id lowest = *std::min_element(qs.begin(), qs.end());
+
+ std::size_t offset = 1ull << lowest;
+ if (isDiagonal(b))
+ {
+ std::size_t mask = make_mask(qs);
+ amplitude phase = std::exp(amplitude(0., -phi));
+
+ for (std::intptr_t x = 0; x < static_cast(wfn.size()); x++)
+ wfn[x] *= (std::bitset<64>(x & mask).count() % 2 ? phase : std::conj(phase));
+ }
+ else {
+ std::size_t xy_bits = 0;
+ std::size_t yz_bits = 0;
+ int y_count = 0;
+ for (size_t i = 0; i < b.size(); ++i)
+ {
+ switch (b[i])
+ {
+ case Gates::Basis::PauliX:
+ xy_bits |= (1ull << qs[i]);
+ break;
+ case Gates::Basis::PauliY:
+ xy_bits |= (1ull << qs[i]);
+ yz_bits |= (1ull << qs[i]);
+ ++y_count;
+ break;
+ case Gates::Basis::PauliZ:
+ yz_bits |= (1ull << qs[i]);
+ break;
+ case Gates::Basis::PauliI:
+ break;
+ default:
+ break;
+ }
+ }
+
+ amplitude alpha = std::cos(phi);
+ amplitude beta = std::sin(phi) * iExp(3 * y_count + 1);
+ amplitude gamma = std::sin(phi) * iExp(y_count + 1);
+
+ for (std::intptr_t x = 0; x < static_cast(wfn.size()); x++)
+ {
+ std::intptr_t t = x ^ xy_bits;
+ if (x < t)
+ {
+ bool parity = std::bitset<64>(x & yz_bits).count() % 2;
+ auto a = wfn[x];
+ auto b = wfn[t];
+ wfn[x] = alpha * a + (parity ? -beta : beta) * b;
+ wfn[t] = alpha * b + (parity ? -gamma : gamma) * a;
+ }
+ }
+ }
+ }
+
+ // Assertions for equality of amplitude types
+ inline void assert_double_equality_with_tolerance(double value1, double value2) {
+ REQUIRE(value1 == Approx(value2).margin(TEST_TOLERANCE));
+ }
+
+ void assert_amplitude_equality(amplitude amp, double real, double imag) {
+ assert_double_equality_with_tolerance(real, amp.real());
+ assert_double_equality_with_tolerance(imag, amp.imag());
+ }
+
+ void assert_amplitude_equality(amplitude expected_amp, amplitude actual_amp) {
+ assert_amplitude_equality(actual_amp, expected_amp.real(), expected_amp.imag());
+ }
+}
\ No newline at end of file
diff --git a/src/Simulation/NativeSparseSimulator/TestHelpers.hpp b/src/Simulation/NativeSparseSimulator/TestHelpers.hpp
new file mode 100644
index 00000000000..424b5839fb5
--- /dev/null
+++ b/src/Simulation/NativeSparseSimulator/TestHelpers.hpp
@@ -0,0 +1,29 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#pragma once
+
+#include "SparseSimulator.h"
+#include
+#include
+
+using namespace Microsoft::Quantum::SPARSESIMULATOR;
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+namespace SparseSimulatorTestHelpers
+{
+ void apply_exp(
+ std::vector& wfn,
+ std::vector b,
+ double phi,
+ std::vector qs);
+
+ void assert_amplitude_equality(amplitude amp, double real, double imag);
+
+ void assert_amplitude_equality(amplitude expected_amp, amplitude actual_amp);
+
+ // Prepares some qubits, then checks whether various Pauli exponentials work
+}
diff --git a/src/Simulation/NativeSparseSimulator/basic_quantum_state.hpp b/src/Simulation/NativeSparseSimulator/basic_quantum_state.hpp
new file mode 100644
index 00000000000..882789ac2c4
--- /dev/null
+++ b/src/Simulation/NativeSparseSimulator/basic_quantum_state.hpp
@@ -0,0 +1,73 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#pragma once
+
+#include
+
+#include "types.h"
+#include "gates.h"
+
+namespace Microsoft::Quantum::SPARSESIMULATOR
+{
+
+// Virtual class for QuantumState
+// This is not templated, so it allows SparseSimulator types to avoid templates
+class BasicQuantumState
+{
+public:
+
+ virtual logical_qubit_id get_num_qubits() = 0;
+
+ virtual void DumpWavefunction(size_t indent = 0) = 0;
+
+ virtual void set_random_seed(std::mt19937::result_type seed = std::mt19937::default_seed) = 0;
+
+ virtual void set_precision(double new_precision) = 0;
+
+ virtual float get_load_factor() = 0;
+
+ virtual void set_load_factor(float new_load_factor) = 0;
+
+ virtual size_t get_wavefunction_size() = 0;
+
+ virtual void PauliCombination(std::vector const&, std::vector const&, amplitude, amplitude) = 0;
+ virtual void MCPauliCombination(std::vector const&, std::vector const&, std::vector const&, amplitude, amplitude) = 0;
+
+ virtual unsigned M(logical_qubit_id) = 0;
+
+ virtual void Reset(logical_qubit_id) = 0;
+
+
+
+ virtual void Assert(std::vector const&, std::vector const&, bool) = 0;
+
+ virtual double MeasurementProbability(std::vector const&, std::vector const&) = 0;
+ virtual unsigned Measure(std::vector const&, std::vector const&) = 0;
+
+
+ virtual amplitude probe(std::string const& label) = 0;
+
+ virtual bool dump_qubits(std::vector