diff --git a/.gitignore b/.gitignore index 962637a6fc6..395280e2678 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,9 @@ 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/SparseQuantumSimulator.dll +src/Simulation/Native/linux/libSparseQuantumSimulator.so +src/Simulation/Native/osx/libSparseQuantumSimulator.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 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..7c4ff8880ec 100644 --- a/bootstrap.ps1 +++ b/bootstrap.ps1 @@ -21,11 +21,15 @@ 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/pack.ps1 b/build/pack.ps1 index 37882ee7581..55fc09f27c6 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/libSparseQuantumSimulator.dylib") { + Copy-Item -Verbose "$DROP/libSparseQuantumSimulator.dylib" "osx/libSparseQuantumSimulator.dylib" + } + If (Test-Path "$DROP/libSparseQuantumSimulator.so") { + Copy-Item -Verbose "$DROP/libSparseQuantumSimulator.so" "linux/libSparseQuantumSimulator.so" + } + If (Test-Path "$DROP/SparseQuantumSimulator.dll") { + Copy-Item -Verbose "$DROP/SparseQuantumSimulator.dll" "win10/SparseQuantumSimulator.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 = "", 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 e0159cc7c61..1353e992d05 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "msbuild-sdks": { - "Microsoft.Quantum.Sdk": "0.22.192799-alpha" + "Microsoft.Quantum.Sdk": "0.22.192949-beta" } } diff --git a/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp index dc526c4a303..99250f57bb0 100644 --- a/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp +++ b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp @@ -159,11 +159,11 @@ namespace Quantum { std::cout << "*********************" << std::endl; this->GetState( - [](size_t idx, double re, double im) + [](const char* idx, double re, double im) { if (!Close(re, 0.0) || !Close(im, 0.0)) { - std::cout << "|" << std::bitset<8>(idx) << ">: " << re << "+" << im << "i" << std::endl; + std::cout << "|" << idx << ">: " << re << "+" << im << "i" << std::endl; } return true; }); 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/Tests/FullstateSimulator/FullstateSimulatorTests.cpp b/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp index 5bcc353ec39..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 @@ -347,9 +348,14 @@ TEST_CASE("Fullstate simulator: get qubit state of Bell state", "[fullstate_simu // 1/sqrt(2)(|00> + |11>)x|0> dynamic_cast(sim.get())->GetState( - [](size_t idx, double re, double im) + [](const char* idxStr, double re, double im) { 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) { @@ -372,9 +378,14 @@ TEST_CASE("Fullstate simulator: get qubit state of Bell state", "[fullstate_simu // 1/sqrt(2)(|00> + |11>)xi|1> dynamic_cast(sim.get())->GetState( - [](size_t idx, double re, double im) + [](const char* idxStr, double re, double im) { 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: diff --git a/src/Qir/Tools/Microsoft.Quantum.Qir.Runtime.Tools.csproj b/src/Qir/Tools/Microsoft.Quantum.Qir.Runtime.Tools.csproj index da648478d84..ae41db65e77 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/Simulation/AutoSubstitution/Microsoft.Quantum.AutoSubstitution.csproj b/src/Simulation/AutoSubstitution/Microsoft.Quantum.AutoSubstitution.csproj index d41d382af8d..aa3e5f38b79 100644 --- a/src/Simulation/AutoSubstitution/Microsoft.Quantum.AutoSubstitution.csproj +++ b/src/Simulation/AutoSubstitution/Microsoft.Quantum.AutoSubstitution.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Simulation/Common/Simulators.Dev.props b/src/Simulation/Common/Simulators.Dev.props index 8e10e59a7b9..56ea45a6260 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 @@ + + + + + - + diff --git a/src/Simulation/EntryPointDriver/Driver.cs b/src/Simulation/EntryPointDriver/Driver.cs index acdbce2b752..5042361a97d 100644 --- a/src/Simulation/EntryPointDriver/Driver.cs +++ b/src/Simulation/EntryPointDriver/Driver.cs @@ -189,6 +189,7 @@ public Driver(DriverSettings settings, IReadOnlyCollection entryPoi suggestions: new[] { this.settings.QuantumSimulatorName, + // this.settings.SparseSimulatorName, // uncomment to enable listing the sparse simulator in the command line help 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/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..ad63362b786 --- /dev/null +++ b/src/Simulation/NativeSparseSimulator/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.10) +project(SparseQuantumSimulator) + +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(SparseQuantumSimulator SHARED factory.cpp capi.cpp) + +# Windows adds a special dllexport command which must be defined +if (WIN32) + target_compile_options(SparseQuantumSimulator PUBLIC -fdeclspec) + target_compile_definitions(SparseQuantumSimulator PRIVATE BUILD_DLL=1) +endif() +# Try to optimize with gcc +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options(SparseQuantumSimulator 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/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/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..f01ed172cfa 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(); } } } @@ -407,62 +563,98 @@ public void QSimVerifyExpX() [Fact] public void QSimVerifyExpFrac() { - 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, 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/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/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/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/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/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/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/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/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/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..4ebcd5b10ec --- /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 = "SparseQuantumSimulator"; + + [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/Xunit/Microsoft.Quantum.Xunit.nuspec b/src/Xunit/Microsoft.Quantum.Xunit.nuspec index 30d7ff01748..9371b2cd744 100644 --- a/src/Xunit/Microsoft.Quantum.Xunit.nuspec +++ b/src/Xunit/Microsoft.Quantum.Xunit.nuspec @@ -22,7 +22,7 @@ - +