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 const& qubits, std::functionconst&) = 0; + + virtual void dump_all(logical_qubit_id max_qubit_id, std::functionconst&) = 0; + + virtual void phase_and_permute(std::listconst &) = 0; + + virtual void R(Gates::Basis b, double phi, logical_qubit_id index) = 0; + virtual void MCR (std::vector const&, Gates::Basis, double, logical_qubit_id) = 0; + + virtual void H(logical_qubit_id index) = 0; + virtual void MCH(std::vector const& controls, logical_qubit_id index) = 0; + + virtual bool is_qubit_zero(logical_qubit_id) = 0; + virtual std::pair is_qubit_classical(logical_qubit_id) = 0; + + virtual universal_wavefunction get_universal_wavefunction() = 0; + + virtual std::function get_rng() = 0; + + virtual std::string Sample() = 0; +}; + +} // namespace Microsoft::Quantum::SPARSESIMULATOR diff --git a/src/Simulation/NativeSparseSimulator/build.ps1 b/src/Simulation/NativeSparseSimulator/build.ps1 new file mode 100644 index 00000000000..d852772b93e --- /dev/null +++ b/src/Simulation/NativeSparseSimulator/build.ps1 @@ -0,0 +1,73 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +#Requires -Version 6.0 + +Write-Host "##[info]Build NativeSparseSimulator for $Env:BUILD_CONFIGURATION" + +& (Join-Path $PSScriptRoot .. .. .. build set-env.ps1) +$FailureCommands = { + Write-Host "##vso[task.logissue type=error;] Failed to build NativeSparseSimulator. See errors above or below." + Pop-Location + Exit 1 +} + +$buildType = $Env:BUILD_CONFIGURATION +if ($buildType -eq "Release") { + $buildType = "RelWithDebInfo" +} + +# mkdir build +$BuildDir = (Join-Path $PSScriptRoot "build") +if (-not (Test-Path $BuildDir)) { + New-Item -Path $BuildDir -ItemType "directory" | Out-Null +} + +# pushd build +Push-Location $BuildDir + + $CmakeConfigCommand = "cmake -G Ninja -D CMAKE_VERBOSE_MAKEFILE:BOOL=ON -D CMAKE_BUILD_TYPE=$buildType -S .. " # Without `-G Ninja` the compiler chosen is always `cl.exe`. + + if (($IsMacOS) -or ((Test-Path Env:/AGENT_OS) -and ($Env:AGENT_OS.StartsWith("Darwin")))) { + Write-Host "On MacOS build using the default C/C++ compiler (should be AppleClang)" + } + else { + if (($IsLinux) -or ((Test-Path Env:AGENT_OS) -and ($Env:AGENT_OS.StartsWith("Lin")))) { + Write-Host "On Linux build using Clang" + $CC = "clang" + $CXX = "clang++" + #$clangTidy = "-DCMAKE_CXX_CLANG_TIDY=clang-tidy-11" + } + elseif (($IsWindows) -or ((Test-Path Env:AGENT_OS) -and ($Env:AGENT_OS.StartsWith("Win")))) { + Write-Host "On Windows build using Clang" + $CC = "clang.exe" + $CXX = "clang++.exe" + + if (!(Get-Command clang -ErrorAction SilentlyContinue) -and (choco find --idonly -l llvm) -contains "llvm") { + # LLVM was installed by Chocolatey, so add the install location to the path. + $env:PATH += ";$($env:SystemDrive)\Program Files\LLVM\bin" + } + + #if (Get-Command clang-tidy -ErrorAction SilentlyContinue) { + # # Only run clang-tidy if it's installed. This is because the package used by chocolatey on + # # the build pipeline doesn't include clang-tidy, so we allow skipping that there and let + # # the Linux build catch tidy issues. + # $clangTidy = "-DCMAKE_CXX_CLANG_TIDY=clang-tidy" + #} + } + else { + Write-Host "##vso[task.logissue type=error;] Failed to determine the platform." + $FailureCommands.Invoke() + } + + $CmakeConfigCommand += " -D CMAKE_C_COMPILER=$CC -D CMAKE_CXX_COMPILER=$CXX " + } + + # Generate the build scripts: + ( Invoke-Expression $CmakeConfigCommand ) || ( $FailureCommands.Invoke() ) + + # Invoke the build scripts: + ( cmake --build . ) || ( $FailureCommands.Invoke() ) + +# popd +Pop-Location diff --git a/src/Simulation/NativeSparseSimulator/capi.cpp b/src/Simulation/NativeSparseSimulator/capi.cpp new file mode 100644 index 00000000000..b1b7bdd70a0 --- /dev/null +++ b/src/Simulation/NativeSparseSimulator/capi.cpp @@ -0,0 +1,315 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Wrapper functions for basic C++ functions +// All have the same logic: use the sim_id argument as an +// index into the vector of simulators, +// then call a member function + +#include +#include +#include +#include + +#include "capi.hpp" +#include "SparseSimulator.h" +#include "factory.hpp" + +using namespace Microsoft::Quantum::SPARSESIMULATOR; + +std::string sample_string; + +extern "C" +{ + + MICROSOFT_QUANTUM_DECL simulator_id_type init_cpp(logical_qubit_id num_qubits) + { + return createSimulator(num_qubits); + } + + + MICROSOFT_QUANTUM_DECL void destroy_cpp(simulator_id_type sim_id) + { + destroySimulator(sim_id); + } + + MICROSOFT_QUANTUM_DECL void seed_cpp(simulator_id_type sim_id, _In_ unsigned int s){ + getSimulator(sim_id)->set_random_seed(s); + } + + MICROSOFT_QUANTUM_DECL void allocateQubit_cpp(simulator_id_type sim_id, logical_qubit_id q) + { + getSimulator(sim_id)->allocate_specific_qubit(q); + } + + MICROSOFT_QUANTUM_DECL bool releaseQubit_cpp(simulator_id_type sim_id, logical_qubit_id q) + { + return (getSimulator(sim_id)->release(q)); + } + + MICROSOFT_QUANTUM_DECL logical_qubit_id num_qubits_cpp(simulator_id_type sim_id) + { + return getSimulator(sim_id)->get_num_qubits(); + } + +// Generic single-qubit gate +#define FWDGATE1(G) \ + MICROSOFT_QUANTUM_DECL void G##_cpp(simulator_id_type sim_id, _In_ logical_qubit_id q) \ + { \ + getSimulator(sim_id)->G(q); \ + } +// Generic multi-qubit gate +#define FWDCSGATE1(G) \ + MICROSOFT_QUANTUM_DECL void MC##G##_cpp(simulator_id_type sim_id, _In_ int n, _In_reads_(n) logical_qubit_id* c, _In_ logical_qubit_id q) \ + { \ + \ + getSimulator(sim_id)->MC##G(std::vector(c, c + n), q); \ + } +#define FWD(G) FWDGATE1(G) + + // single-qubit gates + FWD(X) + FWD(Y) + FWD(Z) + FWD(H) + + FWD(S) + FWD(T) + FWD(AdjS) + FWD(AdjT) + +#define MFWD(G) FWDCSGATE1(G) + MFWD(H) + MFWD(X) + MFWD(Y) + MFWD(Z) + +#undef FWDGATE1 +#undef FWDGATE2 +#undef FWDGATE3 +#undef FWDCSGATE1 +#undef FWD + + + + MICROSOFT_QUANTUM_DECL void SWAP_cpp(simulator_id_type sim_id, _In_ logical_qubit_id q1, _In_ logical_qubit_id q2){ + getSimulator(sim_id)->SWAP(q1, q2); + } + + MICROSOFT_QUANTUM_DECL void MCSWAP_cpp(simulator_id_type sim_id, _In_ int n, _In_reads_(n) logical_qubit_id* c, _In_ logical_qubit_id q1, _In_ logical_qubit_id q2){ + getSimulator(sim_id)->CSWAP(std::vector(c, c + n), q1, q2); + } + + MICROSOFT_QUANTUM_DECL void MCApplyAnd_cpp(simulator_id_type sim_id,_In_ int length, _In_reads_(length) logical_qubit_id* controls, _In_ logical_qubit_id target){ + getSimulator(sim_id)->MCApplyAnd(std::vector(controls, controls + length), target); + } + + MICROSOFT_QUANTUM_DECL void MCAdjointApplyAnd_cpp(simulator_id_type sim_id,_In_ int length, _In_reads_(length) logical_qubit_id* controls, _In_ logical_qubit_id target){ + getSimulator(sim_id)->MCApplyAndAdj(std::vector(controls, controls + length), target); + } + + // rotations + + MICROSOFT_QUANTUM_DECL void R_cpp(simulator_id_type sim_id, _In_ int b, _In_ double phi, _In_ logical_qubit_id q) + { + getSimulator(sim_id)->R(static_cast(b), phi, q); + } + MICROSOFT_QUANTUM_DECL void Rfrac_cpp(simulator_id_type sim_id, _In_ int b, _In_ std::int64_t numerator, _In_ std::int64_t power, _In_ logical_qubit_id q) + { + getSimulator(sim_id)->RFrac(static_cast(b), numerator, power, q); + } + MICROSOFT_QUANTUM_DECL void R1_cpp(simulator_id_type sim_id,_In_ double phi, _In_ logical_qubit_id q) + { + getSimulator(sim_id)->R1(phi, q); + } + MICROSOFT_QUANTUM_DECL void R1frac_cpp(simulator_id_type sim_id, _In_ std::int64_t numerator, _In_ std::int64_t power, _In_ logical_qubit_id q) + { + getSimulator(sim_id)->R1Frac(numerator, power, q); + } + + // multi-controlled rotations + MICROSOFT_QUANTUM_DECL void MCR_cpp( + simulator_id_type sim_id, + _In_ int b, + _In_ double phi, + _In_ logical_qubit_id nc, + _In_reads_(nc) logical_qubit_id* c, + _In_ logical_qubit_id q) + { + std::vector cv(c, c + nc); + getSimulator(sim_id)->MCR(cv, static_cast(b), phi, q); + } + + MICROSOFT_QUANTUM_DECL void MCRFrac_cpp( + simulator_id_type sim_id, + _In_ int b, + _In_ std::int64_t numerator, + _In_ std::int64_t power, + _In_ logical_qubit_id nc, + _In_reads_(nc) logical_qubit_id* c, + _In_ logical_qubit_id q) + { + std::vector cv(c, c + nc); + getSimulator(sim_id)->MCRFrac(cv, static_cast(b), numerator, power, q); + } + + MICROSOFT_QUANTUM_DECL void MCR1_cpp( + simulator_id_type sim_id, + _In_ double phi, + _In_ int nc, + _In_reads_(nc) logical_qubit_id* c, + _In_ logical_qubit_id q) + { + std::vector cv(c, c + nc); + getSimulator(sim_id)->MCR1(cv, phi, q); + } + + MICROSOFT_QUANTUM_DECL void MCR1Frac_cpp( + simulator_id_type sim_id, + _In_ std::int64_t numerator, + _In_ std::int64_t power, + _In_ int nc, + _In_reads_(nc) logical_qubit_id* c, + _In_ logical_qubit_id q) + { + std::vector cv(c, c + nc); + getSimulator(sim_id)->MCR1Frac(cv, numerator, power, q); + } + + // Exponential of Pauli operators + MICROSOFT_QUANTUM_DECL void Exp_cpp( + simulator_id_type sim_id, + _In_ int n, + _In_reads_(n) int* b, + _In_ double phi, + _In_reads_(n) logical_qubit_id* q) + { + std::vector bv; + bv.reserve(n); + for (int i = 0; i < n; ++i) + bv.push_back(static_cast(*(b + i))); + std::vector qv(q, q + n); + getSimulator(sim_id)->Exp(bv, phi, qv); + } + + MICROSOFT_QUANTUM_DECL void MCExp_cpp( + simulator_id_type sim_id, + _In_ int nc, + _In_ int n, + _In_reads_(nc) logical_qubit_id* c, + _In_reads_(n) int* b, + _In_ double phi, + _In_reads_(n) logical_qubit_id* q) + { + std::vector bv; + bv.reserve(n); + for (int i = 0; i < n; ++i) + bv.push_back(static_cast(*(b + i))); + std::vector qv(q, q + n); + std::vector cv(c, c + nc); + getSimulator(sim_id)->MCExp(cv, bv, phi, qv); + } + + // measurements + MICROSOFT_QUANTUM_DECL unsigned M_cpp(simulator_id_type sim_id, _In_ logical_qubit_id q) + { + return getSimulator(sim_id)->M(q); + } + + MICROSOFT_QUANTUM_DECL void Reset_cpp(simulator_id_type sim_id, _In_ logical_qubit_id q){ + getSimulator(sim_id)->Reset(q); + } + + MICROSOFT_QUANTUM_DECL unsigned Measure_cpp( + simulator_id_type sim_id, + _In_ int n, + _In_reads_(n) int* b, + _In_reads_(n) logical_qubit_id* q) + { + std::vector bv; + bv.reserve(n); + for (int i = 0; i < n; ++i) + bv.push_back(static_cast(*(b + i))); + std::vector qv(q, q + n); + return getSimulator(sim_id)->Measure(bv, qv); + } + + // Extracts the probability of measuring a One result on qubits q with basis b + MICROSOFT_QUANTUM_DECL double JointEnsembleProbability_cpp( + simulator_id_type sim_id, + _In_ int n, + _In_reads_(n) int* b, + _In_reads_(n) logical_qubit_id* q) + { + std::vector bv; + bv.reserve(n); + for (int i = 0; i < n; ++i) + bv.push_back(static_cast(*(b + i))); + std::vector qv(q, q + n); + return getSimulator(sim_id)->MeasurementProbability(bv, qv); + } + + + // Iterates through the entire wavefunction and calls `callback` on every state in the superposition + // It will write the label of the state, in binary, from qubit 0 to `max_qubit_id`, into the char* pointer, then call `callback` + // with the real and complex values as the double arguments + MICROSOFT_QUANTUM_DECL void Dump_cpp(simulator_id_type sim_id, _In_ bool (*callback)(const char*, double, double)){ + return getSimulator(sim_id)->dump_all(callback); + } + + MICROSOFT_QUANTUM_DECL void ExtendedDump_cpp(simulator_id_type sim_id, _In_ bool (*callback)(const char*, double, double, void*), _In_ void* arg){ + return getSimulator(sim_id)->dump_all_ext(callback, arg); + } + + // Same as Dump_cpp, but only dumps the wavefunction on the qubits in `q`, ensuring they are separable from the rest of the state first + MICROSOFT_QUANTUM_DECL bool DumpQubits_cpp( + simulator_id_type sim_id, + _In_ int n, + _In_reads_(n) logical_qubit_id* q, + _In_ bool (*callback)(const char*, double, double)) + { + std::vector qs(q, q + n); + return getSimulator(sim_id)->dump_qubits(qs, callback); + } + + MICROSOFT_QUANTUM_DECL bool ExtendedDumpQubits_cpp( + simulator_id_type sim_id, + _In_ int n, + _In_reads_(n) logical_qubit_id* q, + _In_ bool (*callback)(const char*, double, double, void*), + _In_ void* arg) + { + std::vector qs(q, q + n); + return getSimulator(sim_id)->dump_qubits_ext(qs, callback, arg); + } + + // // dump the list of logical qubit ids to given callback + // MICROSOFT_QUANTUM_DECL void DumpIds(_In_ unsigned id, _In_ void (*callback)(unsigned)) + // { + // Microsoft::Quantum::Simulator::get(id)->dumpIds(callback); + // } + MICROSOFT_QUANTUM_DECL void QubitIds_cpp(simulator_id_type sim_id, void (*callback)(logical_qubit_id)) + { + getSimulator(sim_id)->dump_ids(callback); + } + + // Asserts that the gates in `b`, measured on the qubits in `q`, return `result` + MICROSOFT_QUANTUM_DECL bool Assert_cpp(simulator_id_type sim_id, _In_ int n, _In_reads_(n) int* b, _In_reads_(n) logical_qubit_id* q, bool result){ + std::vector bv; + bv.reserve(n); + for (int i = 0; i < n; ++i) + bv.push_back(static_cast(*(b + i))); + std::vector qv(q, q + n); + try { + getSimulator(sim_id)->Assert(bv, qv, result); + } + catch(const std::exception&){ + // C# will not call "Dispose" + // after this exception, so this cleans up manually + destroySimulator(sim_id); + return false; + } + return true; + } + +} diff --git a/src/Simulation/NativeSparseSimulator/capi.hpp b/src/Simulation/NativeSparseSimulator/capi.hpp new file mode 100644 index 00000000000..c2e2d3d8c28 --- /dev/null +++ b/src/Simulation/NativeSparseSimulator/capi.hpp @@ -0,0 +1,148 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include "types.h" + +// SAL only defined in windows. +#ifndef _In_ +#define _In_ +#define _In_reads_(n) +#endif +#ifndef _Out_ +#define _Out_ +#endif + +#ifdef BUILD_DLL +#define MICROSOFT_QUANTUM_DECL __declspec(dllexport) +#else +#define MICROSOFT_QUANTUM_DECL +#endif +#define MICROSOFT_QUANTUM_DECL_IMPORT __declspec(dllimport) + +using namespace Microsoft::Quantum::SPARSESIMULATOR; + +// All of these are called by the C# SparseSimulator class +extern "C" +{ + MICROSOFT_QUANTUM_DECL simulator_id_type init_cpp(logical_qubit_id num_qubits); + MICROSOFT_QUANTUM_DECL void destroy_cpp(simulator_id_type sim_id); + + MICROSOFT_QUANTUM_DECL void seed_cpp(simulator_id_type sim_id, _In_ unsigned int s); + // allocate and release + MICROSOFT_QUANTUM_DECL void allocateQubit_cpp(simulator_id_type sim_id, logical_qubit_id q); + MICROSOFT_QUANTUM_DECL bool releaseQubit_cpp(simulator_id_type sim_id, logical_qubit_id q); + MICROSOFT_QUANTUM_DECL logical_qubit_id num_qubits_cpp(simulator_id_type sim_id); + + // single-qubit gates + MICROSOFT_QUANTUM_DECL void X_cpp(simulator_id_type sim_id, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void Y_cpp(simulator_id_type sim_id, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void Z_cpp(simulator_id_type sim_id, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void H_cpp(simulator_id_type sim_id, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void S_cpp(simulator_id_type sim_id, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void T_cpp(simulator_id_type sim_id, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void AdjS_cpp(simulator_id_type sim_id, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void AdjT_cpp(simulator_id_type sim_id, _In_ logical_qubit_id q); + + + MICROSOFT_QUANTUM_DECL void MCX_cpp(simulator_id_type sim_id, _In_ int n, _In_reads_(n) logical_qubit_id* c, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void MCY_cpp(simulator_id_type sim_id, _In_ int n, _In_reads_(n) logical_qubit_id* c, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void MCZ_cpp(simulator_id_type sim_id, _In_ int n, _In_reads_(n) logical_qubit_id* c, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void MCH_cpp(simulator_id_type sim_id, _In_ int n, _In_reads_(n) logical_qubit_id* c, _In_ logical_qubit_id q); + + MICROSOFT_QUANTUM_DECL void SWAP_cpp(simulator_id_type sim_id, _In_ logical_qubit_id q1, _In_ logical_qubit_id q2); + MICROSOFT_QUANTUM_DECL void MCSWAP_cpp(simulator_id_type sim_id, _In_ int n, _In_reads_(n) logical_qubit_id* c, _In_ logical_qubit_id q1, _In_ logical_qubit_id q2); + MICROSOFT_QUANTUM_DECL void MCAnd_cpp(simulator_id_type sim_id,_In_ int length, _In_reads_(length) logical_qubit_id* controls, _In_ logical_qubit_id target); + MICROSOFT_QUANTUM_DECL void MCAdjointAnd_cpp(simulator_id_type sim_id,_In_ int length, _In_reads_(length) logical_qubit_id* controls, _In_ logical_qubit_id target); + + // rotations + MICROSOFT_QUANTUM_DECL void R_cpp(simulator_id_type sim_id, _In_ int b, _In_ double phi, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void Rfrac_cpp(simulator_id_type sim_id, _In_ int b, _In_ std::int64_t numerator, std::int64_t power, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void R1_cpp(simulator_id_type sim_id, _In_ double phi, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void R1frac_cpp(simulator_id_type sim_id, _In_ std::int64_t numerator, std::int64_t power, _In_ logical_qubit_id q); + + + + // multi-controlled rotations + MICROSOFT_QUANTUM_DECL void MCR_cpp( + simulator_id_type sim_id, + _In_ int b, + _In_ double phi, + _In_ logical_qubit_id n, + _In_reads_(n) logical_qubit_id* c, + _In_ logical_qubit_id q); + + MICROSOFT_QUANTUM_DECL void MCRFrac_cpp( + simulator_id_type sim_id, + _In_ int b, + _In_ std::int64_t numerator, + _In_ std::int64_t power, + _In_ logical_qubit_id nc, + _In_reads_(nc) logical_qubit_id* c, + _In_ logical_qubit_id q); + + MICROSOFT_QUANTUM_DECL void MCR1_cpp( + simulator_id_type sim_id, + _In_ double phi, + _In_ int n, + _In_reads_(n) logical_qubit_id* c, + _In_ logical_qubit_id q); + + MICROSOFT_QUANTUM_DECL void MCR1Frac_cpp( + simulator_id_type sim_id, + _In_ std::int64_t numerator, + _In_ std::int64_t power, + _In_ int nc, + _In_reads_(nc) logical_qubit_id* c, + _In_ logical_qubit_id q); + + // Exponential of Pauli operators + MICROSOFT_QUANTUM_DECL void Exp_cpp( + simulator_id_type sim_id, + _In_ int n, + _In_reads_(n) int* b, + _In_ double phi, + _In_reads_(n) logical_qubit_id* q); + MICROSOFT_QUANTUM_DECL void MCExp_cpp( + simulator_id_type sim_id, + _In_ int nc, + _In_ int n, + _In_reads_(nc) logical_qubit_id* c, + _In_reads_(n) int* b, + _In_ double phi, + _In_reads_(n) logical_qubit_id* q); + + // measurements + MICROSOFT_QUANTUM_DECL unsigned M_cpp(simulator_id_type sim_id, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void Reset_cpp(simulator_id_type sim_id, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL unsigned Measure_cpp( + simulator_id_type sim_id, + _In_ int n, + _In_reads_(n) int* b, + _In_reads_(n) logical_qubit_id* q); + + + MICROSOFT_QUANTUM_DECL double JointEnsembleProbability_cpp( + simulator_id_type sim_id, + _In_ int n, + _In_reads_(n) int* b, + _In_reads_(n) logical_qubit_id* q); + + MICROSOFT_QUANTUM_DECL void Dump_cpp(simulator_id_type sim_id, _In_ bool (*callback)(const char*, double, double)); + MICROSOFT_QUANTUM_DECL void ExtendedDump_cpp(simulator_id_type sim_id, _In_ bool (*callback)(const char*, double, double, void*), void*); + MICROSOFT_QUANTUM_DECL bool DumpQubits_cpp( + simulator_id_type sim_id, + _In_ int n, + _In_reads_(n) logical_qubit_id* q, + _In_ bool (*callback)(const char*, double, double)); + MICROSOFT_QUANTUM_DECL bool ExtendedDumpQubits_cpp( + simulator_id_type sim_id, + _In_ int n, + _In_reads_(n) logical_qubit_id* q, + _In_ bool (*callback)(const char*, double, double, void*), + _In_ void*); + MICROSOFT_QUANTUM_DECL void QubitIds_cpp(simulator_id_type sim_id, void (*callback)(logical_qubit_id)); + + MICROSOFT_QUANTUM_DECL bool Assert_cpp(simulator_id_type sim_id, _In_ int n, _In_reads_(n) int* b, _In_reads_(n) logical_qubit_id* q, bool result); +} diff --git a/src/Simulation/NativeSparseSimulator/factory.cpp b/src/Simulation/NativeSparseSimulator/factory.cpp new file mode 100644 index 00000000000..c0a660d2db4 --- /dev/null +++ b/src/Simulation/NativeSparseSimulator/factory.cpp @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Manages simulators in a vector of pointers to simulators + +#include +#include + +#include "factory.hpp" +#include "SparseSimulator.h" +#include "types.h" + +namespace Microsoft::Quantum::SPARSESIMULATOR +{ + +// Ensures exclusive access to _simulators, the vector of simulators +std::shared_mutex _mutex; +std::vector> _simulators; + +simulator_id_type createSimulator(logical_qubit_id num_qubits) +{ + if (num_qubits > MAX_QUBITS) + throw std::runtime_error("Max number of qubits is exceeded!"); + std::lock_guard lock(_mutex); + size_t emptySlot = -1; + for (auto const& s : _simulators) + { + if (s == nullptr) + { + emptySlot = &s - &_simulators[0]; + break; + } + } + if (emptySlot == -1) + { + _simulators.push_back(std::make_shared(num_qubits)); + emptySlot = _simulators.size() - 1; + } + else + { + _simulators[emptySlot] = std::make_shared(num_qubits); + } + + return static_cast(emptySlot); +} + +// Deletes a simulator in the vector +void destroySimulator(simulator_id_type id) +{ + std::lock_guard lock(_mutex); + + _simulators[id].reset(); // Set pointer to nullptr +} + +// Returns a simulator at some id (used for the C++/C# API) +std::shared_ptr& getSimulator(simulator_id_type id) +{ + std::shared_lock shared_lock(_mutex); + + return _simulators[id]; +} + +} // namespace Microsoft::Quantum::SPARSESIMULATOR diff --git a/src/Simulation/NativeSparseSimulator/factory.hpp b/src/Simulation/NativeSparseSimulator/factory.hpp new file mode 100644 index 00000000000..451acc2cca6 --- /dev/null +++ b/src/Simulation/NativeSparseSimulator/factory.hpp @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Manages simulators in a vector of pointers to simulators + +#pragma once + +#include "types.h" +#include "SparseSimulator.h" + +namespace Microsoft::Quantum::SPARSESIMULATOR +{ + +simulator_id_type createSimulator(logical_qubit_id); +void destroySimulator(simulator_id_type); + +std::shared_ptr& getSimulator(simulator_id_type); + +} // namespace Microsoft::Quantum::SPARSESIMULATOR diff --git a/src/Simulation/NativeSparseSimulator/gates.h b/src/Simulation/NativeSparseSimulator/gates.h new file mode 100644 index 00000000000..6f7ca0dc9a8 --- /dev/null +++ b/src/Simulation/NativeSparseSimulator/gates.h @@ -0,0 +1,257 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +namespace Microsoft::Quantum::SPARSESIMULATOR +{ + +// Used to track differnet kind of gates, +// mainly for timing summary data +enum class OP { + MCPhase, + Phase, + T, + AdjT, + S, + AdjS, + H, + MCH, + X, + MCX, + Y, + MCY, + Z, + MCZ, + M, + Measure, + Exp, + MCExp, + R1, + MCR1, + Rx, + Rz, + Ry, + MCRx, + MCRz, + MCRy, + SWAP, + MCSWAP, + PermuteSmall, + PermuteLarge, + Proj, + MCProj, + Allocate, + Release, + NUM_OPS // counts gate types; do not add gates after this! +}; + +// Strings for the names of the various gate types +const static std::string op_name(OP gate_type) { + switch (gate_type) { + case OP::MCPhase: + return "MCPhase"; + case OP::Phase: + return "Phase"; + case OP::T: + return "T"; + case OP::AdjT: + return "AdjT"; + case OP::S: + return "S"; + case OP::AdjS: + return "AdjS"; + case OP::H: + return "H"; + case OP::MCH: + return "MCH"; + case OP::X: + return "X"; + case OP::MCX: + return "MCX"; + case OP::Y: + return "Y"; + case OP::MCY: + return "MCY"; + case OP::Z: + return "Z"; + case OP::MCZ: + return "MCZ"; + case OP::M: + return "M"; + case OP::Measure: + return "Measure"; + case OP::Exp: + return "Exp"; + case OP::MCExp: + return "MCExp"; + case OP::R1: + return "R1"; + case OP::MCR1: + return "MCR1"; + case OP::Rx: + return "Rx"; + case OP::Rz: + return "Rz"; + case OP::Ry: + return "Ry"; + case OP::MCRx: + return "MCRx"; + case OP::MCRz: + return "MCRz"; + case OP::MCRy: + return "MCRy"; + case OP::SWAP: + return "SWAP"; + case OP::MCSWAP: + return "MCSWAP"; + case OP::PermuteSmall: + return "Perm_S"; + case OP::PermuteLarge: + return "Perm_L"; + case OP::Proj: + return "Project"; + case OP::MCProj: + return "MCProj"; + case OP::Allocate: + return "Alloc"; + case OP::Release: + return "Release"; + default: + return "Not a gate"; + } +} + +// Used in operation queues for phases/permutations +// Different constructors correspond to different +// kinds of gates, so some data is not initialized +// for certain types of gates +struct operation { + OP gate_type; + logical_qubit_id target; + operation(OP gate_type_arg, + logical_qubit_id target_arg) : + gate_type(gate_type_arg), + target(target_arg) {} + + std::vector controls; + operation(OP gate_type_arg, + logical_qubit_id target_arg, + std::vector controls_arg + ) : gate_type(gate_type_arg), + target(target_arg), + controls(controls_arg){} + + logical_qubit_id shift; + logical_qubit_id target_2; + //swap + operation (OP gate_type_arg, + logical_qubit_id target1_arg, + logical_qubit_id shift_arg, + logical_qubit_id target_2_arg + ) :gate_type(gate_type_arg), + target(target1_arg), + shift(shift_arg), + target_2(target_2_arg){} + //mcswap + operation(OP gate_type_arg, + logical_qubit_id target1_arg, + logical_qubit_id shift_arg, + std::vector controls_arg, + logical_qubit_id target_2_arg + ) : gate_type(gate_type_arg), + target(target1_arg), + shift(shift_arg), + controls(controls_arg), + target_2(target_2_arg){} + amplitude phase; + // Phase + operation(OP gate_type_arg, + logical_qubit_id target_arg, + amplitude phase_arg + ) :gate_type(gate_type_arg), + target(target_arg), + phase(phase_arg){} + // MCPhase + operation(OP gate_type_arg, + logical_qubit_id target_arg, + std::vector controls_arg, + amplitude phase_arg + ) : gate_type(gate_type_arg), + target(target_arg), + controls(controls_arg), + phase(phase_arg) + {} +}; + +// Also represents operations, but uses +// bitsets instead of vectors of qubit ids +// to save time/space +template +struct condensed_operation { + OP gate_type; + logical_qubit_id target; + condensed_operation(OP gate_type_arg, + logical_qubit_id target_arg) : + gate_type(gate_type_arg), + target(target_arg) + {} + + std::bitset controls; + condensed_operation(OP gate_type_arg, + logical_qubit_id target_arg, + std::bitset const& controls_arg + ) : gate_type(gate_type_arg), + target(target_arg), + controls(controls_arg){} + + logical_qubit_id target_2; + //swap + condensed_operation (OP gate_type_arg, + logical_qubit_id target1_arg, + logical_qubit_id target_2_arg + ) :gate_type(gate_type_arg), + target(target1_arg), + target_2(target_2_arg){} + //mcswap + condensed_operation(OP gate_type_arg, + logical_qubit_id target1_arg, + std::bitset const& controls_arg, + logical_qubit_id target_2_arg + ) : gate_type(gate_type_arg), + target(target1_arg), + controls(controls_arg), + target_2(target_2_arg){} + amplitude phase; + // Phase + condensed_operation(OP gate_type_arg, + logical_qubit_id target_arg, + amplitude phase_arg + ) :gate_type(gate_type_arg), + target(target_arg), + phase(phase_arg){} + // MCPhase + condensed_operation(OP gate_type_arg, + logical_qubit_id target_arg, + std::bitset const& controls_arg, + amplitude phase_arg + ) : gate_type(gate_type_arg), + target(target_arg), + controls(controls_arg), + phase(phase_arg) + {} +}; + +namespace Gates +{ + /// a type for runtime basis specification + enum class Basis + { + PauliI = 0, + PauliX = 1, + PauliY = 3, + PauliZ = 2 + }; +} // namespace Gates + +} // namespace Microsoft::Quantum::SPARSESIMULATOR diff --git a/src/Simulation/NativeSparseSimulator/quantum_state.hpp b/src/Simulation/NativeSparseSimulator/quantum_state.hpp new file mode 100644 index 00000000000..ba16738e297 --- /dev/null +++ b/src/Simulation/NativeSparseSimulator/quantum_state.hpp @@ -0,0 +1,1186 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "basic_quantum_state.hpp" + +#include "types.h" +#include "gates.h" + +using namespace std::literals::complex_literals; + +namespace Microsoft::Quantum::SPARSESIMULATOR +{ + +// power of square root of -1 +inline amplitude iExp(int power) +{ + 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; +} + +template +bool get_parity(std::bitset const& bitstring){ + return bitstring.count() % 2; +} + +// Compares two bitsets as through they were bitstrings +// Used to enforce an ordering on bitsets, though currently not referenced + +template +inline bool operator<(const std::bitset& lhs, const std::bitset& rhs) { + std::bitset mask = lhs ^ rhs; + std::bitset const ull_mask = std::bitset((unsigned long long) -1); + for (int i = static_cast(N - 8*sizeof(unsigned long long)); i > 0; i-= static_cast(8*sizeof(unsigned long long))){ + if (((mask >> i) & ull_mask).to_ullong() > 0){ + return ((lhs >> i) & ull_mask).to_ullong() < ((rhs >> i) & ull_mask).to_ullong(); + } + } + return ((lhs) & ull_mask).to_ullong() < ((rhs) & ull_mask).to_ullong(); +} + +// Transforms a vector of indices into a bitset where the indices indicate precisely +// which bits are non-zero +template +std::bitset get_mask(std::vector const& indices){ + std::bitset mask; + for (logical_qubit_id index : indices) { + mask.set(index); + } + return mask; +} + +template +class QuantumState : public BasicQuantumState +{ +public: + // Type for qubit labels, with a specific size built-in + using qubit_label = qubit_label_type; + + // Type of hash maps with the required labels + using wavefunction = abstract_wavefunction; + + QuantumState() { + _qubit_data = wavefunction(); + _qubit_data.max_load_factor(_load_factor); + // Create an initial all-zeros state + _qubit_data.emplace((logical_qubit_id)0, 1); + // Initialize randomness + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution dist(0, 1); + _rng = [gen, dist]() mutable { return dist(gen); }; + } + + // Copy data from an existing simulator + // This is used to move between different qubit sizes + // without needing a lot of templated functions + QuantumState(std::shared_ptr old_state) { + // Copy any needed data + _rng = old_state->get_rng(); + // Outputs the previous data with labels as strings + universal_wavefunction old_qubit_data = old_state->get_universal_wavefunction(); + _qubit_data = wavefunction(old_qubit_data.size()); + _load_factor = old_state->get_load_factor(); + _qubit_data.max_load_factor(_load_factor); + // Writes this into the current wavefunction as qubit_label types + for (auto current_state = old_qubit_data.begin(); current_state != old_qubit_data.end(); ++current_state) { + _qubit_data.emplace(qubit_label(current_state->first), current_state->second); + } + } + + logical_qubit_id get_num_qubits() { + return (logical_qubit_id)num_qubits; + } + + // Outputs all states and amplitudes to the console + void DumpWavefunction(size_t indent = 0){ + DumpWavefunction(_qubit_data, indent); + } + + // Outputs all states and amplitudes from an input wavefunction to the console + void DumpWavefunction(wavefunction &wfn, size_t indent = 0){ + std::string spacing(indent, ' '); + std::cout << spacing << "Wavefunction:\n"; + auto line_dump = [spacing](qubit_label label, amplitude val) -> bool { + std::cout << spacing << " " << label.to_string() << ": "; + std::cout << val.real(); + std::cout << (val.imag() < 0 ? " - " : " + ") << std::abs(val.imag()) << "i\n"; + return true; + }; + _DumpWavefunction_base(wfn, line_dump); + std::cout << spacing << "--end wavefunction\n"; + } + + + void set_random_seed(std::mt19937::result_type seed) { + std::mt19937 gen(seed); + std::uniform_real_distribution dist(0, 1); + _rng = [gen, dist]() mutable { return dist(gen); }; + } + + // Used to decide when an amplitude is close enough to 0 to discard + void set_precision(double new_precision) { + _precision = new_precision; + _rotation_precision = new_precision; + } + + // Load factor of the underlying hash map + float get_load_factor() { + return _load_factor; + } + + void set_load_factor(float new_load_factor) { + _load_factor = new_load_factor; + } + + // Returns the number of states in superposition + size_t get_wavefunction_size() { + return _qubit_data.size(); + } + + + + // Applies the operator id_coeff*I + pauli_coeff * P + // where P is the Pauli operators defined by axes applied to the qubits in qubits. + void PauliCombination(std::vector const& axes, std::vector const& qubits, amplitude id_coeff, amplitude pauli_coeff) { + // Bit-vectors indexing where gates of each type are applied + qubit_label XYs = 0; + qubit_label YZs = 0; + logical_qubit_id ycount = 0; + for (int i=0; i < axes.size(); i++){ + switch (axes[i]){ + case Gates::Basis::PauliY: + YZs.set(qubits[i]); + XYs.set(qubits[i]); + ycount++; + break; + case Gates::Basis::PauliX: + XYs.set(qubits[i]); + break; + case Gates::Basis::PauliZ: + YZs.set(qubits[i]); + break; + case Gates::Basis::PauliI: + break; + default: + throw std::runtime_error("Bad Pauli basis"); + } + } + + // All identity + if (XYs.none() && YZs.none()) { + return; + } + + // This branch handles purely Z Pauli vectors + // Purely Z has no addition, which would cause + // problems in the comparison in the next section + if (XYs.none()) { + // 0 terms get the sum of the coefficients + // 1 terms get the difference + pauli_coeff += id_coeff; // id_coeff + pauli_coeff + id_coeff *= 2; + id_coeff -= pauli_coeff; // id_coeff - pauli_coeff + + // To avoid saving states of zero amplitude, these if/else + // check for when one of the coefficients is + // close enough to zero to regard as zero + if (std::norm(pauli_coeff) > _rotation_precision){ + if (std::norm(id_coeff) > _rotation_precision){ + // If both coefficients are non-zero, we can just modify the state in-place + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + current_state->second *= (get_parity(current_state->first & YZs) ? id_coeff : pauli_coeff); + } + } else { + // If id_coeff = 0, then we make a new wavefunction and only add in those that will be multiplied + // by the pauli_coeff + wavefunction new_qubit_data = make_wavefunction(_qubit_data.size()); + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + if (!get_parity(current_state->first & YZs)){ + new_qubit_data.emplace(current_state->first, current_state->second * pauli_coeff); + } + } + _qubit_data = std::move(new_qubit_data); + } + } else { + // If pauli_coeff=0, don't add states multiplied by the pauli_coeff + wavefunction new_qubit_data = make_wavefunction(_qubit_data.size()); + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + if (get_parity(current_state->first & YZs)){ + new_qubit_data.emplace(current_state->first, current_state->second * id_coeff); + } + } + _qubit_data = std::move(new_qubit_data); + } + } else { // There are some X or Y gates + + // Each Y Pauli adds a global phase of i + switch (ycount % 4) { + case 1: + pauli_coeff *= 1i; + break; + case 2: + pauli_coeff *= -1; + break; + case 3: + pauli_coeff *= -1i; + break; + default: + break; + } + // When both the state and flipped state are in superposition, when adding the contribution of + // the flipped state, we add phase depending on the 1s in the flipped state + // This phase would be the parity of (flipped_state->first ^ YZs) + // However, we know that flipped_state->first = current_state->first ^ YXs + // So the parity of the flipped state will be the parity of the current state, plus + // the parity of YZs & YXs, i.e., the parity of the number of Ys + // Since this is constant for all states, we compute it once here and save it + // Then we only compute the parity of the current state + amplitude pauli_coeff_alt = ycount % 2 ? -pauli_coeff : pauli_coeff; + wavefunction new_qubit_data = make_wavefunction(_qubit_data.size() * 2); + amplitude new_state; + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + auto alt_state = _qubit_data.find(current_state->first ^ XYs); + if (alt_state == _qubit_data.end()) { // no matching value + new_qubit_data.emplace(current_state->first, current_state->second * id_coeff); + new_qubit_data.emplace(current_state->first ^ XYs, current_state->second * (get_parity(current_state->first & YZs) ? -pauli_coeff : pauli_coeff)); + } + else if (current_state->first < alt_state->first) { + // Each Y and Z gate adds a phase (since Y=iXZ) + bool parity = get_parity(current_state->first & YZs); + new_state = current_state->second * id_coeff + alt_state->second * (parity ? -pauli_coeff_alt : pauli_coeff_alt); + if (std::norm(new_state) > _rotation_precision) { + new_qubit_data.emplace(current_state->first, new_state); + } + + new_state = alt_state->second * id_coeff + current_state->second * (parity ? -pauli_coeff : pauli_coeff); + if (std::norm(new_state) > _rotation_precision) { + new_qubit_data.emplace(alt_state->first, new_state); + } + } + } + _qubit_data = std::move(new_qubit_data); + } + } + + // Applies the operator id_coeff*I + pauli_coeff * P + // where P is the Pauli operators defined by axes applied to the qubits in qubits. + // Controlled version + void MCPauliCombination(std::vector const& controls, std::vector const& axes, std::vector const& qubits, amplitude id_coeff, amplitude pauli_coeff) { + // Bit-vectors indexing where gates of each type are applied + qubit_label cmask = _get_mask(controls); + qubit_label XYs = 0; + qubit_label YZs = 0; + logical_qubit_id ycount = 0; + // Used for comparing pairs + logical_qubit_id any_xy = -1; + for (int i=0; i < axes.size(); i++){ + switch (axes[i]){ + case Gates::Basis::PauliY: + YZs.set(qubits[i]); + XYs.set(qubits[i]); + ycount++; + any_xy = qubits[i]; + break; + case Gates::Basis::PauliX: + XYs.set(qubits[i]); + any_xy = qubits[i]; + break; + case Gates::Basis::PauliZ: + YZs.set(qubits[i]); + break; + case Gates::Basis::PauliI: + break; + default: + throw std::runtime_error("Bad Pauli basis"); + } + } + + // This branch handles purely Z Pauli vectors + // Purely Z has no addition, which would cause + // problems in the comparison in the next section + if (XYs.none()) { + // 0 terms get the sum of the coefficients + // 1 terms get the difference + pauli_coeff += id_coeff; // <- id_coeff + pauli_coeff + id_coeff *= 2; + id_coeff -= pauli_coeff; // <- id_coeff - pauli_coeff + + // To avoid saving states of zero amplitude, these if/else + // check for when one of the coefficients is + // close enough to zero to regard as zero + if (std::norm(pauli_coeff) > _rotation_precision){ + if (std::norm(id_coeff) > _rotation_precision){ + // If both coefficients are non-zero, we can just modify the state in-place + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + if ((current_state->first & cmask)==cmask) { + current_state->second *= (get_parity(current_state->first & YZs) ? id_coeff : pauli_coeff); + } + } + } else { + // If id_coeff = 0, then we make a new wavefunction and only add in those that will be multiplied + // by the pauli_coeff + wavefunction new_qubit_data = make_wavefunction(_qubit_data.size()); + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + if (!get_parity(current_state->first & YZs) && (current_state->first & cmask)==cmask){ + new_qubit_data.emplace(current_state->first, current_state->second * pauli_coeff); + } + } + _qubit_data = std::move(new_qubit_data); + } + } else { + // If pauli_coeff=0, don't add states multiplied by the pauli_coeff + wavefunction new_qubit_data = make_wavefunction(_qubit_data.size()); + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + if (get_parity(current_state->first & YZs) && (current_state->first & cmask)==cmask){ + new_qubit_data.emplace(current_state->first, current_state->second * id_coeff); + } + } + _qubit_data = std::move(new_qubit_data); + } + } else { // There are some X or Y gates + // Each Y Pauli adds a global phase of i + switch (ycount % 4) { + case 1: + pauli_coeff *= 1i; + break; + case 2: + pauli_coeff *= -1; + break; + case 3: + pauli_coeff *= -1i; + break; + default: + break; + } + // When both the state and flipped state are in superposition, when adding the contribution of + // the flipped state, we add phase depending on the 1s in the flipped state + // This phase would be the parity of (flipped_state->first ^ YZs) + // However, we know that flipped_state->first = current_state->first ^ YXs + // So the parity of the flipped state will be the parity of the current state, plus + // the parity of YZs & YXs, i.e., the parity of the number of Ys + // Since this is constant for all states, we compute it once here and save it + // Then we only compute the parity of the current state + amplitude pauli_coeff_alt = ycount % 2 ? -pauli_coeff : pauli_coeff; + wavefunction new_qubit_data = make_wavefunction(_qubit_data.size() * 2); + amplitude new_state; + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + if ((current_state->first & cmask)==cmask) { + auto alt_state = _qubit_data.find(current_state->first ^ XYs); + if (alt_state == _qubit_data.end()) { // no matching value + new_qubit_data.emplace(current_state->first, current_state->second * id_coeff); + new_qubit_data.emplace(current_state->first ^ XYs, current_state->second * (get_parity(current_state->first & YZs) ? -pauli_coeff : pauli_coeff)); + } + else if (current_state->first < alt_state->first) { //current_state->first[any_xy]){// + // Each Y and Z gate adds a phase (since Y=iXZ) + bool parity = get_parity(current_state->first & YZs); + new_state = current_state->second * id_coeff + alt_state->second * (parity ? -pauli_coeff_alt : pauli_coeff_alt); + if (std::norm(new_state) > _rotation_precision) { + new_qubit_data.emplace(current_state->first, new_state); + } + + new_state = alt_state->second * id_coeff + current_state->second * (parity ? -pauli_coeff : pauli_coeff); + if (std::norm(new_state) > _rotation_precision) { + new_qubit_data.emplace(alt_state->first, new_state); + } + } + } else { + new_qubit_data.emplace(current_state->first, current_state->second); + } + } + _qubit_data = std::move(new_qubit_data); + } + } + + + unsigned M(logical_qubit_id target) { + double zero_probability = 0.0; + double one_probability = 0.0; + + // Writes data into a ones or zeros wavefunction + // as it adds up probability + // Once it's finished, it picks one randomly, normalizes + // then keeps that one as the new wavefunction + wavefunction ones = make_wavefunction(_qubit_data.size()/2); + wavefunction zeros = make_wavefunction(_qubit_data.size()/2); + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + double square_amplitude = std::norm(current_state->second); + if (current_state->first[target]) { + one_probability += square_amplitude; + ones.emplace(current_state->first, current_state->second); + } + else { + zero_probability += square_amplitude; + zeros.emplace(current_state->first, current_state->second); + } + } + // Randomly select + unsigned result = (_rng() <= one_probability) ? 1 : 0; + + wavefunction &new_qubit_data = (result == 1) ? ones : zeros; + // Create a new, normalized state + double normalizer = 1.0/std::sqrt((result == 1) ? one_probability : zero_probability); + for (auto current_state = (new_qubit_data).begin(); current_state != (new_qubit_data).end(); ++current_state) { + current_state->second *= normalizer; + } + _qubit_data = std::move(new_qubit_data); + + return result; + } + + void Reset(logical_qubit_id target) { + double zero_probability = 0.0; + double one_probability = 0.0; + + // Writes data into a ones or zeros wavefunction + // as it adds up probability + // Once it's finished, it picks one randomly, normalizes + // then keeps that one as the new wavefunction + + // Used to set the qubit to 0 in the measured result + qubit_label new_mask = qubit_label(); + new_mask.set(); // sets all bits to 1 + new_mask.set(target, 0); + wavefunction ones = make_wavefunction(_qubit_data.size()/2); + wavefunction zeros = make_wavefunction(_qubit_data.size()/2); + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + double square_amplitude = std::norm(current_state->second); + if (current_state->first[target]) { + one_probability += square_amplitude; + ones.emplace(current_state->first & new_mask, current_state->second); + } + else { + zero_probability += square_amplitude; + zeros.emplace(current_state->first & new_mask, current_state->second); + } + } + // Randomly select + bool result = (_rng() <= one_probability); + + wavefunction &new_qubit_data = result ? ones : zeros; + // Create a new, normalized state + double normalizer = 1.0/std::sqrt((result) ? one_probability : zero_probability); + for (auto current_state = (new_qubit_data).begin(); current_state != (new_qubit_data).end(); ++current_state) { + current_state->second *= normalizer; + } + _qubit_data = std::move(new_qubit_data); + } + + + // Samples a state from the superposition with probably proportion to + // the amplitude, returning a string of the bits of that state. + // Unlike measurement, this does not modify the state + std::string Sample() { + double probability = _rng(); + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + double square_amplitude = std::norm(current_state->second); + probability -= square_amplitude; + if (probability <= 0){ + return current_state->first.to_string(); + } + } + return _qubit_data.begin()->first.to_string(); + } + + void Assert(std::vector const& axes, std::vector const& qubits, bool result) { + // Bit-vectors indexing where gates of each type are applied + qubit_label XYs = 0; + qubit_label YZs = 0; + logical_qubit_id ycount = 0; + for (int i=0; i < axes.size(); i++){ + switch (axes[i]){ + case Gates::Basis::PauliY: + YZs.set(qubits[i]); + XYs.set(qubits[i]); + ycount++; + break; + case Gates::Basis::PauliX: + XYs.set(qubits[i]); + break; + case Gates::Basis::PauliZ: + YZs.set(qubits[i]); + break; + case Gates::Basis::PauliI: + break; + default: + throw std::runtime_error("Bad Pauli basis"); + } + } + + amplitude phaseShift = result ? -1 : 1; + // Each Y Pauli adds a global phase of i + switch (ycount % 4) { + case 1: + phaseShift *= 1i; + break; + case 2: + phaseShift *= -1; + break; + case 3: + phaseShift *= -1i; + break; + default: + break; + } + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + // The amplitude of current_state should always be non-zero, if the data structure + // is properly maintained. Since the flipped state should match the amplitude (up to phase), + // if the flipped state is not in _qubit_data, it implicitly has an ampltude of 0.0, which + // is *not* a match, so the assertion should fail. + auto flipped_state = _qubit_data.find(current_state->first ^ XYs); + if (flipped_state == _qubit_data.end() || + std::norm(flipped_state->second - current_state->second * (get_parity(current_state->first & YZs) ? -phaseShift : phaseShift)) > _precision) { + qubit_label label = current_state->first; + amplitude val = current_state->second; + std::cout << "Problematic state: " << label << "\n"; + std::cout << "Expected " << val * (get_parity(current_state->first & YZs) ? -phaseShift : phaseShift); + std::cout << ", got " << (flipped_state == _qubit_data.end() ? 0.0 : flipped_state->second) << "\n"; + std::cout << "Wavefunction size: " << _qubit_data.size() << "\n"; + throw std::runtime_error("Not an eigenstate"); + } + } + } + + // 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) { + // Bit-vectors indexing where gates of each type are applied + qubit_label XYs = 0; + qubit_label YZs = 0; + logical_qubit_id ycount = 0; + for (int i=0; i < axes.size(); i++){ + switch (axes[i]){ + case Gates::Basis::PauliY: + YZs.set(qubits[i]); + XYs.set(qubits[i]); + ycount++; + break; + case Gates::Basis::PauliX: + XYs.set(qubits[i]); + break; + case Gates::Basis::PauliZ: + YZs.set(qubits[i]); + break; + case Gates::Basis::PauliI: + break; + default: + throw std::runtime_error("Bad Pauli basis"); + } + } + amplitude phaseShift = 1; + + // Each Y Pauli adds a global phase of i + switch (ycount % 4) { + case 1: + phaseShift *= amplitude(0, 1); + break; + case 2: + phaseShift *= -1; + break; + case 3: + phaseShift *= amplitude(0, -1); + break; + default: + break; + } + // Let P be the pauli operation, |psi> the state + // projection = + + // _qubit_data represents |psi> as sum_x a_x |x>, + // where all |x> are orthonormal. Thus, the projection + // will be the product of a_x and a_P(x), where P|x>=|P(x)> + // Thus, for each |x>, we compute P(x) and look for that state + // If there is a match, we add the product of their coefficients + // to the projection, times a phase dependent on how many Ys and Zs match + // the 1 bits of x + amplitude projection = 0.0; + auto flipped_state = _qubit_data.end(); + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + flipped_state = _qubit_data.find(current_state->first ^ XYs); // no match returns _qubit_data.end() + projection += current_state->second * (flipped_state == _qubit_data.end() ? 0 : std::conj(flipped_state->second)) * (get_parity(current_state->first & YZs) ? -phaseShift : phaseShift); + } + // The projector onto the -1 eigenspace (a result of "One") is 0.5 * (I - P) + // So = 0.5 - 0.5* + // should always be real so this only takes the real part + return 0.5 - 0.5 * projection.real(); + } + + unsigned Measure(std::vector const& axes, std::vector const& qubits){ + // Find a probability to get a specific result + double probability = MeasurementProbability(axes, qubits); + bool result = _rng() <= probability; + if (!result) + probability = 1-probability; + probability = std::sqrt(probability); + // This step executes immediately so that we reduce the number of states in superposition + PauliCombination(axes, qubits, 0.5/probability, (result ? -0.5 : 0.5)/probability); + return result; + } + + + // Probe the amplitude of a single basis state + amplitude probe(qubit_label const& label) { + auto qubit = _qubit_data.find(label); + // States not in the hash map are assumed to be 0 + if (qubit == _qubit_data.end()) { + return amplitude(0.0, 0.0); + } + else { + return qubit->second; + } + } + + amplitude probe(std::string const& label) { + qubit_label bit_label = qubit_label(label); + return probe(bit_label); + } + + using 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) { + // Create two wavefunctions + // check if they are tensor products + wavefunction dump_wfn; + wavefunction leftover_wfn; + if (!_split_wavefunction(_get_mask(qubits), dump_wfn, leftover_wfn)){ + return false; + } else { + _DumpWavefunction_base(dump_wfn, [qubits, callback](qubit_label label, amplitude val) -> bool { + std::string masked(qubits.size(),'0'); + for (std::size_t i = 0; i < qubits.size(); ++i) + masked[i] = label[qubits[i]] ? '1' : '0'; + return callback(masked.c_str(), val.real(), val.imag()); + }); + return true; + } + } + + // Dumps all the states in superposition via a callback function + void dump_all(logical_qubit_id max_qubit_id, callback_t const& callback) { + _DumpWavefunction_base(_qubit_data, [max_qubit_id, callback](qubit_label label, amplitude val) -> bool { + return callback(label.to_string().substr(num_qubits-1-max_qubit_id).c_str(), val.real(), val.imag()); + }); + } + + // Execute a queue of phase/permutation gates + void phase_and_permute(std::list const &operation_list){ + if (operation_list.size()==0){return;} + + // Condense the list into a memory-efficient vector with qubit labels + // TODO: Is this still needed after multithreading is removed? Can we work off operation_list? + std::vector operation_vector; + operation_vector.reserve(operation_list.size()); + + for (auto op : operation_list){ + switch (op.gate_type) { + case OP::X: + case OP::Y: + case OP::Z: + operation_vector.push_back(internal_operation(op.gate_type, op.target)); + break; + case OP::MCX: + case OP::MCY: + operation_vector.push_back(internal_operation(op.gate_type, op.target, _get_mask(op.controls))); + break; + case OP::MCZ: + operation_vector.push_back(internal_operation(op.gate_type, op.target, _get_mask(op.controls).set(op.target))); + break; + case OP::Phase: + operation_vector.push_back(internal_operation(op.gate_type, op.target, op.phase)); + break; + case OP::MCPhase: + operation_vector.push_back(internal_operation(op.gate_type, op.target, _get_mask(op.controls).set(op.target), op.phase)); + break; + case OP::SWAP: + operation_vector.push_back(internal_operation(op.gate_type, op.target, op.target_2)); + break; + case OP::MCSWAP: + operation_vector.push_back(internal_operation(op.gate_type, op.target, _get_mask(op.controls), op.target_2)); + break; + default: + throw std::runtime_error("Unsupported operation"); + break; + } + } + + wavefunction new_qubit_data = make_wavefunction(); + + // Iterates through and applies all operations + for (auto current_state = _qubit_data.begin(); current_state != _qubit_data.end(); ++current_state){ + qubit_label label = current_state->first; + amplitude val = current_state->second; + // Iterate through vector of operations and apply each gate + for (int i=0; i < operation_vector.size(); i++) { + auto &op = operation_vector[i]; + switch (op.gate_type) { + case OP::X: + label.flip(op.target); + break; + case OP::MCX: + if ((op.controls & label) == op.controls){ + label.flip(op.target); + } + break; + case OP::Y: + label.flip(op.target); + val *= (label[op.target]) ? 1i : -1i; + break; + case OP::MCY: + if ((op.controls & label) == op.controls){ + label.flip(op.target); + val *= (label[op.target]) ? 1i : -1i; + } + break; + case OP::Z: + val *= (label[op.target] ? -1 : 1); + break; + case OP::MCZ: + val *= ((op.controls & label) == op.controls) ? -1 : 1; + break; + case OP::Phase: + val *= label[op.target] ? op.phase : 1; + break; + case OP::MCPhase: + val *= ((op.controls & label) == op.controls) ? op.phase : 1; + break; + case OP::SWAP: + if (label[op.target] != label[op.target_2]){ + label.flip(op.target); + label.flip(op.target_2); + } + break; + case OP::MCSWAP: + if (((label & op.controls) == op.controls) && (label[op.target] != label[op.target_2])){ + label.flip(op.target); + label.flip(op.target_2); + } + break; + default: + throw std::runtime_error("Unsupported operation"); + break; + } + } + // Insert the new state into the new wavefunction + new_qubit_data.emplace(label, val); + } + _qubit_data = std::move(new_qubit_data); + operation_vector.clear(); + } + + void R(Gates::Basis b, double phi, logical_qubit_id index){ + // Z rotation can be done in-place + if (b == Gates::Basis::PauliZ) { + amplitude exp_0 = std::polar(1.0, -0.5*phi); + amplitude exp_1 = std::polar(1.0, 0.5*phi); + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + current_state->second *= current_state->first[index] ? exp_1 : exp_0; + } + } + else if (b == Gates::Basis::PauliX || b == Gates::Basis::PauliY) { + amplitude M00 = std::cos(phi / 2.0); + amplitude M01 = -1i*std::sin(0.5 * phi) * (b == Gates::Basis::PauliY ? -1i : 1); + if (std::norm(M00) <= _rotation_precision){ + // This is just a Y or X gate + phase_and_permute(std::list{operation(b==Gates::Basis::PauliY ? OP::Y : OP::X, index)}); + return; + } else if (std::norm(M01) <= _rotation_precision){ + // just an identity + return; + } + + amplitude M10 = M01 * (b == Gates::Basis::PauliY ? -1. : 1.); + // Holds the amplitude of the new state to make it easier to check if it's non-zero + amplitude new_state; + qubit_label flip(0); + flip.set(index); + wavefunction new_qubit_data = make_wavefunction(); + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + auto flipped_state = _qubit_data.find(current_state->first ^ flip); + if (flipped_state == _qubit_data.end()) { // no matching value + if (current_state->first[index]) {// 1 on that qubit + new_qubit_data.emplace(current_state->first ^ flip, current_state->second * M01); + new_qubit_data.emplace(current_state->first, current_state->second * M00); + } + else { + new_qubit_data.emplace(current_state->first, current_state->second * M00); + new_qubit_data.emplace(current_state->first ^ flip, current_state->second * M10); + } + } + // Add up the two values, only when reaching the zero value + else if (!(current_state->first[index])) { + new_state = current_state->second * M00 + flipped_state->second * M01; // zero state + if (std::norm(new_state) > _rotation_precision) { + new_qubit_data.emplace(current_state->first, new_state); + } + new_state = current_state->second * M10 + flipped_state->second * M00; // one state + if (std::norm(new_state) > _rotation_precision) { + new_qubit_data.emplace(flipped_state->first, new_state); + } + } + } + _qubit_data = std::move(new_qubit_data); + } + } + + // Multi-controlled rotation + void MCR (std::vector const& controls, Gates::Basis b, double phi, logical_qubit_id target) { + qubit_label checks = _get_mask(controls); + // A Z-rotation can be done without recreating the wavefunction + if (b == Gates::Basis::PauliZ) { + amplitude exp_0 = std::polar(1.0, -0.5*phi); + amplitude exp_1 = std::polar(1.0, 0.5*phi); + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + if ((current_state->first & checks)==checks){ + current_state->second *= current_state->first[target] ? exp_1 : exp_0; + } + } + } + // X or Y requires a new wavefunction + else if (b == Gates::Basis::PauliX || b == Gates::Basis::PauliY) { + amplitude M00 = std::cos(0.5 * phi); + amplitude M01 = -1i*std::sin(0.5 * phi) * (b == Gates::Basis::PauliY ? -1i : 1); + amplitude M10 = (b == Gates::Basis::PauliY ? -1.0 : 1.0) * M01; + + if (std::norm(M00) <= _rotation_precision){ + // This is just an MCY or MCX gate, but with a phase + // So we need to preprocess with a multi-controlled phase + if (b==Gates::Basis::PauliY){ + amplitude phase = -1i*std::sin(0.5 * phi); + phase_and_permute(std::list{ + operation(OP::MCPhase, controls[0], controls, phase), + operation(OP::MCY, target, controls) + }); + } else { + amplitude phase = -1i*std::sin(0.5 * phi); + phase_and_permute(std::list{ + operation(OP::MCPhase, controls[0], controls, phase), + operation(OP::MCX, target, controls) + }); + } + return; + } else if (std::norm(M01) <= _rotation_precision){ + phase_and_permute(std::list{operation(OP::MCPhase, controls[0], controls, M00)}); + return; + } + + amplitude new_state; + qubit_label flip(0); + flip.set(target); + wavefunction new_qubit_data = make_wavefunction(); + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + if ((current_state->first & checks)==checks){ + auto flipped_state = _qubit_data.find(current_state->first ^ flip); + if (flipped_state == _qubit_data.end()) { // no matching value + if (current_state->first[target]) {// 1 on that qubit + new_qubit_data.emplace(current_state->first ^ flip, current_state->second * M01); + new_qubit_data.emplace(current_state->first, current_state->second * M00); + } + else { + new_qubit_data.emplace(current_state->first, current_state->second * M00); + new_qubit_data.emplace(current_state->first ^ flip, current_state->second * M10); + } + } + // Add up the two values, only when reaching the zero val + else if (!(current_state->first[target])) { + new_state = current_state->second * M00 + flipped_state->second * M01; // zero state + if (std::norm(new_state) > _rotation_precision) { + new_qubit_data.emplace(current_state->first, new_state); + } + new_state = current_state->second * M10 + flipped_state->second * M00; // one state + if (std::norm(new_state) > _rotation_precision) { + new_qubit_data.emplace(flipped_state->first, new_state); + } + } + } else { + new_qubit_data.emplace(current_state->first, current_state->second); + } + } + _qubit_data = std::move(new_qubit_data); + } + } + + void H(logical_qubit_id index){ + // Initialize a new wavefunction, which will store the modified state + // We initialize with twice as much space as the current one, + // as this is the worst case result of an H gate + wavefunction new_qubit_data = make_wavefunction(_qubit_data.size() * 2); + // This label makes it easier to find associated labels (where the index is flipped) + qubit_label flip(0); + flip.set(index); + // The amplitude for the new state + amplitude new_state; + // Loops over all states in the wavefunction _qubit_data + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + // An iterator pointing to the state labelled by the flip + auto flipped_state = _qubit_data.find(current_state->first ^ flip); + // Checks for whether it needs to add amplitudes from matching states + // or create two new states + if (flipped_state == _qubit_data.end()) { // no matching value + new_qubit_data.emplace(current_state->first & (~flip), current_state->second * _normalizer); + // Flip the value if the second bit, depending on whether the original had 1 or 0 + new_qubit_data.emplace(current_state->first | flip, current_state->second * (current_state->first[index] ? -_normalizer : _normalizer)); + } + else if (!(current_state->first[index])) { + new_state = current_state->second + flipped_state->second; // zero state + if (std::norm(new_state) > _rotation_precision) { + new_qubit_data.emplace(current_state->first, new_state * _normalizer); + } + + new_state = current_state->second - flipped_state->second; // one state + if (std::norm(new_state) > _rotation_precision) { + new_qubit_data.emplace(current_state->first | flip, new_state * _normalizer); + } + } + } + // Moves the new data back into the old one (thus destroying + // the old data) + _qubit_data = std::move(new_qubit_data); + } + + void MCH(std::vector const& controls, logical_qubit_id index){ + wavefunction new_qubit_data = make_wavefunction(_qubit_data.size() * 2); + qubit_label flip(0); + flip.set(index); + amplitude new_state; + qubit_label checks = _get_mask(controls); + for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + if ((checks & current_state->first) == checks){ + auto flipped_state = _qubit_data.find(current_state->first ^ flip); + if (flipped_state == _qubit_data.end()) { // no matching value + new_qubit_data.emplace(current_state->first & (~flip), current_state->second * _normalizer); + // Flip the value if the second bit, depending on whether the original had 1 or 0 + new_qubit_data.emplace(current_state->first | flip, current_state->second * (current_state->first[index] ? -_normalizer : _normalizer)); + } + else if (!(current_state->first[index])) { + new_state = current_state->second + flipped_state->second; // zero state + if (std::norm(new_state) > _rotation_precision) { + new_qubit_data.emplace(current_state->first, new_state * _normalizer); + } + + new_state = current_state->second - flipped_state->second; // one state + if (std::norm(new_state) > _rotation_precision) { + new_qubit_data.emplace(current_state->first | flip, new_state * _normalizer); + } + } + } else { + new_qubit_data.emplace(current_state->first, current_state->second); + } + } + _qubit_data = std::move(new_qubit_data); + } + + // Checks whether a qubit is 0 in all states in the superposition + bool is_qubit_zero(logical_qubit_id target){ + for (auto current_state = _qubit_data.begin(); current_state != _qubit_data.end(); ++current_state){ + if (current_state->first[target] && std::norm(current_state->second) > _precision) { + return false; + } + } + return true; + } + + // Checks whether a qubit is classical + // result.first is true iff it is classical + // result.second holds its classical value if result.first == true + std::pair is_qubit_classical(logical_qubit_id target){ + bool value_found = false; + bool value = false; + for (auto current_state = _qubit_data.begin(); current_state != _qubit_data.end(); ++current_state){ + if (std::norm(current_state->second) > _precision) { + if (!value_found) { + value_found = true; + value = current_state->first[target]; + } + else if (value != current_state->first[target]) + return std::make_pair(false, false); + } + } + return std::make_pair(true, value); + } + + // Creates a new wavefunction hash map indexed by strings + // Not intended for computations but as a way to transfer between + // simulators templated with different numbers of qubits + universal_wavefunction get_universal_wavefunction() { + universal_wavefunction universal_qubit_data = universal_wavefunction(_qubit_data.bucket_count()); + for (auto current_state = _qubit_data.begin(); current_state != _qubit_data.end(); ++current_state) { + universal_qubit_data.emplace(current_state->first.to_string(), current_state->second); + } + return universal_qubit_data; + } + + // Returns the rng from this simulator + std::function get_rng() { return _rng; } + +private: + // Internal type used to store operations with bitsets + // instead of vectors of qubit ids + using internal_operation = condensed_operation; + + // Hash table of the wavefunction + wavefunction _qubit_data; + + // Internal random numbers + std::function _rng; + + // Threshold to assert that something is zero when asserting it is 0 + double _precision = 1e-11; + // Threshold at which something is zero when + // deciding whether to add it into the superposition + double _rotation_precision = 1e-11; + + // Normalizer for H and T gates (1/sqrt(2) as an amplitude) + const amplitude _normalizer = amplitude(1.0, 0.0) / std::sqrt(2.0); + + // Used when allocating new wavefunctions + float _load_factor = 0.9375; + + // Makes a wavefunction that is preallocated to the right size + // and has the correct load factor + wavefunction make_wavefunction() { + wavefunction data((size_t)(_qubit_data.size() / _load_factor)); + data.max_load_factor(_load_factor); + return data; + } + wavefunction make_wavefunction(uint64_t n_states) { + wavefunction data((size_t)(n_states / _load_factor)); + data.max_load_factor(_load_factor); + return data; + } + + // Creates a qubit_label as a bit mask from a set of indices + qubit_label _get_mask(std::vector const& indices){ + return get_mask(indices); + } + + // Split the wavefunction if separable, otherwise return false + // Idea is that if we have a_bb|b1>|b2> as the first state, then for + // any other state a_xx|x1>|x2>, we must also have a_xb|x1>|b2> and a_bx|b1>|x2> + // in superposition. + // Also, the coefficients must separate as a_bb=c_b*d_b and a_xx = c_x*d_x, implying + // that a_xb = c_x*d_b and a_bx = c_b * d_x, and thus we can check this holds if + // a_bb*a_xx = a_bx * a_xb. + // If this holds: we write (a_xx/a_bx)|x1> into the first wavefunction and (a_xx/a_xb)|x2> + // into the second. + bool _split_wavefunction(qubit_label const& first_mask, wavefunction &wfn1, wavefunction &wfn2){ + qubit_label second_mask = ~first_mask; + // Guesses size + wfn1 = wavefunction((int)std::sqrt(_qubit_data.size())); + wfn2 = wavefunction((int)std::sqrt(_qubit_data.size())); + // base_label_1 = b1 and base_label_2 = b2 in the notation above + auto base_state = _qubit_data.begin(); + for (; base_state != _qubit_data.end() && std::norm(base_state->second) <= _precision; ++base_state); + if (base_state == _qubit_data.end()) + throw std::runtime_error("Invalid state: All amplitudes are ~ zero."); + qubit_label base_label_1 = base_state->first & first_mask; + qubit_label base_label_2 = base_state->first & second_mask; + // base_val = a_bb + amplitude base_val = base_state->second; + double norm1 = 1., norm2 = 1.; + wfn1[base_label_1] = 1.; + wfn2[base_label_2] = 1.; + std::size_t num_nonzero_states = 1; + // From here on, base_state is |x1>|x2> + ++base_state; + for (; base_state != _qubit_data.end(); ++base_state){ + qubit_label label_1 = base_state->first & first_mask; + qubit_label label_2 = base_state->first & second_mask; + // first_state is |x1>|b2>, second_state is |b1>|x2> + auto first_state = _qubit_data.find(label_1 | base_label_2); + auto second_state = _qubit_data.find(base_label_1 | label_2); + // Ensures that both |x1>|b2> and |b1>|x2> are in the superposition + if (first_state == _qubit_data.end() || second_state == _qubit_data.end()){ + // state does not exist + // therefore states are entangled + return false; + } else { // label with base label exists + // Checks that a_bba_xx = a_xb*a_bx + if (std::norm(first_state->second * second_state->second - base_val * base_state->second) > _precision){ + return false; + } else { + if (std::norm(base_state->second) <= _precision) + continue; + num_nonzero_states++; + // Not entangled so far, save the two states, with amplitudes a_xx/a_bx and a_xx/a_xb, respectively + if (wfn1.find(label_1) == wfn1.end()) { + auto amp1 = base_state->second / second_state->second; + auto nrm = std::norm(amp1); + if (nrm > _precision) + wfn1[label_1] = amp1; + norm1 += nrm; + } + if (wfn2.find(label_2) == wfn2.end()) { + auto amp2 = base_state->second / first_state->second; + auto nrm = std::norm(amp2); + if (nrm > _precision) + wfn2[label_2] = amp2; + norm2 += nrm; + } + } + } + } + if (num_nonzero_states != wfn1.size()*wfn2.size()) + return false; + // Normalize + for (auto current_state = wfn1.begin(); current_state != wfn1.end(); ++current_state){ + current_state->second *= 1./std::sqrt(norm1); + } + for (auto current_state = wfn2.begin(); current_state != wfn2.end(); ++current_state){ + current_state->second *= 1./std::sqrt(norm2); + } + return true; + } + + // Iterates through a wavefunction and calls the output function on each value + // It first sorts the labels before outputting + void _DumpWavefunction_base(wavefunction &wfn, std::function output){ + if (wfn.size() == 0){ return; } + using pair_t = std::pair; + std::vector sortedByLabels; + sortedByLabels.reserve(wfn.size()); + for (auto current_state = (wfn).begin(); current_state != (wfn).end(); ++current_state) { + sortedByLabels.push_back(*current_state); + } + std::sort( + sortedByLabels.begin(), + sortedByLabels.end(), + [](const pair_t& lhs, const pair_t& rhs){return lhs.first < rhs.first;}); + amplitude val; + for (pair_t entry : sortedByLabels){ + if(!output(entry.first, entry.second)) + break; + } + } + +}; + + +} // namespace Microsoft::Quantum::SPARSESIMULATOR diff --git a/src/Simulation/NativeSparseSimulator/test.ps1 b/src/Simulation/NativeSparseSimulator/test.ps1 new file mode 100644 index 00000000000..34f55ab06b6 --- /dev/null +++ b/src/Simulation/NativeSparseSimulator/test.ps1 @@ -0,0 +1,17 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Write-Host "##[info]Test Native Sparse Simulator" +Push-Location (Join-Path $PSScriptRoot "build") + +ctest -C "$Env:BUILD_CONFIGURATION" --verbose + +$RetVal = $LastExitCode + +if ($RetVal -ne 0) { + Write-Host "##vso[task.logissue type=error;]Failed to test Native Sparse Simulator" +} + +Pop-Location + +Exit $RetVal diff --git a/src/Simulation/NativeSparseSimulator/types.h b/src/Simulation/NativeSparseSimulator/types.h new file mode 100644 index 00000000000..03590d974c7 --- /dev/null +++ b/src/Simulation/NativeSparseSimulator/types.h @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include +#include +#include +#include + +namespace Microsoft::Quantum::SPARSESIMULATOR +{ + +// Runtime may use multiple simulators so a simulator id is used to identify the simulator needed. +using simulator_id_type = std::uint32_t; + +// Logical qubit id is visible to the clients and is immutable during the lifetime of the qubit. +using logical_qubit_id = std::uint32_t; + +using real_type = double; + +using amplitude = std::complex; + +template +using qubit_label_type = std::bitset; + +// Wavefunctions are hash maps of some key (std::bitset or a string) +template +using abstract_wavefunction = std::unordered_map; + +// Wavefunctions with strings as keys are "universal" in that they do not depend +// on the total number of qubits +using universal_wavefunction = abstract_wavefunction; + +} // namespace Microsoft::Quantum::SPARSESIMULATOR diff --git a/src/Simulation/QCTraceSimulator.Tests/Tests.Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime.csproj b/src/Simulation/QCTraceSimulator.Tests/Tests.Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime.csproj index 1758c237de4..fc50851f28d 100644 --- a/src/Simulation/QCTraceSimulator.Tests/Tests.Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime.csproj +++ b/src/Simulation/QCTraceSimulator.Tests/Tests.Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime.csproj @@ -4,7 +4,7 @@ - netcoreapp3.1 + net6.0 false false false diff --git a/src/Simulation/QSharpFoundation/Convert/Convert.qs b/src/Simulation/QSharpFoundation/Convert/Convert.qs index fb501496b73..ac3ad6dd85f 100644 --- a/src/Simulation/QSharpFoundation/Convert/Convert.qs +++ b/src/Simulation/QSharpFoundation/Convert/Convert.qs @@ -85,10 +85,19 @@ namespace Microsoft.Quantum.Convert { /// The 0 element of the array is the least significant bit of the big integer. /// # Remarks /// See [C# BigInteger constructor](https://docs.microsoft.com/dotnet/api/system.numerics.biginteger.-ctor?view=netframework-4.7.2#System_Numerics_BigInteger__ctor_System_Int64_) for more details. + /// Note that the Boolean array is padded of the right with `false` values to a length that is a multiple of 8, + /// and then treated as a little-endian notation of a positive or negative number following two's complement semantics. + /// + /// # Example + /// ```qsharp + /// let bi1 = BoolArrayAsBigInt([true, false, true]); // Padded to 10100000 -> 5 + /// let bi2 = BoolArrayAsBigInt([false, false, false, false, false, false, false, true]); // Not padded -> -128 + /// ``` function BoolArrayAsBigInt(a : Bool[]) : BigInt { body intrinsic; } + /// # Summary /// Converts a given boolean value to an equivalent string representation. /// diff --git a/src/Simulation/QSharpFoundation/Diagnostics/Facts.qs b/src/Simulation/QSharpFoundation/Diagnostics/Facts.qs index e2c9ddca222..15adc86250a 100644 --- a/src/Simulation/QSharpFoundation/Diagnostics/Facts.qs +++ b/src/Simulation/QSharpFoundation/Diagnostics/Facts.qs @@ -5,20 +5,20 @@ namespace Microsoft.Quantum.Diagnostics { open Microsoft.Quantum.Math; /// # Summary - /// Declares that a classical condition is true. + /// Checks whether a classical condition is true, and throws an exception if it is not. /// /// # Input /// ## actual - /// The condition to be declared. + /// The condition to be checked. /// ## message - /// Failure message string to be printed in the case that the classical + /// Failure message string to be used as an error message if the classical /// condition is false. /// /// # See Also /// - Microsoft.Quantum.Diagnostics.Contradiction /// /// # Example - /// The following Q# snippet will fail: + /// The following Q# snippet will throw an exception: /// ```qsharp /// Fact(false, "Expected true."); /// ``` @@ -27,13 +27,13 @@ namespace Microsoft.Quantum.Diagnostics { } /// # Summary - /// Declares that a classical condition is false. + /// Checks whether a classical condition is false, and throws an exception if it is not. /// /// # Input /// ## actual - /// The condition to be declared. + /// The condition to be checked. /// ## message - /// Failure message string to be printed in the case that the classical + /// Failure message string to be used as an error message if the classical /// condition is true. /// /// # See Also @@ -50,18 +50,18 @@ namespace Microsoft.Quantum.Diagnostics { } /// # Summary - /// Declares that a given floating-point value represents a finite - /// number, failing when this is not the case. + /// Checks whether a given floating-point value represents a finite + /// number, and throws an exception if this is not the case. /// /// # Input /// ## d /// The floating-point value that is to be checked. /// ## message - /// Failure message to be printed in the case that `d` is either + /// Failure message to be used as an error message if `d` is either /// not finite, or not a number. /// /// # Example - /// The following Q# code will fail when run: + /// The following Q# code will throw an exception: /// ```qsharp /// FiniteFact(NaN(), "NaN is not a finite number."); /// ``` diff --git a/src/Simulation/QSharpFoundation/Diagnostics/Properties/NamespaceInfo.qs b/src/Simulation/QSharpFoundation/Diagnostics/Properties/NamespaceInfo.qs deleted file mode 100644 index 60ad51909db..00000000000 --- a/src/Simulation/QSharpFoundation/Diagnostics/Properties/NamespaceInfo.qs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -/// # Summary -/// This namespace contains functions and operations useful for diagnostic -/// purposes, including assert operations and claim functions. -namespace Microsoft.Quantum.Diagnostics {} diff --git a/src/Simulation/QSharpFoundation/Random/Properties/NamespaceInfo.qs b/src/Simulation/QSharpFoundation/Random/Properties/NamespaceInfo.qs new file mode 100644 index 00000000000..74e514199e8 --- /dev/null +++ b/src/Simulation/QSharpFoundation/Random/Properties/NamespaceInfo.qs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/// # Summary +/// This namespace contains functions, operations, and UDTs +/// for working with random values and probability distributions. +namespace Microsoft.Quantum.Random {} diff --git a/src/Simulation/Simulators.Tests/Circuits/Arrays.qs b/src/Simulation/Simulators.Tests/Circuits/Arrays.qs index 6af632a5451..6b39cd5006d 100644 --- a/src/Simulation/Simulators.Tests/Circuits/Arrays.qs +++ b/src/Simulation/Simulators.Tests/Circuits/Arrays.qs @@ -11,7 +11,7 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits { let m = mapper(source[i]); set result = result w/ i <- m; } - + return result; } @@ -28,12 +28,14 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits { } @Test("QuantumSimulator") + @Test("SparseSimulator") function CreateArrayWithPositiveSize() : Unit { let xs = [true, size = 3]; AssertEqual([true, true, true], xs); } @Test("QuantumSimulator") + @Test("SparseSimulator") function CreateArrayWithZeroSize() : Unit { let xs = [true, size = 0]; AssertEqual(0, Length(xs)); @@ -44,6 +46,7 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits { } @Test("QuantumSimulator") + @Test("SparseSimulator") function CreateArrayWithSizeExpression() : Unit { let n = 2; let xs = [7, size = n + 1]; @@ -51,6 +54,7 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits { } @Test("QuantumSimulator") + @Test("SparseSimulator") function CreateArrayWithValueExpression() : Unit { let x = "foo"; let xs = [x + "bar", size = 3]; @@ -58,6 +62,7 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits { } @Test("QuantumSimulator") + @Test("SparseSimulator") function SizedArrayShouldIncrementArrayItemRefCount() : Unit { mutable item = [1]; let items = [item, size = 2]; @@ -68,6 +73,7 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits { } @Test("QuantumSimulator") + @Test("SparseSimulator") function ArrayOfArraysShouldCopyOnUpdate() : Unit { mutable items = [[1], size = 2]; set items w/= 0 <- items[0] w/ 0 <- 2; diff --git a/src/Simulation/Simulators.Tests/Circuits/Default.qs b/src/Simulation/Simulators.Tests/Circuits/Default.qs index 55e1853dfc7..5b39af6ffa5 100644 --- a/src/Simulation/Simulators.Tests/Circuits/Default.qs +++ b/src/Simulation/Simulators.Tests/Circuits/Default.qs @@ -3,52 +3,62 @@ open Microsoft.Quantum.Simulation.Simulators.Tests.Circuits; @Test("QuantumSimulator") + @Test("SparseSimulator") function DefaultUnit() : Unit { AssertEqual((), Default()); } @Test("QuantumSimulator") + @Test("SparseSimulator") function DefaultInt() : Unit { AssertEqual(0, Default()); } @Test("QuantumSimulator") + @Test("SparseSimulator") function DefaultBigInt() : Unit { AssertEqual(0L, Default()); } @Test("QuantumSimulator") + @Test("SparseSimulator") function DefaultDouble() : Unit { AssertEqual(0.0, Default()); } @Test("QuantumSimulator") + @Test("SparseSimulator") function DefaultBool() : Unit { AssertEqual(false, Default()); } @Test("QuantumSimulator") + @Test("SparseSimulator") function DefaultString() : Unit { AssertEqual("", Default()); } @Test("QuantumSimulator") + @Test("SparseSimulator") function DefaultQubit() : Unit { // Creating a default qubit (without using it) should succeed. let _ = Default(); } @Test("QuantumSimulator") + @Test("SparseSimulator") function DefaultPauli() : Unit { AssertEqual(PauliI, Default()); } @Test("QuantumSimulator") + @Test("SparseSimulator") function DefaultResult() : Unit { AssertEqual(Zero, Default()); } @Test("QuantumSimulator") + @Test("SparseSimulator") function DefaultRange() : Unit { let range = Default(); AssertEqual(1, RangeStart(range)); @@ -57,17 +67,20 @@ } @Test("QuantumSimulator") + @Test("SparseSimulator") function DefaultCallable() : Unit { // Creating a default callable (without calling it) should succeed. let _ = Default<(Unit -> Unit)>(); } @Test("QuantumSimulator") + @Test("SparseSimulator") function DefaultArray() : Unit { AssertEqual(new Unit[0], Default()); } @Test("QuantumSimulator") + @Test("SparseSimulator") function DefaultTuple() : Unit { AssertEqual((false, 0), Default<(Bool, Int)>()); AssertEqual((0, Zero, ""), Default<(Int, Result, String)>()); @@ -83,6 +96,7 @@ newtype IntResultString = (Int, Result, String); @Test("QuantumSimulator") + @Test("SparseSimulator") function DefaultUserDefinedType() : Unit { AssertEqual(BoolInt(false, 0), Default()); AssertEqual(IntResultString(0, Zero, ""), Default()); diff --git a/src/Simulation/Simulators.Tests/Circuits/ExpTest.qs b/src/Simulation/Simulators.Tests/Circuits/ExpTest.qs index 430bbd0ccdd..e45a9e70adb 100644 --- a/src/Simulation/Simulators.Tests/Circuits/ExpTest.qs +++ b/src/Simulation/Simulators.Tests/Circuits/ExpTest.qs @@ -4,7 +4,8 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits { open Microsoft.Quantum.Intrinsic; - + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Math; // At some point, this was causing the simulator to crash. @@ -26,7 +27,31 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits { ResetAll(qubits); } } - + + /// Verify that Exp behaves as expected by using it in two decompositions: SWAP and CNOT. + operation VerifyExpUsingDecompositions() : Unit { + AssertOperationsEqualReferenced(2, (qs => SwapFromExp(qs[0], qs[1])), (qs => SWAP(qs[0], qs[1]))); + AssertOperationsEqualReferenced(2, (qs => CnotFromExp(qs[0], qs[1])), (qs => CNOT(qs[0], qs[1]))); + } + + /// This decomposition only holds if the magnitude of the angle used in the Exp rotation is correct. + operation SwapFromExp(q0 : Qubit, q1 : Qubit) : Unit is Adj { + let qs = [q0, q1]; + let theta = PI() / 4.0; + Exp([PauliX, PauliX], theta, qs); + Exp([PauliY, PauliY], theta, qs); + Exp([PauliZ, PauliZ], theta, qs); + } + + /// This decomposition only holds if the magnitude of the angle used in Exp is correct and if the + /// sign convention between Rx, Rz, and Exp is consistent. + operation CnotFromExp(q0 : Qubit, q1 : Qubit) : Unit is Adj { + let qs = [q0, q1]; + let theta = PI() / 4.0; + Rx(-2.0 * theta, q1); + Rz(-2.0 * theta, q0); + Adjoint Exp([PauliZ, PauliX], theta, qs); + } } diff --git a/src/Simulation/Simulators.Tests/Circuits/MemberNames.qs b/src/Simulation/Simulators.Tests/Circuits/MemberNames.qs index 4f7a5a1e583..269f2f8a9d6 100644 --- a/src/Simulation/Simulators.Tests/Circuits/MemberNames.qs +++ b/src/Simulation/Simulators.Tests/Circuits/MemberNames.qs @@ -24,6 +24,7 @@ operation Info() : Unit { } @Test("QuantumSimulator") + @Test("SparseSimulator") operation SupportsReservedOperationNames() : Unit { Body(); AdjointBody(); @@ -35,12 +36,14 @@ } @Test("QuantumSimulator") + @Test("SparseSimulator") operation SupportsConfusingQualifiedNames() : Unit { FooBar.Baz(); Foo.Bar.Baz(); } @Test("QuantumSimulator") + @Test("SparseSimulator") operation SupportsReservedNamedItems() : Unit { let foo = Foo(7); AssertEqual(7, foo::Foo); @@ -54,6 +57,7 @@ } @Test("QuantumSimulator") + @Test("SparseSimulator") operation AvoidsOperationPropertyShadowing1() : Unit { using (q = Qubit()) { let MicrosoftQuantumIntrinsicX = Z; @@ -69,6 +73,7 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits.MemberNames2 { open Microsoft.Quantum.Simulation.Simulators.Tests.Circuits; @Test("QuantumSimulator") + @Test("SparseSimulator") operation AvoidsOperationPropertyShadowing2() : Unit { using (q = Qubit()) { let X = Intrinsic.Z; diff --git a/src/Simulation/Simulators.Tests/Circuits/NativeOperations.cs b/src/Simulation/Simulators.Tests/Circuits/NativeOperations.cs index f0e63cc9dc1..27ba4bc3222 100644 --- a/src/Simulation/Simulators.Tests/Circuits/NativeOperations.cs +++ b/src/Simulation/Simulators.Tests/Circuits/NativeOperations.cs @@ -34,19 +34,14 @@ public class Native : DefaultBody { public Native(IOperationFactory m) : base(m) { } - public override Func __Body__ => (arg) => - { - if (this.__Factory__ is QuantumSimulator) - { - return "Simulator"; - } - else if (this.__Factory__ is ToffoliSimulator) + public override Func __Body__ => (arg) => + this.__Factory__ switch { - return "Toffoli"; - } - - return base.__Body__(arg); - }; + QuantumSimulator _ => "Simulator", + ToffoliSimulator _ => "Toffoli", + SparseSimulator _ => "SparseSimulator", + _ => base.__Body__(arg) + }; } } diff --git a/src/Simulation/Simulators.Tests/Circuits/TeleportTest.qs b/src/Simulation/Simulators.Tests/Circuits/TeleportTest.qs index 829ee22d483..4a93240633e 100644 --- a/src/Simulation/Simulators.Tests/Circuits/TeleportTest.qs +++ b/src/Simulation/Simulators.Tests/Circuits/TeleportTest.qs @@ -6,6 +6,7 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits { open Microsoft.Quantum.Diagnostics; @Test("QuantumSimulator") + @Test("SparseSimulator") // TODO: Disabled until we have a noise model for Rz. // @Test("Microsoft.Quantum.Experimental.OpenSystemsSimulator") operation TestTeleport() : Unit { diff --git a/src/Simulation/Simulators.Tests/Circuits/UnitTests.qs b/src/Simulation/Simulators.Tests/Circuits/UnitTests.qs index 1b68749a058..706b3343623 100644 --- a/src/Simulation/Simulators.Tests/Circuits/UnitTests.qs +++ b/src/Simulation/Simulators.Tests/Circuits/UnitTests.qs @@ -5,21 +5,23 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits { open Microsoft.Quantum.Intrinsic; open Microsoft.Quantum.Diagnostics; - + @Test("QuantumSimulator") @Test("ToffoliSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation QSharpUnitTest() : Unit { Message("Worked!"); } - + @Test("QuantumSimulator") @Test("Microsoft.Quantum.Simulation.Simulators.Tests.TrivialSimulator") @Test("Microsoft.Quantum.Simulation.Simulators.Tests.ModifiedTrivialSimulator") @Test("Microsoft.Quantum.Simulation.Simulators.Tests.UnitTests.TrivialSimulator") + @Test("SparseSimulator") operation ArbitraryUnitTestTarget() : Unit { Message("Worked!"); } - + } diff --git a/src/Simulation/Simulators.Tests/CoreTests.cs b/src/Simulation/Simulators.Tests/CoreTests.cs index a9decef81c9..f3b525cf86a 100644 --- a/src/Simulation/Simulators.Tests/CoreTests.cs +++ b/src/Simulation/Simulators.Tests/CoreTests.cs @@ -43,6 +43,13 @@ public void BasicExecution() Assert.Null(ex); Assert.Equal(0, exitCode); Assert.Empty(error.ToString().Trim()); + + // TODO(kuzminrobin): Uncomment the block below once the following issue is resolved: + // https://github.com/microsoft/qsharp-compiler/issues/1326 + // ProcessRunner.Run("dotnet", $"{exe} --simulator SparseSimulator", out var _, out error, out exitCode, out ex); + // Assert.Null(ex); + // Assert.Equal(0, exitCode); + // Assert.Empty(error.ToString().Trim()); } [Fact] @@ -279,6 +286,7 @@ void RunOne(IOperationFactory s) RunOne(new QCTraceSimulator()); RunOne(new ResourcesEstimator()); RunOne(new QuantumSimulator()); + RunOne(new SparseSimulator()); } diff --git a/src/Simulation/Simulators.Tests/NativeOperationsTests.cs b/src/Simulation/Simulators.Tests/NativeOperationsTests.cs index ee2616eb8f9..d51b5cd858e 100644 --- a/src/Simulation/Simulators.Tests/NativeOperationsTests.cs +++ b/src/Simulation/Simulators.Tests/NativeOperationsTests.cs @@ -37,6 +37,7 @@ void TestOne(IOperationFactory sim, string expected) TestOne(new QuantumSimulator(), "Simulator"); TestOne(new ToffoliSimulator(), "Toffoli"); TestOne(new ResourcesEstimator(), "hello"); + TestOne(new SparseSimulator(), "SparseSimulator"); } [Fact] diff --git a/src/Simulation/Simulators.Tests/OpenSystemsSimulatorTests/TestOperations.qs b/src/Simulation/Simulators.Tests/OpenSystemsSimulatorTests/TestOperations.qs index db149453199..7e927a287d3 100644 --- a/src/Simulation/Simulators.Tests/OpenSystemsSimulatorTests/TestOperations.qs +++ b/src/Simulation/Simulators.Tests/OpenSystemsSimulatorTests/TestOperations.qs @@ -124,6 +124,7 @@ namespace Microsoft.Quantum.Experimental.Tests { @Test("Microsoft.Quantum.Experimental.OpenSystemsSimulator") @Test("QuantumSimulator") // validate against full-state simulator. + @Test("SparseSimulator") operation CheckToffoliOnComputationalBasisStates() : Unit { for in0 in [false, true] { for in1 in [false, true] { @@ -162,6 +163,7 @@ namespace Microsoft.Quantum.Experimental.Tests { @Test("Microsoft.Quantum.Experimental.OpenSystemsSimulator") @Test("QuantumSimulator") // validate against full-state simulator. + @Test("SparseSimulator") operation CheckXHSZSHIsNoOp() : Unit { use q = Qubit(); @@ -178,6 +180,7 @@ namespace Microsoft.Quantum.Experimental.Tests { @Test("Microsoft.Quantum.Experimental.OpenSystemsSimulator") @Test("QuantumSimulator") // validate against full-state simulator. + @Test("SparseSimulator") operation CheckControlledHWorks() : Unit { use control = Qubit(); use target = Qubit(); diff --git a/src/Simulation/Simulators.Tests/OperationsTestHelperSimSupport.cs b/src/Simulation/Simulators.Tests/OperationsTestHelperSimSupport.cs index bef48e8bf27..d75f69a2d55 100644 --- a/src/Simulation/Simulators.Tests/OperationsTestHelperSimSupport.cs +++ b/src/Simulation/Simulators.Tests/OperationsTestHelperSimSupport.cs @@ -25,7 +25,11 @@ private static void InitSimulator(SimulatorBase sim) public static void RunWithMultipleSimulators(Action test) { - var simulators = new SimulatorBase[] { new QuantumSimulator(), new ToffoliSimulator() }; + var simulators = new SimulatorBase[] { + new QuantumSimulator(), + new ToffoliSimulator(), + new SparseSimulator() + }; foreach (var s in simulators) { diff --git a/src/Simulation/Simulators.Tests/QCTraceSimulatorPrimitivesTests.cs b/src/Simulation/Simulators.Tests/QCTraceSimulatorPrimitivesTests.cs index d9a15e1a27c..be2dfc051d3 100644 --- a/src/Simulation/Simulators.Tests/QCTraceSimulatorPrimitivesTests.cs +++ b/src/Simulation/Simulators.Tests/QCTraceSimulatorPrimitivesTests.cs @@ -33,32 +33,44 @@ private static void OverrideOperation, - Simulators.QCTraceSimulators.Implementation.Interface_CX, - Intrinsic.CNOT>(sim); + try + { + OverrideOperation< + ICallable<(Qubit, Qubit), QVoid>, + Simulators.QCTraceSimulators.Implementation.Interface_CX, + Intrinsic.CNOT>(sim); - OverrideOperation< - ICallable<(Pauli, Int64, Int64, Qubit), QVoid>, - Simulators.QCTraceSimulators.Implementation.Interface_RFrac, - Intrinsic.RFrac>(sim); + OverrideOperation< + ICallable<(Pauli, Int64, Int64, Qubit), QVoid>, + Simulators.QCTraceSimulators.Implementation.Interface_RFrac, + Intrinsic.RFrac>(sim); - OverrideOperation< - ICallable<(Pauli, Double, Qubit), QVoid>, - Simulators.QCTraceSimulators.Implementation.Interface_R, - Intrinsic.R>(sim); + OverrideOperation< + ICallable<(Pauli, Double, Qubit), QVoid>, + Simulators.QCTraceSimulators.Implementation.Interface_R, + Intrinsic.R>(sim); - OverrideOperation< - ICallable<(Int64, Pauli, Qubit), QVoid>, - Simulators.QCTraceSimulators.Implementation.Interface_Clifford, - Interface_Clifford>(sim); + OverrideOperation< + ICallable<(Int64, Pauli, Qubit), QVoid>, + Simulators.QCTraceSimulators.Implementation.Interface_Clifford, + Interface_Clifford>(sim); - sim.OnLog += (msg) => { output.WriteLine(msg); }; - sim.OnLog += (msg) => { Debug.WriteLine(msg); }; + sim.OnLog += (msg) => { output.WriteLine(msg); }; + sim.OnLog += (msg) => { Debug.WriteLine(msg); }; - op.TestOperationRunner(sim); + op.TestOperationRunner(sim); + } + finally + { + sim.Dispose(); + } } } } diff --git a/src/Simulation/Simulators.Tests/QuantumSimulatorTests/BasicTests.cs b/src/Simulation/Simulators.Tests/QuantumSimulatorTests/BasicTests.cs index fbc2fcd5fbd..4e9771a1527 100644 --- a/src/Simulation/Simulators.Tests/QuantumSimulatorTests/BasicTests.cs +++ b/src/Simulation/Simulators.Tests/QuantumSimulatorTests/BasicTests.cs @@ -22,58 +22,89 @@ public void QSimConstructor() } } + [Fact] + public void SparseSimConstructor() + { + using var subject = new SparseSimulator(); + Assert.Equal("SparseSimulator", subject.Name); + } + [Fact] public void QSimVerifyPrimitivesCompleteness() { - using (var sim = new QuantumSimulator()) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { - var ops = - from op in typeof(Intrinsic.X).Assembly.GetExportedTypes() - where op.IsSubclassOf(typeof(AbstractCallable)) - where !op.IsNested - select op; + try + { + var ops = + from op in typeof(Intrinsic.X).Assembly.GetExportedTypes() + where op.IsSubclassOf(typeof(AbstractCallable)) + where !op.IsNested + select op; - var missing = new List(); + var missing = new List(); - foreach (var op in ops) - { - try - { - var i = sim.GetInstance(op); - Assert.NotNull(i); - } - catch (Exception) + foreach (var op in ops) { - missing.Add(op); + try + { + var i = sim.GetInstance(op); + Assert.NotNull(i); + } + catch (Exception) + { + missing.Add(op); + } } - } - Assert.Empty(missing); + Assert.Empty(missing); + } + finally + { + sim.Dispose(); + } } } [Fact] public void QSimX() { - using (var sim = new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: false)) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: false), + new SparseSimulator(throwOnReleasingQubitsNotInZeroState: false) + }; + + foreach (var sim in simulators) { - var x = sim.Get(); - var measure = sim.Get(); - var set = sim.Get(); + try + { + var x = sim.Get(); + var measure = sim.Get(); + var set = sim.Get(); - var ctrlX = x.__ControlledBody__.AsAction(); - OperationsTestHelper.ctrlTestShell(sim, ctrlX, (enabled, ctrls, q) => + var ctrlX = x.__ControlledBody__.AsAction(); + OperationsTestHelper.ctrlTestShell(sim, ctrlX, (enabled, ctrls, q) => + { + set.Apply((Result.Zero, q)); + var result = measure.Apply(q); + var expected = Result.Zero; + Assert.Equal(expected, result); + + x.__ControlledBody__((ctrls, q)); + result = measure.Apply(q); + expected = (enabled) ? Result.One : Result.Zero; + Assert.Equal(expected, result); + }); + } + finally { - set.Apply((Result.Zero, q)); - var result = measure.Apply(q); - var expected = Result.Zero; - Assert.Equal(expected, result); - - x.__ControlledBody__((ctrls, q)); - result = measure.Apply(q); - expected = (enabled) ? Result.One : Result.Zero; - Assert.Equal(expected, result); - }); + sim.Dispose(); + } } } @@ -101,42 +132,66 @@ public void QSimMultithreading() [Fact] public void QSimRandom() { - using (var sim = new QuantumSimulator()) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { - var r = sim.Get(); - var probs = new QArray (0.0, 0.0, 0.0, 0.7, 0.0, 0.0); - var result = r.Apply(probs); - Assert.Equal(3, result); + try + { + var r = sim.Get(); + var probs = new QArray (0.0, 0.0, 0.0, 0.7, 0.0, 0.0); + var result = r.Apply(probs); + Assert.Equal(3, result); + } + finally + { + sim.Dispose(); + } } } [Fact] public void QSimAssert() { - using (var sim = new QuantumSimulator()) - { - var assert = sim.Get(); - var h = sim.Get(); + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; - Func, IQArray, Result, string)> mapper = - (q) => (new QArray(Pauli.PauliZ), new QArray (q), Result.Zero, "Assert failed"); - var applyWithZero = new OperationPartial, IQArray, Result, string), QVoid>(assert, mapper); - - OperationsTestHelper.applyTestShell(sim, applyWithZero, (q) => + foreach (var sim in simulators) + { + try { - h.Apply(q); - assert.Apply((new QArray(Pauli.PauliX), new QArray (q), Result.Zero, "Assert failed")); + var assert = sim.Get(); + var h = sim.Get(); - OperationsTestHelper.IgnoreDebugAssert(() => - { - Assert.Throws(() => assert.Apply((new QArray (Pauli.PauliX), new QArray (q), Result.One, "Assert failed"))); + Func, IQArray, Result, string)> mapper = + (q) => (new QArray(Pauli.PauliZ), new QArray (q), Result.Zero, "Assert failed"); + var applyWithZero = new OperationPartial, IQArray, Result, string), QVoid>(assert, mapper); + OperationsTestHelper.applyTestShell(sim, applyWithZero, (q) => + { h.Apply(q); - Assert.Throws(() => assert.Apply((new QArray (Pauli.PauliZ), new QArray (q), Result.One, "Assert failed"))); - }); + assert.Apply((new QArray(Pauli.PauliX), new QArray (q), Result.Zero, "Assert failed")); - assert.Apply((new QArray (Pauli.PauliZ), new QArray(q), Result.Zero, "Assert failed")); - }); + OperationsTestHelper.IgnoreDebugAssert(() => + { + Assert.Throws(() => assert.Apply((new QArray (Pauli.PauliX), new QArray (q), Result.One, "Assert failed"))); + + h.Apply(q); + Assert.Throws(() => assert.Apply((new QArray (Pauli.PauliZ), new QArray (q), Result.One, "Assert failed"))); + }); + + assert.Apply((new QArray (Pauli.PauliZ), new QArray(q), Result.Zero, "Assert failed")); + }); + } + finally + { + sim.Dispose(); + } } } @@ -144,59 +199,71 @@ public void QSimAssert() [Fact] public void QSimAssertProb() { - using (var sim = new QuantumSimulator()) - { - var tolerance = 0.02; - var assertProb = sim.Get(); - var h = sim.Get(); - var allocate = sim.Get(); - var release = sim.Get(); + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; - Func, IQArray, Result, double, string, double)> mapper = (q) => - { - return (new QArray(Pauli.PauliZ), new QArray(q), Result.Zero, 1.0, "Assert failed", tolerance); - }; - var applyWithZero = new OperationPartial, IQArray, Result, double, string, double), QVoid>(assertProb, mapper); - - OperationsTestHelper.applyTestShell(sim, applyWithZero, (q1) => + foreach (var sim in simulators) + { + try { - assertProb.Apply((new QArray (Pauli.PauliZ), new QArray(q1), Result.One, 0.01, "Assert failed", tolerance)); + var tolerance = 0.02; + var assertProb = sim.Get(); + var h = sim.Get(); + var allocate = sim.Get(); + var release = sim.Get(); - // Within tolerance - assertProb.Apply((new QArray (Pauli.PauliX), new QArray (q1), Result.Zero, 0.51, "Assert failed", tolerance)); - assertProb.Apply((new QArray (Pauli.PauliX), new QArray (q1), Result.One, 0.51, "Assert failed", tolerance)); - // Outside of tolerance - OperationsTestHelper.IgnoreDebugAssert(() => + Func, IQArray, Result, double, string, double)> mapper = (q) => { - Assert.Throws(() => assertProb.Apply((new QArray(Pauli.PauliX), new QArray(q1), Result.One, 0.51, "Assert failed", 0.0))); - Assert.Throws(() => assertProb.Apply((new QArray(Pauli.PauliX), new QArray(q1), Result.Zero, 0.51, "Assert failed", 0.0))); - }); - - // Add a qubit - var qubits = allocate.Apply(1); - var q2 = qubits[0]; + return (new QArray(Pauli.PauliZ), new QArray(q), Result.Zero, 1.0, "Assert failed", tolerance); + }; + var applyWithZero = new OperationPartial, IQArray, Result, double, string, double), QVoid>(assertProb, mapper); - assertProb.Apply((new QArray (Pauli.PauliZ), new QArray (q1), Result.Zero, 0.99, "Assert failed", tolerance)); - assertProb.Apply((new QArray (Pauli.PauliZ), new QArray (q1), Result.One, 0.01, "Assert failed", tolerance)); - assertProb.Apply((new QArray (Pauli.PauliZ, Pauli.PauliZ), new QArray (q1, q2), Result.Zero, 0.99, "Assert failed", tolerance)); - assertProb.Apply((new QArray (Pauli.PauliZ, Pauli.PauliZ), new QArray (q1, q2), Result.One, 0.01, "Assert failed", tolerance)); - - assertProb.Apply((new QArray (Pauli.PauliZ, Pauli.PauliX), new QArray (q1, q2), Result.Zero, 0.51, "Assert failed", tolerance)); - assertProb.Apply((new QArray (Pauli.PauliX, Pauli.PauliZ), new QArray (q1, q2), Result.Zero, 0.51, "Assert failed", tolerance)); - assertProb.Apply((new QArray (Pauli.PauliX, Pauli.PauliX), new QArray (q1, q2), Result.Zero, 0.51, "Assert failed", tolerance)); - - OperationsTestHelper.IgnoreDebugAssert(() => + OperationsTestHelper.applyTestShell(sim, applyWithZero, (q1) => { - // Outside of tolerance - Assert.Throws(() => assertProb.Apply((new QArray (Pauli.PauliZ, Pauli.PauliZ), new QArray (q1, q2), Result.Zero, 0.51, "Assert failed", 0.0))); - Assert.Throws(() => assertProb.Apply((new QArray (Pauli.PauliZ, Pauli.PauliZ), new QArray (q1, q2), Result.One, 0.51, "Assert failed", 0.0))); + assertProb.Apply((new QArray (Pauli.PauliZ), new QArray(q1), Result.One, 0.01, "Assert failed", tolerance)); - // Missmatch number of arrays - Assert.Throws(() => assertProb.Apply((new QArray (Pauli.PauliZ, Pauli.PauliZ), new QArray (q1), Result.Zero, 0.51, "Assert failed", tolerance))); + // Within tolerance + assertProb.Apply((new QArray (Pauli.PauliX), new QArray (q1), Result.Zero, 0.51, "Assert failed", tolerance)); + assertProb.Apply((new QArray (Pauli.PauliX), new QArray (q1), Result.One, 0.51, "Assert failed", tolerance)); + // Outside of tolerance + OperationsTestHelper.IgnoreDebugAssert(() => + { + Assert.Throws(() => assertProb.Apply((new QArray(Pauli.PauliX), new QArray(q1), Result.One, 0.51, "Assert failed", 0.0))); + Assert.Throws(() => assertProb.Apply((new QArray(Pauli.PauliX), new QArray(q1), Result.Zero, 0.51, "Assert failed", 0.0))); + }); + + // Add a qubit + var qubits = allocate.Apply(1); + var q2 = qubits[0]; + + assertProb.Apply((new QArray (Pauli.PauliZ), new QArray (q1), Result.Zero, 0.99, "Assert failed", tolerance)); + assertProb.Apply((new QArray (Pauli.PauliZ), new QArray (q1), Result.One, 0.01, "Assert failed", tolerance)); + assertProb.Apply((new QArray (Pauli.PauliZ, Pauli.PauliZ), new QArray (q1, q2), Result.Zero, 0.99, "Assert failed", tolerance)); + assertProb.Apply((new QArray (Pauli.PauliZ, Pauli.PauliZ), new QArray (q1, q2), Result.One, 0.01, "Assert failed", tolerance)); + + assertProb.Apply((new QArray (Pauli.PauliZ, Pauli.PauliX), new QArray (q1, q2), Result.Zero, 0.51, "Assert failed", tolerance)); + assertProb.Apply((new QArray (Pauli.PauliX, Pauli.PauliZ), new QArray (q1, q2), Result.Zero, 0.51, "Assert failed", tolerance)); + assertProb.Apply((new QArray (Pauli.PauliX, Pauli.PauliX), new QArray (q1, q2), Result.Zero, 0.51, "Assert failed", tolerance)); + + OperationsTestHelper.IgnoreDebugAssert(() => + { + // Outside of tolerance + Assert.Throws(() => assertProb.Apply((new QArray (Pauli.PauliZ, Pauli.PauliZ), new QArray (q1, q2), Result.Zero, 0.51, "Assert failed", 0.0))); + Assert.Throws(() => assertProb.Apply((new QArray (Pauli.PauliZ, Pauli.PauliZ), new QArray (q1, q2), Result.One, 0.51, "Assert failed", 0.0))); + + // Missmatch number of arrays + Assert.Throws(() => assertProb.Apply((new QArray (Pauli.PauliZ, Pauli.PauliZ), new QArray (q1), Result.Zero, 0.51, "Assert failed", tolerance))); + }); + + release.Apply(qubits); }); - - release.Apply(qubits); - }); + } + finally + { + sim.Dispose(); + } } } @@ -264,7 +331,7 @@ private static void TestMultiUnitary(IUnitary> gate, IQArray(QuantumSimulator qsim, T gate, Action, IQArray> action) + private void TestOne(CommonNativeSimulator qsim, T gate, Action, IQArray> action) { var allocate = qsim.Get(); var release = qsim.Get(); @@ -300,7 +367,12 @@ public void TestSimpleGateCheckQubits() foreach (var t in gateTypes) { - using (var qsim = new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: false)) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: false), + new SparseSimulator(throwOnReleasingQubitsNotInZeroState: false) + }; + + foreach (var qsim in simulators) { var gate = qsim.Get>(t); TestOne(qsim, gate, TestUnitary); @@ -312,35 +384,59 @@ public void TestSimpleGateCheckQubits() [Fact] public void TestRCheckQubits() { - using (var qsim = new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: false)) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: false), + new SparseSimulator(throwOnReleasingQubitsNotInZeroState: false) + }; + + foreach (var qsim in simulators) { - // R - var mapper = new Func(qubit => (Pauli.PauliZ, 1.0, qubit)); - var gate = qsim.Get(); - var p = gate.Partial(mapper); - TestOne(qsim, p, TestUnitary); + try + { + // R + var mapper = new Func(qubit => (Pauli.PauliZ, 1.0, qubit)); + var gate = qsim.Get(); + var p = gate.Partial(mapper); + TestOne(qsim, p, TestUnitary); + } + finally + { + qsim.Dispose(); + } } } [Fact] public void TestExpCheckQubits() { - using (var qsim = new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: false)) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: false), + new SparseSimulator(throwOnReleasingQubitsNotInZeroState: false) + }; + + foreach (var qsim in simulators) { - // Exp + try { - var mapper = new Func, (IQArray, Double, IQArray)>(qubits => (new QArray(Pauli.PauliZ, Pauli.PauliX, Pauli.PauliY), 1.0, qubits)); - var gate = qsim.Get(); - var p = gate.Partial(mapper); - TestOne(qsim, p, TestMultiUnitary); - } + // Exp + { + var mapper = new Func, (IQArray, Double, IQArray)>(qubits => (new QArray(Pauli.PauliZ, Pauli.PauliX, Pauli.PauliY), 1.0, qubits)); + var gate = qsim.Get(); + var p = gate.Partial(mapper); + TestOne(qsim, p, TestMultiUnitary); + } - // ExpFrac + // ExpFrac + { + var mapper = new Func, (IQArray, long, long, IQArray)>(qubits => (new QArray(Pauli.PauliZ, Pauli.PauliX, Pauli.PauliY), 1, 2, qubits)); + var gate = qsim.Get(); + var p = gate.Partial(mapper); + TestOne(qsim, p, TestMultiUnitary); + } + } + finally { - var mapper = new Func, (IQArray, long, long, IQArray)>(qubits => (new QArray(Pauli.PauliZ, Pauli.PauliX, Pauli.PauliY), 1, 2, qubits)); - var gate = qsim.Get(); - var p = gate.Partial(mapper); - TestOne(qsim, p, TestMultiUnitary); + qsim.Dispose(); } } } @@ -349,23 +445,35 @@ public void TestExpCheckQubits() [Fact] public void TestMeasureCheckQubits() { - using (var qsim = new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: false)) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: false), + new SparseSimulator(throwOnReleasingQubitsNotInZeroState: false) + }; + + foreach (var qsim in simulators) { - // M + try { - var gate = qsim.Get(); - TestOne(qsim, gate, (g, ctrls, t) => TestCallable(g, t[0])); - } + // M + { + var gate = qsim.Get(); + TestOne(qsim, gate, (g, ctrls, t) => TestCallable(g, t[0])); + } - // Measure - { - var gate = qsim.Get(); - var mapper = new Func, (IQArray, IQArray)>(qubits => (new QArray(Pauli.PauliZ, Pauli.PauliI, Pauli.PauliI), qubits)); - var p = gate.Partial(mapper); + // Measure + { + var gate = qsim.Get(); + var mapper = new Func, (IQArray, IQArray)>(qubits => (new QArray(Pauli.PauliZ, Pauli.PauliI, Pauli.PauliI), qubits)); + var p = gate.Partial(mapper); - // On systems that decompose joint measurement a qubit can actually be duplictated in - // the targets, so skip the duplicate qubit check. - TestOne(qsim, p, (g, ctrls, t) => TestMultiCallable(p, t)); + // On systems that decompose joint measurement a qubit can actually be duplictated in + // the targets, so skip the duplicate qubit check. + TestOne(qsim, p, (g, ctrls, t) => TestMultiCallable(p, t)); + } + } + finally + { + qsim.Dispose(); } } } @@ -373,22 +481,34 @@ public void TestMeasureCheckQubits() [Fact] public void TestAssertCheckQubits() { - using (var qsim = new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: false)) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: false), + new SparseSimulator(throwOnReleasingQubitsNotInZeroState: false) + }; + + foreach (var qsim in simulators) { - // Assert + try { - var gate = qsim.Get(); - var mapper = new Func, (IQArray, IQArray, Result, String)>(qubits => (new QArray(Pauli.PauliZ, Pauli.PauliI, Pauli.PauliI), qubits, Result.Zero, "")); - var p = gate.Partial(mapper); - TestOne(qsim, gate, (g, ctrls, t) => TestMultiCallable(p, t)); - } + // Assert + { + var gate = qsim.Get(); + var mapper = new Func, (IQArray, IQArray, Result, String)>(qubits => (new QArray(Pauli.PauliZ, Pauli.PauliI, Pauli.PauliI), qubits, Result.Zero, "")); + var p = gate.Partial(mapper); + TestOne(qsim, gate, (g, ctrls, t) => TestMultiCallable(p, t)); + } - // AssertProb + // AssertProb + { + var gate = qsim.Get(); + var mapper = new Func, (IQArray, IQArray, Result, Double, String, Double)>(qubits => (new QArray(Pauli.PauliZ, Pauli.PauliI, Pauli.PauliI), qubits, Result.Zero, 1.000, "", 0.002)); + var p = gate.Partial(mapper); + TestOne(qsim, p, (g, ctrls, t) => TestMultiCallable(p, t)); + } + } + finally { - var gate = qsim.Get(); - var mapper = new Func, (IQArray, IQArray, Result, Double, String, Double)>(qubits => (new QArray(Pauli.PauliZ, Pauli.PauliI, Pauli.PauliI), qubits, Result.Zero, 1.000, "", 0.002)); - var p = gate.Partial(mapper); - TestOne(qsim, p, (g, ctrls, t) => TestMultiCallable(p, t)); + qsim.Dispose(); } } } diff --git a/src/Simulation/Simulators.Tests/QuantumSimulatorTests/CircuitsTests.cs b/src/Simulation/Simulators.Tests/QuantumSimulatorTests/CircuitsTests.cs index c5852fb1556..df94fc856e9 100644 --- a/src/Simulation/Simulators.Tests/QuantumSimulatorTests/CircuitsTests.cs +++ b/src/Simulation/Simulators.Tests/QuantumSimulatorTests/CircuitsTests.cs @@ -18,9 +18,21 @@ public partial class QuantumSimulatorTests [OperationDriver(TestCasePrefix ="QSim", TestNamespace = "Microsoft.Quantum.Simulation.Simulators.Tests.Circuits")] public void QSimTestTarget( TestOperation op ) { - using (var sim = new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: true)) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: true), + new SparseSimulator(throwOnReleasingQubitsNotInZeroState: true) + }; + + foreach (var sim in simulators) { - op.TestOperationRunner(sim); + try + { + op.TestOperationRunner(sim); + } + finally + { + sim.Dispose(); + } } } } diff --git a/src/Simulation/Simulators.Tests/QuantumSimulatorTests/QubitReleaseTest.cs b/src/Simulation/Simulators.Tests/QuantumSimulatorTests/QubitReleaseTest.cs index 3e2d78ddf7d..123884a1d08 100644 --- a/src/Simulation/Simulators.Tests/QuantumSimulatorTests/QubitReleaseTest.cs +++ b/src/Simulation/Simulators.Tests/QuantumSimulatorTests/QubitReleaseTest.cs @@ -27,47 +27,84 @@ public async Task ZeroStateQubitReleaseTest() [Fact] public async Task MeasuredQubitReleaseTest() { - var sim = new QuantumSimulator(); + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; - //should not throw an exception, as Measured qubits are allowed to be released, and the release aspect is handled in the C++ code - await ReleaseMeasuredQubitCheck.Run(sim); + foreach (var sim in simulators) + { + try + { + //should not throw an exception, as Measured qubits are allowed to be released, and the release aspect is handled in the C++ code + await ReleaseMeasuredQubitCheck.Run(sim); + } + finally + { + sim.Dispose(); + } + } } //test to check that qubits cannot be released after multiple qubit measure [Fact] public async Task MeasuredMultipleQubitsReleaseTest() { - var sim = new QuantumSimulator(); + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; - await Assert.ThrowsAsync(() => ReleaseMeasureMultipleQubitCheck.Run(sim)); + foreach (var sim in simulators) + { + try + { + await Assert.ThrowsAsync(() => ReleaseMeasureMultipleQubitCheck.Run(sim)); + } + finally + { + sim.Dispose(); + } + } } //test to check that qubit that is released and reallocated is in state |0> [Fact] public async Task ReallocateQubitInGroundStateTest() { - var sim = new QuantumSimulator(); - var allocate = sim.Get(); - var release = sim.Get(); - var q1 = allocate.Apply(1); - var q1Id = q1[0].Id; - var gate = sim.Get(); - var measure = sim.Get(); - gate.Apply(q1[0]); - var result1 = measure.Apply(q1[0]); - //Check X operation - Assert.Equal(result1, Result.One); - release.Apply(q1[0]); - var q2 = allocate.Apply(1); - var q2Id = q2[0].Id; - //Assert reallocated qubit has the same id as the one released - Assert.Equal(q1Id, q2Id); - var result2 = measure.Apply(q2[0]); - //Assert reallocated qubit has is initialized in state |0> - Assert.Equal(result2, Result.Zero); - + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; - + foreach (var sim in simulators) + { + try + { + var allocate = sim.Get(); + var release = sim.Get(); + var q1 = allocate.Apply(1); + var q1Id = q1[0].Id; + var gate = sim.Get(); + var measure = sim.Get(); + gate.Apply(q1[0]); + var result1 = measure.Apply(q1[0]); + //Check X operation + Assert.Equal(result1, Result.One); + release.Apply(q1[0]); + var q2 = allocate.Apply(1); + var q2Id = q2[0].Id; + //Assert reallocated qubit has the same id as the one released + Assert.Equal(q1Id, q2Id); + var result2 = measure.Apply(q2[0]); + //Assert reallocated qubit has is initialized in state |0> + Assert.Equal(result2, Result.Zero); + } + finally + { + sim.Dispose(); + } + } } } } diff --git a/src/Simulation/Simulators.Tests/QuantumSimulatorTests/VerifyGates.cs b/src/Simulation/Simulators.Tests/QuantumSimulatorTests/VerifyGates.cs index 170b19ad508..41455c4b9e7 100644 --- a/src/Simulation/Simulators.Tests/QuantumSimulatorTests/VerifyGates.cs +++ b/src/Simulation/Simulators.Tests/QuantumSimulatorTests/VerifyGates.cs @@ -90,121 +90,205 @@ private static void VerifyInvalidAngles(IOperationFactory sim, IUnitary<(double, [Fact] public void QSimVerifyH() { - using (var sim = new QuantumSimulator()) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { - var gate = sim.Get(); - VerifyGate(sim, gate, new State[] - { - new State((sqrt1_2, 0.0), (sqrt1_2, .0)), - new State((sqrt1_2, 0.0), (-1 * sqrt1_2, .0)), - new State((1.0, 0.0), (0.0, 0.0)), - new State((0.0, 0.0), (1.0, 0.0)), - }); + try + { + var gate = sim.Get(); + VerifyGate(sim, gate, new State[] + { + new State((sqrt1_2, 0.0), (sqrt1_2, .0)), + new State((sqrt1_2, 0.0), (-1 * sqrt1_2, .0)), + new State((1.0, 0.0), (0.0, 0.0)), + new State((0.0, 0.0), (1.0, 0.0)), + }); + } + finally + { + sim.Dispose(); + } } } [Fact] public void QSimVerifyX() { - using (var sim = new QuantumSimulator()) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { - var gate = sim.Get(); + try + { + var gate = sim.Get(); - VerifyGate(sim, gate, new State[] + VerifyGate(sim, gate, new State[] + { + new State((0.0, 0.0), (1.0, 0.0)), + new State((1.0, 0.0), (0.0, 0.0)), + new State((sqrt1_2, 0.0), (sqrt1_2, 0.0)), + new State((-sqrt1_2, 0.0), (sqrt1_2, 0.0)) + }); + } + finally { - new State((0.0, 0.0), (1.0, 0.0)), - new State((1.0, 0.0), (0.0, 0.0)), - new State((sqrt1_2, 0.0), (sqrt1_2, 0.0)), - new State((-sqrt1_2, 0.0), (sqrt1_2, 0.0)) - }); + sim.Dispose(); + } } } [Fact] public void QSimVerifyY() { - using (var sim = new QuantumSimulator()) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { - var gate = sim.Get(); + try + { + var gate = sim.Get(); - VerifyGate(sim, gate, new State[] + VerifyGate(sim, gate, new State[] + { + new State((0.0, 0.0), (0.0, 1.0)), + new State((0.0, -1.0), (0.0, 0.0)), + new State((0.0, -sqrt1_2), (0.0, sqrt1_2)), + new State((0.0, sqrt1_2), (0.0, sqrt1_2)), + }); + } + finally { - new State((0.0, 0.0), (0.0, 1.0)), - new State((0.0, -1.0), (0.0, 0.0)), - new State((0.0, -sqrt1_2), (0.0, sqrt1_2)), - new State((0.0, sqrt1_2), (0.0, sqrt1_2)), - }); + sim.Dispose(); + } } } [Fact] public void QSimVerifyZ() { - using (var sim = new QuantumSimulator()) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { - var gate = sim.Get(); + try + { + var gate = sim.Get(); - VerifyGate(sim, gate, new State[] + VerifyGate(sim, gate, new State[] + { + new State((1.0, 0.0), (0.0, 0.0)), + new State((0.0, 0.0), (-1.0, 0.0)), + new State((sqrt1_2, 0.0), (-sqrt1_2, 0.0)), + new State((sqrt1_2, 0.0), (sqrt1_2, 0.0)) + }); + } + finally { - new State((1.0, 0.0), (0.0, 0.0)), - new State((0.0, 0.0), (-1.0, 0.0)), - new State((sqrt1_2, 0.0), (-sqrt1_2, 0.0)), - new State((sqrt1_2, 0.0), (sqrt1_2, 0.0)) - }); + sim.Dispose(); + } } } [Fact] public void QSimVerifyS() { - using (var sim = new QuantumSimulator()) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { - var gate = sim.Get(); + try + { + var gate = sim.Get(); - VerifyGate(sim, gate, new State[] + VerifyGate(sim, gate, new State[] + { + new State((1.0, 0.0), (0.0, 0.0)), + new State((0.0, 0.0), (0.0, 1.0)), + new State((sqrt1_2, 0.0), (0.0, sqrt1_2)), + new State((sqrt1_2, 0.0), (0.0, -sqrt1_2)) + }); + } + finally { - new State((1.0, 0.0), (0.0, 0.0)), - new State((0.0, 0.0), (0.0, 1.0)), - new State((sqrt1_2, 0.0), (0.0, sqrt1_2)), - new State((sqrt1_2, 0.0), (0.0, -sqrt1_2)) - }); + sim.Dispose(); + } } } [Fact] public void QSimVerifyT() { - using (var sim = new QuantumSimulator()) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { - var gate = sim.Get(); + try + { + var gate = sim.Get(); - VerifyGate(sim, gate, new State[] + VerifyGate(sim, gate, new State[] + { + new State((1.0, 0.0), (0.0, 0.0)), + new State((0.0, 0.0), E_i(PI / 4)), + new State((sqrt1_2, 0.0), times(sqrt1_2, E_i(PI / 4))), + new State((sqrt1_2, 0.0), times(-sqrt1_2, E_i(PI / 4))) + }); + } + finally { - new State((1.0, 0.0), (0.0, 0.0)), - new State((0.0, 0.0), E_i(PI / 4)), - new State((sqrt1_2, 0.0), times(sqrt1_2, E_i(PI / 4))), - new State((sqrt1_2, 0.0), times(-sqrt1_2, E_i(PI / 4))) - }); + sim.Dispose(); + } } } [Fact] public void QSimVerifyR1() { - using (var sim = new QuantumSimulator()) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { - var angle = PI * r.NextDouble(); - Func mapper = (q) => (angle, q); + try + { + var angle = PI * r.NextDouble(); + Func mapper = (q) => (angle, q); - var gate = sim.Get().Partial(mapper); + var gate = sim.Get().Partial(mapper); - VerifyGate(sim, gate, new State[] + VerifyGate(sim, gate, new State[] + { + new State((1.0, 0.0), (0.0, 0.0)), + new State((0.0, 0.0), E_i(angle)), + new State((sqrt1_2, 0.0), times(sqrt1_2, E_i(angle))), + new State((sqrt1_2, 0.0), times(-sqrt1_2, E_i(angle))) + }); + } + finally { - new State((1.0, 0.0), (0.0, 0.0)), - new State((0.0, 0.0), E_i(angle)), - new State((sqrt1_2, 0.0), times(sqrt1_2, E_i(angle))), - new State((sqrt1_2, 0.0), times(-sqrt1_2, E_i(angle))) - }); + sim.Dispose(); + } } } @@ -275,12 +359,24 @@ public void QSimVerifyRx() Func mapper = (q) => (angle, q); - using (var sim = new QuantumSimulator()) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { - var gate = sim.Get().Partial(mapper); - VerifyGate(sim, gate, RExpectedStates(Pauli.PauliX, angle)); + try + { + var gate = sim.Get().Partial(mapper); + VerifyGate(sim, gate, RExpectedStates(Pauli.PauliX, angle)); - VerifyInvalidAngles(sim, sim.Get()); + VerifyInvalidAngles(sim, sim.Get()); + } + finally + { + sim.Dispose(); + } } } @@ -291,12 +387,24 @@ public void QSimVerifyRy() Func mapper = (q) => (angle, q); - using (var sim = new QuantumSimulator()) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { - var gate = sim.Get().Partial(mapper); - VerifyGate(sim, gate, RExpectedStates(Pauli.PauliY, angle)); + try + { + var gate = sim.Get().Partial(mapper); + VerifyGate(sim, gate, RExpectedStates(Pauli.PauliY, angle)); - VerifyInvalidAngles(sim, sim.Get()); + VerifyInvalidAngles(sim, sim.Get()); + } + finally + { + sim.Dispose(); + } } } @@ -307,12 +415,24 @@ public void QSimVerifyRz() Func mapper = (q) => (angle, q); - using (var sim = new QuantumSimulator()) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { - var gate = sim.Get().Partial(mapper); - VerifyGate(sim, gate, RExpectedStates(Pauli.PauliZ, angle)); + try + { + var gate = sim.Get().Partial(mapper); + VerifyGate(sim, gate, RExpectedStates(Pauli.PauliZ, angle)); - VerifyInvalidAngles(sim, sim.Get()); + VerifyInvalidAngles(sim, sim.Get()); + } + finally + { + sim.Dispose(); + } } } @@ -325,57 +445,93 @@ public void QSimVerifyR() Func<(double, Qubit), (Pauli, double, Qubit)> needsAngle = (__arg) => (Pauli.PauliX, __arg.Item1, __arg.Item2); - using (var sim = new QuantumSimulator()) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { - var gate = sim.Get().Partial(mapper); - VerifyGate(sim, gate, RExpectedStates(Pauli.PauliI, angle)); + try + { + var gate = sim.Get().Partial(mapper); + VerifyGate(sim, gate, RExpectedStates(Pauli.PauliI, angle)); - var angleGate = sim.Get().Partial(needsAngle); - VerifyInvalidAngles(sim, angleGate); + var angleGate = sim.Get().Partial(needsAngle); + VerifyInvalidAngles(sim, angleGate); + } + finally + { + sim.Dispose(); + } } } [Fact] public void QSimVerifyRFrac() { - using (var sim = new QuantumSimulator()) - { - var allBases = new[] { Pauli.PauliI, Pauli.PauliX, Pauli.PauliZ, Pauli.PauliY }; + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; - for (var k = 0; k < 4; k++) + foreach (var sim in simulators) + { + try { - for (var n = 0; n < 3; n++) + var allBases = new[] { Pauli.PauliI, Pauli.PauliX, Pauli.PauliZ, Pauli.PauliY }; + + for (var k = 0; k < 4; k++) { - foreach (var p in allBases) + for (var n = 0; n < 3; n++) { - Func mapper = (q) => (p, k, n, q); - var gate = sim.Get().Partial(mapper); + foreach (var p in allBases) + { + Func mapper = (q) => (p, k, n, q); + var gate = sim.Get().Partial(mapper); - VerifyGate(sim, gate, RExpectedStates(p, k, n)); + VerifyGate(sim, gate, RExpectedStates(p, k, n)); + } } } } + finally + { + sim.Dispose(); + } } } private void VerifyExp(Pauli pauli) { - using (var sim = new QuantumSimulator()) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { - var angle = 2 * PI * r.NextDouble(); + try + { + var angle = 2 * PI * r.NextDouble(); - Func, double, IQArray)> mapper = (q) - => (new QArray (pauli), angle, new QArray (q)); - var gate = sim.Get().Partial(mapper); + Func, double, IQArray)> mapper = (q) + => (new QArray (pauli), angle, new QArray (q)); + var gate = sim.Get().Partial(mapper); - VerifyGate(sim, gate, ExponentExpectedStates(pauli, angle)); + VerifyGate(sim, gate, ExponentExpectedStates(pauli, angle)); - Func<(double, Qubit), (IQArray, double, IQArray)> needsAngle = (__arg) - => (new QArray (pauli), __arg.Item1, new QArray (__arg.Item2)); - var angleGate = sim.Get().Partial(needsAngle); - if (pauli != Pauli.PauliI) + Func<(double, Qubit), (IQArray, double, IQArray)> needsAngle = (__arg) + => (new QArray (pauli), __arg.Item1, new QArray (__arg.Item2)); + var angleGate = sim.Get().Partial(needsAngle); + if (pauli != Pauli.PauliI) + { + VerifyInvalidAngles(sim, angleGate); + } + } + finally { - VerifyInvalidAngles(sim, angleGate); + sim.Dispose(); } } } @@ -405,64 +561,114 @@ public void QSimVerifyExpX() } [Fact] - public void QSimVerifyExpFrac() + public void QSimVerifyExpUsingDecompositions() { - using (var sim = new QuantumSimulator()) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { - var allBases = new[] { Pauli.PauliI, Pauli.PauliX, Pauli.PauliZ, Pauli.PauliY }; + VerifyExpUsingDecompositions.Run(sim).Wait(); + } + } - for (var k = 0; k < 4; k++) + [Fact] + public void QSimVerifyExpFrac() + { + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) + { + try { - for (var n = 0; n < 3; n++) + var allBases = new[] { Pauli.PauliI, Pauli.PauliX, Pauli.PauliZ, Pauli.PauliY }; + + for (var k = 0; k < 4; k++) { - foreach (var p in allBases) + for (var n = 0; n < 3; n++) { - Func, long, long, IQArray)> mapper = (q) - => (new QArray (p), k, n, new QArray (q)); - var gate = sim.Get().Partial(mapper); - - VerifyGate(sim, gate, ExponentExpectedStates(p, k, n)); + foreach (var p in allBases) + { + Func, long, long, IQArray)> mapper = (q) + => (new QArray (p), k, n, new QArray (q)); + var gate = sim.Get().Partial(mapper); + + VerifyGate(sim, gate, ExponentExpectedStates(p, k, n)); + } } } } + finally + { + sim.Dispose(); + } } } [Fact] public void QSimMeasure() { - using (var sim = new QuantumSimulator()) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { - var op = sim.Get, JointMeasureTest>(); - op.Apply(QVoid.Instance); + try + { + var op = sim.Get, JointMeasureTest>(); + op.Apply(QVoid.Instance); + } + finally + { + sim.Dispose(); + } } } [Fact] public void QSimM() { - using (var sim = new QuantumSimulator()) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { - var m = sim.Get(); + try + { + var m = sim.Get(); - var allocate = sim.Get(); - var release = sim.Get(); - var x = sim.Get(); + var allocate = sim.Get(); + var release = sim.Get(); + var x = sim.Get(); - var qbits = allocate.Apply(1); - Assert.Single(qbits); + var qbits = allocate.Apply(1); + Assert.Single(qbits); - var q = qbits[0]; - var result = m.Apply(q); - Assert.Equal(Result.Zero, result); + var q = qbits[0]; + var result = m.Apply(q); + Assert.Equal(Result.Zero, result); - x.Apply(q); - result = m.Apply(q); - Assert.Equal(Result.One, result); - x.Apply(q); + x.Apply(q); + result = m.Apply(q); + Assert.Equal(Result.One, result); + x.Apply(q); - release.Apply(qbits); - sim.CheckNoQubitLeak(); + release.Apply(qbits); + sim.CheckNoQubitLeak(); + } + finally + { + sim.Dispose(); + } } } } diff --git a/src/Simulation/Simulators.Tests/QuantumTestSuite.cs b/src/Simulation/Simulators.Tests/QuantumTestSuite.cs index e4f06be7dab..ff269fefee7 100644 --- a/src/Simulation/Simulators.Tests/QuantumTestSuite.cs +++ b/src/Simulation/Simulators.Tests/QuantumTestSuite.cs @@ -21,20 +21,44 @@ public QuantumTestSuite(ITestOutputHelper output) [OperationDriver(TestCasePrefix = "QSim:", TestNamespace = "Microsoft.Quantum.Simulation.TestSuite")] public void QSimTestTarget(TestOperation op) { - using (var sim = new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: true )) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: true), + new SparseSimulator(throwOnReleasingQubitsNotInZeroState: true) + }; + + foreach (var sim in simulators) { - sim.OnLog += (msg) => { output.WriteLine(msg); }; - op.TestOperationRunner(sim); + try + { + sim.OnLog += (msg) => { output.WriteLine(msg); }; + op.TestOperationRunner(sim); + } + finally + { + sim.Dispose(); + } } } //[OperationDriver(TestCasePrefix = "QSim:", TestNamespace = "Microsoft.Quantum.Simulation.TestSuite.VeryLong")] private void QSimTestTargetVeryLong(TestOperation op) { - using (var sim = new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: true )) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: true), + new SparseSimulator(throwOnReleasingQubitsNotInZeroState: true) + }; + + foreach (var sim in simulators) { - sim.OnLog += (msg) => { output.WriteLine(msg); }; - op.TestOperationRunner(sim); + try + { + sim.OnLog += (msg) => { output.WriteLine(msg); }; + op.TestOperationRunner(sim); + } + finally + { + sim.Dispose(); + } } } @@ -42,20 +66,32 @@ private void QSimTestTargetVeryLong(TestOperation op) [OperationDriver(TestCasePrefix = "⊗ Fail QSim:", TestNamespace = "Microsoft.Quantum.Simulation.TestSuite", Suffix = "QSimFail", Skip = "These tests are known to fail" )] public void QSimTestTargetFailures(TestOperation op) { - using (var sim = new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: true )) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(throwOnReleasingQubitsNotInZeroState: true), + new SparseSimulator(throwOnReleasingQubitsNotInZeroState: true) + }; + + foreach (var sim in simulators) { - sim.OnLog += (msg) => { output.WriteLine(msg); }; - Action action = () => op.TestOperationRunner(sim); - bool hasThrown = false; try { - action.IgnoreDebugAssert(); + sim.OnLog += (msg) => { output.WriteLine(msg); }; + Action action = () => op.TestOperationRunner(sim); + bool hasThrown = false; + try + { + action.IgnoreDebugAssert(); + } + catch (ExecutionFailException) + { + hasThrown = true; + } + Assert.True(hasThrown, "The operation was known to throw. It does not throw anymore. Congratulations ! You fixed the bug."); } - catch (ExecutionFailException) + finally { - hasThrown = true; + sim.Dispose(); } - Assert.True(hasThrown, "The operation was known to throw. It does not throw anymore. Congratulations ! You fixed the bug."); } } } diff --git a/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs b/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs index 8ba1e3cd373..ae6a5037527 100644 --- a/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs +++ b/src/Simulation/Simulators.Tests/RuntimeMetadataTests.cs @@ -11,6 +11,18 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests { + /// + /// Base test class for those test classes that need the simulator factories. + /// + public class SimulatorFactoryProvider + { + protected Func[] simulatorFactories = new Func[] + { + () => new QuantumSimulator(), + () => new SparseSimulator() + }; + } + public class RuntimeMetadataEqualityTests { [Fact] @@ -186,159 +198,264 @@ public class IntrinsicTests [Fact] public void CNOT() { - var control = new FreeQubit(1); - var target = new FreeQubit(0); - var op = new QuantumSimulator().Get(); - var args = op.__DataIn__((control, target)); - var expected = new RuntimeMetadata() - { - Label = "X", - FormattedNonQubitArgs = "", - IsAdjoint = false, - IsControlled = true, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = new List() { control }, - Targets = new List() { target }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + var control = new FreeQubit(1); + var target = new FreeQubit(0); + var op = sim.Get(); + var args = op.__DataIn__((control, target)); + var expected = new RuntimeMetadata() + { + Label = "X", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = true, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { control }, + Targets = new List() { target }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } [Fact] public void CCNOT() { - var control1 = new FreeQubit(0); - var control2 = new FreeQubit(2); - var target = new FreeQubit(1); - var op = new QuantumSimulator().Get(); - var args = op.__DataIn__((control1, control2, target)); - var expected = new RuntimeMetadata() - { - Label = "X", - FormattedNonQubitArgs = "", - IsAdjoint = false, - IsControlled = true, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = new List() { control1, control2 }, - Targets = new List() { target }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + var control1 = new FreeQubit(0); + var control2 = new FreeQubit(2); + var target = new FreeQubit(1); + var op = sim.Get(); + var args = op.__DataIn__((control1, control2, target)); + var expected = new RuntimeMetadata() + { + Label = "X", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = true, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { control1, control2 }, + Targets = new List() { target }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } [Fact] public void Swap() { - var q1 = new FreeQubit(0); - var q2 = new FreeQubit(1); - var op = new QuantumSimulator().Get(); - var args = op.__DataIn__((q1, q2)); - var expected = new RuntimeMetadata() - { - Label = "SWAP", - FormattedNonQubitArgs = "", - IsAdjoint = false, - IsControlled = false, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { q1, q2 }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + var q1 = new FreeQubit(0); + var q2 = new FreeQubit(1); + var op = sim.Get(); + var args = op.__DataIn__((q1, q2)); + var expected = new RuntimeMetadata() + { + Label = "SWAP", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { q1, q2 }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } [Fact] public void Ry() { - var target = new FreeQubit(0); - var op = new QuantumSimulator().Get(); - var args = op.__DataIn__((2.1, target)); - var expected = new RuntimeMetadata() - { - Label = "Ry", - FormattedNonQubitArgs = "(" + 2.1 + ")", - IsAdjoint = false, - IsControlled = false, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { target }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + var target = new FreeQubit(0); + var op = sim.Get(); + var args = op.__DataIn__((2.1, target)); + var expected = new RuntimeMetadata() + { + Label = "Ry", + FormattedNonQubitArgs = "(" + 2.1 + ")", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { target }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } [Fact] public void M() { - var measureQubit = new FreeQubit(0); - var op = new QuantumSimulator().Get(); - var args = op.__DataIn__(measureQubit); - var expected = new RuntimeMetadata() - { - Label = "M", - FormattedNonQubitArgs = "", - IsAdjoint = false, - IsControlled = false, - IsMeasurement = true, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { measureQubit }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + var measureQubit = new FreeQubit(0); + var op = sim.Get(); + var args = op.__DataIn__(measureQubit); + var expected = new RuntimeMetadata() + { + Label = "M", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = true, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { measureQubit }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } [Fact] public void Reset() { - var target = new FreeQubit(0); - var op = new QuantumSimulator().Get(); - var args = op.__DataIn__(target); - var expected = new RuntimeMetadata() - { - Label = "Reset", - FormattedNonQubitArgs = "", - IsAdjoint = false, - IsControlled = false, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { target }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + var target = new FreeQubit(0); + var op = sim.Get(); + var args = op.__DataIn__(target); + var expected = new RuntimeMetadata() + { + Label = "Reset", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { target }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } [Fact] public void ResetAll() { - IQArray targets = new QArray(new[] { new FreeQubit(0) }); - var op = new QuantumSimulator().Get(); - var args = op.__DataIn__(targets); - var expected = new RuntimeMetadata() - { - Label = "ResetAll", - FormattedNonQubitArgs = "", - IsAdjoint = false, - IsControlled = false, - IsMeasurement = false, - IsComposite = true, - Children = null, - Controls = new List() { }, - Targets = targets, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + IQArray targets = new QArray(new[] { new FreeQubit(0) }); + var op = sim.Get(); + var args = op.__DataIn__(targets); + var expected = new RuntimeMetadata() + { + Label = "ResetAll", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = true, + Children = null, + Controls = new List() { }, + Targets = targets, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } } @@ -347,180 +464,299 @@ public class MeasurementTests [Fact] public void MResetX() { - var measureQubit = new FreeQubit(0); - var op = new QuantumSimulator().Get(); - var args = op.__DataIn__(measureQubit); - var expected = new RuntimeMetadata() - { - Label = "MResetX", - FormattedNonQubitArgs = "", - IsAdjoint = false, - IsControlled = false, - IsMeasurement = true, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { measureQubit }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + var measureQubit = new FreeQubit(0); + var op = sim.Get(); + var args = op.__DataIn__(measureQubit); + var expected = new RuntimeMetadata() + { + Label = "MResetX", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = true, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { measureQubit }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } [Fact] public void MResetY() { - var measureQubit = new FreeQubit(0); - var op = new QuantumSimulator().Get(); - var args = op.__DataIn__(measureQubit); - var expected = new RuntimeMetadata() - { - Label = "MResetY", - FormattedNonQubitArgs = "", - IsAdjoint = false, - IsControlled = false, - IsMeasurement = true, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { measureQubit }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + var measureQubit = new FreeQubit(0); + var op = sim.Get(); + var args = op.__DataIn__(measureQubit); + var expected = new RuntimeMetadata() + { + Label = "MResetY", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = true, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { measureQubit }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } [Fact] public void MResetZ() { - var measureQubit = new FreeQubit(0); - var op = new QuantumSimulator().Get(); - var args = op.__DataIn__(measureQubit); - var expected = new RuntimeMetadata() - { - Label = "MResetZ", - FormattedNonQubitArgs = "", - IsAdjoint = false, - IsControlled = false, - IsMeasurement = true, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { measureQubit }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + var measureQubit = new FreeQubit(0); + var op = sim.Get(); + var args = op.__DataIn__(measureQubit); + var expected = new RuntimeMetadata() + { + Label = "MResetZ", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = true, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { measureQubit }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } } - public class CustomCircuitTests + public class CustomCircuitTests : SimulatorFactoryProvider { [Fact] public void EmptyOperation() { - var measureQubit = new FreeQubit(0); - var op = new QuantumSimulator().Get(); - var args = op.__DataIn__(QVoid.Instance); - var expected = new RuntimeMetadata() - { - Label = "Empty", - FormattedNonQubitArgs = "", - IsAdjoint = false, - IsControlled = false, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + var measureQubit = new FreeQubit(0); + var op = sim.Get(); + var args = op.__DataIn__(QVoid.Instance); + var expected = new RuntimeMetadata() + { + Label = "Empty", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } [Fact] public void OperationAsArgument() { - var q = new FreeQubit(0); - var opArg = new QuantumSimulator().Get(); - var op = new QuantumSimulator().Get(); - var args = op.__DataIn__((opArg, q)); - var expected = new RuntimeMetadata() + foreach(var factory in simulatorFactories) { - Label = "WrapperOp", - FormattedNonQubitArgs = "(HOp)", - IsAdjoint = false, - IsControlled = false, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { q }, - }; + var sim1 = factory(); + var sim2 = factory(); - Assert.Equal(op.GetRuntimeMetadata(args), expected); + try + { + var q = new FreeQubit(0); + var opArg = sim1.Get(); + var op = sim2.Get(); + var args = op.__DataIn__((opArg, q)); + var expected = new RuntimeMetadata() + { + Label = "WrapperOp", + FormattedNonQubitArgs = "(HOp)", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { q }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim2.Dispose(); + sim1.Dispose(); + } + } } [Fact] public void NestedOperation() { - var op = new QuantumSimulator().Get(); - var args = op.__DataIn__(QVoid.Instance); - var expected = new RuntimeMetadata() - { - Label = "NestedOp", - FormattedNonQubitArgs = "", - IsAdjoint = false, - IsControlled = false, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + var op = sim.Get(); + var args = op.__DataIn__(QVoid.Instance); + var expected = new RuntimeMetadata() + { + Label = "NestedOp", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } [Fact] public void DuplicateQubitArgs() { - var q = new FreeQubit(0); - var op = new QuantumSimulator().Get(); - var args = op.__DataIn__((q, q)); - var expected = new RuntimeMetadata() - { - Label = "TwoQubitOp", - FormattedNonQubitArgs = "", - IsAdjoint = false, - IsControlled = false, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { q }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + var q = new FreeQubit(0); + var op = sim.Get(); + var args = op.__DataIn__((q, q)); + var expected = new RuntimeMetadata() + { + Label = "TwoQubitOp", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { q }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } [Fact] public void QArrayArgs() { - var op = new QuantumSimulator().Get(); - IQArray bits = new QArray(new bool[] { false, true }); - var args = op.__DataIn__(bits); - var expected = new RuntimeMetadata() - { - Label = "BoolArrayOp", - FormattedNonQubitArgs = "[False, True]", - IsAdjoint = false, - IsControlled = false, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + var op = sim.Get(); + IQArray bits = new QArray(new bool[] { false, true }); + var args = op.__DataIn__(bits); + var expected = new RuntimeMetadata() + { + Label = "BoolArrayOp", + FormattedNonQubitArgs = "[False, True]", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } } @@ -529,23 +765,38 @@ public class UDTTests [Fact] public void FooUDTOp() { - Qubit target = new FreeQubit(0); - var op = new QuantumSimulator().Get(); - var args = op.__DataIn__(new Circuits.FooUDT(("bar", (target, 2.1)))); - var expected = new RuntimeMetadata() - { - Label = "FooUDTOp", - FormattedNonQubitArgs = "(\"bar\", (" + 2.1 + "))", - IsAdjoint = false, - IsControlled = false, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { target }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + Qubit target = new FreeQubit(0); + var op = sim.Get(); + var args = op.__DataIn__(new Circuits.FooUDT(("bar", (target, 2.1)))); + var expected = new RuntimeMetadata() + { + Label = "FooUDTOp", + FormattedNonQubitArgs = "(\"bar\", (" + 2.1 + "))", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { target }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } } @@ -554,218 +805,353 @@ public class ControlledOpTests [Fact] public void ControlledH() { - IQArray controls = new QArray(new[] { new FreeQubit(0) }); - Qubit target = new FreeQubit(1); - var op = new QuantumSimulator().Get().Controlled; - var args = op.__DataIn__((controls, target)); - var expected = new RuntimeMetadata() - { - Label = "H", - FormattedNonQubitArgs = "", - IsAdjoint = false, - IsControlled = true, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = controls, - Targets = new List() { target }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + IQArray controls = new QArray(new[] { new FreeQubit(0) }); + Qubit target = new FreeQubit(1); + var op = sim.Get().Controlled; + var args = op.__DataIn__((controls, target)); + var expected = new RuntimeMetadata() + { + Label = "H", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = true, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = controls, + Targets = new List() { target }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } [Fact] public void ControlledX() { - IQArray controls = new QArray(new[] { new FreeQubit(0) }); - Qubit target = new FreeQubit(1); - var op = new QuantumSimulator().Get().Controlled; - var args = op.__DataIn__((controls, target)); - var expected = new RuntimeMetadata() - { - Label = "X", - FormattedNonQubitArgs = "", - IsAdjoint = false, - IsControlled = true, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = controls, - Targets = new List() { target }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + IQArray controls = new QArray(new[] { new FreeQubit(0) }); + Qubit target = new FreeQubit(1); + var op = sim.Get().Controlled; + var args = op.__DataIn__((controls, target)); + var expected = new RuntimeMetadata() + { + Label = "X", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = true, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = controls, + Targets = new List() { target }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } [Fact] public void ControlledCNOT() { - IQArray controls = new QArray(new[] { new FreeQubit(0) }); - Qubit control = new FreeQubit(1); - Qubit target = new FreeQubit(2); - var op = new QuantumSimulator().Get().Controlled; - var args = op.__DataIn__((controls, (control, target))); - var expected = new RuntimeMetadata() - { - Label = "X", - FormattedNonQubitArgs = "", - IsAdjoint = false, - IsControlled = true, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = controls.Append(control), - Targets = new List() { target }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + IQArray controls = new QArray(new[] { new FreeQubit(0) }); + Qubit control = new FreeQubit(1); + Qubit target = new FreeQubit(2); + var op = sim.Get().Controlled; + var args = op.__DataIn__((controls, (control, target))); + var expected = new RuntimeMetadata() + { + Label = "X", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = true, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = controls.Append(control), + Targets = new List() { target }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } [Fact] public void ControlledCCNOT() { - Qubit control1 = new FreeQubit(0); - Qubit control2 = new FreeQubit(1); - Qubit control3 = new FreeQubit(2); - Qubit target = new FreeQubit(3); - IQArray controls = new QArray(new[] { control1 }); - var op = new QuantumSimulator().Get().Controlled; - var args = op.__DataIn__((controls, (control2, control3, target))); - var expected = new RuntimeMetadata() - { - Label = "X", - FormattedNonQubitArgs = "", - IsAdjoint = false, - IsControlled = true, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = new List() { control1, control2, control3 }, - Targets = new List() { target }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + Qubit control1 = new FreeQubit(0); + Qubit control2 = new FreeQubit(1); + Qubit control3 = new FreeQubit(2); + Qubit target = new FreeQubit(3); + IQArray controls = new QArray(new[] { control1 }); + var op = sim.Get().Controlled; + var args = op.__DataIn__((controls, (control2, control3, target))); + var expected = new RuntimeMetadata() + { + Label = "X", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = true, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { control1, control2, control3 }, + Targets = new List() { target }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } } - public class AdjointTests + public class AdjointTests : SimulatorFactoryProvider { [Fact] public void AdjointH() { - Qubit target = new FreeQubit(0); - var op = new QuantumSimulator().Get().Adjoint; - var args = op.__DataIn__(target); - var expected = new RuntimeMetadata() - { - Label = "H", - FormattedNonQubitArgs = "", - IsAdjoint = true, - IsControlled = false, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { target }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + Qubit target = new FreeQubit(0); + var op = sim.Get().Adjoint; + var args = op.__DataIn__(target); + var expected = new RuntimeMetadata() + { + Label = "H", + FormattedNonQubitArgs = "", + IsAdjoint = true, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { target }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } [Fact] public void AdjointX() { - Qubit target = new FreeQubit(0); - var op = new QuantumSimulator().Get().Adjoint; - var args = op.__DataIn__(target); - var expected = new RuntimeMetadata() - { - Label = "X", - FormattedNonQubitArgs = "", - IsAdjoint = true, - IsControlled = false, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { target }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + Qubit target = new FreeQubit(0); + var op = sim.Get().Adjoint; + var args = op.__DataIn__(target); + var expected = new RuntimeMetadata() + { + Label = "X", + FormattedNonQubitArgs = "", + IsAdjoint = true, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { target }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } [Fact] public void AdjointAdjointH() { - Qubit target = new FreeQubit(0); - var op = new QuantumSimulator().Get().Adjoint.Adjoint; - var args = op.__DataIn__(target); - var expected = new RuntimeMetadata() - { - Label = "H", - FormattedNonQubitArgs = "", - IsAdjoint = false, - IsControlled = false, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { target }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + Qubit target = new FreeQubit(0); + var op = sim.Get().Adjoint.Adjoint; + var args = op.__DataIn__(target); + var expected = new RuntimeMetadata() + { + Label = "H", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { target }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } [Fact] public void ControlledAdjointH() { - IQArray controls = new QArray(new[] { new FreeQubit(0) }); - Qubit target = new FreeQubit(1); - var op1 = new QuantumSimulator().Get().Controlled.Adjoint; - var op2 = new QuantumSimulator().Get().Adjoint.Controlled; - var args = op1.__DataIn__((controls, target)); - var expected = new RuntimeMetadata() + foreach(var factory in simulatorFactories) { - Label = "H", - FormattedNonQubitArgs = "", - IsAdjoint = true, - IsControlled = true, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = controls, - Targets = new List() { target }, - }; + var sim1 = factory(); + var sim2 = factory(); - Assert.Equal(op1.GetRuntimeMetadata(args), expected); - Assert.Equal(op2.GetRuntimeMetadata(args), expected); + try + { + IQArray controls = new QArray(new[] { new FreeQubit(0) }); + Qubit target = new FreeQubit(1); + var op1 = sim1.Get().Controlled.Adjoint; + var op2 = sim2.Get().Adjoint.Controlled; + var args = op1.__DataIn__((controls, target)); + var expected = new RuntimeMetadata() + { + Label = "H", + FormattedNonQubitArgs = "", + IsAdjoint = true, + IsControlled = true, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = controls, + Targets = new List() { target }, + }; + + Assert.Equal(op1.GetRuntimeMetadata(args), expected); + Assert.Equal(op2.GetRuntimeMetadata(args), expected); + } + finally + { + sim2.Dispose(); + sim1.Dispose(); + } + } } [Fact] public void ControlledAdjointAdjointH() { - IQArray controls = new QArray(new[] { new FreeQubit(0) }); - Qubit target = new FreeQubit(1); - var op1 = new QuantumSimulator().Get().Controlled.Adjoint.Adjoint; - var op2 = new QuantumSimulator().Get().Adjoint.Controlled.Adjoint; - var op3 = new QuantumSimulator().Get().Adjoint.Adjoint.Controlled; - var args = op1.__DataIn__((controls, target)); - var expected = new RuntimeMetadata() + foreach(var factory in simulatorFactories) { - Label = "H", - FormattedNonQubitArgs = "", - IsAdjoint = false, - IsControlled = true, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = controls, - Targets = new List() { target }, - }; + var sim1 = factory(); + var sim2 = factory(); + var sim3 = factory(); - Assert.Equal(op1.GetRuntimeMetadata(args), expected); - Assert.Equal(op2.GetRuntimeMetadata(args), expected); - Assert.Equal(op3.GetRuntimeMetadata(args), expected); + try + { + IQArray controls = new QArray(new[] { new FreeQubit(0) }); + Qubit target = new FreeQubit(1); + var op1 = sim1.Get().Controlled.Adjoint.Adjoint; + var op2 = sim2.Get().Adjoint.Controlled.Adjoint; + var op3 = sim3.Get().Adjoint.Adjoint.Controlled; + var args = op1.__DataIn__((controls, target)); + var expected = new RuntimeMetadata() + { + Label = "H", + FormattedNonQubitArgs = "", + IsAdjoint = false, + IsControlled = true, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = controls, + Targets = new List() { target }, + }; + + Assert.Equal(op1.GetRuntimeMetadata(args), expected); + Assert.Equal(op2.GetRuntimeMetadata(args), expected); + Assert.Equal(op3.GetRuntimeMetadata(args), expected); + } + finally + { + sim3.Dispose(); + sim2.Dispose(); + sim1.Dispose(); + } + } } } @@ -775,47 +1161,77 @@ public class PartialOpTests [Fact] public void PartialRy() { - var target = new FreeQubit(0); - var op = new QuantumSimulator().Get().Partial((double d) => - new ValueTuple(d, target)); - var args = op.__DataIn__(2.1); - var expected = new RuntimeMetadata() - { - Label = "Ry", - FormattedNonQubitArgs = "(" + 2.1 + ")", - IsAdjoint = false, - IsControlled = false, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { target }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + var target = new FreeQubit(0); + var op = sim.Get().Partial((double d) => + new ValueTuple(d, target)); + var args = op.__DataIn__(2.1); + var expected = new RuntimeMetadata() + { + Label = "Ry", + FormattedNonQubitArgs = "(" + 2.1 + ")", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { target }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } [Fact] public void PartialUDT() { - var target = new FreeQubit(0); - var op = new QuantumSimulator().Get>(typeof(Circuits.FooUDT)) - .Partial((double d) => (("bar", (target, d)))); - var args = new QTuple(2.1); - var expected = new RuntimeMetadata() - { - Label = "FooUDT", - FormattedNonQubitArgs = "(\"bar\", (" + 2.1 + "))", - IsAdjoint = false, - IsControlled = false, - IsMeasurement = false, - IsComposite = false, - Children = null, - Controls = new List() { }, - Targets = new List() { }, + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() }; - Assert.Equal(op.GetRuntimeMetadata(args), expected); + foreach (var sim in simulators) + { + try + { + var target = new FreeQubit(0); + var op = sim.Get>(typeof(Circuits.FooUDT)) + .Partial((double d) => (("bar", (target, d)))); + var args = new QTuple(2.1); + var expected = new RuntimeMetadata() + { + Label = "FooUDT", + FormattedNonQubitArgs = "(\"bar\", (" + 2.1 + "))", + IsAdjoint = false, + IsControlled = false, + IsMeasurement = false, + IsComposite = false, + Children = null, + Controls = new List() { }, + Targets = new List() { }, + }; + + Assert.Equal(op.GetRuntimeMetadata(args), expected); + } + finally + { + sim.Dispose(); + } + } } } } diff --git a/src/Simulation/Simulators.Tests/StackTraceTests.cs b/src/Simulation/Simulators.Tests/StackTraceTests.cs index dedab0e4ddf..1cca42c0cbc 100644 --- a/src/Simulation/Simulators.Tests/StackTraceTests.cs +++ b/src/Simulation/Simulators.Tests/StackTraceTests.cs @@ -31,40 +31,53 @@ public StackTraceTests(ITestOutputHelper output) [Fact] public void AllocateQubit2Test() { - using var sim = new QuantumSimulator(); - try - { - IgnorableAssert.Disable(); - QVoid res = sim.Execute(QVoid.Instance); - } - catch (ExecutionFailException) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { - var stackFrames = sim.CallStack; + try + { + IgnorableAssert.Disable(); + QVoid res = sim.Execute(QVoid.Instance); + } + catch (ExecutionFailException) + { + var stackFrames = sim.CallStack; - // Make sure that the call stack isn't null before proceeding. - Assert.NotNull(stackFrames); + // Make sure that the call stack isn't null before proceeding. + Assert.NotNull(stackFrames); - // The following assumes that Assert is on Q# stack. - Assert.Equal(2, stackFrames!.Length); + // The following assumes that Assert is on Q# stack. + Assert.Equal(2, stackFrames!.Length); - Assert.Equal("Microsoft.Quantum.Diagnostics.AssertMeasurementProbability", stackFrames[0].Callable.FullName); - Assert.Equal(namespacePrefix + "AllocateQubit2", stackFrames[1].Callable.FullName); + Assert.Equal("Microsoft.Quantum.Diagnostics.AssertMeasurementProbability", stackFrames[0].Callable.FullName); + Assert.Equal(namespacePrefix + "AllocateQubit2", stackFrames[1].Callable.FullName); - Assert.Equal(OperationFunctor.Body, stackFrames[0].Callable.Variant); - Assert.Equal(OperationFunctor.Body, stackFrames[1].Callable.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[0].Callable.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[1].Callable.Variant); - Assert.Equal(94, stackFrames[1].FailedLineNumber); - } - finally - { - IgnorableAssert.Enable(); + Assert.Equal(94, stackFrames[1].FailedLineNumber); + } + finally + { + IgnorableAssert.Enable(); + sim.Dispose(); + } } } [Fact] public void AlwaysFail4Test() { - using (var sim = new QuantumSimulator()) + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) { try { @@ -119,6 +132,10 @@ public void AlwaysFail4Test() output.WriteLine(stackFrames[i].GetOperationSourceFromPDB()); } } + finally + { + sim.Dispose(); + } } } diff --git a/src/Simulation/Simulators.Tests/TestProjects/HoneywellExe/HoneywellExe.csproj b/src/Simulation/Simulators.Tests/TestProjects/HoneywellExe/HoneywellExe.csproj index 0f398b00f5b..f8341701491 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/HoneywellExe/HoneywellExe.csproj +++ b/src/Simulation/Simulators.Tests/TestProjects/HoneywellExe/HoneywellExe.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net6.0 false false diff --git a/src/Simulation/Simulators.Tests/TestProjects/HoneywellExe/HoneywellSimulation.qs b/src/Simulation/Simulators.Tests/TestProjects/HoneywellExe/HoneywellSimulation.qs index 5325dca658a..61c00c06b53 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/HoneywellExe/HoneywellSimulation.qs +++ b/src/Simulation/Simulators.Tests/TestProjects/HoneywellExe/HoneywellSimulation.qs @@ -9,288 +9,336 @@ namespace Microsoft.Quantum.Simulation.Testing.Honeywell { @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation MeasureInMiddleTest() : Unit { MeasureInMiddle(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation QubitAfterMeasurementTest() : Unit { QubitAfterMeasurement(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation BranchOnMeasurementTest() : Unit { BranchOnMeasurement(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation BasicLiftTest() : Unit { BasicLift(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation LiftLoopsTest() : Unit { LiftLoops(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation LiftSingleNonCallTest() : Unit { LiftSingleNonCall(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation LiftSelfContainedMutableTest() : Unit { LiftSelfContainedMutable(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ArgumentsPartiallyResolveTypeParametersTest() : Unit { ArgumentsPartiallyResolveTypeParameters(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation LiftFunctorApplicationTest() : Unit { LiftFunctorApplication(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation LiftPartialApplicationTest() : Unit { LiftPartialApplication(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation LiftArrayItemCallTest() : Unit { LiftArrayItemCall(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation LiftOneNotBothTest() : Unit { LiftOneNotBoth(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ApplyIfZeroTest() : Unit { ApplyIfZero_Test(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ApplyIfOneTest() : Unit { ApplyIfOne_Test(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ApplyIfZeroElseOneTest() : Unit { ApplyIfZeroElseOne(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ApplyIfOneElseZeroTest() : Unit { ApplyIfOneElseZero(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation IfElifTest() : Unit { IfElif(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation AndConditionTest() : Unit { AndCondition(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation OrConditionTest() : Unit { OrCondition(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ApplyConditionallyTest() : Unit { ApplyConditionally(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ApplyConditionallyWithNoOpTest() : Unit { ApplyConditionallyWithNoOp(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation InequalityWithApplyConditionallyTest() : Unit { InequalityWithApplyConditionally(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation InequalityWithApplyIfOneElseZeroTest() : Unit { InequalityWithApplyIfOneElseZero(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation InequalityWithApplyIfZeroElseOneTest() : Unit { InequalityWithApplyIfZeroElseOne(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation InequalityWithApplyIfOneTest() : Unit { InequalityWithApplyIfOne(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation InequalityWithApplyIfZeroTest() : Unit { InequalityWithApplyIfZero(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation LiteralOnTheLeftTest() : Unit { LiteralOnTheLeft(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation GenericsSupportTest() : Unit { GenericsSupport(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation WithinBlockSupportTest() : Unit { WithinBlockSupport(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation AdjointSupportProvidedTest() : Unit { AdjointSupportProvided(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation AdjointSupportSelfTest() : Unit { AdjointSupportSelf(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation AdjointSupportInvertTest() : Unit { AdjointSupportInvert(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledSupportProvidedTest() : Unit { ControlledSupportProvided(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledSupportDistributeTest() : Unit { ControlledSupportDistribute(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportProvided_ProvidedBodyTest() : Unit { ControlledAdjointSupportProvided_ProvidedBody(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportProvided_ProvidedAdjointTest() : Unit { ControlledAdjointSupportProvided_ProvidedAdjoint(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportProvided_ProvidedControlledTest() : Unit { ControlledAdjointSupportProvided_ProvidedControlled(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportProvided_ProvidedAllTest() : Unit { ControlledAdjointSupportProvided_ProvidedAll(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportDistribute_DistributeBodyTest() : Unit { ControlledAdjointSupportDistribute_DistributeBody(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportDistribute_DistributeAdjointTest() : Unit { ControlledAdjointSupportDistribute_DistributeAdjoint(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportDistribute_DistributeControlledTest() : Unit { ControlledAdjointSupportDistribute_DistributeControlled(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportDistribute_DistributeAllTest() : Unit { ControlledAdjointSupportDistribute_DistributeAll(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportInvert_InvertBodyTest() : Unit { ControlledAdjointSupportInvert_InvertBody(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportInvert_InvertAdjointTest() : Unit { ControlledAdjointSupportInvert_InvertAdjoint(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportInvert_InvertControlledTest() : Unit { ControlledAdjointSupportInvert_InvertControlled(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportInvert_InvertAllTest() : Unit { ControlledAdjointSupportInvert_InvertAll(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportSelf_SelfBodyTest() : Unit { ControlledAdjointSupportSelf_SelfBody(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportSelf_SelfControlledTest() : Unit { ControlledAdjointSupportSelf_SelfControlled(); } diff --git a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Arrays/Tests.qs b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Arrays/Tests.qs index 518bb5128ea..959d04e94e2 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Arrays/Tests.qs +++ b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Arrays/Tests.qs @@ -9,6 +9,7 @@ namespace Microsoft.Quantum.Arrays { @Test("QuantumSimulator") @Test("ToffoliSimulator") @Test("Microsoft.Quantum.Experimental.OpenSystemsSimulator") + @Test("SparseSimulator") function EmptyArraysAreEmpty() : Unit { Fact( Length(EmptyArray()) == 0, diff --git a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Bitwise/Tests.qs b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Bitwise/Tests.qs index b30b1a73817..0c21b53017a 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Bitwise/Tests.qs +++ b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Bitwise/Tests.qs @@ -6,26 +6,31 @@ namespace Microsoft.Quantum.Tests { open Microsoft.Quantum.Diagnostics; @Test("QuantumSimulator") + @Test("SparseSimulator") function XorIsCorrect() : Unit { EqualityFactI(199, Xor(248, 63), "Xor was incorrect."); } @Test("QuantumSimulator") + @Test("SparseSimulator") function AndIsCorrect() : Unit { EqualityFactI(56, And(248, 63), "And was incorrect."); } @Test("QuantumSimulator") + @Test("SparseSimulator") function OrIsCorrect() : Unit { EqualityFactI(255, Or(248, 63), "Or was incorrect."); } @Test("QuantumSimulator") + @Test("SparseSimulator") function NotIsCorrect() : Unit { EqualityFactI(-249, Not(248), "Not was incorrect."); } @Test("QuantumSimulator") + @Test("SparseSimulator") function XBitsIsCorrect() : Unit { // We expect this to be 3, 3 = 0011₂. In little endian representation, // we start with the 1s digit, so we get that 3 is what we get from @@ -34,6 +39,7 @@ namespace Microsoft.Quantum.Tests { } @Test("QuantumSimulator") + @Test("SparseSimulator") function ZBitsIsCorrect() : Unit { EqualityFactI(ZBits([PauliX, PauliY, PauliZ, PauliI]), 6, "XBits was incorrect."); } diff --git a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Convert/Tests.qs b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Convert/Tests.qs index 436fc5ebbb3..ff2c2edff3c 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Convert/Tests.qs +++ b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Convert/Tests.qs @@ -6,11 +6,13 @@ namespace Microsoft.Quantum.Tests { open Microsoft.Quantum.Diagnostics; @Test("QuantumSimulator") + @Test("SparseSimulator") function DoubleAsStringIsCorrect() : Unit { EqualityFactS(DoubleAsString(12.345), "12.345", "DoubleAsString was incorrect."); } @Test("QuantumSimulator") + @Test("SparseSimulator") function IntAsStringIsCorrect() : Unit { EqualityFactS(IntAsString(12345), "12345", "IntAsString was incorrect."); } diff --git a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/IntrinsicTests.csproj b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/IntrinsicTests.csproj index f1f41173fbc..9b0a01262cb 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/IntrinsicTests.csproj +++ b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/IntrinsicTests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net6.0 false false diff --git a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Math/Tests.qs b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Math/Tests.qs index f252b3f0309..7551b0d0e81 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Math/Tests.qs +++ b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Math/Tests.qs @@ -6,6 +6,7 @@ namespace Microsoft.Quantum.Tests { open Microsoft.Quantum.Diagnostics; @Test("QuantumSimulator") + @Test("SparseSimulator") function AbsDIsCorrect() : Unit { EqualityFactD(AbsD(-1.23), 1.23, "AbsD was incorrect for negative numbers."); EqualityFactD(AbsD(1.23), 1.23, "AbsD was incorrect for positive numbers."); @@ -13,6 +14,7 @@ namespace Microsoft.Quantum.Tests { } @Test("QuantumSimulator") + @Test("SparseSimulator") function AbsIIsCorrect() : Unit { EqualityFactI(AbsI(-123), 123, "AbsI was incorrect for negative numbers."); EqualityFactI(AbsI(123), 123, "AbsI was incorrect for positive numbers."); @@ -20,6 +22,7 @@ namespace Microsoft.Quantum.Tests { } @Test("QuantumSimulator") + @Test("SparseSimulator") function AbsLIsCorrect() : Unit { EqualityFactL(AbsL(-123L), 123L, "AbsL was incorrect for negative numbers."); EqualityFactL(AbsL(123L), 123L, "AbsL was incorrect for positive numbers."); @@ -27,6 +30,7 @@ namespace Microsoft.Quantum.Tests { } @Test("QuantumSimulator") + @Test("SparseSimulator") function Log10IsCorrect() : Unit { EqualityWithinToleranceFact(Log10(0.456), -0.341035157335565, 1e-7, "Log10(0.456) was incorrect."); EqualityWithinToleranceFact(Log10(1.0), 0.0, 1e-7, "Log10(1.0) was incorrect."); @@ -37,16 +41,19 @@ namespace Microsoft.Quantum.Tests { } @Test("QuantumSimulator") + @Test("SparseSimulator") function MaxDIsCorrect() : Unit { EqualityFactD(MaxD(-1.0, 2.0), 2.0, "MaxD was incorrect."); } @Test("QuantumSimulator") + @Test("SparseSimulator") function MaxIIsCorrect() : Unit { EqualityFactI(MaxI(-1, 2), 2, "MaxI was incorrect."); } @Test("QuantumSimulator") + @Test("SparseSimulator") function CeilingIsCorrect() : Unit { EqualityFactI(Ceiling(3.1), 4, "Ceiling(3.1) was incorrect."); EqualityFactI(Ceiling(3.7), 4, "Ceiling(3.7) was incorrect."); @@ -55,6 +62,7 @@ namespace Microsoft.Quantum.Tests { } @Test("QuantumSimulator") + @Test("SparseSimulator") function FloorIsCorrect() : Unit { EqualityFactI(Floor(3.1), 3, "Floor(3.1) was incorrect."); EqualityFactI(Floor(3.7), 3, "Floor(3.7) was incorrect."); @@ -63,6 +71,7 @@ namespace Microsoft.Quantum.Tests { } @Test("QuantumSimulator") + @Test("SparseSimulator") function RoundIsCorrect() : Unit { EqualityFactI(Round(3.1), 3, "Round(3.1) was incorrect."); EqualityFactI(Round(3.7), 4, "Round(3.7) was incorrect."); @@ -71,6 +80,7 @@ namespace Microsoft.Quantum.Tests { } @Test("QuantumSimulator") + @Test("SparseSimulator") function PowDIsCorrect() : Unit { EqualityWithinToleranceFact(PowD(2.1234, 3.4567), 13.5036405192181, 1e-7, "PowD(2.1234, 3.4567) was incorrect."); EqualityWithinToleranceFact(PowD(0.4567, 9.10111213), 0.000798479316935851, 1e-8, "PowD(0.4567, 9.10111213) was incorrect."); @@ -78,6 +88,7 @@ namespace Microsoft.Quantum.Tests { } @Test("QuantumSimulator") + @Test("SparseSimulator") function ModPowLIsCorrect() : Unit { EqualityFactL(ModPowL(117L, 391L, 119L), 110L, "ModPowL(117L, 391L, 119L) was incorrect."); EqualityFactL(ModPowL(117L, 5792L, 119L), 18L, "ModPowL(117L, 5792L, 119L) was incorrect."); @@ -85,6 +96,7 @@ namespace Microsoft.Quantum.Tests { } @Test("QuantumSimulator") + @Test("SparseSimulator") function NaNIsNotEqualToAnything() : Unit { Contradiction(NaN() == NaN(), "NaN should not equal NaN."); Contradiction(NaN() == 42.0, "NaN should not equal any finite number."); @@ -92,6 +104,7 @@ namespace Microsoft.Quantum.Tests { } @Test("QuantumSimulator") + @Test("SparseSimulator") function NaNIsNaN() : Unit { Fact(IsNaN(NaN()), "NaN was not NaN."); Contradiction(IsNaN(42.0), "42.0 should not be NaN."); @@ -99,6 +112,7 @@ namespace Microsoft.Quantum.Tests { } @Test("QuantumSimulator") + @Test("SparseSimulator") function InfinityIsInfinite() : Unit { Contradiction(IsInfinite(NaN()), "NaN should not be infinite."); Contradiction(IsInfinite(42.0), "42.0 should not be infinite."); @@ -107,6 +121,7 @@ namespace Microsoft.Quantum.Tests { } @Test("QuantumSimulator") + @Test("SparseSimulator") function FiniteNumbersAreFinite() : Unit { Contradiction(IsFinite(NaN()), "NaN should not be finite."); Fact(IsFinite(42.0), "42.0 should be finite."); @@ -115,6 +130,7 @@ namespace Microsoft.Quantum.Tests { } @Test("QuantumSimulator") + @Test("SparseSimulator") function FiniteFactIsCorrect() : Unit { FiniteFact(42.0, "42.0 should be finite."); } diff --git a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Random/Tests.qs b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Random/Tests.qs index 3dfe4f48f75..ea0c7f4fb44 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Random/Tests.qs +++ b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Random/Tests.qs @@ -61,6 +61,7 @@ namespace Microsoft.Quantum.Tests { @Test("QuantumSimulator") @Test("ToffoliSimulator") @Test("Microsoft.Quantum.Experimental.OpenSystemsSimulator") + @Test("SparseSimulator") operation CheckDrawRandomDoubleObeysRanges() : Unit { for j in 0..10000 { let random = DrawRandomDouble(0.0, 1.0); @@ -75,6 +76,7 @@ namespace Microsoft.Quantum.Tests { @Test("QuantumSimulator") @Test("ToffoliSimulator") @Test("Microsoft.Quantum.Experimental.OpenSystemsSimulator") + @Test("SparseSimulator") operation CheckDrawRandomIntObeysRanges() : Unit { mutable randomInt = DrawRandomInt(0, 45); if (randomInt > 45 or randomInt < 0) { @@ -96,11 +98,12 @@ namespace Microsoft.Quantum.Tests { fail $"DrawRandomInt(3, 3) returned {randomInt}, outside the allowed range."; } } - + /// # Summary /// Checks that @"microsoft.quantum.random.continuousuniformdistribution" has the /// expected moments. @Test("QuantumSimulator") + @Test("SparseSimulator") operation CheckContinuousUniformDistributionHasRightMoments() : Unit { CheckMeanAndVariance( "uniform", @@ -115,6 +118,7 @@ namespace Microsoft.Quantum.Tests { /// Checks that @"microsoft.quantum.random.standardnormaldistribution" has the /// expected moments. @Test("QuantumSimulator") + @Test("SparseSimulator") operation CheckStandardNormalDistributionHasRightMoments() : Unit { CheckMeanAndVariance( "standard normal", @@ -129,6 +133,7 @@ namespace Microsoft.Quantum.Tests { /// Checks that @"microsoft.quantum.random.normaldistribution" has the /// expected moments. @Test("QuantumSimulator") + @Test("SparseSimulator") operation CheckNormalDistributionHasRightMoments() : Unit { CheckMeanAndVariance( "normal(-2.0, 5.0)", @@ -145,6 +150,7 @@ namespace Microsoft.Quantum.Tests { /// trial, it is entirely characterized by its first moment; we don't need /// to check variance here. @Test("QuantumSimulator") + @Test("SparseSimulator") operation CheckDrawRandomBoolHasRightExpectation() : Unit { // NB: DrawMany isn't available yet, since it's in the // Microsoft.Quantum.Standard package, not QSharpCore. @@ -172,6 +178,7 @@ namespace Microsoft.Quantum.Tests { /// # Summary /// Checks that DrawCategorical never draws elements with probability zero. @Test("QuantumSimulator") + @Test("SparseSimulator") operation CheckImpossibleEventsAreNotDrawn() : Unit { let distribution = CategoricalDistribution([0.5, 0.0, 0.5]); let nTrials = 100000; @@ -183,7 +190,7 @@ namespace Microsoft.Quantum.Tests { ); } } - + // We define a couple callables to help us run continuous tests on discrete // distributions as well. @@ -196,6 +203,7 @@ namespace Microsoft.Quantum.Tests { } @Test("QuantumSimulator") + @Test("SparseSimulator") operation CheckCategoricalMomentsAreCorrect() : Unit { let categorical = DiscreteAsContinuous( CategoricalDistribution([0.2, 0.5, 0.3]) @@ -204,7 +212,7 @@ namespace Microsoft.Quantum.Tests { let variance = PowD(0.0 - expected, 2.0) * 0.2 + PowD(1.0 - expected, 2.0) * 0.5 + PowD(2.0 - expected, 2.0) * 0.3; - + CheckMeanAndVariance( "categorical([0.2, 0.5, 0.3])", categorical, @@ -215,6 +223,7 @@ namespace Microsoft.Quantum.Tests { } @Test("QuantumSimulator") + @Test("SparseSimulator") operation CheckRescaledCategoricalMomentsAreCorrect() : Unit { let categorical = DiscreteAsContinuous( CategoricalDistribution([2.0, 5.0, 3.0]) @@ -223,7 +232,7 @@ namespace Microsoft.Quantum.Tests { let variance = PowD(0.0 - expected, 2.0) * 0.2 + PowD(1.0 - expected, 2.0) * 0.5 + PowD(2.0 - expected, 2.0) * 0.3; - + CheckMeanAndVariance( "categorical([0.2, 0.5, 0.3])", categorical, @@ -232,8 +241,9 @@ namespace Microsoft.Quantum.Tests { 0.04 ); } - + @Test("QuantumSimulator") + @Test("SparseSimulator") operation CheckCategoricalHistogramIsCorrect() : Unit { let categorical = CategoricalDistribution([0.2, 0.5, 0.3]); mutable counts = new Int[3]; @@ -250,6 +260,7 @@ namespace Microsoft.Quantum.Tests { } @Test("QuantumSimulator") + @Test("SparseSimulator") operation CheckDiscreteUniformMomentsAreCorrect() : Unit { let (min, max) = (-3, 7); let expected = 0.5 * (IntAsDouble(min + max)); diff --git a/src/Simulation/Simulators.Tests/TestProjects/IonQExe/IonQExe.csproj b/src/Simulation/Simulators.Tests/TestProjects/IonQExe/IonQExe.csproj index 09f2810e6f2..8f31d2f0139 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/IonQExe/IonQExe.csproj +++ b/src/Simulation/Simulators.Tests/TestProjects/IonQExe/IonQExe.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net6.0 false false diff --git a/src/Simulation/Simulators.Tests/TestProjects/IonQExe/IonQSimulation.qs b/src/Simulation/Simulators.Tests/TestProjects/IonQExe/IonQSimulation.qs index 46ae721dce1..b848aa753d4 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/IonQExe/IonQSimulation.qs +++ b/src/Simulation/Simulators.Tests/TestProjects/IonQExe/IonQSimulation.qs @@ -8,12 +8,14 @@ namespace Microsoft.Quantum.Simulation.Testing.IonQ { @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation MeasureInMiddleTest() : Unit { MeasureInMiddle(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation QubitAfterMeasurementTest() : Unit { QubitAfterMeasurement(); } diff --git a/src/Simulation/Simulators.Tests/TestProjects/MicrosoftSimulatorExe/MicrosoftSimulatorExe.csproj b/src/Simulation/Simulators.Tests/TestProjects/MicrosoftSimulatorExe/MicrosoftSimulatorExe.csproj index a784cdb95d2..376b1bf8327 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/MicrosoftSimulatorExe/MicrosoftSimulatorExe.csproj +++ b/src/Simulation/Simulators.Tests/TestProjects/MicrosoftSimulatorExe/MicrosoftSimulatorExe.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net6.0 microsoft.simulator diff --git a/src/Simulation/Simulators.Tests/TestProjects/QCIExe/QCIExe.csproj b/src/Simulation/Simulators.Tests/TestProjects/QCIExe/QCIExe.csproj index 578b29c890a..1db0baf4488 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/QCIExe/QCIExe.csproj +++ b/src/Simulation/Simulators.Tests/TestProjects/QCIExe/QCIExe.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net6.0 false false diff --git a/src/Simulation/Simulators.Tests/TestProjects/QCIExe/QCISimulation.qs b/src/Simulation/Simulators.Tests/TestProjects/QCIExe/QCISimulation.qs index 8585758f894..37ddea0b100 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/QCIExe/QCISimulation.qs +++ b/src/Simulation/Simulators.Tests/TestProjects/QCIExe/QCISimulation.qs @@ -9,288 +9,336 @@ namespace Microsoft.Quantum.Simulation.Testing.QCI { @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation MeasureInMiddleTest() : Unit { MeasureInMiddle(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation QubitAfterMeasurementTest() : Unit { QubitAfterMeasurement(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation BranchOnMeasurementTest() : Unit { BranchOnMeasurement(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation BasicLiftTest() : Unit { BasicLift(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation LiftLoopsTest() : Unit { LiftLoops(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation LiftSingleNonCallTest() : Unit { LiftSingleNonCall(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation LiftSelfContainedMutableTest() : Unit { LiftSelfContainedMutable(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ArgumentsPartiallyResolveTypeParametersTest() : Unit { ArgumentsPartiallyResolveTypeParameters(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation LiftFunctorApplicationTest() : Unit { LiftFunctorApplication(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation LiftPartialApplicationTest() : Unit { LiftPartialApplication(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation LiftArrayItemCallTest() : Unit { LiftArrayItemCall(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation LiftOneNotBothTest() : Unit { LiftOneNotBoth(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ApplyIfZeroTest() : Unit { ApplyIfZero_Test(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ApplyIfOneTest() : Unit { ApplyIfOne_Test(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ApplyIfZeroElseOneTest() : Unit { ApplyIfZeroElseOne(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ApplyIfOneElseZeroTest() : Unit { ApplyIfOneElseZero(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation IfElifTest() : Unit { IfElif(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation AndConditionTest() : Unit { AndCondition(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation OrConditionTest() : Unit { OrCondition(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ApplyConditionallyTest() : Unit { ApplyConditionally(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ApplyConditionallyWithNoOpTest() : Unit { ApplyConditionallyWithNoOp(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation InequalityWithApplyConditionallyTest() : Unit { InequalityWithApplyConditionally(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation InequalityWithApplyIfOneElseZeroTest() : Unit { InequalityWithApplyIfOneElseZero(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation InequalityWithApplyIfZeroElseOneTest() : Unit { InequalityWithApplyIfZeroElseOne(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation InequalityWithApplyIfOneTest() : Unit { InequalityWithApplyIfOne(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation InequalityWithApplyIfZeroTest() : Unit { InequalityWithApplyIfZero(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation LiteralOnTheLeftTest() : Unit { LiteralOnTheLeft(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation GenericsSupportTest() : Unit { GenericsSupport(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation WithinBlockSupportTest() : Unit { WithinBlockSupport(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation AdjointSupportProvidedTest() : Unit { AdjointSupportProvided(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation AdjointSupportSelfTest() : Unit { AdjointSupportSelf(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation AdjointSupportInvertTest() : Unit { AdjointSupportInvert(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledSupportProvidedTest() : Unit { ControlledSupportProvided(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledSupportDistributeTest() : Unit { ControlledSupportDistribute(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportProvided_ProvidedBodyTest() : Unit { ControlledAdjointSupportProvided_ProvidedBody(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportProvided_ProvidedAdjointTest() : Unit { ControlledAdjointSupportProvided_ProvidedAdjoint(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportProvided_ProvidedControlledTest() : Unit { ControlledAdjointSupportProvided_ProvidedControlled(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportProvided_ProvidedAllTest() : Unit { ControlledAdjointSupportProvided_ProvidedAll(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportDistribute_DistributeBodyTest() : Unit { ControlledAdjointSupportDistribute_DistributeBody(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportDistribute_DistributeAdjointTest() : Unit { ControlledAdjointSupportDistribute_DistributeAdjoint(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportDistribute_DistributeControlledTest() : Unit { ControlledAdjointSupportDistribute_DistributeControlled(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportDistribute_DistributeAllTest() : Unit { ControlledAdjointSupportDistribute_DistributeAll(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportInvert_InvertBodyTest() : Unit { ControlledAdjointSupportInvert_InvertBody(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportInvert_InvertAdjointTest() : Unit { ControlledAdjointSupportInvert_InvertAdjoint(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportInvert_InvertControlledTest() : Unit { ControlledAdjointSupportInvert_InvertControlled(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportInvert_InvertAllTest() : Unit { ControlledAdjointSupportInvert_InvertAll(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportSelf_SelfBodyTest() : Unit { ControlledAdjointSupportSelf_SelfBody(); } @Test("QuantumSimulator") @Test("ResourcesEstimator") + @Test("SparseSimulator") operation ControlledAdjointSupportSelf_SelfControlledTest() : Unit { ControlledAdjointSupportSelf_SelfControlled(); } diff --git a/src/Simulation/Simulators.Tests/TestProjects/QSharpExe/QSharpExe.csproj b/src/Simulation/Simulators.Tests/TestProjects/QSharpExe/QSharpExe.csproj index 6e39a0df1c6..40b100c89da 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/QSharpExe/QSharpExe.csproj +++ b/src/Simulation/Simulators.Tests/TestProjects/QSharpExe/QSharpExe.csproj @@ -1,7 +1,7 @@ Exe - netcoreapp3.1 + net6.0 false false diff --git a/src/Simulation/Simulators.Tests/TestProjects/QirExe/QirExe.csproj b/src/Simulation/Simulators.Tests/TestProjects/QirExe/QirExe.csproj index 307a706b6d5..68eba7502c1 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/QirExe/QirExe.csproj +++ b/src/Simulation/Simulators.Tests/TestProjects/QirExe/QirExe.csproj @@ -1,7 +1,7 @@ Exe - netcoreapp3.1 + net6.0 false false false diff --git a/src/Simulation/Simulators.Tests/TestProjects/TargetedExe/TargetedExe.csproj b/src/Simulation/Simulators.Tests/TestProjects/TargetedExe/TargetedExe.csproj index 18c8591760c..a6e5eaad9c5 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/TargetedExe/TargetedExe.csproj +++ b/src/Simulation/Simulators.Tests/TestProjects/TargetedExe/TargetedExe.csproj @@ -1,7 +1,7 @@ Exe - netcoreapp3.1 + net6.0 false false diff --git a/src/Simulation/Simulators.Tests/TestProjects/UnitTests/SpaceTests.qs b/src/Simulation/Simulators.Tests/TestProjects/UnitTests/SpaceTests.qs index 2faa5616e2b..2aafbf2c4a5 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/UnitTests/SpaceTests.qs +++ b/src/Simulation/Simulators.Tests/TestProjects/UnitTests/SpaceTests.qs @@ -3,6 +3,7 @@ open Microsoft.Quantum.Testing; @Test("QuantumSimulator") + @Test("SparseSimulator") function LibraryWithSpacesTest() : Unit { FactS("Hello quantum world!", LibraryWithSpaces.HelloQ()); } diff --git a/src/Simulation/Simulators.Tests/TestProjects/UnitTests/SparseSimulator.qs b/src/Simulation/Simulators.Tests/TestProjects/UnitTests/SparseSimulator.qs new file mode 100644 index 00000000000..8a9d6a89621 --- /dev/null +++ b/src/Simulation/Simulators.Tests/TestProjects/UnitTests/SparseSimulator.qs @@ -0,0 +1,526 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Testing.SparseSimulator { + + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Convert; + open Microsoft.Quantum.Arrays; + open Microsoft.Quantum.Math; + + internal operation ApplyToEachCA<'T> (singleElementOperation : ('T => Unit is Adj + Ctl), register : 'T[]) + : Unit is Adj + Ctl { + for idxQubit in IndexRange(register) { + singleElementOperation(register[idxQubit]); + } + } + + internal operation ApplyToFirstTwoQubitsCA (op : ((Qubit, Qubit) => Unit is Adj + Ctl), register : Qubit[]) + : Unit is Adj + Ctl { + if (Length(register) < 2) + { + fail $"Must have at least two qubits to act on."; + } + + op(register[0], register[1]); + } + + internal function Zipped<'T, 'U>(left : 'T[], right : 'U[]) : ('T, 'U)[] { + let nElements = Length(left) < Length(right) + ? Length(left) + | Length(right); + mutable output = new ('T, 'U)[nElements]; + + for idxElement in 0 .. nElements - 1 { + set output w/= idxElement <- (left[idxElement], right[idxElement]); + } + + return output; + } + + operation _R(pauli : Pauli, theta : Double, qubits : Qubit[]) : Unit is Adj + Ctl { + R(pauli, theta, qubits[0]); + } + operation _MCR(pauli : Pauli, theta : Double, qubits : Qubit[]) : Unit is Adj + Ctl { + (Controlled R)(qubits[1..Length(qubits)-1], (pauli, theta, qubits[0])); + } + operation _MCExp(pauli : Pauli, theta : Double, qubits : Qubit[]) : Unit is Adj + Ctl { + (Controlled Exp)(qubits[1..Length(qubits)-1], ([pauli], theta, [qubits[0]])); + } + + @Test("SparseSimulator") + operation ExpRCompare() : Unit { + for angle in 0..2*314 { + AssertOperationsEqualReferenced(1, _R(PauliX, -IntAsDouble(angle)/100.0, _), Exp([PauliX], IntAsDouble(angle)/200.0, _)); + AssertOperationsEqualReferenced(1, _R(PauliZ, -IntAsDouble(angle)/100.0, _), Exp([PauliZ], IntAsDouble(angle)/200.0, _)); + AssertOperationsEqualReferenced(1, _R(PauliY, -IntAsDouble(angle)/100.0, _), Exp([PauliY], IntAsDouble(angle)/200.0, _)); + AssertOperationsEqualReferenced(1, _R(PauliI, -IntAsDouble(angle)/100.0, _), Exp([PauliI], IntAsDouble(angle)/200.0, _)); + } + } + + @Test("SparseSimulator") + operation MCExpMCRCompare() : Unit { + for angle in 0..2*314 { + AssertOperationsEqualReferenced(1, _MCR(PauliX, -IntAsDouble(angle)/100.0, _), _MCExp(PauliX, IntAsDouble(angle)/200.0, _)); + AssertOperationsEqualReferenced(1, _MCR(PauliZ, -IntAsDouble(angle)/100.0, _), _MCExp(PauliZ, IntAsDouble(angle)/200.0, _)); + AssertOperationsEqualReferenced(1, _MCR(PauliY, -IntAsDouble(angle)/100.0, _), _MCExp(PauliY, IntAsDouble(angle)/200.0, _)); + AssertOperationsEqualReferenced(1, _MCR(PauliI, -IntAsDouble(angle)/100.0, _), _MCExp(PauliI, IntAsDouble(angle)/200.0, _)); + } + } + + internal operation ControlledRz(angle : Double, (control : Qubit, target : Qubit)) : Unit is Adj + Ctl { + Controlled Rz([control], (angle, target)); + DumpMachine(); + } + + internal operation ControlledRzAsR1(angle : Double, (control : Qubit, target : Qubit)) : Unit is Adj + Ctl { + Controlled R1([control], (angle, target)); + R1(-angle / 2.0, control); + DumpMachine(); + } + + @Test("SparseSimulator") + operation TestEqualityOfControlledRz() : Unit { + for _ in 1..10 { + let angle = Microsoft.Quantum.Random.DrawRandomDouble(0.0, 2.0 * PI()); + AssertOperationsEqualReferenced(2, ApplyToFirstTwoQubitsCA(ControlledRzAsR1(angle, _), _), ApplyToFirstTwoQubitsCA(ControlledRz(angle, _), _)); + } + } + + @Test("SparseSimulator") + operation LargeStateTests() : Unit { + let nqubits = 12; + LargeStateTestWrapper(Rotation1CompareTest, nqubits); + LargeStateTestWrapper(RotationCompareTest, nqubits); + LargeStateTestWrapper(SWAPTest, nqubits); + LargeStateTestWrapper(CSWAPTest, nqubits); + LargeStateTestWrapper(CNOTTest, nqubits); + LargeStateTestWrapper(ResetTest, nqubits); + LargeStateTestWrapper(AssertTest, nqubits); + LargeStateTestWrapper(AllocationTest, nqubits); + LargeStateTestWrapper(Rotation1CompareTest, nqubits); + LargeStateTestWrapper(RotationFracCompareTest, nqubits); + + } + + operation _ToffoliCSwap(targets : Qubit[]) : Unit is Ctl + Adj { + CCNOT(targets[0], targets[1], targets[2]); + CCNOT(targets[0], targets[2], targets[1]); + CCNOT(targets[0], targets[1], targets[2]); + } + operation _ArrayCSwap(targets : Qubit[]) : Unit is Ctl + Adj { + (Controlled SWAP)([targets[0]], (targets[1], targets[2])); + } + + @Test("SparseSimulator") + operation CCNOTvToffoliTest() : Unit { + AssertOperationsEqualReferenced(3, _ArrayCSwap, _ToffoliCSwap); + } + + // Creates a big state so an existing test forces parallel execution + operation LargeStateTestWrapper(test : (Unit => Unit), nqubits : Int) : Unit { + let prob = PowD(0.5, IntAsDouble(nqubits)); + use qubits = Qubit[nqubits]; + for idx in 0..nqubits - 1 { + H(qubits[idx]); + } + for idy in 1..128 { + CCNOT(qubits[idy % nqubits], qubits[(idy+1) % nqubits], qubits[(idy+2) % nqubits]); + } + test(); + ResetAll(qubits); + } + + @Test("SparseSimulator") + operation PartialDumpTest() : Unit { + use qubits = Qubit[4] { + ApplyToEachCA(H, qubits); + CNOT(qubits[2], qubits[3]); + DumpRegister("Test_file_1", qubits[0..1]); + DumpRegister("Test_file_2", qubits[2..3]); + CNOT(qubits[2], qubits[3]); + H(qubits[2]); + CNOT(qubits[0], qubits[2]); + DumpRegister("Test_file_3", qubits[0..1]); + DumpRegister("Test_file_4", qubits); + ResetAll(qubits); + } + } + + + internal operation FakeR1Frac(numerator : Int, denominator : Int, qubit : Qubit[]) : Unit is Adj + Ctl { + RFrac(PauliZ, -numerator, denominator + 1, qubit[0]); + RFrac(PauliI, numerator, denominator + 1, qubit[0]); + } + + internal operation R1FracWithArray(numerator : Int, denominator : Int, qubit : Qubit[]) : Unit is Adj + Ctl { + R1Frac(numerator, denominator, qubit[0]); + } + + internal operation FakeR1(angle : Double, qubit : Qubit[]) : Unit is Adj + Ctl { + R(PauliZ, angle, qubit[0]); + R(PauliI, -angle, qubit[0]); + } + internal operation R1WithArray(angle : Double, qubit : Qubit[]) : Unit is Adj + Ctl { + R1(angle, qubit[0]); + } + + @Test("SparseSimulator") + operation Rotation1CompareTest() : Unit { + for denom in 0..5{ + for num in 1..2..(2^denom - 1){ + AssertOperationsEqualReferenced(1, R1FracWithArray(num, denom, _), FakeR1Frac(num, denom, _)); + AssertOperationsEqualReferenced(1, FakeR1Frac(num, denom, _), R1FracWithArray(num, denom, _)); + } + } + for angle in 0..314 { + AssertOperationsEqualReferenced(1, R1WithArray(IntAsDouble(angle)/100.0, _), FakeR1(IntAsDouble(angle)/100.0, _)); + AssertOperationsEqualReferenced(1, FakeR1(IntAsDouble(angle)/100.0, _), R1WithArray(IntAsDouble(angle)/100.0, _)); + } + } + + internal operation RFracWithArray(axis : Pauli, num : Int, denom : Int, qubit : Qubit[]) : Unit is Adj + Ctl { + RFrac(axis, num, denom, qubit[0]); + } + internal operation RWithArray(axis : Pauli, angle : Double, qubit : Qubit[]) : Unit is Adj + Ctl { + R(axis, angle, qubit[0]); + } + + @Test("SparseSimulator") + operation RotationFracCompareTest() : Unit { + for denom in 0..5{ + for num in 1..2..(2^denom - 1){ + let angle = -3.14159265*IntAsDouble(num)/(2.0 ^IntAsDouble(denom-1)); + AssertOperationsEqualReferenced(1, RFracWithArray(PauliX, num, denom, _), RWithArray(PauliX, angle, _)); + AssertOperationsEqualReferenced(1, RWithArray(PauliX, angle, _), RFracWithArray(PauliX, num, denom, _)); + AssertOperationsEqualReferenced(1, RFracWithArray(PauliY, num, denom, _), RWithArray(PauliY, angle, _)); + AssertOperationsEqualReferenced(1, RWithArray(PauliY, angle, _), RFracWithArray(PauliY, num, denom, _)); + AssertOperationsEqualReferenced(1, RFracWithArray(PauliZ, num, denom, _), RWithArray(PauliZ, angle, _)); + AssertOperationsEqualReferenced(1, RWithArray(PauliZ, angle, _), RFracWithArray(PauliZ, num, denom, _)); + } + } + } + + operation _HadamardByRotations(qubit : Qubit[]) : Unit is Adj + Ctl { + RFrac(PauliY, -1, 2, qubit[0]); + RFrac(PauliX, -1, 1, qubit[0]); + } + + @Test("SparseSimulator") + operation RotationCompareTest() : Unit { + AssertOperationsEqualReferenced(1, _HadamardByRotations(_), ApplyToEachCA(H, _)); + AssertOperationsEqualReferenced(1, ApplyToEachCA(R1Frac(1,0,_), _), ApplyToEachCA(Z, _)); + AssertOperationsEqualReferenced(1, ApplyToEachCA(R1Frac(1,1,_), _), ApplyToEachCA(S, _)); + AssertOperationsEqualReferenced(1, ApplyToEachCA(R1Frac(1,2,_), _), ApplyToEachCA(T, _)); + AssertOperationsEqualReferenced(1, ApplyToEachCA(RFrac(PauliX, 1, 1, _), _), ApplyToEachCA(X, _)); + AssertOperationsEqualReferenced(1, ApplyToEachCA(RFrac(PauliY, 1, 1, _), _), ApplyToEachCA(Y, _)); + AssertOperationsEqualReferenced(1, ApplyToEachCA(RFrac(PauliZ, 1, 1, _), _), ApplyToEachCA(Z, _)); + } + + @Test("SparseSimulator") + operation AllocationTest() : Unit { + use qubits = Qubit[512]{ + for idx in 0..511 { + X(qubits[idx]); + } + ResetAll(qubits); + } + } + + operation DumpMCXFrac() : Unit { + use qubits = Qubit[2] { + H(qubits[0]); + H(qubits[1]); + (Controlled RFrac)([qubits[0]], (PauliX, -1, 3, qubits[1])); + DumpMachine(); + (Adjoint Controlled RFrac)([qubits[0]], (PauliX, -1, 3, qubits[1])); + H(qubits[0]); + H(qubits[1]); + H(qubits[0]); + H(qubits[1]); + (Controlled R)([qubits[0]], (PauliX, 0.25*Microsoft.Quantum.Math.PI(), qubits[1])); + DumpMachine(); + (Adjoint Controlled R)([qubits[0]], (PauliX, 0.25*Microsoft.Quantum.Math.PI(), qubits[1])); + H(qubits[0]); + H(qubits[1]); + } + } + + operation DumpMCZFrac() : Unit { + use qubits = Qubit[2] { + H(qubits[0]); + H(qubits[1]); + (Controlled RFrac)([qubits[0]], (PauliZ, -1, 3, qubits[1])); + DumpMachine(); + (Adjoint Controlled RFrac)([qubits[0]], (PauliZ, -1, 3, qubits[1])); + H(qubits[0]); + H(qubits[1]); + H(qubits[0]); + H(qubits[1]); + (Controlled R)([qubits[0]], (PauliZ, 0.25*Microsoft.Quantum.Math.PI(), qubits[1])); + DumpMachine(); + (Adjoint Controlled R)([qubits[0]], (PauliZ, 0.25*Microsoft.Quantum.Math.PI(), qubits[1])); + H(qubits[0]); + H(qubits[1]); + } + } + + @Test("SparseSimulator") + operation AssertProbTest() : Unit { + let tolerance = 0.000001; + use qubit = Qubit() { + AssertMeasurementProbability([PauliZ], [qubit], Zero, 1.0, "Failed assert Z on |0>", tolerance); + AssertMeasurementProbability([PauliX], [qubit], Zero, 0.5, "Failed assert X on |0>", tolerance); + AssertMeasurementProbability([PauliY], [qubit], Zero, 0.5, "Failed assert Y on |0>", tolerance); + H(qubit); + AssertMeasurementProbability([PauliZ], [qubit], Zero, 0.5, "Failed assert Z on |+>", tolerance); + AssertMeasurementProbability([PauliX], [qubit], Zero, 1.0, "Failed assert X on |+>", tolerance); + AssertMeasurementProbability([PauliY], [qubit], Zero, 0.5, "Failed assert X on |+>", tolerance); + S(qubit); + AssertMeasurementProbability([PauliZ], [qubit], Zero, 0.5, "Failed assert Z on |+>", tolerance); + AssertMeasurementProbability([PauliX], [qubit], Zero, 0.5, "Failed assert X on |+>", tolerance); + AssertMeasurementProbability([PauliY], [qubit], Zero, 1.0, "Failed assert Y on |i>", tolerance); + S(qubit); + use buddy = Qubit() { + CNOT(qubit, buddy); + AssertMeasurementProbability([PauliZ, PauliZ], [qubit, buddy], Zero, 1.0, "Failed assert ZZ on |++>", tolerance); + AssertMeasurementProbability([PauliZ, PauliI], [qubit, buddy], Zero, 0.5, "Failed assert ZI on |++>", tolerance); + AssertMeasurementProbability([PauliX, PauliX], [qubit, buddy], Zero, 0.0, "Failed assert XX on |++>", tolerance); + AssertMeasurementProbability([PauliX, PauliI], [qubit, buddy], Zero, 0.5, "Failed assert XI on |++>", tolerance); + AssertMeasurementProbability([PauliY, PauliY], [qubit, buddy], Zero, 1.0, "Failed assert YY on |++>", tolerance); + AssertMeasurementProbability([PauliY, PauliI], [qubit, buddy], Zero, 0.5, "Failed assert YI on |++>", tolerance); + AssertMeasurementProbability([PauliX, PauliZ], [qubit, buddy], Zero, 0.5, "Failed assert XZ on |++>", tolerance); + AssertMeasurementProbability([PauliX, PauliY], [qubit, buddy], Zero, 0.5, "Failed assert XY on |++>", tolerance); + AssertMeasurementProbability([PauliY, PauliZ], [qubit, buddy], Zero, 0.5, "Failed assert YZ on |++>", tolerance); + Z(qubit); + AssertMeasurementProbability([PauliZ, PauliZ], [qubit, buddy], Zero, 1.0, "Failed assert ZZ on |-->", tolerance); + AssertMeasurementProbability([PauliZ, PauliI], [qubit, buddy], Zero, 0.5, "Failed assert ZI on |-->", tolerance); + AssertMeasurementProbability([PauliX, PauliX], [qubit, buddy], Zero, 1.0, "Failed assert XX on |-->", tolerance); + AssertMeasurementProbability([PauliX, PauliI], [qubit, buddy], Zero, 0.5, "Failed assert XI on |-->", tolerance); + AssertMeasurementProbability([PauliY, PauliY], [qubit, buddy], Zero, 0.0, "Failed assert YY on |-->", tolerance); + AssertMeasurementProbability([PauliY, PauliI], [qubit, buddy], Zero, 0.5, "Failed assert YI on |-->", tolerance); + AssertMeasurementProbability([PauliX, PauliZ], [qubit, buddy], Zero, 0.5, "Failed assert XZ on |-->", tolerance); + AssertMeasurementProbability([PauliX, PauliY], [qubit, buddy], Zero, 0.5, "Failed assert XY on |-->", tolerance); + AssertMeasurementProbability([PauliY, PauliZ], [qubit, buddy], Zero, 0.5, "Failed assert YZ on |-->", tolerance); + Reset(buddy); + } + Reset(qubit); + } + } + + @Test("SparseSimulator") + operation AssertTest() : Unit { + use qubits = Qubit[3] { + AssertMeasurement([PauliZ, PauliZ, PauliZ], qubits, Zero, "Assert fails Pauli Z"); + X(qubits[0]); + X(qubits[1]); + X(qubits[2]); + AssertMeasurement([PauliZ, PauliZ, PauliZ], qubits, One, "Assert fails Pauli Z"); + AssertMeasurement([PauliZ, PauliZ, PauliI], qubits, Zero, "Assert fails Pauli Z"); + X(qubits[2]); + X(qubits[1]); + X(qubits[0]); + H(qubits[0]); + CNOT(qubits[0], qubits[1]); + CNOT(qubits[0], qubits[2]); + AssertMeasurement([PauliX, PauliX, PauliX], qubits, Zero, "Assert fails Pauli X"); + Z(qubits[0]); + AssertMeasurement([PauliX, PauliX, PauliX], qubits, One, "Assert fails Pauli X"); + S(qubits[0]); + AssertMeasurement([PauliY, PauliY, PauliY], qubits, Zero, "Assert fails Pauli Y"); + Z(qubits[0]); + AssertMeasurement([PauliY, PauliY, PauliY], qubits, One, "Assert fails Pauli Y"); + ResetAll(qubits); + } + } + + @Test("SparseSimulator") + operation MTest() : Unit { + use qubit = Qubit() { + X(qubit); + let test = M(qubit); + Fact(M(qubit) == test, "M does not preserves state"); + X(qubit); + let test2 = M(qubit); + Fact(M(qubit) == test2, "M does not preserve state"); + } + } + + @Test("SparseSimulator") + operation ResetTest() : Unit { + use qubit = Qubit() { + Reset(qubit); + Fact(M(qubit) == Zero, "Failed reset"); + X(qubit); + Reset(qubit); + Fact(M(qubit) == Zero, "Failed reset"); + use buddy = Qubit() { + X(qubit); + CNOT(qubit, buddy); + Reset(qubit); + Fact(M(qubit) == Zero, "Failed entangled reset"); + Fact(M(buddy) == One, "Failed entangled reset"); + Reset(buddy); + Fact(M(buddy) == Zero, "Failed entangled reset"); + } + } + } + + @Test("SparseSimulator") + operation SWAPTest () : Unit { + use qubits = Qubit[2] { + SWAP(qubits[0], qubits[1]); + Fact(M(qubits[0]) == Zero, "Bad swap on 00"); + Fact(M(qubits[1]) == Zero, "Bad swap on 00"); + X(qubits[0]); + SWAP(qubits[0], qubits[1]); + Fact(M(qubits[0]) == Zero, "Bad swap on 01"); + Fact(M(qubits[1]) == One, "Bad swap on 01"); + X(qubits[0]); + SWAP(qubits[0], qubits[1]); + Fact(M(qubits[0]) == One, "Bad swap on 11"); + Fact(M(qubits[1]) == One, "Bad swap on 11"); + X(qubits[0]); + SWAP(qubits[0], qubits[1]); + Fact(M(qubits[0]) == One, "Bad swap on 10"); + Fact(M(qubits[1]) == Zero, "Bad swap on 10"); + X(qubits[0]); + } + } + + operation AreAllQubitsOne(qubits : Qubit[]) : Bool { + mutable qubits_all_true = true; + for idx in 0..Length(qubits) - 1 { + if (M(qubits[idx]) == Zero){ + set qubits_all_true = false; + } + } + return qubits_all_true; + } + + operation CNOTTestInternal(target : Qubit) : Unit { + body (...){ + + } + controlled (controls, ...){ + if (AreAllQubitsOne(controls)){ + (Controlled X)(controls, target); + Fact(M(target) == One, "Bad CNOT"); + (Controlled X)(controls, target); + Fact(M(target) == Zero, "Bad CNOT"); + } else { + (Controlled X)(controls, target); + Fact(M(target) == Zero, "Bad CNOT"); + X(target); + (Controlled X)(controls, target); + Fact(M(target) == One, "Bad CNOT"); + X(target); + } + } + } + + @Test("SparseSimulator") + operation CNOTTest() : Unit { + use qubits = Qubit[3] { + (Controlled CNOTTestInternal)(qubits[0..1], qubits[2]); + X(qubits[0]); + (Controlled CNOTTestInternal)(qubits[0..1], qubits[2]); + X(qubits[1]); + (Controlled CNOTTestInternal)(qubits[0..1], qubits[2]); + X(qubits[0]); + (Controlled CNOTTestInternal)(qubits[0..1], qubits[2]); + X(qubits[1]); + } + } + + operation CSwapTestInternal (qubits : Qubit[]) : Unit { + body (...){ + // nothing + } + controlled (controls, ...) { + if (not AreAllQubitsOne(controls)) { + (Controlled SWAP)(controls, (qubits[0], qubits[1])); + Fact(M(qubits[0]) == Zero, "Bad swap on 00"); + Fact(M(qubits[1]) == Zero, "Bad swap on 00"); + X(qubits[0]); + (Controlled SWAP)(controls, (qubits[0], qubits[1])); + Fact(M(qubits[0]) == One, "Bad swap on 01"); + Fact(M(qubits[1]) == Zero, "Bad swap on 01"); + X(qubits[1]); + (Controlled SWAP)(controls, (qubits[0], qubits[1])); + Fact(M(qubits[0]) == One, "Bad swap on 11"); + Fact(M(qubits[1]) == One, "Bad swap on 11"); + X(qubits[0]); + (Controlled SWAP)(controls, (qubits[0], qubits[1])); + Fact(M(qubits[0]) == Zero, "Bad swap on 10"); + Fact(M(qubits[1]) == One, "Bad swap on 10"); + X(qubits[1]); + } else { + (Controlled SWAP)(controls, (qubits[0], qubits[1])); + Fact(M(qubits[0]) == Zero, "Bad swap on 00"); + Fact(M(qubits[1]) == Zero, "Bad swap on 00"); + X(qubits[0]); + (Controlled SWAP)(controls, (qubits[0], qubits[1])); + Fact(M(qubits[0]) == Zero, "Bad swap on 01"); + Fact(M(qubits[1]) == One, "Bad swap on 01"); + X(qubits[0]); + (Controlled SWAP)(controls, (qubits[0], qubits[1])); + Fact(M(qubits[0]) == One, "Bad swap on 11"); + Fact(M(qubits[1]) == One, "Bad swap on 11"); + X(qubits[0]); + (Controlled SWAP)(controls, (qubits[0], qubits[1])); + Fact(M(qubits[0]) == One, "Bad swap on 10"); + Fact(M(qubits[1]) == Zero, "Bad swap on 10"); + X(qubits[0]); + } + } + } + + @Test("SparseSimulator") + operation CSWAPTest () : Unit { + use qubits = Qubit[4] { + (Controlled CSwapTestInternal)(qubits[0..1], qubits[2..3]); + X(qubits[0]); + (Controlled CSwapTestInternal)(qubits[0..1], qubits[2..3]); + X(qubits[1]); + (Controlled CSwapTestInternal)(qubits[0..1], qubits[2..3]); + X(qubits[0]); + (Controlled CSwapTestInternal)(qubits[0..1], qubits[2..3]); + X(qubits[1]); + } + use qubits = Qubit[7] { + (Controlled CSwapTestInternal)([qubits[0]], [qubits[6], qubits[3]]); + X(qubits[0]); + (Controlled CSwapTestInternal)([qubits[0]], [qubits[6], qubits[3]]); + X(qubits[0]); + } + } + + @Test("SparseSimulator") + operation BasicTest () : Unit { + use new_qubit = Qubit() { + H(new_qubit); + X(new_qubit); + Z(new_qubit); + Y(new_qubit); + T(new_qubit); + (Adjoint T)(new_qubit); + S(new_qubit); + (Adjoint S)(new_qubit); + Y(new_qubit); + Z(new_qubit); + X(new_qubit); + H(new_qubit); + } + use new_qubits = Qubit[4] { + for idx in 0..3 { + H(new_qubits[idx]); + } + for idx in 0..3 { + H(new_qubits[idx]); + } + } + } +} diff --git a/src/Simulation/Simulators.Tests/TestProjects/UnitTests/TestNameTests.qs b/src/Simulation/Simulators.Tests/TestProjects/UnitTests/TestNameTests.qs index 68a3ee38845..f1d330b398e 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/UnitTests/TestNameTests.qs +++ b/src/Simulation/Simulators.Tests/TestProjects/UnitTests/TestNameTests.qs @@ -5,13 +5,15 @@ open Microsoft.Quantum.Testing; @Test("QuantumSimulator") + @Test("SparseSimulator") operation BothCallables () : Unit { - + FactI(1, Library1.LibraryId()); FactI(2, Library2.LibraryId()); } @Test("QuantumSimulator") + @Test("SparseSimulator") operation OneCallable () : Unit { FactS("Library1", Microsoft.Quantum.Library.DllName()); @@ -19,8 +21,9 @@ } @Test("QuantumSimulator") + @Test("SparseSimulator") operation BothTypes () : Unit { - + let i1 = Library1.MyInt(1); let i2 = Library2.MyInt(2); FactMyInt1(1, i1); @@ -28,8 +31,9 @@ } @Test("QuantumSimulator") + @Test("SparseSimulator") operation OneType () : Unit { - + let s1 = Microsoft.Quantum.Library.MyString("Library1"); let s2 = Library2.MyString("Library2"); FactMyString1("Library1", s1); @@ -37,6 +41,7 @@ } @Test("QuantumSimulator") + @Test("SparseSimulator") operation ConflictingWithSource () : Unit { let h1 = Library1.Hello(Library1.Token()); diff --git a/src/Simulation/Simulators.Tests/TestProjects/UnitTests/UnitTests.csproj b/src/Simulation/Simulators.Tests/TestProjects/UnitTests/UnitTests.csproj index 1a3eeecd2d0..2883976e647 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/UnitTests/UnitTests.csproj +++ b/src/Simulation/Simulators.Tests/TestProjects/UnitTests/UnitTests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net6.0 false true diff --git a/src/Simulation/Simulators.Tests/TypeExtensionsTest.cs b/src/Simulation/Simulators.Tests/TypeExtensionsTest.cs index 2707ca2b150..c5c9012a0c9 100644 --- a/src/Simulation/Simulators.Tests/TypeExtensionsTest.cs +++ b/src/Simulation/Simulators.Tests/TypeExtensionsTest.cs @@ -22,7 +22,7 @@ public ApplyData(T data) IEnumerable IApplyData.Qubits => QubitsExtractor.Get(typeof(T))?.Extract(Data); } - public class GetNonQubitArgumentsAsStringTests + public class GetNonQubitArgumentsAsStringTests : SimulatorFactoryProvider { [Fact] public void BasicTypes() @@ -36,11 +36,25 @@ public void BasicTypes() [Fact] public void OperationTypes() { - var op = new QuantumSimulator().Get(); - Assert.Equal("H", op.GetNonQubitArgumentsAsString()); - - var op2 = new QuantumSimulator().Get(); - Assert.Equal("CNOT", op2.GetNonQubitArgumentsAsString()); + foreach(var factory in simulatorFactories) + { + var sim1 = factory(); + var sim2 = factory(); + + try + { + var op = sim1.Get(); + Assert.Equal("H", op.GetNonQubitArgumentsAsString()); + + var op2 = sim2.Get(); + Assert.Equal("CNOT", op2.GetNonQubitArgumentsAsString()); + } + finally + { + sim2.Dispose(); + sim1.Dispose(); + } + } } [Fact] @@ -68,9 +82,24 @@ public void TupleTypes() Assert.Equal("(\"foo\", (\"bar\", \"car\"))", ("foo", ("bar", "car")).GetNonQubitArgumentsAsString()); Assert.Equal("((\"foo\"), (\"bar\", \"car\"))", (("foo", new FreeQubit(0)), ("bar", "car")).GetNonQubitArgumentsAsString()); - var op = new QuantumSimulator().Get(); - var opTuple = new QTuple<(ICallable, string)>((op, "foo")); - Assert.Equal("(H, \"foo\")", opTuple.GetNonQubitArgumentsAsString()); + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) + { + try + { + var op = sim.Get(); + var opTuple = new QTuple<(ICallable, string)>((op, "foo")); + Assert.Equal("(H, \"foo\")", opTuple.GetNonQubitArgumentsAsString()); + } + finally + { + sim.Dispose(); + } + } var qtuple = new QTuple<(Qubit, string)>((new FreeQubit(0), "foo")); Assert.Equal("(\"foo\")", qtuple.GetNonQubitArgumentsAsString()); @@ -82,12 +111,28 @@ public void ArrayTypes() Assert.Equal("[1, 2, 3]", new[] { 1, 2, 3 }.GetNonQubitArgumentsAsString()); Assert.Equal("[\"foo\", \"bar\"]", new[] { "foo", "bar" }.GetNonQubitArgumentsAsString()); - var opArr = new ICallable[] { - new QuantumSimulator().Get(), - new QuantumSimulator().Get(), - new QuantumSimulator().Get(), - }; - Assert.Equal("[H, CNOT, Ry]", opArr.GetNonQubitArgumentsAsString()); + foreach (var factory in simulatorFactories) + { + var sim1 = factory(); + var sim2 = factory(); + var sim3 = factory(); + try + { + var opArr = new ICallable[] { + sim1.Get(), + sim2.Get(), + sim3.Get(), + }; + + Assert.Equal("[H, CNOT, Ry]", opArr.GetNonQubitArgumentsAsString()); + } + finally + { + sim3.Dispose(); + sim2.Dispose(); + sim1.Dispose(); + } + } var qTupleArr = new[] { (new FreeQubit(0), "foo"), @@ -112,9 +157,24 @@ public void IApplyDataTypes() data = new ApplyData("Foo"); Assert.Equal("\"Foo\"", data.GetNonQubitArgumentsAsString()); - var op = new QuantumSimulator().Get(); - data = new ApplyData(op); - Assert.Equal("H", data.GetNonQubitArgumentsAsString()); + var simulators = new CommonNativeSimulator[] { + new QuantumSimulator(), + new SparseSimulator() + }; + + foreach (var sim in simulators) + { + try + { + var op = sim.Get(); + data = new ApplyData(op); + Assert.Equal("H", data.GetNonQubitArgumentsAsString()); + } + finally + { + sim.Dispose(); + } + } data = new ApplyData>((1, "foo")); Assert.Equal("(1, \"foo\")", data.GetNonQubitArgumentsAsString()); diff --git a/src/Simulation/Simulators.Type1.Tests/Tests.Microsoft.Quantum.Simulators.Type1.csproj b/src/Simulation/Simulators.Type1.Tests/Tests.Microsoft.Quantum.Simulators.Type1.csproj index 197bdb2173d..5f1c4583cf7 100644 --- a/src/Simulation/Simulators.Type1.Tests/Tests.Microsoft.Quantum.Simulators.Type1.csproj +++ b/src/Simulation/Simulators.Type1.Tests/Tests.Microsoft.Quantum.Simulators.Type1.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Simulation/Simulators.Type2.Tests/Tests.Microsoft.Quantum.Simulators.Type2.csproj b/src/Simulation/Simulators.Type2.Tests/Tests.Microsoft.Quantum.Simulators.Type2.csproj index 18f70718414..4fab0d44a49 100644 --- a/src/Simulation/Simulators.Type2.Tests/Tests.Microsoft.Quantum.Simulators.Type2.csproj +++ b/src/Simulation/Simulators.Type2.Tests/Tests.Microsoft.Quantum.Simulators.Type2.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Simulation/Simulators.Type3.Tests/Tests.Microsoft.Quantum.Simulators.Type3.csproj b/src/Simulation/Simulators.Type3.Tests/Tests.Microsoft.Quantum.Simulators.Type3.csproj index 0df36c4db25..30416f0148c 100644 --- a/src/Simulation/Simulators.Type3.Tests/Tests.Microsoft.Quantum.Simulators.Type3.csproj +++ b/src/Simulation/Simulators.Type3.Tests/Tests.Microsoft.Quantum.Simulators.Type3.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Simulation/Simulators/CommonNativeSimulator/Assert.cs b/src/Simulation/Simulators/CommonNativeSimulator/Assert.cs index 9d7d1681aa8..1e5e7405146 100644 --- a/src/Simulation/Simulators/CommonNativeSimulator/Assert.cs +++ b/src/Simulation/Simulators/CommonNativeSimulator/Assert.cs @@ -35,7 +35,6 @@ public QSimAssert(CommonNativeSimulator m) : base(m) var tolerance = 1.0e-10; var expectedPr = result == Result.Zero ? 0.0 : 1.0; - var ensemblePr = this.Simulator.JointEnsembleProbability((uint)paulis.Length, paulis.ToArray(), qubits.GetIds()); if (Abs(ensemblePr - expectedPr) > tolerance) diff --git a/src/Simulation/Simulators/CommonNativeSimulator/AssertProb.cs b/src/Simulation/Simulators/CommonNativeSimulator/AssertProb.cs index fb038b23b4b..ba8a3e9224d 100644 --- a/src/Simulation/Simulators/CommonNativeSimulator/AssertProb.cs +++ b/src/Simulation/Simulators/CommonNativeSimulator/AssertProb.cs @@ -41,7 +41,6 @@ public QSimAssertProb(CommonNativeSimulator m) : base(m) { expectedPr = 1 - expectedPr; } - var ensemblePr = this.Simulator.JointEnsembleProbability((uint)paulis.Length, paulis.ToArray(), qubits.GetIds()); if (Abs(ensemblePr - expectedPr) > tol) diff --git a/src/Simulation/Simulators/CommonNativeSimulator/DisplayableState.cs b/src/Simulation/Simulators/CommonNativeSimulator/DisplayableState.cs index 0cdf4fd272c..7bd73d0612a 100644 --- a/src/Simulation/Simulators/CommonNativeSimulator/DisplayableState.cs +++ b/src/Simulation/Simulators/CommonNativeSimulator/DisplayableState.cs @@ -52,10 +52,27 @@ public enum BasisStateLabelingConvention /// public class DisplayableState { + /// + /// Converst basis state label from unsigned little-endian bit string to BigInteger, e.g. "001" to 4. + /// + public static BigInteger BasisStateLabelToBigInt(string str) + { + string tmpStr = str.Reverse(); + + BigInteger retVal = 0; + foreach(char c in tmpStr) + { + retVal <<= 1; + retVal += (c - '0'); + } + return retVal; + } + private static readonly IComparer ToIntComparer = Comparer.Create((label1, label2) => - Comparer.Default.Compare( - Int32.Parse(label1), Int32.Parse(label2) + Comparer.Default.Compare( + BasisStateLabelToBigInt(label1), + BasisStateLabelToBigInt(label2) ) ); @@ -86,7 +103,7 @@ public class DisplayableState /// . /// [JsonProperty("amplitudes")] - public IDictionary? Amplitudes { get; set; } + public IDictionary? Amplitudes { get; set; } /// /// An enumerable source of the significant amplitudes of this state @@ -135,25 +152,14 @@ public class DisplayableState /// into an integer index in the little-endian encoding. /// public string BasisStateLabel( - BasisStateLabelingConvention convention, int index + BasisStateLabelingConvention convention, BigInteger index // index is a string of bits ) => convention switch { BasisStateLabelingConvention.Bitstring => - String.Concat( - System - .Convert - .ToString(index, 2) - .PadLeft(NQubits, '0') - .Reverse() - ), + index.ToUnsignedBitString(NQubits), BasisStateLabelingConvention.BigEndian => - System.Convert.ToInt64( - String.Concat( - System.Convert.ToString(index, 2).PadLeft(NQubits, '0').Reverse() - ), - fromBase: 2 - ) - .ToString(), + BasisStateLabelToBigInt( + index.ToUnsignedBitString(NQubits, true)).ToString(), BasisStateLabelingConvention.LittleEndian => index.ToString(), _ => throw new ArgumentException($"Invalid basis state labeling convention {convention}.") diff --git a/src/Simulation/Simulators/CommonNativeSimulator/Dump.cs b/src/Simulation/Simulators/CommonNativeSimulator/Dump.cs index ce3bcf38f63..bfac0e8ea5b 100644 --- a/src/Simulation/Simulators/CommonNativeSimulator/Dump.cs +++ b/src/Simulation/Simulators/CommonNativeSimulator/Dump.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Numerics; +using System.Runtime.InteropServices; using Microsoft.Quantum.Simulation.Core; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -119,7 +120,7 @@ public StateDumper(CommonNativeSimulator qsim) /// The real portion of the amplitude of the given basis state vector. /// The imaginary portion of the amplitude of the given basis state vector. /// true if dumping should continue, false to stop dumping. - public abstract bool Callback(uint idx, double real, double img); + public abstract bool Callback([MarshalAs(UnmanagedType.LPStr)] string idx, double real, double img); /// /// The Simulator being reported. @@ -151,7 +152,7 @@ public virtual bool Dump(IQArray? qubits = null) public class DisplayableStateDumper : StateDumper { private long _count = -1; - private IDictionary? _data = null; + private IDictionary? _data = null; /// /// A method to call to output a string representation. @@ -170,10 +171,11 @@ public DisplayableStateDumper(CommonNativeSimulator sim, Action? fileWri /// Used by the simulator to provide states when dumping. /// Not intended to be called directly. /// - public override bool Callback(uint idx, double real, double img) + public override bool Callback([MarshalAs(UnmanagedType.LPStr)] string idx, double real, double img) { if (_data == null) throw new Exception("Expected data buffer to be initialized before callback, but it was null."); - _data[(int)idx] = new Complex(real, img); + _data[DisplayableState.BasisStateLabelToBigInt(idx)] = new Complex(real, img); + return true; } @@ -188,7 +190,7 @@ public override bool Dump(IQArray? qubits = null) _count = qubits == null ? this.Simulator.QubitManager.AllocatedQubitsCount : qubits.Length; - _data = new Dictionary(); // If 0 qubits are allocated then the array has + _data = new Dictionary(); // If 0 qubits are allocated then the array has // a single element. The Hilbert space of the system is // ℂ¹ (that is, complex-valued scalars). var result = base.Dump(qubits); diff --git a/src/Simulation/Simulators/CommonNativeSimulator/Extensions.cs b/src/Simulation/Simulators/CommonNativeSimulator/Extensions.cs index 2e38358e372..a9661336383 100644 --- a/src/Simulation/Simulators/CommonNativeSimulator/Extensions.cs +++ b/src/Simulation/Simulators/CommonNativeSimulator/Extensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Numerics; using System.Diagnostics; using System.Linq; @@ -13,6 +14,31 @@ namespace Microsoft.Quantum.Simulation public static partial class Extensions { + /// + /// Reverses the string. + /// + public static string Reverse(this string s) + { + char[] charArray = s.ToCharArray(); + Array.Reverse(charArray); + return new string(charArray); + } + + public static string ToUnsignedBitString(this BigInteger bigInt, int qubitCount, bool bigEndian = false) + { + byte[] bytes = bigInt.ToByteArray(); // `bytes[0]` is the least significant byte. + System.Text.StringBuilder sb = new System.Text.StringBuilder(bytes.Length * 8); + for(uint idx = 0; idx < bytes.Length; ++idx) + { + sb.Append(System.Convert.ToString(bytes[idx], // 0x4 + 2) // "100" + .PadLeft(qubitCount, '0') // "000100" (qubitCount: 6) + .Reverse()); // "001000" (0x4 as little endian bit string) + } + string retVal = sb.ToString(); + return (bigEndian ? retVal.Reverse() : retVal); + } + /// /// Returns the ids of a qubit array as a uint[] /// diff --git a/src/Simulation/Simulators/CommonNativeSimulator/IsingXX.cs b/src/Simulation/Simulators/CommonNativeSimulator/IsingXX.cs index 5f114db803a..ee02e94e7cb 100644 --- a/src/Simulation/Simulators/CommonNativeSimulator/IsingXX.cs +++ b/src/Simulation/Simulators/CommonNativeSimulator/IsingXX.cs @@ -15,7 +15,7 @@ void IIntrinsicIsingXX.Body(double angle, Qubit target1, Qubit target2) CheckAngle(angle); this.CheckQubits(targets); - Exp((uint)targets.Length, paulis, angle * 2.0, targets.GetIds()); + Exp((uint)targets.Length, paulis, angle / -2.0, targets.GetIds()); } void IIntrinsicIsingXX.AdjointBody(double angle, Qubit target1, Qubit target2) @@ -36,7 +36,7 @@ void IIntrinsicIsingXX.ControlledBody(IQArray controls, double angle, Qub CheckAngle(angle); this.CheckQubits(QArray.Add(controls, targets)); - MCExp((uint)targets.Length, paulis, angle * 2.0, (uint)controls.Length, controls.GetIds(), targets.GetIds()); + MCExp((uint)targets.Length, paulis, angle / -2.0, (uint)controls.Length, controls.GetIds(), targets.GetIds()); } } diff --git a/src/Simulation/Simulators/CommonNativeSimulator/IsingYY.cs b/src/Simulation/Simulators/CommonNativeSimulator/IsingYY.cs index 1aa000ec554..377eb1992cf 100644 --- a/src/Simulation/Simulators/CommonNativeSimulator/IsingYY.cs +++ b/src/Simulation/Simulators/CommonNativeSimulator/IsingYY.cs @@ -15,7 +15,7 @@ void IIntrinsicIsingYY.Body(double angle, Qubit target1, Qubit target2) CheckAngle(angle); this.CheckQubits(targets); - Exp((uint)targets.Length, paulis, angle * 2.0, targets.GetIds()); + Exp((uint)targets.Length, paulis, angle / -2.0, targets.GetIds()); } void IIntrinsicIsingYY.AdjointBody(double angle, Qubit target1, Qubit target2) @@ -36,7 +36,7 @@ void IIntrinsicIsingYY.ControlledBody(IQArray controls, double angle, Qub CheckAngle(angle); this.CheckQubits(QArray.Add(controls, targets)); - MCExp((uint)targets.Length, paulis, angle * 2.0, (uint)controls.Length, controls.GetIds(), targets.GetIds()); + MCExp((uint)targets.Length, paulis, angle / -2.0, (uint)controls.Length, controls.GetIds(), targets.GetIds()); } } diff --git a/src/Simulation/Simulators/CommonNativeSimulator/IsingZZ.cs b/src/Simulation/Simulators/CommonNativeSimulator/IsingZZ.cs index ffdef477228..862792910eb 100644 --- a/src/Simulation/Simulators/CommonNativeSimulator/IsingZZ.cs +++ b/src/Simulation/Simulators/CommonNativeSimulator/IsingZZ.cs @@ -15,7 +15,7 @@ void IIntrinsicIsingZZ.Body(double angle, Qubit target1, Qubit target2) CheckAngle(angle); this.CheckQubits(targets); - Exp((uint)targets.Length, paulis, angle * 2.0, targets.GetIds()); + Exp((uint)targets.Length, paulis, angle / -2.0, targets.GetIds()); } void IIntrinsicIsingZZ.AdjointBody(double angle, Qubit target1, Qubit target2) @@ -36,7 +36,7 @@ void IIntrinsicIsingZZ.ControlledBody(IQArray controls, double angle, Qub CheckAngle(angle); this.CheckQubits(QArray.Add(controls, targets)); - MCExp((uint)targets.Length, paulis, angle * 2.0, (uint)controls.Length, controls.GetIds(), targets.GetIds()); + MCExp((uint)targets.Length, paulis, angle / -2.0, (uint)controls.Length, controls.GetIds(), targets.GetIds()); } } diff --git a/src/Simulation/Simulators/CommonNativeSimulator/NativeWrappers.cs b/src/Simulation/Simulators/CommonNativeSimulator/NativeWrappers.cs index 42bfca58c8d..42e775f34cd 100644 --- a/src/Simulation/Simulators/CommonNativeSimulator/NativeWrappers.cs +++ b/src/Simulation/Simulators/CommonNativeSimulator/NativeWrappers.cs @@ -1,4 +1,5 @@ using Microsoft.Quantum.Simulation.Core; +using System.Runtime.InteropServices; namespace Microsoft.Quantum.Simulation.Simulators { @@ -30,7 +31,7 @@ public partial class CommonNativeSimulator protected abstract void Z(uint qubit); protected abstract void MCZ(uint count, uint[] ctrls, uint qubit); - protected delegate bool DumpCallback(uint idx, double real, double img); + protected delegate bool DumpCallback([MarshalAs(UnmanagedType.LPStr)] string idx, double real, double img); protected abstract void sim_Dump(DumpCallback callback); protected abstract bool sim_DumpQubits(uint count, uint[] ids, DumpCallback callback); } diff --git a/src/Simulation/Simulators/CommonNativeSimulator/QubitManager.cs b/src/Simulation/Simulators/CommonNativeSimulator/QubitManager.cs index 4c1c9d73756..1c433002f06 100644 --- a/src/Simulation/Simulators/CommonNativeSimulator/QubitManager.cs +++ b/src/Simulation/Simulators/CommonNativeSimulator/QubitManager.cs @@ -31,8 +31,6 @@ public QSimQubitManager(bool throwOnReleasingQubitsNotInZeroState = true, long q public override Qubit CreateQubitObject(long id) { - Debug.Assert(id < 50, "Using a qubit id > 50. This is a full-state simulator! Validating ids uniqueness might start becoming slow."); - if (id >= this.MaxId) { this.MaxId = id + 1; diff --git a/src/Simulation/Simulators/QuantumSimulator/Dump.cs b/src/Simulation/Simulators/QuantumSimulator/Dump.cs index d89a153c50f..9d0d4887300 100644 --- a/src/Simulation/Simulators/QuantumSimulator/Dump.cs +++ b/src/Simulation/Simulators/QuantumSimulator/Dump.cs @@ -25,6 +25,5 @@ public override uint[] QubitIds return ids.ToArray(); } } - } } diff --git a/src/Simulation/Simulators/SparseSimulator/Dump.cs b/src/Simulation/Simulators/SparseSimulator/Dump.cs new file mode 100644 index 00000000000..d1c7413d9cf --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/Dump.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; + +#nullable enable +namespace Microsoft.Quantum.Simulation.Simulators +{ + using QubitIdType = System.UInt32; + + public partial class SparseSimulator + { + /// + /// Returns the list of the qubits' ids currently allocated in the simulator. + /// + public override uint[] QubitIds + { + get + { + var ids = new List(); + + QubitIds_cpp(this.Id, ids.Add); + return ids.Select(id => (uint)id).ToArray(); + } + } + } +} diff --git a/src/Simulation/Simulators/SparseSimulator/NativeImports.cs b/src/Simulation/Simulators/SparseSimulator/NativeImports.cs new file mode 100644 index 00000000000..e0aa234b608 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/NativeImports.cs @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Runtime.InteropServices; +using Microsoft.Quantum.Simulation.Core; + +#nullable enable +namespace Microsoft.Quantum.Simulation.Simulators +{ + using QubitIdType = System.UInt32; + using SimulatorIdType = System.UInt32; + + public partial class SparseSimulator + { + private const string simulatorDll = "Microsoft.Quantum.SparseSimulator.Runtime"; + + [DllImport(simulatorDll)] + private static extern QubitIdType num_qubits_cpp(SimulatorIdType sim); + + [DllImport(simulatorDll)] + private static extern SimulatorIdType init_cpp(QubitIdType numQubits); + + [DllImport(simulatorDll)] + private static extern void destroy_cpp(SimulatorIdType sim); + + [DllImport(simulatorDll)] + private static extern void seed_cpp(SimulatorIdType sim, uint newSeed); + + [DllImport(simulatorDll)] + private static extern void allocateQubit_cpp(SimulatorIdType sim, QubitIdType qubitId); + + [DllImport(simulatorDll)] + private static extern bool releaseQubit_cpp(SimulatorIdType sim, QubitIdType qubitId); + + [DllImport(simulatorDll)] + private static extern void Exp_cpp(SimulatorIdType sim, int length, int[] b, double phi, QubitIdType[] q); + + [DllImport(simulatorDll)] + private static extern void MCExp_cpp(SimulatorIdType sim, int controlsLength, int length, QubitIdType[] c, int[] b, double phi, QubitIdType[] q); + + [DllImport(simulatorDll)] + private static extern void H_cpp(SimulatorIdType sim, QubitIdType qubitId); + + [DllImport(simulatorDll)] + private static extern void MCH_cpp(SimulatorIdType sim, int length, QubitIdType[] controls, QubitIdType target); + + [DllImport(simulatorDll)] + private static extern uint M_cpp(SimulatorIdType sim, QubitIdType qubitId); + + [DllImport(simulatorDll)] + private static extern uint Measure_cpp(SimulatorIdType sim, int length, int[] basis, QubitIdType[] qubits); + + [DllImport(simulatorDll)] + private static extern void R_cpp(SimulatorIdType sim, int axis, double theta, QubitIdType qubitId); + + [DllImport(simulatorDll)] + private static extern void MCR_cpp(SimulatorIdType sim, int basis, double angle, int length, QubitIdType[] controls, QubitIdType target); + + [DllImport(simulatorDll)] + private static extern void MCR1_cpp(SimulatorIdType sim, double angle, int length, QubitIdType[] controls, QubitIdType target); + + [DllImport(simulatorDll)] + private static extern void S_cpp(SimulatorIdType sim, QubitIdType qubitId); + + [DllImport(simulatorDll)] + private static extern void AdjS_cpp(SimulatorIdType sim, QubitIdType qubitId); + + [DllImport(simulatorDll)] + private static extern void T_cpp(SimulatorIdType sim, QubitIdType qubitId); + + [DllImport(simulatorDll)] + private static extern void AdjT_cpp(SimulatorIdType sim, QubitIdType qubitId); + + [DllImport(simulatorDll)] + private static extern void X_cpp(SimulatorIdType sim, QubitIdType qubitId); + + [DllImport(simulatorDll)] + private static extern void MCX_cpp(SimulatorIdType sim, int length, QubitIdType[] controls, QubitIdType target); + + [DllImport(simulatorDll)] + private static extern void Y_cpp(SimulatorIdType sim, QubitIdType qubitId); + + [DllImport(simulatorDll)] + private static extern void MCY_cpp(SimulatorIdType sim, int length, QubitIdType[] controls, QubitIdType target); + + [DllImport(simulatorDll)] + private static extern void Z_cpp(SimulatorIdType sim, QubitIdType qubitId); + + [DllImport(simulatorDll)] + private static extern void MCZ_cpp(SimulatorIdType sim, int length, QubitIdType[] controls, QubitIdType target); + + [DllImport(simulatorDll)] + private static extern void Dump_cpp(SimulatorIdType sim, DumpCallback callback); + + [DllImport(simulatorDll)] + [return: MarshalAs(UnmanagedType.I1)] // necessary because C++ and C# represent bools differently + private static extern bool DumpQubits_cpp(SimulatorIdType sim, int length, QubitIdType[] qubitIds, DumpCallback callback); + + private delegate void IdsCallback(QubitIdType id); + [DllImport(simulatorDll)] + private static extern void QubitIds_cpp(SimulatorIdType sim, IdsCallback callback); + + [DllImport(simulatorDll)] + private static extern double JointEnsembleProbability_cpp(SimulatorIdType sim, int length, int[] basis, QubitIdType[] qubits); + + } +} diff --git a/src/Simulation/Simulators/SparseSimulator/NativeWrappers.cs b/src/Simulation/Simulators/SparseSimulator/NativeWrappers.cs new file mode 100644 index 00000000000..3152423f64d --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/NativeWrappers.cs @@ -0,0 +1,164 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Common; +using System.Linq; + +#nullable enable + +namespace Microsoft.Quantum.Simulation.Simulators +{ + using QubitIdType = System.UInt32; + using SimulatorIdType = System.UInt32; + + public partial class SparseSimulator + { + protected override void MCX(uint count, uint[] ctrls, uint qubit) + { + MCX_cpp(this.Id, (int)count, ctrls, (QubitIdType)qubit); + } + + protected override void MCZ(uint count, uint[] ctrls, uint qubit) + { + MCZ_cpp(this.Id, (int)count, ctrls, (QubitIdType)qubit); + + } + + protected override void H(uint qubit) + { + H_cpp(this.Id, (QubitIdType)qubit); + } + protected override void MCH(uint count, uint[] ctrls, uint qubit) + { + MCH_cpp(this.Id, (int)count, ctrls, (QubitIdType)qubit); + } + + protected override void R(Pauli basis, double angle, uint qubit) + { + R_cpp(this.Id, (int)basis, angle, (QubitIdType)qubit); + } + + protected override void S(uint qubit) + { + S_cpp(this.Id, (QubitIdType)qubit); + } + + protected override void AdjS(uint qubit) + { + AdjS_cpp(this.Id, (QubitIdType)qubit); + } + + protected override void T(uint qubit) + { + T_cpp(this.Id, (QubitIdType)qubit); + } + + protected override void AdjT(uint qubit) + { + AdjT_cpp(this.Id, (QubitIdType)qubit); + } + + protected override void X(uint qubit) + { + X_cpp(this.Id, (QubitIdType)qubit); + } + + protected override void Y(uint qubit) + { + Y_cpp(this.Id, (QubitIdType)qubit); + } + + protected override void Z(uint qubit) + { + Z_cpp(this.Id, (QubitIdType)qubit); + } + + protected override double JointEnsembleProbability(uint n, Pauli[] b, uint[] q) + { + int[] bases = b.Cast().ToArray(); + return JointEnsembleProbability_cpp(this.Id, (int)n, bases, q); + } + + protected override void Exp(uint n, Pauli[] paulis, double angle, uint[] ids) + { + int[] bases = paulis.Cast().ToArray(); + Exp_cpp(this.Id, (int)n, bases, angle, ids); + } + + protected override void MCExp(uint n, Pauli[] paulis, double angle, uint nc, uint[] ctrls, uint[] ids) + { + int[] bases = paulis.Cast().ToArray(); + MCExp_cpp(this.Id, (int)nc, (int)n, ctrls, bases, angle, ids); + } + + protected override uint M(uint q) + { + return M_cpp(this.Id, (QubitIdType)q); + } + + protected override uint Measure(uint n, Pauli[] b, uint[] ids) + { + int[] bases = b.Cast().ToArray(); + QubitIdType[] qids = ids.Select(c => (QubitIdType)c).ToArray(); + return Measure_cpp(this.Id, (int)n, bases, qids); + } + + protected override void MCR(Pauli basis, double angle, uint count, uint[] ctrls, uint qubit) + { + QubitIdType[] controls = ctrls.Select(c => (QubitIdType)c).ToArray(); + MCR_cpp(this.Id, (int)basis, angle, (int)count, controls, (QubitIdType)qubit); + } + + protected override void MCS(uint count, uint[] ctrls, uint qubit) + { + QubitIdType[] controls = ctrls.Select(c => (QubitIdType)c).ToArray(); + MCR1_cpp(this.Id, 0.5*System.Math.PI, (int)count, controls, (QubitIdType)qubit); + } + + protected override void MCAdjS(uint count, uint[] ctrls, uint qubit) + { + QubitIdType[] controls = ctrls.Select(c => (QubitIdType)c).ToArray(); + MCR1_cpp(this.Id, -0.5*System.Math.PI, (int)count, controls, (QubitIdType)qubit); + } + + protected override void sim_Dump(DumpCallback callback) + { + Dump_cpp(this.Id, callback); + } + + protected override bool sim_DumpQubits(uint count, uint[] ids, DumpCallback callback) + { + QubitIdType[] qs = ids.Select(q => (QubitIdType)q).ToArray(); + return DumpQubits_cpp(this.Id, (int)count, qs, callback); + } + + protected override void MCT(uint count, uint[] ctrls, uint qubit) + { + QubitIdType[] controls = ctrls.Select(c => (QubitIdType)c).ToArray(); + MCR1_cpp(this.Id, 0.25*System.Math.PI, (int)count, controls, (QubitIdType)qubit); + } + + protected override void MCAdjT(uint count, uint[] ctrls, uint qubit) + { + QubitIdType[] controls = ctrls.Select(c => (QubitIdType)c).ToArray(); + MCR1_cpp(this.Id, -0.25*System.Math.PI, (int)count, controls, (QubitIdType)qubit); + } + + protected override void MCY(uint count, uint[] ctrls, uint qubit) + { + QubitIdType[] controls = ctrls.Select(c => (QubitIdType)c).ToArray(); + MCY_cpp(this.Id, (int)count, controls, (QubitIdType)qubit); + } + + protected override void AllocateOne(uint qubit_id) + { + allocateQubit_cpp(this.Id, (QubitIdType)qubit_id); + } + protected override bool ReleaseOne(uint qubit_id) + { + return releaseQubit_cpp(this.Id, (QubitIdType)qubit_id); + } + + } +} diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulator.cs b/src/Simulation/Simulators/SparseSimulator/SparseSimulator.cs new file mode 100644 index 00000000000..357258d7f29 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulator.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Common; +using System.Runtime.InteropServices; +using Microsoft.Quantum.Simulation.Simulators.Exceptions; +using Microsoft.Quantum.Intrinsic.Interfaces; +using System.Collections.Generic; +using System.Diagnostics; + +#nullable enable + +namespace Microsoft.Quantum.Simulation.Simulators +{ + using QubitIdType = System.UInt32; + + public partial class SparseSimulator : CommonNativeSimulator + { + /// + /// Creates a an instance of a sparse simulator. + /// + /// If set to true, the exception is thrown when trying to release qubits not in zero state. + /// Seed for the random number generator used by a simulator for measurement outcomes and the Random operation. + /// If true, Borrowing qubits will be disabled, and a new qubit will be allocated instead every time borrowing is requested. Performance may improve. + /// Qubit capacity. + public SparseSimulator( + bool throwOnReleasingQubitsNotInZeroState = true, + UInt32? randomNumberGeneratorSeed = null, + bool disableBorrowing = false, + uint numQubits = 64) + : base(throwOnReleasingQubitsNotInZeroState, + randomNumberGeneratorSeed, + disableBorrowing) + { + Id = init_cpp((QubitIdType)numQubits); + + // Make sure that the same seed used by the built-in System.Random + // instance is also used by the native simulator itself. + seed_cpp(this.Id, (uint)this.Seed); + } + + public override void Dispose() + { + destroy_cpp(this.Id); + } + + public override string Name + { + get + { + return "SparseSimulator"; + } + } + } +} diff --git a/src/Simulation/TargetDefinitions/Decompositions/ExpUtilFromIsing.qs b/src/Simulation/TargetDefinitions/Decompositions/ExpUtilFromIsing.qs index ebc50d9aca4..0840705a66f 100644 --- a/src/Simulation/TargetDefinitions/Decompositions/ExpUtilFromIsing.qs +++ b/src/Simulation/TargetDefinitions/Decompositions/ExpUtilFromIsing.qs @@ -14,11 +14,11 @@ namespace Microsoft.Quantum.Intrinsic { } apply { if (paulis[0] == PauliX) { - IsingXX(theta / 2.0, qubits[0], qubits[1]); + IsingXX(-2.0 * theta , qubits[0], qubits[1]); } elif (paulis[0] == PauliY) { - IsingYY(theta / 2.0, qubits[0], qubits[1]); + IsingYY(-2.0 * theta, qubits[0], qubits[1]); } elif (paulis[0] == PauliZ) { - IsingZZ(theta / 2.0, qubits[0], qubits[1]); + IsingZZ(-2.0 * theta, qubits[0], qubits[1]); } else { fail "Type2 decompositions do not support PauliI as an input to Exp"; } @@ -35,7 +35,7 @@ namespace Microsoft.Quantum.Intrinsic { SpreadZ(qubits[1], qubits[2 .. Length(qubits) - 1]); } apply { - IsingZZ(theta / 2.0, qubits[0], qubits[1]); + IsingZZ(-2.0 * theta, qubits[0], qubits[1]); } } } diff --git a/src/Simulation/TargetDefinitions/Intrinsic/Properties/NamespaceInfo.qs b/src/Simulation/TargetDefinitions/Intrinsic/Properties/NamespaceInfo.qs new file mode 100644 index 00000000000..62809ad635c --- /dev/null +++ b/src/Simulation/TargetDefinitions/Intrinsic/Properties/NamespaceInfo.qs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/// # Summary +/// This namespace contains built-in operations that represent +/// commonly used quantum gates and measurements. +/// +/// # Description +/// To learn more about the operations in this namespace, see +/// [The Prelude](xref:microsoft.quantum.libraries.overview.standard.prelude). +namespace Microsoft.Quantum.Intrinsic {} diff --git a/src/Simulation/qdk_sim_rs/.config/dotnet-tools.json b/src/Simulation/qdk_sim_rs/.config/dotnet-tools.json deleted file mode 100644 index 8e180ea3016..00000000000 --- a/src/Simulation/qdk_sim_rs/.config/dotnet-tools.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": 1, - "isRoot": true, - "tools": { - "dotnet-script": { - "version": "1.1.0", - "commands": [ - "dotnet-script" - ] - } - } -} \ No newline at end of file diff --git a/src/Simulation/qdk_sim_rs/.gitignore b/src/Simulation/qdk_sim_rs/.gitignore index a740f3ce698..0af728fc99f 100644 --- a/src/Simulation/qdk_sim_rs/.gitignore +++ b/src/Simulation/qdk_sim_rs/.gitignore @@ -4,8 +4,6 @@ win10 target drop -# We inject version numbers into Cargo.toml, so don't want them stored in repo. -Cargo.toml # In the future, it would be good to enable reproducible builds by committing # the lockfile and using --locked in calls to cargo. Cargo.lock diff --git a/src/Simulation/qdk_sim_rs/Cargo.toml.template b/src/Simulation/qdk_sim_rs/Cargo.toml similarity index 99% rename from src/Simulation/qdk_sim_rs/Cargo.toml.template rename to src/Simulation/qdk_sim_rs/Cargo.toml index f589d6d411b..3855ffe6a13 100644 --- a/src/Simulation/qdk_sim_rs/Cargo.toml.template +++ b/src/Simulation/qdk_sim_rs/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "qdk_sim_experimental" -version = "0.1.0" +version = "0.0.1-alpha" authors = ["Microsoft"] edition = "2018" license = "MIT" diff --git a/src/Simulation/qdk_sim_rs/inject-version.csx b/src/Simulation/qdk_sim_rs/inject-version.csx deleted file mode 100644 index ec71338f066..00000000000 --- a/src/Simulation/qdk_sim_rs/inject-version.csx +++ /dev/null @@ -1,39 +0,0 @@ -#r "nuget: System.CommandLine, 2.0.0-beta1.21216.1" -#r "nuget: Tommy, 2.0.0" - -using System.CommandLine; -using System.Linq; -using System.CommandLine.Invocation; -using Tommy; - -// Create a root command with some options -var rootCommand = new RootCommand -{ - new Option( - "--template", - description: "A file to use as the template for cargo manifest."), - new Option( - "--out-path", - description: "Path to write the generated manifest to."), - new Option( - "--version", - description: "The version number to inject.") -}; - -// Note that the parameters of the handler method are matched according to the names of the options -rootCommand.Handler = CommandHandler.Create((template, outPath, version) => -{ - Console.Out.WriteLine($"Injecting version {version} into {template} and writing to {outPath}."); - using var reader = new StreamReader(File.OpenRead(template.FullName)); - var table = TOML.Parse(reader); - - // Set the version number in the table. - table["package"]["version"] = version; - - using var writer = new StreamWriter(File.OpenWrite(outPath)); - table.WriteTo(writer); - // Remember to flush the data if needed! - writer.Flush(); -}); - -await rootCommand.InvokeAsync(Args.ToArray()); diff --git a/src/Xunit/Microsoft.Quantum.Xunit.nuspec b/src/Xunit/Microsoft.Quantum.Xunit.nuspec index 2066e99d582..128e4757e97 100644 --- a/src/Xunit/Microsoft.Quantum.Xunit.nuspec +++ b/src/Xunit/Microsoft.Quantum.Xunit.nuspec @@ -22,7 +22,7 @@ - +