From 86b3451eeab616284018b3518bfb968256793c4e Mon Sep 17 00:00:00 2001 From: Sam Jaques Date: Mon, 5 Jul 2021 18:42:16 +0100 Subject: [PATCH 01/25] Initial sparse simulator commit --- .../SparseSimulator/Native/CMakeLists.txt | 39 + .../SparseSimulator/Native/SparseSimulator.h | 846 +++++++++ .../Native/basic_quantum_state.hpp | 82 + .../SparseSimulator/Native/capi.cpp | 303 ++++ .../SparseSimulator/Native/capi.hpp | 146 ++ .../SparseSimulator/Native/factory.cpp | 68 + .../SparseSimulator/Native/factory.hpp | 22 + .../SparseSimulator/Native/flat_hash_map.hpp | 1502 ++++++++++++++++ .../Simulators/SparseSimulator/Native/gates.h | 284 ++++ .../Native/quantum_hash_map.hpp | 1302 ++++++++++++++ .../SparseSimulator/Native/quantum_state.hpp | 1511 +++++++++++++++++ .../Simulators/SparseSimulator/Native/types.h | 51 + .../Simulators/SparseSimulator/README.md | 155 ++ .../SparseQuantumSimulator.sln | 65 + .../SparseSimQSharpTests/Program.qs | 643 +++++++ .../SparseSimQSharpTests.csproj | 15 + .../SparseSimulatorCS/Probes.cs | 357 ++++ .../SparseSimulatorCS/Probes.qs | 33 + .../SparseSimulatorCS/Simulator.ApplyAnd.cs | 59 + .../SparseSimulatorCS/SparseSimulator.cs | 385 +++++ .../SparseSimulatorCS/SparseSimulator.csproj | 28 + .../SparseSimulatorTests/CMakeLists.txt | 19 + .../CSharpIntegrationTests.cpp | 306 ++++ .../SparseSimulatorTests.cpp | 772 +++++++++ .../SparseSimulatorTests.vcxproj | 176 ++ .../SparseSimulatorTests.vcxproj.filters | 39 + .../SparseSimulatorTests/TestHelpers.cpp | 159 ++ .../SparseSimulatorTests/TestHelpers.hpp | 34 + .../SparseSimulatorTests/pch.cpp | 5 + .../SparseSimulatorTests/pch.h | 13 + .../Simulators/SparseSimulator/build.cake | 72 + 31 files changed, 9491 insertions(+) create mode 100644 src/Simulation/Simulators/SparseSimulator/Native/CMakeLists.txt create mode 100644 src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h create mode 100644 src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp create mode 100644 src/Simulation/Simulators/SparseSimulator/Native/capi.cpp create mode 100644 src/Simulation/Simulators/SparseSimulator/Native/capi.hpp create mode 100644 src/Simulation/Simulators/SparseSimulator/Native/factory.cpp create mode 100644 src/Simulation/Simulators/SparseSimulator/Native/factory.hpp create mode 100644 src/Simulation/Simulators/SparseSimulator/Native/flat_hash_map.hpp create mode 100644 src/Simulation/Simulators/SparseSimulator/Native/gates.h create mode 100644 src/Simulation/Simulators/SparseSimulator/Native/quantum_hash_map.hpp create mode 100644 src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp create mode 100644 src/Simulation/Simulators/SparseSimulator/Native/types.h create mode 100644 src/Simulation/Simulators/SparseSimulator/README.md create mode 100644 src/Simulation/Simulators/SparseSimulator/SparseQuantumSimulator.sln create mode 100644 src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/Program.qs create mode 100644 src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/SparseSimQSharpTests.csproj create mode 100644 src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.cs create mode 100644 src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.qs create mode 100644 src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Simulator.ApplyAnd.cs create mode 100644 src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.cs create mode 100644 src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.csproj create mode 100644 src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CMakeLists.txt create mode 100644 src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CSharpIntegrationTests.cpp create mode 100644 src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp create mode 100644 src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj create mode 100644 src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj.filters create mode 100644 src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.cpp create mode 100644 src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.hpp create mode 100644 src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/pch.cpp create mode 100644 src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/pch.h create mode 100644 src/Simulation/Simulators/SparseSimulator/build.cake diff --git a/src/Simulation/Simulators/SparseSimulator/Native/CMakeLists.txt b/src/Simulation/Simulators/SparseSimulator/Native/CMakeLists.txt new file mode 100644 index 00000000000..9540c4edb78 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/Native/CMakeLists.txt @@ -0,0 +1,39 @@ +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 OpenMp if it is available +find_package(OpenMP REQUIRED) +if(OpenMP_CXX_FOUND) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") + if (OpenMP_CXX_VERSION_MAJOR GREATER_EQUAL 3) + target_compile_definitions(SparseQuantumSimulator PRIVATE DOMP_GE_V3=1) + endif() +endif() + + + +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 -fopenmp) +endif() + +# Sets NDEBUG to true for all non-debug settings +if (NOT (CMAKE_BUILD_TYPE:STRING STREQUAL "Debug")) + target_compile_definitions(SparseQuantumSimulator PRIVATE NDEBUG=1) +endif() + +message("Compiler flags: ${CMAKE_CXX_FLAGS_RELEASE}") diff --git a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h new file mode 100644 index 00000000000..809cd8f16a5 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h @@ -0,0 +1,846 @@ +#pragma once +#include +#include "types.h" +#include "gates.h" +#include +#include +#include +#include "quantum_state.hpp" +#include "basic_quantum_state.hpp" +#include +#include +#include +#include + +#include + + +#define MAX_QUBITS 1024 +#define MIN_QUBITS 64 + +using namespace std::literals::complex_literals; + +namespace Microsoft +{ +namespace Quantum +{ +namespace SPARSESIMULATOR +{ +#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); +} + + + + + 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; + + _Ry_queue = std::vector(num_qubits, 0); + _Rx_queue = std::vector(num_qubits, 0); + _H_queue = std::vector(num_qubits, 0); + _Rx_angles = std::vector(num_qubits, 0.0); + _Ry_angles = std::vector(num_qubits, 0.0); + + } + + + ~SparseSimulator() { + _execute_queued_ops(); + } + + // 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(unsigned 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) { + size_t 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); + _Ry_queue.resize(num_qubits, 0); + _Rx_queue.resize(num_qubits, 0); + _H_queue.resize(num_qubits, 0); + _Rx_angles.resize(num_qubits, 0.0); + _Ry_angles.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); + + if (!_quantum_state->is_qubit_zero(qubit_id)){ + throw std::runtime_error("Released qubit not in zero state"); + } + + } + _set_qubit_to_zero(qubit_id); + return true; + } + + + void X(logical_qubit_id index) { + // XY = - YX + if (_Ry_queue[index]){ + _Ry_angles[index] *= -1.0; + } + // Rx trivially commutes + if (_H_queue[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) { + // 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 (_Ry_queue[controls[0]] || _Rx_queue[controls[0]] || (_H_queue[controls[0]] && !_H_queue[target])){ + _execute_queued_ops(controls, OP::Ry); + } + } + // Ry on the target causes issues + if (_Ry_queue[target]){ + _execute_queued_ops(target, OP::Ry); + } + // Rx on the target trivially commutes + + // An H on the target flips the operation + if (_H_queue[target]){ + // If it is a CNOT and there is also an H on the control, we swap control and target + if (controls.size() == 1 && _H_queue[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 AND(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 AdjAND(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 (_Rx_queue[index]){ + _Rx_angles[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) { + _execute_if(controls); + // Commutes with Ry on the target, not Rx + if (_Rx_queue[target]){ + _execute_queued_ops(target, OP::Rx); + } + // YH = -YH, so we add a phase to track this + if (_H_queue[target]){ + // The phase added does not depend on the target + // Thus we use one of the controls as a target + _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 (_Ry_queue[index]){ + _Ry_angles[index] *= -1; + } + // XZ = -ZX + if (_Rx_queue[index]){ + _Rx_angles[index] *= -1; + } + // HZ = XH + if (_H_queue[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 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 (_Ry_queue[control] || _Rx_queue[control]){ + count += 2; + } + if (_H_queue[control]){ + count++; + } + } + if (_Ry_queue[target] || _Rx_queue[target]){ + count +=2; + } + if (_H_queue[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 + std::vector new_controls(controls); + for (logical_qubit_id control : controls){ + if (_H_queue[control]){ + std::swap(new_controls[control], target); + } + } + _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 (_Ry_queue[index] || _Ry_queue[index] || _H_queue[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){ + _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(amplitude(std::cos(angle), std::sin(angle)), index); + } + + void MCR1(std::vector const& controls, double const& angle, logical_qubit_id target){ + MCPhase(controls, amplitude(std::cos(angle), std::sin(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){ + MCR1(controls, (double)numerator * pow(0.5, power) * M_PI, 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){ + _Ry_queue[index] = true; + _Ry_angles[index] += phi; + _set_qubit_to_nonzero(index); + return; + } else if (_Ry_queue[index]) { + _execute_queued_ops(index, OP::Ry); + } + + if (b == Gates::Basis::PauliX){ + _Rx_queue[index] = true; + _Rx_angles[index] += phi; + _set_qubit_to_nonzero(index); + return; + } else if (_Rx_queue[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 (_H_queue[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 (b == Gates::Basis::PauliI){ + // Controlled I rotations are equivalent to controlled phase gates + if (controls.size() > 1){ + MCPhase(controls, amplitude(std::cos(phi),std::sin(phi)), controls[0]); + } else { + Phase(amplitude(std::cos(phi),std::sin(phi)), controls[0]); + } + return; + } + + _execute_if(controls); + // The target can commute with rotations of the same type + if (_Ry_queue[target] && b != Gates::Basis::PauliY){ + _execute_queued_ops(target, OP::Ry); + } + if (_Rx_queue[target] && b != Gates::Basis::PauliX){ + _execute_queued_ops(target, OP::Rx); + } + if (_H_queue[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 * 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){ + 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 + _Ry_angles[index] *= (_Ry_queue[index] ? -1.0 : 1.0); + // Commuting with Rx creates a phase, but on the wrong side + // So we execute any Rx immediately + if (_Rx_queue[index]){ + _execute_queued_ops(index, OP::Rx); + } + _H_queue[index] = !_H_queue[index]; + _set_qubit_to_nonzero(index); + } + + void MCH(std::vector const& controls, logical_qubit_id target) { + // No commutation on controls + _execute_if(controls); + // No Ry or Rx commutation on target + if (_Ry_queue[target] || _Rx_queue[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 + _Ry_queue.swap(_Ry_queue[index_1], _Ry_queue[index_2]); + std::swap(_Ry_angles[index_1], _Ry_angles[index_2]); + _Rx_queue.swap(_Rx_queue[index_1], _Rx_queue[index_2]); + std::swap(_Rx_angles[index_1], _Rx_angles[index_2]); + _H_queue.swap(_H_queue[index_1], _H_queue[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 (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); + } + } + + bool M(logical_qubit_id target) { + // Do nothing if the qubit is known to be 0 + if (!_occupied_qubits[target]){ + return false; + } + // 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 + if (_quantum_state->M(target)){ + _set_qubit_to_nonzero(target); + } else { + _set_qubit_to_zero(target); + } + return _occupied_qubits[target]; + + } + + void Reset(logical_qubit_id target) { + if (!_occupied_qubits[target]){ return; } + // If we get a measurement, we take it as soon as we can + _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 (_Rx_queue[qubit] || _Ry_queue[qubit]){ + _execute_queued_ops(qubits, OP::Ry); + } + } + bool isAllZ = true; + 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 (_H_queue[qubits[i]]){ + result ^= _H_queue[qubits[i]]; + } + isAllZ = false; + isEmpty = false; + break; + case Gates::Basis::PauliX: + // HX = ZH + if (_H_queue[qubits[i]]){ + axes[i] = Gates::Basis::PauliZ; + } else { + isAllZ = false; + } + isEmpty = false; + break; + case Gates::Basis::PauliZ: + // HZ = XH + if (_H_queue[qubits[i]]){ + axes[i] = Gates::Basis::PauliX; + isAllZ = false; + } + isEmpty = false; + break; + default: + break; + } + } + if (isEmpty) { + return; + } + // Z assertions are like phase gates + // If it's in release mode, it will queue them + // as a phase/permutation gate + // This means if an assert fails, it will fail + // at some future point, not at the point of failure + #if NDEBUG + if (isAllZ) { + _queued_operations.push_back(operation(OP::Assert, qubits, result)); + return; + } + #endif + // X or Y assertions require execution + _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); + } + + + + bool Measure(std::vector const& axes, std::vector const& qubits){ + _execute_queued_ops(qubits, OP::Ry); + bool 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 label) { + _execute_queued_ops(); + return _quantum_state->probe(label); + } + + std::string Sample() { + _execute_queued_ops(); + return _quantum_state->Sample(); + } + + // 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 qubits, void (*callback)(char*, double, double)) { + _execute_queued_ops(qubits, OP::Ry); + return _quantum_state->dump_qubits(qubits, callback); + } + + // Dumps all the states in superposition via a callback function + void dump_all(logical_qubit_id max_qubit_id, void (*callback)(char*, double, double)) { + _execute_queued_ops(); + _quantum_state->dump_all(max_qubit_id, callback); + } + + // 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 _H_queue; + std::vector _Rx_queue; + std::vector _Ry_queue; + + std::vector _Rx_angles; + std::vector _Ry_angles; + + // 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 (_H_queue[index]){ + _quantum_state->H(index); + _H_queue[index] = false; + } + if (_Rx_queue[index]){ + _quantum_state->R(Gates::Basis::PauliX, _Rx_angles[index], index); + _Rx_angles[index] = 0.0; + _Rx_queue[index] = false; + } + if (_Ry_queue[index]){ + _quantum_state->R(Gates::Basis::PauliY, _Ry_angles[index], index); + _Ry_angles[index] = 0.0; + _Ry_queue[index] = false; + } + } + + void _execute_RxH_single_qubit(logical_qubit_id const &index){ + if (_H_queue[index]){ + _quantum_state->H(index); + _H_queue[index] = false; + } + if (_Rx_queue[index]){ + _quantum_state->R(Gates::Basis::PauliX, _Rx_angles[index], index); + _Rx_angles[index] = 0.0; + _Rx_queue[index] = false; + } + } + + void _execute_H_single_qubit(logical_qubit_id const &index){ + if (_H_queue[index]){ + _quantum_state->H(index); + _H_queue[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 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 (_Ry_queue[target] || _Rx_queue[target] || _H_queue[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 (_Ry_queue[control] || _Rx_queue[control] || _H_queue[control]){ + _execute_queued_ops(controls, OP::Ry); + return; + } + } + } + + }; + + +} // namespace SPARSESIMULATOR +} // namespace Quantum +} // namespace Microsoft diff --git a/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp new file mode 100644 index 00000000000..330261881f5 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp @@ -0,0 +1,82 @@ +#pragma once +#include "types.h" +#include "gates.h" +#include + + +namespace Microsoft +{ +namespace Quantum +{ +namespace SPARSESIMULATOR +{ + + + + // Virtual class for QuantumState + // This is not templated, so it allows SparseSimulator types to avoid templates + class BasicQuantumState + { + public: + + BasicQuantumState() {} + + virtual logical_qubit_id get_num_qubits() = 0; + + virtual void DumpWavefunction(size_t indent = 0) = 0; + + virtual void set_random_seed(unsigned 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 bool 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 bool Measure(std::vector const&, std::vector const&) = 0; + + + virtual amplitude probe(std::string label) = 0; + + virtual bool dump_qubits(std::vector qubits, void (*callback)(char*, double, double)) = 0; + + virtual void dump_all(logical_qubit_id max_qubit_id, void (*callback)(char*, double, double)) = 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 universal_wavefunction get_universal_wavefunction() = 0; + + virtual std::function get_rng() = 0; + + virtual void complete_threads() = 0; + virtual int get_num_threads() = 0; + + virtual std::string Sample() = 0; + }; + + +} // namespace SPARSESIMULATOR +} // namespace Quantum +} // namespace Microsoft diff --git a/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp b/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp new file mode 100644 index 00000000000..24bd58358d2 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp @@ -0,0 +1,303 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// 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 "capi.hpp" +#include "SparseSimulator.h" +#include "factory.hpp" +#include +#include + +using namespace Microsoft::Quantum::SPARSESIMULATOR; + + +#include +#include + +std::string sample_string; + +extern "C" +{ + + MICROSOFT_QUANTUM_DECL unsigned init_cpp(logical_qubit_id num_qubits) + { + return createSimulator(num_qubits); + } + + + MICROSOFT_QUANTUM_DECL void destroy_cpp(unsigned sim_id) + { + destroySimulator(sim_id); + } + + MICROSOFT_QUANTUM_DECL void seed_cpp(unsigned sim_id, _In_ unsigned s){ + getSimulator(sim_id)->set_random_seed(s); + } + + MICROSOFT_QUANTUM_DECL void allocateQubit_cpp(unsigned sim_id, logical_qubit_id q) + { + getSimulator(sim_id)->allocate_specific_qubit(q); + } + + MICROSOFT_QUANTUM_DECL void releaseQubit_cpp(unsigned sim_id, logical_qubit_id q) + { + getSimulator(sim_id)->release(q); + } + + MICROSOFT_QUANTUM_DECL logical_qubit_id num_qubits_cpp(unsigned sim_id) + { + return getSimulator(sim_id)->get_num_qubits(); + } + +// Generic single-qubit gate +#define FWDGATE1(G) \ + MICROSOFT_QUANTUM_DECL void G##_cpp(unsigned 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(unsigned 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(unsigned sim_id, _In_ logical_qubit_id q1, _In_ logical_qubit_id q2){ + getSimulator(sim_id)->SWAP(q1, q2); + } + + MICROSOFT_QUANTUM_DECL void MCSWAP_cpp(unsigned 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 MCAnd_cpp(unsigned sim_id,_In_ int length, _In_reads_(length) logical_qubit_id* controls, _In_ logical_qubit_id target){ + getSimulator(sim_id)->AND(std::vector(controls, controls + length), target); + } + + MICROSOFT_QUANTUM_DECL void MCAdjointAnd_cpp(unsigned sim_id,_In_ int length, _In_reads_(length) logical_qubit_id* controls, _In_ logical_qubit_id target){ + getSimulator(sim_id)->AdjAND(std::vector(controls, controls + length), target); + } + + // rotations + + MICROSOFT_QUANTUM_DECL void R_cpp(unsigned 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(unsigned 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(unsigned sim_id,_In_ double phi, _In_ logical_qubit_id q) + { + getSimulator(sim_id)->R1(phi, q); + } + MICROSOFT_QUANTUM_DECL void R1frac_cpp(unsigned 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( + unsigned 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( + unsigned 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( + unsigned 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( + unsigned 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( + unsigned sim_id, + _In_ int n, + _In_reads_(n) int* b, + _In_ double phi, + _In_reads_(n) logical_qubit_id* q) + { + std::vector bv; + 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( + unsigned 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; + 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 bool M_cpp(unsigned sim_id, _In_ logical_qubit_id q) + { + return getSimulator(sim_id)->M(q); + } + + MICROSOFT_QUANTUM_DECL void Reset_cpp(unsigned sim_id, _In_ logical_qubit_id q){ + getSimulator(sim_id)->Reset(q); + } + + MICROSOFT_QUANTUM_DECL bool Measure_cpp( + unsigned sim_id, + _In_ int n, + _In_reads_(n) int* b, + _In_reads_(n) logical_qubit_id* q) + { + std::vector bv; + 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( + unsigned 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(unsigned sim_id, _In_ logical_qubit_id max_qubit_id, _In_ void (*callback)(char* , double, double)){ + return getSimulator(sim_id)->dump_all(max_qubit_id, callback); + } + + // 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( + unsigned sim_id, + _In_ logical_qubit_id n, + _In_reads_(n) logical_qubit_id* q, + _In_ void (*callback)(char* , double, double)) + { + std::vector qs(q, q + n); + return getSimulator(sim_id)->dump_qubits(qs, callback); + } + + // Writes the real and imaginary parts of the amplitude of the state `label` + // (a binary bistring of length `length`) to the arguments of `real` and `imag` + MICROSOFT_QUANTUM_DECL void GetAmplitude_cpp(_In_ unsigned sim_id, _In_ size_t length, _In_ const char* label, _Out_ double &real, _Out_ double &imag) + { + amplitude val = getSimulator(sim_id)->probe(std::string(label, length)); + real = val.real(); + imag = val.imag(); + } + + + // Asserts that the gates in `b`, measured on the qubits in `q`, return `result` + MICROSOFT_QUANTUM_DECL bool Assert_cpp(unsigned sim_id, _In_ int n, _In_reads_(n) int* b, _In_reads_(n) logical_qubit_id* q, bool result){ + std::vector bv; + 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; + } + + // Returns a character string of the label of a randomly sampled state + MICROSOFT_QUANTUM_DECL const char* Sample_cpp(unsigned sim_id){ + sample_string = getSimulator(sim_id)->Sample(); + return sample_string.c_str(); + } + +} diff --git a/src/Simulation/Simulators/SparseSimulator/Native/capi.hpp b/src/Simulation/Simulators/SparseSimulator/Native/capi.hpp new file mode 100644 index 00000000000..85add5e5b84 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/Native/capi.hpp @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + + + +#include +#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 unsigned init_cpp(logical_qubit_id num_qubits); + MICROSOFT_QUANTUM_DECL void destroy_cpp(unsigned sim_id); + + MICROSOFT_QUANTUM_DECL void seed_cpp(unsigned sim_id, _In_ unsigned s); + // allocate and release + MICROSOFT_QUANTUM_DECL void allocateQubit_cpp(unsigned sim_id, logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void releaseQubit_cpp(unsigned sim_id, logical_qubit_id q); + MICROSOFT_QUANTUM_DECL logical_qubit_id num_qubits_cpp(unsigned sim_id); + + // single-qubit gates + MICROSOFT_QUANTUM_DECL void X_cpp(unsigned sim_id, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void Y_cpp(unsigned sim_id, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void Z_cpp(unsigned sim_id, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void H_cpp(unsigned sim_id, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void S_cpp(unsigned sim_id, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void T_cpp(unsigned sim_id, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void AdjS_cpp(unsigned sim_id, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void AdjT_cpp(unsigned sim_id, _In_ logical_qubit_id q); + + + MICROSOFT_QUANTUM_DECL void MCX_cpp(unsigned sim_id, _In_ int n, _In_reads_(n) logical_qubit_id* c, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void MCY_cpp(unsigned sim_id, _In_ int n, _In_reads_(n) logical_qubit_id* c, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void MCZ_cpp(unsigned sim_id, _In_ int n, _In_reads_(n) logical_qubit_id* c, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void MCH_cpp(unsigned sim_id, _In_ int n, _In_reads_(n) logical_qubit_id* c, _In_ logical_qubit_id q); + + MICROSOFT_QUANTUM_DECL void SWAP_cpp(unsigned sim_id, _In_ logical_qubit_id q1, _In_ logical_qubit_id q2); + MICROSOFT_QUANTUM_DECL void MCSWAP_cpp(unsigned 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(unsigned sim_id,_In_ int length, _In_reads_(length) logical_qubit_id* controls, _In_ logical_qubit_id target); + MICROSOFT_QUANTUM_DECL void MCAdjointAnd_cpp(unsigned sim_id,_In_ int length, _In_reads_(length) logical_qubit_id* controls, _In_ logical_qubit_id target); + + // rotations + MICROSOFT_QUANTUM_DECL void R_cpp(unsigned sim_id, _In_ int b, _In_ double phi, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void Rfrac_cpp(unsigned 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(unsigned sim_id, _In_ double phi, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void R1frac_cpp(unsigned 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( + unsigned 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( + unsigned 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( + unsigned 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( + unsigned 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( + unsigned 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( + unsigned 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 bool M_cpp(unsigned sim_id, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL void Reset_cpp(unsigned sim_id, _In_ logical_qubit_id q); + MICROSOFT_QUANTUM_DECL bool Measure_cpp( + unsigned sim_id, + _In_ int n, + _In_reads_(n) int* b, + _In_reads_(n) logical_qubit_id* q); + + + MICROSOFT_QUANTUM_DECL double JointEnsembleProbability_cpp( + unsigned sim_id, + _In_ int n, + _In_reads_(n) int* b, + _In_reads_(n) logical_qubit_id* q); + + MICROSOFT_QUANTUM_DECL void Dump_cpp(unsigned sim_id, _In_ logical_qubit_id max_qubit_id, _In_ void (*callback)(char*, double, double)); + MICROSOFT_QUANTUM_DECL bool DumpQubits_cpp( + unsigned sim_id, + _In_ logical_qubit_id n, + _In_reads_(n) logical_qubit_id* q, + _In_ void (*callback)(char* , double, double)); + + MICROSOFT_QUANTUM_DECL void GetAmplitude_cpp(_In_ unsigned sim_id, _In_ size_t length, _In_ const char* label, _Out_ double &real, _Out_ double &imag); + + MICROSOFT_QUANTUM_DECL bool Assert_cpp(unsigned sim_id, _In_ int n, _In_reads_(n) int* b, _In_reads_(n) logical_qubit_id* q, bool result); + MICROSOFT_QUANTUM_DECL const char* Sample_cpp(unsigned sim_id); +} diff --git a/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp b/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp new file mode 100644 index 00000000000..fa42fd3c5fe --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// Manages simulators in a vector of pointers to simulators + +#include "factory.hpp" +#include "SparseSimulator.h" +#include "types.h" +#include +#include + + +namespace Microsoft +{ +namespace Quantum +{ +namespace SPARSESIMULATOR +{ +// Ensures exclusive access to _simulators, the vector of simulators +std::shared_mutex _mutex; +std::vector> _simulators; + +unsigned createSimulator(logical_qubit_id num_qubits) +{ + if (num_qubits > MAX_QUBITS) + throw std::runtime_error("Max number of qubits is 1024!"); + std::lock_guard lock(_mutex); + size_t emptySlot = -1; + for (auto const& s : _simulators) + { + if (s == NULL) + { + emptySlot = &s - &_simulators[0]; + break; + } + } + if (emptySlot == -1) + { + _simulators.push_back(std::shared_ptr(new SparseSimulator(num_qubits))); + emptySlot = _simulators.size() - 1; + } + else + { + _simulators[emptySlot] = std::shared_ptr(new SparseSimulator(num_qubits)); + } + + return static_cast(emptySlot); +} + +// Deletes a simulator in the vector +void destroySimulator(unsigned id) +{ + std::lock_guard lock(_mutex); + + _simulators[id].reset(); +} + +// Returns a simulator at some id (used for the C++/C# API) +std::shared_ptr& getSimulator(unsigned id) +{ + std::shared_lock shared_lock(_mutex); + + return _simulators[id]; +} + +} // namespace Simulator +} // namespace Quantum +} // namespace Microsoft diff --git a/src/Simulation/Simulators/SparseSimulator/Native/factory.hpp b/src/Simulation/Simulators/SparseSimulator/Native/factory.hpp new file mode 100644 index 00000000000..52b95f5c64a --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/Native/factory.hpp @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// Manages simulators in a vector of pointers to simulators + +#pragma once +#include "types.h" +#include "SparseSimulator.h" + +namespace Microsoft +{ +namespace Quantum +{ +namespace SPARSESIMULATOR +{ +unsigned createSimulator(logical_qubit_id); +void destroySimulator(unsigned); + +std::shared_ptr& getSimulator(unsigned); +} // namespace Simulator +} // namespace Quantum +} // namespace Microsoft diff --git a/src/Simulation/Simulators/SparseSimulator/Native/flat_hash_map.hpp b/src/Simulation/Simulators/SparseSimulator/Native/flat_hash_map.hpp new file mode 100644 index 00000000000..c228b2dc8cd --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/Native/flat_hash_map.hpp @@ -0,0 +1,1502 @@ +// Copyright Malte Skarupke 2017. +// Distributed under the Boost Software License, Version 1.0. +// (See http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#define SKA_NOINLINE(...) __declspec(noinline) __VA_ARGS__ +#else +#define SKA_NOINLINE(...) __VA_ARGS__ __attribute__((noinline)) +#endif + +namespace ska +{ + struct prime_number_hash_policy; + struct power_of_two_hash_policy; + struct fibonacci_hash_policy; + + namespace detailv3 + { + template + struct functor_storage : Functor + { + functor_storage() = default; + functor_storage(const Functor& functor) + : Functor(functor) + { + } + template + Result operator()(Args &&... args) + { + return static_cast(*this)(std::forward(args)...); + } + template + Result operator()(Args &&... args) const + { + return static_cast(*this)(std::forward(args)...); + } + }; + template + struct functor_storage + { + typedef Result(*function_ptr)(Args...); + function_ptr function; + functor_storage(function_ptr function) + : function(function) + { + } + Result operator()(Args... args) const + { + return function(std::forward(args)...); + } + operator function_ptr& () + { + return function; + } + operator const function_ptr& () + { + return function; + } + }; + template + struct KeyOrValueHasher : functor_storage + { + typedef functor_storage hasher_storage; + KeyOrValueHasher() = default; + KeyOrValueHasher(const hasher& hash) + : hasher_storage(hash) + { + } + size_t operator()(const key_type& key) + { + return static_cast(*this)(key); + } + size_t operator()(const key_type& key) const + { + return static_cast(*this)(key); + } + size_t operator()(const value_type& value) + { + return static_cast(*this)(value.first); + } + size_t operator()(const value_type& value) const + { + return static_cast(*this)(value.first); + } + template + size_t operator()(const std::pair& value) + { + return static_cast(*this)(value.first); + } + template + size_t operator()(const std::pair& value) const + { + return static_cast(*this)(value.first); + } + }; + template + struct KeyOrValueEquality : functor_storage + { + typedef functor_storage equality_storage; + KeyOrValueEquality() = default; + KeyOrValueEquality(const key_equal& equality) + : equality_storage(equality) + { + } + bool operator()(const key_type& lhs, const key_type& rhs) + { + return static_cast(*this)(lhs, rhs); + } + bool operator()(const key_type& lhs, const value_type& rhs) + { + return static_cast(*this)(lhs, rhs.first); + } + bool operator()(const value_type& lhs, const key_type& rhs) + { + return static_cast(*this)(lhs.first, rhs); + } + bool operator()(const value_type& lhs, const value_type& rhs) + { + return static_cast(*this)(lhs.first, rhs.first); + } + template + bool operator()(const key_type& lhs, const std::pair& rhs) + { + return static_cast(*this)(lhs, rhs.first); + } + template + bool operator()(const std::pair& lhs, const key_type& rhs) + { + return static_cast(*this)(lhs.first, rhs); + } + template + bool operator()(const value_type& lhs, const std::pair& rhs) + { + return static_cast(*this)(lhs.first, rhs.first); + } + template + bool operator()(const std::pair& lhs, const value_type& rhs) + { + return static_cast(*this)(lhs.first, rhs.first); + } + template + bool operator()(const std::pair& lhs, const std::pair& rhs) + { + return static_cast(*this)(lhs.first, rhs.first); + } + }; + static constexpr int8_t min_lookups = 4; + template + struct sherwood_v3_entry + { + sherwood_v3_entry() + { + } + sherwood_v3_entry(int8_t distance_from_desired) + : distance_from_desired(distance_from_desired) + { + } + ~sherwood_v3_entry() + { + } + static sherwood_v3_entry* empty_default_table() + { + static sherwood_v3_entry result[min_lookups] = { {}, {}, {}, {special_end_value} }; + return result; + } + + bool has_value() const + { + return distance_from_desired >= 0; + } + bool is_empty() const + { + return distance_from_desired < 0; + } + bool is_at_desired_position() const + { + return distance_from_desired <= 0; + } + template + void emplace(int8_t distance, Args &&... args) + { + new (std::addressof(value)) T(std::forward(args)...); + distance_from_desired = distance; + } + + void destroy_value() + { + value.~T(); + distance_from_desired = -1; + } + + int8_t distance_from_desired = -1; + static constexpr int8_t special_end_value = 0; + union { T value; }; + }; + + inline int8_t log2(size_t value) + { + static constexpr int8_t table[64] = + { + 63, 0, 58, 1, 59, 47, 53, 2, + 60, 39, 48, 27, 54, 33, 42, 3, + 61, 51, 37, 40, 49, 18, 28, 20, + 55, 30, 34, 11, 43, 14, 22, 4, + 62, 57, 46, 52, 38, 26, 32, 41, + 50, 36, 17, 19, 29, 10, 13, 21, + 56, 45, 25, 31, 35, 16, 9, 12, + 44, 24, 15, 8, 23, 7, 6, 5 + }; + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value |= value >> 32; + + // Modification: Cast is necessary because VS decides that when right-shifting signed integers, + // the sign bit should be copied to the new spaces + return table[(unsigned long long)((value - (value >> 1)) * 0x07EDD5E59A4E28C2) >> 58]; + } + + template + struct AssignIfTrue + { + void operator()(T& lhs, const T& rhs) + { + lhs = rhs; + } + void operator()(T& lhs, T&& rhs) + { + lhs = std::move(rhs); + } + }; + template + struct AssignIfTrue + { + void operator()(T&, const T&) + { + } + void operator()(T&, T&&) + { + } + }; + + inline size_t next_power_of_two(size_t i) + { + --i; + i |= i >> 1; + i |= i >> 2; + i |= i >> 4; + i |= i >> 8; + i |= i >> 16; + i |= i >> 32; + ++i; + return i; + } + + template using void_t = void; + + template + struct HashPolicySelector + { + typedef fibonacci_hash_policy type; + }; + template + struct HashPolicySelector> + { + typedef typename T::hash_policy type; + }; + + template + class sherwood_v3_table : private EntryAlloc, private Hasher, private Equal + { + using Entry = detailv3::sherwood_v3_entry; + using AllocatorTraits = std::allocator_traits; + using EntryPointer = typename AllocatorTraits::pointer; + struct convertible_to_iterator; + + public: + + using value_type = T; + using size_type = size_t; + using difference_type = std::ptrdiff_t; + using hasher = ArgumentHash; + using key_equal = ArgumentEqual; + using allocator_type = EntryAlloc; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + + sherwood_v3_table() + { + } + explicit sherwood_v3_table(size_type bucket_count, const ArgumentHash& hash = ArgumentHash(), const ArgumentEqual& equal = ArgumentEqual(), const ArgumentAlloc& alloc = ArgumentAlloc()) + : EntryAlloc(alloc), Hasher(hash), Equal(equal) + { + rehash(bucket_count); + } + sherwood_v3_table(size_type bucket_count, const ArgumentAlloc& alloc) + : sherwood_v3_table(bucket_count, ArgumentHash(), ArgumentEqual(), alloc) + { + } + sherwood_v3_table(size_type bucket_count, const ArgumentHash& hash, const ArgumentAlloc& alloc) + : sherwood_v3_table(bucket_count, hash, ArgumentEqual(), alloc) + { + } + explicit sherwood_v3_table(const ArgumentAlloc& alloc) + : EntryAlloc(alloc) + { + } + template + sherwood_v3_table(It first, It last, size_type bucket_count = 0, const ArgumentHash& hash = ArgumentHash(), const ArgumentEqual& equal = ArgumentEqual(), const ArgumentAlloc& alloc = ArgumentAlloc()) + : sherwood_v3_table(bucket_count, hash, equal, alloc) + { + insert(first, last); + } + template + sherwood_v3_table(It first, It last, size_type bucket_count, const ArgumentAlloc& alloc) + : sherwood_v3_table(first, last, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) + { + } + template + sherwood_v3_table(It first, It last, size_type bucket_count, const ArgumentHash& hash, const ArgumentAlloc& alloc) + : sherwood_v3_table(first, last, bucket_count, hash, ArgumentEqual(), alloc) + { + } + sherwood_v3_table(std::initializer_list il, size_type bucket_count = 0, const ArgumentHash& hash = ArgumentHash(), const ArgumentEqual& equal = ArgumentEqual(), const ArgumentAlloc& alloc = ArgumentAlloc()) + : sherwood_v3_table(bucket_count, hash, equal, alloc) + { + if (bucket_count == 0) + rehash(il.size()); + insert(il.begin(), il.end()); + } + sherwood_v3_table(std::initializer_list il, size_type bucket_count, const ArgumentAlloc& alloc) + : sherwood_v3_table(il, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) + { + } + sherwood_v3_table(std::initializer_list il, size_type bucket_count, const ArgumentHash& hash, const ArgumentAlloc& alloc) + : sherwood_v3_table(il, bucket_count, hash, ArgumentEqual(), alloc) + { + } + sherwood_v3_table(const sherwood_v3_table& other) + : sherwood_v3_table(other, AllocatorTraits::select_on_container_copy_construction(other.get_allocator())) + { + } + sherwood_v3_table(const sherwood_v3_table& other, const ArgumentAlloc& alloc) + : EntryAlloc(alloc), Hasher(other), Equal(other), _max_load_factor(other._max_load_factor) + { + rehash_for_other_container(other); + try + { + insert(other.begin(), other.end()); + } + catch (...) + { + clear(); + deallocate_data(entries, num_slots_minus_one, max_lookups); + throw; + } + } + sherwood_v3_table(sherwood_v3_table&& other) noexcept + : EntryAlloc(std::move(other)), Hasher(std::move(other)), Equal(std::move(other)) + { + swap_pointers(other); + } + sherwood_v3_table(sherwood_v3_table&& other, const ArgumentAlloc& alloc) noexcept + : EntryAlloc(alloc), Hasher(std::move(other)), Equal(std::move(other)) + { + swap_pointers(other); + } + sherwood_v3_table& operator=(const sherwood_v3_table& other) + { + if (this == std::addressof(other)) + return *this; + + clear(); + if (AllocatorTraits::propagate_on_container_copy_assignment::value) + { + if (static_cast(*this) != static_cast(other)) + { + reset_to_empty_state(); + } + AssignIfTrue()(*this, other); + } + _max_load_factor = other._max_load_factor; + static_cast(*this) = other; + static_cast(*this) = other; + rehash_for_other_container(other); + insert(other.begin(), other.end()); + return *this; + } + sherwood_v3_table& operator=(sherwood_v3_table&& other) noexcept + { + if (this == std::addressof(other)) + return *this; + else if (AllocatorTraits::propagate_on_container_move_assignment::value) + { + clear(); + reset_to_empty_state(); + AssignIfTrue()(*this, std::move(other)); + swap_pointers(other); + } + else if (static_cast(*this) == static_cast(other)) + { + swap_pointers(other); + } + else + { + clear(); + _max_load_factor = other._max_load_factor; + rehash_for_other_container(other); + for (T& elem : other) + emplace(std::move(elem)); + other.clear(); + } + static_cast(*this) = std::move(other); + static_cast(*this) = std::move(other); + return *this; + } + ~sherwood_v3_table() + { + clear(); + deallocate_data(entries, num_slots_minus_one, max_lookups); + } + + const allocator_type& get_allocator() const + { + return static_cast(*this); + } + const ArgumentEqual& key_eq() const + { + return static_cast(*this); + } + const ArgumentHash& hash_function() const + { + return static_cast(*this); + } + + template + struct templated_iterator + { + templated_iterator() = default; + templated_iterator(EntryPointer current) + : current(current) + { + } + EntryPointer current = EntryPointer(); + + using iterator_category = std::forward_iterator_tag; + using value_type = ValueType; + using difference_type = ptrdiff_t; + using pointer = ValueType*; + using reference = ValueType&; + + friend bool operator==(const templated_iterator& lhs, const templated_iterator& rhs) + { + return lhs.current == rhs.current; + } + friend bool operator!=(const templated_iterator& lhs, const templated_iterator& rhs) + { + return !(lhs == rhs); + } + + templated_iterator& operator++() + { + do + { + ++current; + } while (current->is_empty()); + return *this; + } + templated_iterator operator++(int) + { + templated_iterator copy(*this); + ++* this; + return copy; + } + + ValueType& operator*() const + { + return current->value; + } + ValueType* operator->() const + { + return std::addressof(current->value); + } + + operator templated_iterator() const + { + return { current }; + } + }; + using iterator = templated_iterator; + using const_iterator = templated_iterator; + + iterator begin() + { + for (EntryPointer it = entries;; ++it) + { + if (it->has_value()) + return { it }; + } + } + const_iterator begin() const + { + for (EntryPointer it = entries;; ++it) + { + if (it->has_value()) + return { it }; + } + } + const_iterator cbegin() const + { + return begin(); + } + iterator end() + { + return { entries + static_cast(num_slots_minus_one + max_lookups) }; + } + const_iterator end() const + { + return { entries + static_cast(num_slots_minus_one + max_lookups) }; + } + const_iterator cend() const + { + return end(); + } + + iterator find(const FindKey& key) + { + size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); + EntryPointer it = entries + ptrdiff_t(index); + for (int8_t distance = 0; it->distance_from_desired >= distance; ++distance, ++it) + { + if (compares_equal(key, it->value)) + return { it }; + } + return end(); + } + const_iterator find(const FindKey& key) const + { + return const_cast(this)->find(key); + } + size_t count(const FindKey& key) const + { + return find(key) == end() ? 0 : 1; + } + std::pair equal_range(const FindKey& key) + { + iterator found = find(key); + if (found == end()) + return { found, found }; + else + return { found, std::next(found) }; + } + std::pair equal_range(const FindKey& key) const + { + const_iterator found = find(key); + if (found == end()) + return { found, found }; + else + return { found, std::next(found) }; + } + + template + std::pair emplace(Key&& key, Args &&... args) + { + size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); + EntryPointer current_entry = entries + ptrdiff_t(index); + int8_t distance_from_desired = 0; + for (; current_entry->distance_from_desired >= distance_from_desired; ++current_entry, ++distance_from_desired) + { + if (compares_equal(key, current_entry->value)) + return { { current_entry }, false }; + } + return emplace_new_key(distance_from_desired, current_entry, std::forward(key), std::forward(args)...); + } + + std::pair insert(const value_type& value) + { + return emplace(value); + } + std::pair insert(value_type&& value) + { + return emplace(std::move(value)); + } + template + iterator emplace_hint(const_iterator, Args &&... args) + { + return emplace(std::forward(args)...).first; + } + iterator insert(const_iterator, const value_type& value) + { + return emplace(value).first; + } + iterator insert(const_iterator, value_type&& value) + { + return emplace(std::move(value)).first; + } + + template + void insert(It begin, It end) + { + for (; begin != end; ++begin) + { + emplace(*begin); + } + } + void insert(std::initializer_list il) + { + insert(il.begin(), il.end()); + } + + void rehash(size_t num_buckets) + { + num_buckets = std::max(num_buckets, static_cast(std::ceil(num_elements / static_cast(_max_load_factor)))); + if (num_buckets == 0) + { + reset_to_empty_state(); + return; + } + auto new_prime_index = hash_policy.next_size_over(num_buckets); + if (num_buckets == bucket_count()) + return; + int8_t new_max_lookups = compute_max_lookups(num_buckets); + EntryPointer new_buckets(AllocatorTraits::allocate(*this, num_buckets + new_max_lookups)); + EntryPointer special_end_item = new_buckets + static_cast(num_buckets + new_max_lookups - 1); + for (EntryPointer it = new_buckets; it != special_end_item; ++it) + it->distance_from_desired = -1; + special_end_item->distance_from_desired = Entry::special_end_value; + std::swap(entries, new_buckets); + std::swap(num_slots_minus_one, num_buckets); + --num_slots_minus_one; + hash_policy.commit(new_prime_index); + int8_t old_max_lookups = max_lookups; + max_lookups = new_max_lookups; + num_elements = 0; + for (EntryPointer it = new_buckets, end = it + static_cast(num_buckets + old_max_lookups); it != end; ++it) + { + if (it->has_value()) + { + emplace(std::move(it->value)); + it->destroy_value(); + } + } + deallocate_data(new_buckets, num_buckets, old_max_lookups); + } + + void reserve(size_t num_elements) + { + size_t required_buckets = num_buckets_for_reserve(num_elements); + if (required_buckets > bucket_count()) + rehash(required_buckets); + } + + // the return value is a type that can be converted to an iterator + // the reason for doing this is that it's not free to find the + // iterator pointing at the next element. if you care about the + // next iterator, turn the return value into an iterator + convertible_to_iterator erase(const_iterator to_erase) + { + EntryPointer current = to_erase.current; + current->destroy_value(); + --num_elements; + for (EntryPointer next = current + ptrdiff_t(1); !next->is_at_desired_position(); ++current, ++next) + { + current->emplace(next->distance_from_desired - 1, std::move(next->value)); + next->destroy_value(); + } + return { to_erase.current }; + } + + iterator erase(const_iterator begin_it, const_iterator end_it) + { + if (begin_it == end_it) + return { begin_it.current }; + for (EntryPointer it = begin_it.current, end = end_it.current; it != end; ++it) + { + if (it->has_value()) + { + it->destroy_value(); + --num_elements; + } + } + if (end_it == this->end()) + return this->end(); + ptrdiff_t num_to_move = std::min(static_cast(end_it.current->distance_from_desired), end_it.current - begin_it.current); + EntryPointer to_return = end_it.current - num_to_move; + for (EntryPointer it = end_it.current; !it->is_at_desired_position();) + { + EntryPointer target = it - num_to_move; + target->emplace(it->distance_from_desired - num_to_move, std::move(it->value)); + it->destroy_value(); + ++it; + num_to_move = std::min(static_cast(it->distance_from_desired), num_to_move); + } + return { to_return }; + } + + size_t erase(const FindKey& key) + { + auto found = find(key); + if (found == end()) + return 0; + else + { + erase(found); + return 1; + } + } + + void clear() + { + for (EntryPointer it = entries, end = it + static_cast(num_slots_minus_one + max_lookups); it != end; ++it) + { + if (it->has_value()) + it->destroy_value(); + } + num_elements = 0; + } + + void shrink_to_fit() + { + rehash_for_other_container(*this); + } + + void swap(sherwood_v3_table& other) + { + using std::swap; + swap_pointers(other); + swap(static_cast(*this), static_cast(other)); + swap(static_cast(*this), static_cast(other)); + if (AllocatorTraits::propagate_on_container_swap::value) + swap(static_cast(*this), static_cast(other)); + } + + size_t size() const + { + return num_elements; + } + size_t max_size() const + { + return (AllocatorTraits::max_size(*this)) / sizeof(Entry); + } + size_t bucket_count() const + { + return num_slots_minus_one ? num_slots_minus_one + 1 : 0; + } + size_type max_bucket_count() const + { + return (AllocatorTraits::max_size(*this) - min_lookups) / sizeof(Entry); + } + size_t bucket(const FindKey& key) const + { + return hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); + } + float load_factor() const + { + size_t buckets = bucket_count(); + if (buckets) + return static_cast(num_elements) / bucket_count(); + else + return 0; + } + void max_load_factor(float value) + { + _max_load_factor = value; + } + float max_load_factor() const + { + return _max_load_factor; + } + + bool empty() const + { + return num_elements == 0; + } + + private: + EntryPointer entries = Entry::empty_default_table(); + size_t num_slots_minus_one = 0; + typename HashPolicySelector::type hash_policy; + int8_t max_lookups = detailv3::min_lookups - 1; + float _max_load_factor = 0.5f; + size_t num_elements = 0; + + static int8_t compute_max_lookups(size_t num_buckets) + { + int8_t desired = detailv3::log2(num_buckets); + return std::max(detailv3::min_lookups, desired); + } + + size_t num_buckets_for_reserve(size_t num_elements) const + { + return static_cast(std::ceil(num_elements / std::min(0.5, static_cast(_max_load_factor)))); + } + void rehash_for_other_container(const sherwood_v3_table& other) + { + rehash(std::min(num_buckets_for_reserve(other.size()), other.bucket_count())); + } + + void swap_pointers(sherwood_v3_table& other) + { + using std::swap; + swap(hash_policy, other.hash_policy); + swap(entries, other.entries); + swap(num_slots_minus_one, other.num_slots_minus_one); + swap(num_elements, other.num_elements); + swap(max_lookups, other.max_lookups); + swap(_max_load_factor, other._max_load_factor); + } + + template + SKA_NOINLINE(std::pair) emplace_new_key(int8_t distance_from_desired, EntryPointer current_entry, Key&& key, Args &&... args) + { + using std::swap; + if (num_slots_minus_one == 0 || distance_from_desired == max_lookups || num_elements + 1 > (num_slots_minus_one + 1) * static_cast(_max_load_factor)) + { + grow(); + return emplace(std::forward(key), std::forward(args)...); + } + else if (current_entry->is_empty()) + { + current_entry->emplace(distance_from_desired, std::forward(key), std::forward(args)...); + ++num_elements; + return { { current_entry }, true }; + } + value_type to_insert(std::forward(key), std::forward(args)...); + swap(distance_from_desired, current_entry->distance_from_desired); + swap(to_insert, current_entry->value); + iterator result = { current_entry }; + for (++distance_from_desired, ++current_entry;; ++current_entry) + { + if (current_entry->is_empty()) + { + current_entry->emplace(distance_from_desired, std::move(to_insert)); + ++num_elements; + return { result, true }; + } + else if (current_entry->distance_from_desired < distance_from_desired) + { + swap(distance_from_desired, current_entry->distance_from_desired); + swap(to_insert, current_entry->value); + ++distance_from_desired; + } + else + { + ++distance_from_desired; + if (distance_from_desired == max_lookups) + { + swap(to_insert, result.current->value); + grow(); + return emplace(std::move(to_insert)); + } + } + } + } + + void grow() + { + rehash(std::max(size_t(4), 2 * bucket_count())); + } + + void deallocate_data(EntryPointer begin, size_t num_slots_minus_one, int8_t max_lookups) + { + if (begin != Entry::empty_default_table()) + { + AllocatorTraits::deallocate(*this, begin, num_slots_minus_one + max_lookups + 1); + } + } + + void reset_to_empty_state() + { + deallocate_data(entries, num_slots_minus_one, max_lookups); + entries = Entry::empty_default_table(); + num_slots_minus_one = 0; + hash_policy.reset(); + max_lookups = detailv3::min_lookups - 1; + } + + template + size_t hash_object(const U& key) + { + return static_cast(*this)(key); + } + template + size_t hash_object(const U& key) const + { + return static_cast(*this)(key); + } + template + bool compares_equal(const L& lhs, const R& rhs) + { + return static_cast(*this)(lhs, rhs); + } + + struct convertible_to_iterator + { + EntryPointer it; + + operator iterator() + { + if (it->has_value()) + return { it }; + else + return ++iterator{ it }; + } + operator const_iterator() + { + if (it->has_value()) + return { it }; + else + return ++const_iterator{ it }; + } + }; + + }; + } + + struct prime_number_hash_policy + { + static size_t mod0(size_t) { return 0llu; } + static size_t mod2(size_t hash) { return hash % 2llu; } + static size_t mod3(size_t hash) { return hash % 3llu; } + static size_t mod5(size_t hash) { return hash % 5llu; } + static size_t mod7(size_t hash) { return hash % 7llu; } + static size_t mod11(size_t hash) { return hash % 11llu; } + static size_t mod13(size_t hash) { return hash % 13llu; } + static size_t mod17(size_t hash) { return hash % 17llu; } + static size_t mod23(size_t hash) { return hash % 23llu; } + static size_t mod29(size_t hash) { return hash % 29llu; } + static size_t mod37(size_t hash) { return hash % 37llu; } + static size_t mod47(size_t hash) { return hash % 47llu; } + static size_t mod59(size_t hash) { return hash % 59llu; } + static size_t mod73(size_t hash) { return hash % 73llu; } + static size_t mod97(size_t hash) { return hash % 97llu; } + static size_t mod127(size_t hash) { return hash % 127llu; } + static size_t mod151(size_t hash) { return hash % 151llu; } + static size_t mod197(size_t hash) { return hash % 197llu; } + static size_t mod251(size_t hash) { return hash % 251llu; } + static size_t mod313(size_t hash) { return hash % 313llu; } + static size_t mod397(size_t hash) { return hash % 397llu; } + static size_t mod499(size_t hash) { return hash % 499llu; } + static size_t mod631(size_t hash) { return hash % 631llu; } + static size_t mod797(size_t hash) { return hash % 797llu; } + static size_t mod1009(size_t hash) { return hash % 1009llu; } + static size_t mod1259(size_t hash) { return hash % 1259llu; } + static size_t mod1597(size_t hash) { return hash % 1597llu; } + static size_t mod2011(size_t hash) { return hash % 2011llu; } + static size_t mod2539(size_t hash) { return hash % 2539llu; } + static size_t mod3203(size_t hash) { return hash % 3203llu; } + static size_t mod4027(size_t hash) { return hash % 4027llu; } + static size_t mod5087(size_t hash) { return hash % 5087llu; } + static size_t mod6421(size_t hash) { return hash % 6421llu; } + static size_t mod8089(size_t hash) { return hash % 8089llu; } + static size_t mod10193(size_t hash) { return hash % 10193llu; } + static size_t mod12853(size_t hash) { return hash % 12853llu; } + static size_t mod16193(size_t hash) { return hash % 16193llu; } + static size_t mod20399(size_t hash) { return hash % 20399llu; } + static size_t mod25717(size_t hash) { return hash % 25717llu; } + static size_t mod32401(size_t hash) { return hash % 32401llu; } + static size_t mod40823(size_t hash) { return hash % 40823llu; } + static size_t mod51437(size_t hash) { return hash % 51437llu; } + static size_t mod64811(size_t hash) { return hash % 64811llu; } + static size_t mod81649(size_t hash) { return hash % 81649llu; } + static size_t mod102877(size_t hash) { return hash % 102877llu; } + static size_t mod129607(size_t hash) { return hash % 129607llu; } + static size_t mod163307(size_t hash) { return hash % 163307llu; } + static size_t mod205759(size_t hash) { return hash % 205759llu; } + static size_t mod259229(size_t hash) { return hash % 259229llu; } + static size_t mod326617(size_t hash) { return hash % 326617llu; } + static size_t mod411527(size_t hash) { return hash % 411527llu; } + static size_t mod518509(size_t hash) { return hash % 518509llu; } + static size_t mod653267(size_t hash) { return hash % 653267llu; } + static size_t mod823117(size_t hash) { return hash % 823117llu; } + static size_t mod1037059(size_t hash) { return hash % 1037059llu; } + static size_t mod1306601(size_t hash) { return hash % 1306601llu; } + static size_t mod1646237(size_t hash) { return hash % 1646237llu; } + static size_t mod2074129(size_t hash) { return hash % 2074129llu; } + static size_t mod2613229(size_t hash) { return hash % 2613229llu; } + static size_t mod3292489(size_t hash) { return hash % 3292489llu; } + static size_t mod4148279(size_t hash) { return hash % 4148279llu; } + static size_t mod5226491(size_t hash) { return hash % 5226491llu; } + static size_t mod6584983(size_t hash) { return hash % 6584983llu; } + static size_t mod8296553(size_t hash) { return hash % 8296553llu; } + static size_t mod10453007(size_t hash) { return hash % 10453007llu; } + static size_t mod13169977(size_t hash) { return hash % 13169977llu; } + static size_t mod16593127(size_t hash) { return hash % 16593127llu; } + static size_t mod20906033(size_t hash) { return hash % 20906033llu; } + static size_t mod26339969(size_t hash) { return hash % 26339969llu; } + static size_t mod33186281(size_t hash) { return hash % 33186281llu; } + static size_t mod41812097(size_t hash) { return hash % 41812097llu; } + static size_t mod52679969(size_t hash) { return hash % 52679969llu; } + static size_t mod66372617(size_t hash) { return hash % 66372617llu; } + static size_t mod83624237(size_t hash) { return hash % 83624237llu; } + static size_t mod105359939(size_t hash) { return hash % 105359939llu; } + static size_t mod132745199(size_t hash) { return hash % 132745199llu; } + static size_t mod167248483(size_t hash) { return hash % 167248483llu; } + static size_t mod210719881(size_t hash) { return hash % 210719881llu; } + static size_t mod265490441(size_t hash) { return hash % 265490441llu; } + static size_t mod334496971(size_t hash) { return hash % 334496971llu; } + static size_t mod421439783(size_t hash) { return hash % 421439783llu; } + static size_t mod530980861(size_t hash) { return hash % 530980861llu; } + static size_t mod668993977(size_t hash) { return hash % 668993977llu; } + static size_t mod842879579(size_t hash) { return hash % 842879579llu; } + static size_t mod1061961721(size_t hash) { return hash % 1061961721llu; } + static size_t mod1337987929(size_t hash) { return hash % 1337987929llu; } + static size_t mod1685759167(size_t hash) { return hash % 1685759167llu; } + static size_t mod2123923447(size_t hash) { return hash % 2123923447llu; } + static size_t mod2675975881(size_t hash) { return hash % 2675975881llu; } + static size_t mod3371518343(size_t hash) { return hash % 3371518343llu; } + static size_t mod4247846927(size_t hash) { return hash % 4247846927llu; } + static size_t mod5351951779(size_t hash) { return hash % 5351951779llu; } + static size_t mod6743036717(size_t hash) { return hash % 6743036717llu; } + static size_t mod8495693897(size_t hash) { return hash % 8495693897llu; } + static size_t mod10703903591(size_t hash) { return hash % 10703903591llu; } + static size_t mod13486073473(size_t hash) { return hash % 13486073473llu; } + static size_t mod16991387857(size_t hash) { return hash % 16991387857llu; } + static size_t mod21407807219(size_t hash) { return hash % 21407807219llu; } + static size_t mod26972146961(size_t hash) { return hash % 26972146961llu; } + static size_t mod33982775741(size_t hash) { return hash % 33982775741llu; } + static size_t mod42815614441(size_t hash) { return hash % 42815614441llu; } + static size_t mod53944293929(size_t hash) { return hash % 53944293929llu; } + static size_t mod67965551447(size_t hash) { return hash % 67965551447llu; } + static size_t mod85631228929(size_t hash) { return hash % 85631228929llu; } + static size_t mod107888587883(size_t hash) { return hash % 107888587883llu; } + static size_t mod135931102921(size_t hash) { return hash % 135931102921llu; } + static size_t mod171262457903(size_t hash) { return hash % 171262457903llu; } + static size_t mod215777175787(size_t hash) { return hash % 215777175787llu; } + static size_t mod271862205833(size_t hash) { return hash % 271862205833llu; } + static size_t mod342524915839(size_t hash) { return hash % 342524915839llu; } + static size_t mod431554351609(size_t hash) { return hash % 431554351609llu; } + static size_t mod543724411781(size_t hash) { return hash % 543724411781llu; } + static size_t mod685049831731(size_t hash) { return hash % 685049831731llu; } + static size_t mod863108703229(size_t hash) { return hash % 863108703229llu; } + static size_t mod1087448823553(size_t hash) { return hash % 1087448823553llu; } + static size_t mod1370099663459(size_t hash) { return hash % 1370099663459llu; } + static size_t mod1726217406467(size_t hash) { return hash % 1726217406467llu; } + static size_t mod2174897647073(size_t hash) { return hash % 2174897647073llu; } + static size_t mod2740199326961(size_t hash) { return hash % 2740199326961llu; } + static size_t mod3452434812973(size_t hash) { return hash % 3452434812973llu; } + static size_t mod4349795294267(size_t hash) { return hash % 4349795294267llu; } + static size_t mod5480398654009(size_t hash) { return hash % 5480398654009llu; } + static size_t mod6904869625999(size_t hash) { return hash % 6904869625999llu; } + static size_t mod8699590588571(size_t hash) { return hash % 8699590588571llu; } + static size_t mod10960797308051(size_t hash) { return hash % 10960797308051llu; } + static size_t mod13809739252051(size_t hash) { return hash % 13809739252051llu; } + static size_t mod17399181177241(size_t hash) { return hash % 17399181177241llu; } + static size_t mod21921594616111(size_t hash) { return hash % 21921594616111llu; } + static size_t mod27619478504183(size_t hash) { return hash % 27619478504183llu; } + static size_t mod34798362354533(size_t hash) { return hash % 34798362354533llu; } + static size_t mod43843189232363(size_t hash) { return hash % 43843189232363llu; } + static size_t mod55238957008387(size_t hash) { return hash % 55238957008387llu; } + static size_t mod69596724709081(size_t hash) { return hash % 69596724709081llu; } + static size_t mod87686378464759(size_t hash) { return hash % 87686378464759llu; } + static size_t mod110477914016779(size_t hash) { return hash % 110477914016779llu; } + static size_t mod139193449418173(size_t hash) { return hash % 139193449418173llu; } + static size_t mod175372756929481(size_t hash) { return hash % 175372756929481llu; } + static size_t mod220955828033581(size_t hash) { return hash % 220955828033581llu; } + static size_t mod278386898836457(size_t hash) { return hash % 278386898836457llu; } + static size_t mod350745513859007(size_t hash) { return hash % 350745513859007llu; } + static size_t mod441911656067171(size_t hash) { return hash % 441911656067171llu; } + static size_t mod556773797672909(size_t hash) { return hash % 556773797672909llu; } + static size_t mod701491027718027(size_t hash) { return hash % 701491027718027llu; } + static size_t mod883823312134381(size_t hash) { return hash % 883823312134381llu; } + static size_t mod1113547595345903(size_t hash) { return hash % 1113547595345903llu; } + static size_t mod1402982055436147(size_t hash) { return hash % 1402982055436147llu; } + static size_t mod1767646624268779(size_t hash) { return hash % 1767646624268779llu; } + static size_t mod2227095190691797(size_t hash) { return hash % 2227095190691797llu; } + static size_t mod2805964110872297(size_t hash) { return hash % 2805964110872297llu; } + static size_t mod3535293248537579(size_t hash) { return hash % 3535293248537579llu; } + static size_t mod4454190381383713(size_t hash) { return hash % 4454190381383713llu; } + static size_t mod5611928221744609(size_t hash) { return hash % 5611928221744609llu; } + static size_t mod7070586497075177(size_t hash) { return hash % 7070586497075177llu; } + static size_t mod8908380762767489(size_t hash) { return hash % 8908380762767489llu; } + static size_t mod11223856443489329(size_t hash) { return hash % 11223856443489329llu; } + static size_t mod14141172994150357(size_t hash) { return hash % 14141172994150357llu; } + static size_t mod17816761525534927(size_t hash) { return hash % 17816761525534927llu; } + static size_t mod22447712886978529(size_t hash) { return hash % 22447712886978529llu; } + static size_t mod28282345988300791(size_t hash) { return hash % 28282345988300791llu; } + static size_t mod35633523051069991(size_t hash) { return hash % 35633523051069991llu; } + static size_t mod44895425773957261(size_t hash) { return hash % 44895425773957261llu; } + static size_t mod56564691976601587(size_t hash) { return hash % 56564691976601587llu; } + static size_t mod71267046102139967(size_t hash) { return hash % 71267046102139967llu; } + static size_t mod89790851547914507(size_t hash) { return hash % 89790851547914507llu; } + static size_t mod113129383953203213(size_t hash) { return hash % 113129383953203213llu; } + static size_t mod142534092204280003(size_t hash) { return hash % 142534092204280003llu; } + static size_t mod179581703095829107(size_t hash) { return hash % 179581703095829107llu; } + static size_t mod226258767906406483(size_t hash) { return hash % 226258767906406483llu; } + static size_t mod285068184408560057(size_t hash) { return hash % 285068184408560057llu; } + static size_t mod359163406191658253(size_t hash) { return hash % 359163406191658253llu; } + static size_t mod452517535812813007(size_t hash) { return hash % 452517535812813007llu; } + static size_t mod570136368817120201(size_t hash) { return hash % 570136368817120201llu; } + static size_t mod718326812383316683(size_t hash) { return hash % 718326812383316683llu; } + static size_t mod905035071625626043(size_t hash) { return hash % 905035071625626043llu; } + static size_t mod1140272737634240411(size_t hash) { return hash % 1140272737634240411llu; } + static size_t mod1436653624766633509(size_t hash) { return hash % 1436653624766633509llu; } + static size_t mod1810070143251252131(size_t hash) { return hash % 1810070143251252131llu; } + static size_t mod2280545475268481167(size_t hash) { return hash % 2280545475268481167llu; } + static size_t mod2873307249533267101(size_t hash) { return hash % 2873307249533267101llu; } + static size_t mod3620140286502504283(size_t hash) { return hash % 3620140286502504283llu; } + static size_t mod4561090950536962147(size_t hash) { return hash % 4561090950536962147llu; } + static size_t mod5746614499066534157(size_t hash) { return hash % 5746614499066534157llu; } + static size_t mod7240280573005008577(size_t hash) { return hash % 7240280573005008577llu; } + static size_t mod9122181901073924329(size_t hash) { return hash % 9122181901073924329llu; } + static size_t mod11493228998133068689(size_t hash) { return hash % 11493228998133068689llu; } + static size_t mod14480561146010017169(size_t hash) { return hash % 14480561146010017169llu; } + static size_t mod18446744073709551557(size_t hash) { return hash % 18446744073709551557llu; } + + using mod_function = size_t(*)(size_t); + + mod_function next_size_over(size_t& size) const + { + // prime numbers generated by the following method: + // 1. start with a prime p = 2 + // 2. go to wolfram alpha and get p = NextPrime(2 * p) + // 3. repeat 2. until you overflow 64 bits + // you now have large gaps which you would hit if somebody called reserve() with an unlucky number. + // 4. to fill the gaps for every prime p go to wolfram alpha and get ClosestPrime(p * 2^(1/3)) and ClosestPrime(p * 2^(2/3)) and put those in the gaps + // 5. get PrevPrime(2^64) and put it at the end + static constexpr const size_t prime_list[] = + { + 2llu, 3llu, 5llu, 7llu, 11llu, 13llu, 17llu, 23llu, 29llu, 37llu, 47llu, + 59llu, 73llu, 97llu, 127llu, 151llu, 197llu, 251llu, 313llu, 397llu, + 499llu, 631llu, 797llu, 1009llu, 1259llu, 1597llu, 2011llu, 2539llu, + 3203llu, 4027llu, 5087llu, 6421llu, 8089llu, 10193llu, 12853llu, 16193llu, + 20399llu, 25717llu, 32401llu, 40823llu, 51437llu, 64811llu, 81649llu, + 102877llu, 129607llu, 163307llu, 205759llu, 259229llu, 326617llu, + 411527llu, 518509llu, 653267llu, 823117llu, 1037059llu, 1306601llu, + 1646237llu, 2074129llu, 2613229llu, 3292489llu, 4148279llu, 5226491llu, + 6584983llu, 8296553llu, 10453007llu, 13169977llu, 16593127llu, 20906033llu, + 26339969llu, 33186281llu, 41812097llu, 52679969llu, 66372617llu, + 83624237llu, 105359939llu, 132745199llu, 167248483llu, 210719881llu, + 265490441llu, 334496971llu, 421439783llu, 530980861llu, 668993977llu, + 842879579llu, 1061961721llu, 1337987929llu, 1685759167llu, 2123923447llu, + 2675975881llu, 3371518343llu, 4247846927llu, 5351951779llu, 6743036717llu, + 8495693897llu, 10703903591llu, 13486073473llu, 16991387857llu, + 21407807219llu, 26972146961llu, 33982775741llu, 42815614441llu, + 53944293929llu, 67965551447llu, 85631228929llu, 107888587883llu, + 135931102921llu, 171262457903llu, 215777175787llu, 271862205833llu, + 342524915839llu, 431554351609llu, 543724411781llu, 685049831731llu, + 863108703229llu, 1087448823553llu, 1370099663459llu, 1726217406467llu, + 2174897647073llu, 2740199326961llu, 3452434812973llu, 4349795294267llu, + 5480398654009llu, 6904869625999llu, 8699590588571llu, 10960797308051llu, + 13809739252051llu, 17399181177241llu, 21921594616111llu, 27619478504183llu, + 34798362354533llu, 43843189232363llu, 55238957008387llu, 69596724709081llu, + 87686378464759llu, 110477914016779llu, 139193449418173llu, + 175372756929481llu, 220955828033581llu, 278386898836457llu, + 350745513859007llu, 441911656067171llu, 556773797672909llu, + 701491027718027llu, 883823312134381llu, 1113547595345903llu, + 1402982055436147llu, 1767646624268779llu, 2227095190691797llu, + 2805964110872297llu, 3535293248537579llu, 4454190381383713llu, + 5611928221744609llu, 7070586497075177llu, 8908380762767489llu, + 11223856443489329llu, 14141172994150357llu, 17816761525534927llu, + 22447712886978529llu, 28282345988300791llu, 35633523051069991llu, + 44895425773957261llu, 56564691976601587llu, 71267046102139967llu, + 89790851547914507llu, 113129383953203213llu, 142534092204280003llu, + 179581703095829107llu, 226258767906406483llu, 285068184408560057llu, + 359163406191658253llu, 452517535812813007llu, 570136368817120201llu, + 718326812383316683llu, 905035071625626043llu, 1140272737634240411llu, + 1436653624766633509llu, 1810070143251252131llu, 2280545475268481167llu, + 2873307249533267101llu, 3620140286502504283llu, 4561090950536962147llu, + 5746614499066534157llu, 7240280573005008577llu, 9122181901073924329llu, + 11493228998133068689llu, 14480561146010017169llu, 18446744073709551557llu + }; + static constexpr size_t(* const mod_functions[])(size_t) = + { + &mod0, &mod2, &mod3, &mod5, &mod7, &mod11, &mod13, &mod17, &mod23, &mod29, &mod37, + &mod47, &mod59, &mod73, &mod97, &mod127, &mod151, &mod197, &mod251, &mod313, &mod397, + &mod499, &mod631, &mod797, &mod1009, &mod1259, &mod1597, &mod2011, &mod2539, &mod3203, + &mod4027, &mod5087, &mod6421, &mod8089, &mod10193, &mod12853, &mod16193, &mod20399, + &mod25717, &mod32401, &mod40823, &mod51437, &mod64811, &mod81649, &mod102877, + &mod129607, &mod163307, &mod205759, &mod259229, &mod326617, &mod411527, &mod518509, + &mod653267, &mod823117, &mod1037059, &mod1306601, &mod1646237, &mod2074129, + &mod2613229, &mod3292489, &mod4148279, &mod5226491, &mod6584983, &mod8296553, + &mod10453007, &mod13169977, &mod16593127, &mod20906033, &mod26339969, &mod33186281, + &mod41812097, &mod52679969, &mod66372617, &mod83624237, &mod105359939, &mod132745199, + &mod167248483, &mod210719881, &mod265490441, &mod334496971, &mod421439783, + &mod530980861, &mod668993977, &mod842879579, &mod1061961721, &mod1337987929, + &mod1685759167, &mod2123923447, &mod2675975881, &mod3371518343, &mod4247846927, + &mod5351951779, &mod6743036717, &mod8495693897, &mod10703903591, &mod13486073473, + &mod16991387857, &mod21407807219, &mod26972146961, &mod33982775741, &mod42815614441, + &mod53944293929, &mod67965551447, &mod85631228929, &mod107888587883, &mod135931102921, + &mod171262457903, &mod215777175787, &mod271862205833, &mod342524915839, + &mod431554351609, &mod543724411781, &mod685049831731, &mod863108703229, + &mod1087448823553, &mod1370099663459, &mod1726217406467, &mod2174897647073, + &mod2740199326961, &mod3452434812973, &mod4349795294267, &mod5480398654009, + &mod6904869625999, &mod8699590588571, &mod10960797308051, &mod13809739252051, + &mod17399181177241, &mod21921594616111, &mod27619478504183, &mod34798362354533, + &mod43843189232363, &mod55238957008387, &mod69596724709081, &mod87686378464759, + &mod110477914016779, &mod139193449418173, &mod175372756929481, &mod220955828033581, + &mod278386898836457, &mod350745513859007, &mod441911656067171, &mod556773797672909, + &mod701491027718027, &mod883823312134381, &mod1113547595345903, &mod1402982055436147, + &mod1767646624268779, &mod2227095190691797, &mod2805964110872297, &mod3535293248537579, + &mod4454190381383713, &mod5611928221744609, &mod7070586497075177, &mod8908380762767489, + &mod11223856443489329, &mod14141172994150357, &mod17816761525534927, + &mod22447712886978529, &mod28282345988300791, &mod35633523051069991, + &mod44895425773957261, &mod56564691976601587, &mod71267046102139967, + &mod89790851547914507, &mod113129383953203213, &mod142534092204280003, + &mod179581703095829107, &mod226258767906406483, &mod285068184408560057, + &mod359163406191658253, &mod452517535812813007, &mod570136368817120201, + &mod718326812383316683, &mod905035071625626043, &mod1140272737634240411, + &mod1436653624766633509, &mod1810070143251252131, &mod2280545475268481167, + &mod2873307249533267101, &mod3620140286502504283, &mod4561090950536962147, + &mod5746614499066534157, &mod7240280573005008577, &mod9122181901073924329, + &mod11493228998133068689, &mod14480561146010017169, &mod18446744073709551557 + }; + const size_t* found = std::lower_bound(std::begin(prime_list), std::end(prime_list) - 1, size); + size = *found; + return mod_functions[1 + found - prime_list]; + } + void commit(mod_function new_mod_function) + { + current_mod_function = new_mod_function; + } + void reset() + { + current_mod_function = &mod0; + } + + size_t index_for_hash(size_t hash, size_t /*num_slots_minus_one*/) const + { + return current_mod_function(hash); + } + size_t keep_in_range(size_t index, size_t num_slots_minus_one) const + { + return index > num_slots_minus_one ? current_mod_function(index) : index; + } + + private: + mod_function current_mod_function = &mod0; + }; + + struct power_of_two_hash_policy + { + size_t index_for_hash(size_t hash, size_t num_slots_minus_one) const + { + return hash & num_slots_minus_one; + } + size_t keep_in_range(size_t index, size_t num_slots_minus_one) const + { + return index_for_hash(index, num_slots_minus_one); + } + int8_t next_size_over(size_t& size) const + { + size = detailv3::next_power_of_two(size); + return 0; + } + void commit(int8_t) + { + } + void reset() + { + } + + }; + + // See here for a description: + // https://probablydance.com/2018/06/16/fibonacci-hashing-the-optimization-that-the-world-forgot-or-a-better-alternative-to-integer-modulo/ + struct fibonacci_hash_policy + { + size_t index_for_hash(size_t hash, size_t /*num_slots_minus_one*/) const + { + return (11400714819323198485ull * hash) >> shift; + } + size_t keep_in_range(size_t index, size_t num_slots_minus_one) const + { + return index & num_slots_minus_one; + } + + int8_t next_size_over(size_t& size) const + { + auto mid_result = detailv3::next_power_of_two(size); + size = std::max(size_t(2), detailv3::next_power_of_two(size)); + auto result = detailv3::log2(size); + return 64 - detailv3::log2(size); + } + void commit(int8_t shift) + { + this->shift = shift; + } + void reset() + { + shift = 63; + } + + private: + int8_t shift = 63; + }; + + template, typename E = std::equal_to, typename A = std::allocator > > + class flat_hash_map + : public detailv3::sherwood_v3_table + < + std::pair, + K, + H, + detailv3::KeyOrValueHasher, H>, + E, + detailv3::KeyOrValueEquality, E>, + A, + typename std::allocator_traits::template rebind_alloc>> + > + { + using Table = detailv3::sherwood_v3_table + < + std::pair, + K, + H, + detailv3::KeyOrValueHasher, H>, + E, + detailv3::KeyOrValueEquality, E>, + A, + typename std::allocator_traits::template rebind_alloc>> + >; + public: + + using key_type = K; + using mapped_type = V; + + using Table::Table; + flat_hash_map() + { + } + + inline V& operator[](const K& key) + { + return emplace(key, convertible_to_value()).first->second; + } + inline V& operator[](K&& key) + { + return emplace(std::move(key), convertible_to_value()).first->second; + } + V& at(const K& key) + { + auto found = this->find(key); + if (found == this->end()) + throw std::out_of_range("Argument passed to at() was not in the map."); + return found->second; + } + const V& at(const K& key) const + { + auto found = this->find(key); + if (found == this->end()) + throw std::out_of_range("Argument passed to at() was not in the map."); + return found->second; + } + + using Table::emplace; + std::pair emplace() + { + return emplace(key_type(), convertible_to_value()); + } + template + std::pair insert_or_assign(const key_type& key, M&& m) + { + auto emplace_result = emplace(key, std::forward(m)); + if (!emplace_result.second) + emplace_result.first->second = std::forward(m); + return emplace_result; + } + template + std::pair insert_or_assign(key_type&& key, M&& m) + { + auto emplace_result = emplace(std::move(key), std::forward(m)); + if (!emplace_result.second) + emplace_result.first->second = std::forward(m); + return emplace_result; + } + template + typename Table::iterator insert_or_assign(typename Table::const_iterator, const key_type& key, M&& m) + { + return insert_or_assign(key, std::forward(m)).first; + } + template + typename Table::iterator insert_or_assign(typename Table::const_iterator, key_type&& key, M&& m) + { + return insert_or_assign(std::move(key), std::forward(m)).first; + } + + friend bool operator==(const flat_hash_map& lhs, const flat_hash_map& rhs) + { + if (lhs.size() != rhs.size()) + return false; + for (const typename Table::value_type& value : lhs) + { + auto found = rhs.find(value.first); + if (found == rhs.end()) + return false; + else if (value.second != found->second) + return false; + } + return true; + } + friend bool operator!=(const flat_hash_map& lhs, const flat_hash_map& rhs) + { + return !(lhs == rhs); + } + + private: + struct convertible_to_value + { + operator V() const + { + return V(); + } + }; + }; + + template, typename E = std::equal_to, typename A = std::allocator > + class flat_hash_set + : public detailv3::sherwood_v3_table + < + T, + T, + H, + detailv3::functor_storage, + E, + detailv3::functor_storage, + A, + typename std::allocator_traits::template rebind_alloc> + > + { + using Table = detailv3::sherwood_v3_table + < + T, + T, + H, + detailv3::functor_storage, + E, + detailv3::functor_storage, + A, + typename std::allocator_traits::template rebind_alloc> + >; + public: + + using key_type = T; + + using Table::Table; + flat_hash_set() + { + } + + template + std::pair emplace(Args &&... args) + { + return Table::emplace(T(std::forward(args)...)); + } + std::pair emplace(const key_type& arg) + { + return Table::emplace(arg); + } + std::pair emplace(key_type& arg) + { + return Table::emplace(arg); + } + std::pair emplace(const key_type&& arg) + { + return Table::emplace(std::move(arg)); + } + std::pair emplace(key_type&& arg) + { + return Table::emplace(std::move(arg)); + } + + friend bool operator==(const flat_hash_set& lhs, const flat_hash_set& rhs) + { + if (lhs.size() != rhs.size()) + return false; + for (const T& value : lhs) + { + if (rhs.find(value) == rhs.end()) + return false; + } + return true; + } + friend bool operator!=(const flat_hash_set& lhs, const flat_hash_set& rhs) + { + return !(lhs == rhs); + } + }; + + + template + struct power_of_two_std_hash : std::hash + { + typedef ska::power_of_two_hash_policy hash_policy; + }; + +} // end namespace ska diff --git a/src/Simulation/Simulators/SparseSimulator/Native/gates.h b/src/Simulation/Simulators/SparseSimulator/Native/gates.h new file mode 100644 index 00000000000..5b05870fa21 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/Native/gates.h @@ -0,0 +1,284 @@ +#pragma once + + +namespace Microsoft +{ + namespace Quantum + { + namespace SPARSESIMULATOR + { + // Used to track differnet kind of gates, + // mainly for timing summary data + enum 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, + Assert, + 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::Assert: + return "Assert"; + 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) + {} + bool result; + // Assert + operation(OP gate_type_arg, + std::vector Zs_arg, + bool result_arg + ) : target(0), + gate_type(gate_type_arg), + controls(Zs_arg), + result(result_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 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 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 controls_arg, + amplitude phase_arg + ) : gate_type(gate_type_arg), + target(target_arg), + controls(controls_arg), + phase(phase_arg) + {} + bool result; + // Assert + condensed_operation(OP gate_type_arg, + std::bitset Zs_arg, + bool result_arg + ) : target(0), + gate_type(gate_type_arg), + controls(Zs_arg), + result(result_arg){} + }; + + namespace Gates + { + /// a type for runtime basis specification + enum class Basis + { + PauliI = 0, + PauliX = 1, + PauliY = 3, + PauliZ = 2 + }; + + + + } + } + } +} \ No newline at end of file diff --git a/src/Simulation/Simulators/SparseSimulator/Native/quantum_hash_map.hpp b/src/Simulation/Simulators/SparseSimulator/Native/quantum_hash_map.hpp new file mode 100644 index 00000000000..d3ca6fe391c --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/Native/quantum_hash_map.hpp @@ -0,0 +1,1302 @@ +// Copyright Malte Skarupke 2017. +// Distributed under the Boost Software License, Version 1.0. +// (See http://www.boost.org/LICENSE_1_0.txt) + +// This has small modifications from the original bytell_hash_map, +// noted in comments + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "flat_hash_map.hpp" +#include +#include + +namespace ska +{ + + namespace detailv8 + { + using ska::detailv3::functor_storage; + using ska::detailv3::KeyOrValueHasher; + using ska::detailv3::KeyOrValueEquality; + using ska::detailv3::AssignIfTrue; + using ska::detailv3::HashPolicySelector; + + template + struct sherwood_v8_constants + { + static constexpr int8_t magic_for_empty = int8_t(0b11111111); + static constexpr int8_t magic_for_reserved = int8_t(0b11111110); + static constexpr int8_t bits_for_direct_hit = int8_t(0b10000000); + static constexpr int8_t magic_for_direct_hit = int8_t(0b00000000); + static constexpr int8_t magic_for_list_entry = int8_t(0b10000000); + + static constexpr int8_t bits_for_distance = int8_t(0b01111111); + inline static int distance_from_metadata(int8_t metadata) + { + return metadata & bits_for_distance; + } + + static constexpr int num_jump_distances = 126; + // jump distances chosen like this: + // 1. pick the first 16 integers to promote staying in the same block + // 2. add the next 66 triangular numbers to get even jumps when + // the hash table is a power of two + // 3. add 44 more triangular numbers at a much steeper growth rate + // to get a sequence that allows large jumps so that a table + // with 10000 sequential numbers doesn't endlessly re-allocate + // + // Visual Studio requires static casts on the larger numbers + static constexpr size_t jump_distances[num_jump_distances] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + + 21, 28, 36, 45, 55, 66, 78, 91, 105, 120, 136, 153, 171, 190, 210, 231, + 253, 276, 300, 325, 351, 378, 406, 435, 465, 496, 528, 561, 595, 630, + 666, 703, 741, 780, 820, 861, 903, 946, 990, 1035, 1081, 1128, 1176, + 1225, 1275, 1326, 1378, 1431, 1485, 1540, 1596, 1653, 1711, 1770, 1830, + 1891, 1953, 2016, 2080, 2145, 2211, 2278, 2346, 2415, 2485, 2556, + + 3741, 8385, 18915, 42486, 95703, 215496, 485605, 1091503, 2456436, + 5529475, 12437578, 27986421, 62972253, 141700195, 318819126, static_cast(717314626), + static_cast(1614000520), static_cast(3631437253), static_cast(8170829695), + static_cast(18384318876), static_cast(41364501751), static_cast(93070021080), + static_cast(93070021080), static_cast(209407709220), + static_cast(471167588430), static_cast(1060127437995), + static_cast(5366895564381), static_cast(12075513791265), static_cast(27169907873235), static_cast(61132301007778), + static_cast(137547673121001), static_cast(309482258302503), static_cast(696335090510256), static_cast(1566753939653640), + static_cast(3525196427195653), static_cast(7931691866727775), static_cast(17846306747368716), + static_cast(40154190394120111), static_cast(90346928493040500), static_cast(203280588949935750), + static_cast(457381324898247375), static_cast(1029107980662394500), static_cast(2315492957028380766), + static_cast(5209859150892887590), + }; + }; + template + constexpr int8_t sherwood_v8_constants::magic_for_empty; + template + constexpr int8_t sherwood_v8_constants::magic_for_reserved; + template + constexpr int8_t sherwood_v8_constants::bits_for_direct_hit; + template + constexpr int8_t sherwood_v8_constants::magic_for_direct_hit; + template + constexpr int8_t sherwood_v8_constants::magic_for_list_entry; + + template + constexpr int8_t sherwood_v8_constants::bits_for_distance; + + template + constexpr int sherwood_v8_constants::num_jump_distances; + template + constexpr size_t sherwood_v8_constants::jump_distances[num_jump_distances]; + + template + + struct sherwood_v8_block + { + sherwood_v8_block() + { + } + ~sherwood_v8_block() + { + } + int8_t control_bytes[BlockSize]; + union + { + T data[BlockSize]; + }; + + static sherwood_v8_block* empty_block() + { + static std::array empty_bytes = [] + { + std::array result; + result.fill(sherwood_v8_constants<>::magic_for_empty); + return result; + }(); + return reinterpret_cast(&empty_bytes); + } + + int first_empty_index() const + { + for (int i = 0; i < BlockSize; ++i) + { + if (control_bytes[i] == sherwood_v8_constants<>::magic_for_empty) + return i; + } + return -1; + } + + void fill_control_bytes(int8_t value) + { + std::fill(std::begin(control_bytes), std::end(control_bytes), value); + } + }; + + template + class sherwood_v8_table : private ByteAlloc, private Hasher, private Equal + { + using AllocatorTraits = std::allocator_traits; + using BlockType = sherwood_v8_block; + using BlockPointer = BlockType*; + using BytePointer = typename AllocatorTraits::pointer; + struct convertible_to_iterator; + using Constants = sherwood_v8_constants<>; + + public: + + using value_type = T; + using size_type = size_t; + using difference_type = std::ptrdiff_t; + using hasher = ArgumentHash; + using key_equal = ArgumentEqual; + using allocator_type = ByteAlloc; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + + sherwood_v8_table() + { + } + explicit sherwood_v8_table(size_type bucket_count, const ArgumentHash& hash = ArgumentHash(), const ArgumentEqual& equal = ArgumentEqual(), const ArgumentAlloc& alloc = ArgumentAlloc()) + : ByteAlloc(alloc), Hasher(hash), Equal(equal) + { + if (bucket_count) + rehash(bucket_count); + } + sherwood_v8_table(size_type bucket_count, const ArgumentAlloc& alloc) + : sherwood_v8_table(bucket_count, ArgumentHash(), ArgumentEqual(), alloc) + { + } + sherwood_v8_table(size_type bucket_count, const ArgumentHash& hash, const ArgumentAlloc& alloc) + : sherwood_v8_table(bucket_count, hash, ArgumentEqual(), alloc) + { + } + explicit sherwood_v8_table(const ArgumentAlloc& alloc) + : ByteAlloc(alloc) + { + } + template + sherwood_v8_table(It first, It last, size_type bucket_count = 0, const ArgumentHash& hash = ArgumentHash(), const ArgumentEqual& equal = ArgumentEqual(), const ArgumentAlloc& alloc = ArgumentAlloc()) + : sherwood_v8_table(bucket_count, hash, equal, alloc) + { + insert(first, last); + } + template + sherwood_v8_table(It first, It last, size_type bucket_count, const ArgumentAlloc& alloc) + : sherwood_v8_table(first, last, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) + { + } + template + sherwood_v8_table(It first, It last, size_type bucket_count, const ArgumentHash& hash, const ArgumentAlloc& alloc) + : sherwood_v8_table(first, last, bucket_count, hash, ArgumentEqual(), alloc) + { + } + sherwood_v8_table(std::initializer_list il, size_type bucket_count = 0, const ArgumentHash& hash = ArgumentHash(), const ArgumentEqual& equal = ArgumentEqual(), const ArgumentAlloc& alloc = ArgumentAlloc()) + : sherwood_v8_table(bucket_count, hash, equal, alloc) + { + if (bucket_count == 0) + rehash(il.size()); + insert(il.begin(), il.end()); + } + sherwood_v8_table(std::initializer_list il, size_type bucket_count, const ArgumentAlloc& alloc) + : sherwood_v8_table(il, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) + { + } + sherwood_v8_table(std::initializer_list il, size_type bucket_count, const ArgumentHash& hash, const ArgumentAlloc& alloc) + : sherwood_v8_table(il, bucket_count, hash, ArgumentEqual(), alloc) + { + } + sherwood_v8_table(const sherwood_v8_table& other) + : sherwood_v8_table(other, AllocatorTraits::select_on_container_copy_construction(other.get_allocator())) + { + } + sherwood_v8_table(const sherwood_v8_table& other, const ArgumentAlloc& alloc) + : ByteAlloc(alloc), Hasher(other), Equal(other), _max_load_factor(other._max_load_factor) + { + rehash_for_other_container(other); + try + { + insert(other.begin(), other.end()); + } + catch (...) + { + clear(); + deallocate_data(entries, num_slots_minus_one); + throw; + } + } + sherwood_v8_table(sherwood_v8_table&& other) noexcept + : ByteAlloc(std::move(other)), Hasher(std::move(other)), Equal(std::move(other)) + , _max_load_factor(other._max_load_factor) + { + swap_pointers(other); + } + sherwood_v8_table(sherwood_v8_table&& other, const ArgumentAlloc& alloc) noexcept + : ByteAlloc(alloc), Hasher(std::move(other)), Equal(std::move(other)) + , _max_load_factor(other._max_load_factor) + { + swap_pointers(other); + } + sherwood_v8_table& operator=(const sherwood_v8_table& other) + { + if (this == std::addressof(other)) + return *this; + + clear(); + if (AllocatorTraits::propagate_on_container_copy_assignment::value) + { + if (static_cast(*this) != static_cast(other)) + { + reset_to_empty_state(); + } + AssignIfTrue()(*this, other); + } + _max_load_factor = other._max_load_factor; + static_cast(*this) = other; + static_cast(*this) = other; + rehash_for_other_container(other); + insert(other.begin(), other.end()); + return *this; + } + sherwood_v8_table& operator=(sherwood_v8_table&& other) noexcept + { + if (this == std::addressof(other)) + return *this; + else if (AllocatorTraits::propagate_on_container_move_assignment::value) + { + clear(); + reset_to_empty_state(); + AssignIfTrue()(*this, std::move(other)); + swap_pointers(other); + } + else if (static_cast(*this) == static_cast(other)) + { + swap_pointers(other); + } + else + { + clear(); + _max_load_factor = other._max_load_factor; + rehash_for_other_container(other); + for (T& elem : other) + emplace(std::move(elem)); + other.clear(); + } + static_cast(*this) = std::move(other); + static_cast(*this) = std::move(other); + return *this; + } + ~sherwood_v8_table() + { + clear(); + deallocate_data(entries, num_slots_minus_one); + } + + const allocator_type& get_allocator() const + { + return static_cast(*this); + } + const ArgumentEqual& key_eq() const + { + return static_cast(*this); + } + const ArgumentHash& hash_function() const + { + return static_cast(*this); + } + + template + struct templated_iterator + { + private: + friend class sherwood_v8_table; + BlockPointer current = BlockPointer(); + size_t index = 0; + + public: + templated_iterator() + { + } + templated_iterator(BlockPointer entries, size_t index) + : current(entries) + , index(index) + { + } + + using iterator_category = std::forward_iterator_tag; + using value_type = ValueType; + using difference_type = ptrdiff_t; + using pointer = ValueType*; + using reference = ValueType&; + + friend bool operator==(const templated_iterator& lhs, const templated_iterator& rhs) + { + return lhs.index == rhs.index; + } + friend bool operator!=(const templated_iterator& lhs, const templated_iterator& rhs) + { + return !(lhs == rhs); + } + + templated_iterator& operator++() + { + do + { + if (index % BlockSize == 0) + --current; + if (index-- == 0) + break; + } while (current->control_bytes[index % BlockSize] == Constants::magic_for_empty); + return *this; + } + + // Jumps forward by some number of blocks + // Added for faster quantum state iteration + templated_iterator& jump_forward(size_t n = 1){ + if (index < n*BlockSize){ + current -= (index - 1)/BlockSize; + index = std::numeric_limits::max(); + } else { + index -= n*BlockSize; + current -= n; + while (current->control_bytes[index % BlockSize] == Constants::magic_for_empty) { + if (index % BlockSize == 0) + --current; + if (index-- == 0) + break; + } + } + return *this; + } + + templated_iterator operator++(int) + { + templated_iterator copy(*this); + ++* this; + return copy; + } + + ValueType& operator*() const + { + return current->data[index % BlockSize]; + } + ValueType* operator->() const + { + return current->data + index % BlockSize; + } + + operator templated_iterator() const + { + return { current, index }; + } + }; + using iterator = templated_iterator; + using const_iterator = templated_iterator; + + iterator begin() + { + size_t num_slots = num_slots_minus_one ? num_slots_minus_one + 1 : 0; + return ++iterator{ entries + num_slots / BlockSize, num_slots }; + } + const_iterator begin() const + { + size_t num_slots = num_slots_minus_one ? num_slots_minus_one + 1 : 0; + return ++iterator{ entries + num_slots / BlockSize, num_slots }; + } + const_iterator cbegin() const + { + return begin(); + } + iterator end() + { + return { entries - 1, std::numeric_limits::max() }; + } + const_iterator end() const + { + return { entries - 1, std::numeric_limits::max() }; + } + const_iterator cend() const + { + return end(); + } + + + + inline iterator find(const FindKey& key) + { + size_t index = hash_object(key); + size_t num_slots_minus_one = this->num_slots_minus_one; + BlockPointer entries = this->entries; + index = hash_policy.index_for_hash(index, num_slots_minus_one); + bool first = true; + for (;;) + { + // These are positions within the blocks of headers + // see https://youtu.be/M2fKMP47slQ?t=2135 + size_t block_index = index / BlockSize; + int index_in_block = index % BlockSize; + BlockPointer block = entries + block_index; + int8_t metadata = block->control_bytes[index_in_block]; + if (first) + { + // If the first bit is non-zero: return "end()" + // end() is the address at the end of the table + // It signifies the data is not found + // This occurs here because the metadata indicates that the slot is empty + // see here https://youtu.be/M2fKMP47slQ + // for the description of how the "magic_for_direct_hit" is a bit + // telling us whether or not this is a "real" hash value + if ((metadata & Constants::bits_for_direct_hit) != Constants::magic_for_direct_hit) + return end(); + first = false; + } + // Rreturn the element if the key matches the currently indexed element + if (compares_equal(key, block->data[index_in_block])) + return { block, index }; + // Get the jump distance index + int8_t to_next_index = metadata & Constants::bits_for_distance; + // If there is no more jump distance, we have hit the end of the table + if (to_next_index == 0) + return end(); + // Go to the next slot indicated by the jump distance + // see https://youtu.be/M2fKMP47slQ + index += Constants::jump_distances[to_next_index]; + index = hash_policy.keep_in_range(index, num_slots_minus_one); + } + } + inline const_iterator find(const FindKey& key) const + { + return const_cast(this)->find(key); + } + size_t count(const FindKey& key) const + { + return find(key) == end() ? 0 : 1; + } + std::pair equal_range(const FindKey& key) + { + iterator found = find(key); + if (found == end()) + return { found, found }; + else + return { found, std::next(found) }; + } + std::pair equal_range(const FindKey& key) const + { + const_iterator found = find(key); + if (found == end()) + return { found, found }; + else + return { found, std::next(found) }; + } + + + template + inline std::pair emplace(Key&& key, Args &&... args) + { + size_t index = hash_object(key); + size_t num_slots_minus_one = this->num_slots_minus_one; + BlockPointer entries = this->entries; + index = hash_policy.index_for_hash(index, num_slots_minus_one); + bool first = true; + for (;;) + { + size_t block_index = index / BlockSize; + int index_in_block = index % BlockSize; + BlockPointer block = entries + block_index; + int8_t metadata = block->control_bytes[index_in_block]; + if (first) + { + if ((metadata & Constants::bits_for_direct_hit) != Constants::magic_for_direct_hit) + return emplace_direct_hit({ index, block }, std::forward(key), std::forward(args)...); + first = false; + } + if (compares_equal(key, block->data[index_in_block])) + return { { block, index }, false }; + int8_t to_next_index = metadata & Constants::bits_for_distance; + if (to_next_index == 0) + return emplace_new_key({ index, block }, std::forward(key), std::forward(args)...); + index += Constants::jump_distances[to_next_index]; + index = hash_policy.keep_in_range(index, num_slots_minus_one); + } + } + + std::pair insert(const value_type& value) + { + return emplace(value); + } + std::pair insert(value_type&& value) + { + return emplace(std::move(value)); + } + template + iterator emplace_hint(const_iterator, Args &&... args) + { + return emplace(std::forward(args)...).first; + } + iterator insert(const_iterator, const value_type& value) + { + return emplace(value).first; + } + iterator insert(const_iterator, value_type&& value) + { + return emplace(std::move(value)).first; + } + + template + void insert(It begin, It end) + { + for (; begin != end; ++begin) + { + emplace(*begin); + } + } + void insert(std::initializer_list il) + { + insert(il.begin(), il.end()); + } + + void rehash(size_t num_items) + { + num_items = std::max(num_items, static_cast(std::ceil(num_elements / static_cast(_max_load_factor)))); + if (num_items == 0) + { + reset_to_empty_state(); + return; + } + auto new_prime_index = hash_policy.next_size_over(num_items); + if (num_items == num_slots_minus_one + 1) + return; + size_t num_blocks = num_items / BlockSize; + if (num_items % BlockSize) + ++num_blocks; + size_t memory_requirement = calculate_memory_requirement(num_blocks); + unsigned char* new_memory = &*AllocatorTraits::allocate(*this, memory_requirement); + BlockPointer new_buckets = reinterpret_cast(new_memory); + + BlockPointer special_end_item = new_buckets + num_blocks; + for (BlockPointer it = new_buckets; it <= special_end_item; ++it) + it->fill_control_bytes(Constants::magic_for_empty); + using std::swap; + swap(entries, new_buckets); + swap(num_slots_minus_one, num_items); + --num_slots_minus_one; + hash_policy.commit(new_prime_index); + num_elements = 0; + if (num_items) + ++num_items; + size_t old_num_blocks = num_items / BlockSize; + if (num_items % BlockSize) + ++old_num_blocks; + for (BlockPointer it = new_buckets, end = new_buckets + old_num_blocks; it != end; ++it) + { + for (int i = 0; i < BlockSize; ++i) + { + int8_t metadata = it->control_bytes[i]; + if (metadata != Constants::magic_for_empty && metadata != Constants::magic_for_reserved) + { + emplace(std::move(it->data[i])); + AllocatorTraits::destroy(*this, it->data + i); + } + } + } + deallocate_data(new_buckets, num_items - 1); + } + + void reserve(size_t num_elements) + { + size_t required_buckets = num_buckets_for_reserve(num_elements); + if (required_buckets > bucket_count()) + rehash(required_buckets); + } + + // the return value is a type that can be converted to an iterator + // the reason for doing this is that it's not free to find the + // iterator pointing at the next element. if you care about the + // next iterator, turn the return value into an iterator + convertible_to_iterator erase(const_iterator to_erase) + { + LinkedListIt current = { to_erase.index, to_erase.current }; + if (current.has_next()) + { + LinkedListIt previous = current; + LinkedListIt next = current.next(*this); + while (next.has_next()) + { + previous = next; + next = next.next(*this); + } + AllocatorTraits::destroy(*this, std::addressof(*current)); + AllocatorTraits::construct(*this, std::addressof(*current), std::move(*next)); + AllocatorTraits::destroy(*this, std::addressof(*next)); + next.set_metadata(Constants::magic_for_empty); + previous.clear_next(); + } + else + { + if (!current.is_direct_hit()) + find_parent_block(current).clear_next(); + AllocatorTraits::destroy(*this, std::addressof(*current)); + current.set_metadata(Constants::magic_for_empty); + } + --num_elements; + return { to_erase.current, to_erase.index }; + } + + iterator erase(const_iterator begin_it, const_iterator end_it) + { + if (begin_it == end_it) + return { begin_it.current, begin_it.index }; + if (std::next(begin_it) == end_it) + return erase(begin_it); + if (begin_it == begin() && end_it == end()) + { + clear(); + return { end_it.current, end_it.index }; + } + std::vector> depth_in_chain; + for (const_iterator it = begin_it; it != end_it; ++it) + { + LinkedListIt list_it(it.index, it.current); + if (list_it.is_direct_hit()) + depth_in_chain.emplace_back(0, list_it); + else + { + LinkedListIt root = find_direct_hit(list_it); + int distance = 1; + for (;;) + { + LinkedListIt next = root.next(*this); + if (next == list_it) + break; + ++distance; + root = next; + } + depth_in_chain.emplace_back(distance, list_it); + } + } + std::sort(depth_in_chain.begin(), depth_in_chain.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); + for (auto it = depth_in_chain.rbegin(), end = depth_in_chain.rend(); it != end; ++it) + { + erase(it->second.it()); + } + + if (begin_it.current->control_bytes[begin_it.index % BlockSize] == Constants::magic_for_empty) + return ++iterator{ begin_it.current, begin_it.index }; + else + return { begin_it.current, begin_it.index }; + } + + size_t erase(const FindKey& key) + { + auto found = find(key); + if (found == end()) + return 0; + else + { + erase(found); + return 1; + } + } + + void clear() + { + if (!num_slots_minus_one) + return; + size_t num_slots = num_slots_minus_one + 1; + size_t num_blocks = num_slots / BlockSize; + if (num_slots % BlockSize) + ++num_blocks; + for (BlockPointer it = entries, end = it + num_blocks; it != end; ++it) + { + for (int i = 0; i < BlockSize; ++i) + { + if (it->control_bytes[i] != Constants::magic_for_empty) + { + AllocatorTraits::destroy(*this, std::addressof(it->data[i])); + it->control_bytes[i] = Constants::magic_for_empty; + } + } + } + num_elements = 0; + } + + void shrink_to_fit() + { + rehash_for_other_container(*this); + } + + void swap(sherwood_v8_table& other) + { + using std::swap; + swap_pointers(other); + swap(static_cast(*this), static_cast(other)); + swap(static_cast(*this), static_cast(other)); + if (AllocatorTraits::propagate_on_container_swap::value) + swap(static_cast(*this), static_cast(other)); + } + + size_t size() const + { + return num_elements; + } + size_t max_size() const + { + return (AllocatorTraits::max_size(*this)) / sizeof(T); + } + size_t bucket_count() const + { + return num_slots_minus_one ? num_slots_minus_one + 1 : 0; + } + size_type max_bucket_count() const + { + return (AllocatorTraits::max_size(*this)) / sizeof(T); + } + size_t bucket(const FindKey& key) const + { + return hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); + } + float load_factor() const + { + return static_cast(num_elements) / (num_slots_minus_one + 1); + } + void max_load_factor(float value) + { + _max_load_factor = value; + } + float max_load_factor() const + { + return _max_load_factor; + } + + bool empty() const + { + return num_elements == 0; + } + + private: + BlockPointer entries = BlockType::empty_block(); + size_t num_slots_minus_one = 0; + typename HashPolicySelector::type hash_policy; + float _max_load_factor = 0.9375f; + size_t num_elements = 0; + + size_t num_buckets_for_reserve(size_t num_elements) const + { + return static_cast(std::ceil(num_elements / static_cast(_max_load_factor))); + } + void rehash_for_other_container(const sherwood_v8_table& other) + { + rehash(std::min(num_buckets_for_reserve(other.size()), other.bucket_count())); + } + bool is_full() const + { + if (!num_slots_minus_one) + return true; + else + return num_elements + 1 > (num_slots_minus_one + 1) * static_cast(_max_load_factor); + } + + void swap_pointers(sherwood_v8_table& other) + { + using std::swap; + swap(hash_policy, other.hash_policy); + swap(entries, other.entries); + swap(num_slots_minus_one, other.num_slots_minus_one); + swap(num_elements, other.num_elements); + swap(_max_load_factor, other._max_load_factor); + } + + struct LinkedListIt + { + size_t index = 0; + BlockPointer block = nullptr; + + LinkedListIt() + { + } + LinkedListIt(size_t index, BlockPointer block) + : index(index), block(block) + { + } + + iterator it() const + { + return { block, index }; + } + int index_in_block() const + { + return index % BlockSize; + } + bool is_direct_hit() const + { + return (metadata() & Constants::bits_for_direct_hit) == Constants::magic_for_direct_hit; + } + bool is_empty() const + { + return metadata() == Constants::magic_for_empty; + } + bool has_next() const + { + return jump_index() != 0; + } + int8_t jump_index() const + { + return Constants::distance_from_metadata(metadata()); + } + int8_t metadata() const + { + return block->control_bytes[index_in_block()]; + } + void set_metadata(int8_t metadata) + { + block->control_bytes[index_in_block()] = metadata; + } + + LinkedListIt next(sherwood_v8_table& table) const + { + int8_t distance = jump_index(); + size_t next_index = table.hash_policy.keep_in_range(index + Constants::jump_distances[distance], table.num_slots_minus_one); + return { next_index, table.entries + next_index / BlockSize }; + } + void set_next(int8_t jump_index) + { + int8_t& metadata = block->control_bytes[index_in_block()]; + metadata = (metadata & ~Constants::bits_for_distance) | jump_index; + } + void clear_next() + { + set_next(0); + } + + value_type& operator*() const + { + return block->data[index_in_block()]; + } + bool operator!() const + { + return !block; + } + explicit operator bool() const + { + return block != nullptr; + } + bool operator==(const LinkedListIt& other) const + { + return index == other.index; + } + bool operator!=(const LinkedListIt& other) const + { + return !(*this == other); + } + }; + + template + SKA_NOINLINE(std::pair) emplace_direct_hit(LinkedListIt block, Args &&... args) + { + using std::swap; + if (is_full()) + { + grow(); + return emplace(std::forward(args)...); + } + if (block.metadata() == Constants::magic_for_empty) + { + AllocatorTraits::construct(*this, std::addressof(*block), std::forward(args)...); + block.set_metadata(Constants::magic_for_direct_hit); + ++num_elements; + return { block.it(), true }; + } + else + { + LinkedListIt parent_block = find_parent_block(block); + std::pair free_block = find_free_index(parent_block); + if (!free_block.first) + { + grow(); + return emplace(std::forward(args)...); + } + value_type new_value(std::forward(args)...); + for (LinkedListIt it = block;;) + { + AllocatorTraits::construct(*this, std::addressof(*free_block.second), std::move(*it)); + AllocatorTraits::destroy(*this, std::addressof(*it)); + parent_block.set_next(free_block.first); + free_block.second.set_metadata(Constants::magic_for_list_entry); + if (!it.has_next()) + { + it.set_metadata(Constants::magic_for_empty); + break; + } + LinkedListIt next = it.next(*this); + it.set_metadata(Constants::magic_for_empty); + block.set_metadata(Constants::magic_for_reserved); + it = next; + parent_block = free_block.second; + free_block = find_free_index(free_block.second); + if (!free_block.first) + { + grow(); + return emplace(std::move(new_value)); + } + } + AllocatorTraits::construct(*this, std::addressof(*block), std::move(new_value)); + block.set_metadata(Constants::magic_for_direct_hit); + ++num_elements; + return { block.it(), true }; + } + } + + template + SKA_NOINLINE(std::pair) emplace_new_key(LinkedListIt parent, Args &&... args) + { + if (is_full()) + { + grow(); + return emplace(std::forward(args)...); + } + std::pair free_block = find_free_index(parent); + if (!free_block.first) + { + grow(); + return emplace(std::forward(args)...); + } + AllocatorTraits::construct(*this, std::addressof(*free_block.second), std::forward(args)...); + free_block.second.set_metadata(Constants::magic_for_list_entry); + parent.set_next(free_block.first); + ++num_elements; + return { free_block.second.it(), true }; + } + + LinkedListIt find_direct_hit(LinkedListIt child) const + { + size_t to_move_hash = hash_object(*child); + size_t to_move_index = hash_policy.index_for_hash(to_move_hash, num_slots_minus_one); + return { to_move_index, entries + to_move_index / BlockSize }; + } + LinkedListIt find_parent_block(LinkedListIt child) + { + LinkedListIt parent_block = find_direct_hit(child); + for (;;) + { + LinkedListIt next = parent_block.next(*this); + if (next == child) + return parent_block; + parent_block = next; + } + } + + std::pair find_free_index(LinkedListIt parent) const + { + for (int8_t jump_index = 1; jump_index < Constants::num_jump_distances; ++jump_index) + { + size_t index = hash_policy.keep_in_range(parent.index + Constants::jump_distances[jump_index], num_slots_minus_one); + BlockPointer block = entries + index / BlockSize; + if (block->control_bytes[index % BlockSize] == Constants::magic_for_empty) + return { jump_index, { index, block } }; + } + return { 0, {} }; + } + + void grow() + { + rehash(std::max(size_t(10), 2 * bucket_count())); + } + + size_t calculate_memory_requirement(size_t num_blocks) + { + size_t memory_required = sizeof(BlockType) * num_blocks; + memory_required += BlockSize; // for metadata of past-the-end pointer + return memory_required; + } + + void deallocate_data(BlockPointer begin, size_t num_slots_minus_one) + { + if (begin == BlockType::empty_block()) + return; + + ++num_slots_minus_one; + size_t num_blocks = num_slots_minus_one / BlockSize; + if (num_slots_minus_one % BlockSize) + ++num_blocks; + size_t memory = calculate_memory_requirement(num_blocks); + unsigned char* as_byte_pointer = reinterpret_cast(begin); + AllocatorTraits::deallocate(*this, typename AllocatorTraits::pointer(as_byte_pointer), memory); + } + + void reset_to_empty_state() + { + deallocate_data(entries, num_slots_minus_one); + entries = BlockType::empty_block(); + num_slots_minus_one = 0; + hash_policy.reset(); + } + + template + size_t hash_object(const U& key) + { + return static_cast(*this)(key); + } + template + size_t hash_object(const U& key) const + { + return static_cast(*this)(key); + } + template + bool compares_equal(const L& lhs, const R& rhs) + { + return static_cast(*this)(lhs, rhs); + } + + struct convertible_to_iterator + { + BlockPointer it; + size_t index; + + operator iterator() + { + if (it->control_bytes[index % BlockSize] == Constants::magic_for_empty) + return ++iterator{ it, index }; + else + return { it, index }; + } + operator const_iterator() + { + if (it->control_bytes[index % BlockSize] == Constants::magic_for_empty) + return ++iterator{ it, index }; + else + return { it, index }; + } + }; + }; + template + struct AlignmentOr8Bytes + { + static constexpr size_t value = 8; + }; + template + struct AlignmentOr8Bytes= 1>::type> + { + static constexpr size_t value = alignof(T); + }; + template + struct CalculateBytellBlockSize; + template + struct CalculateBytellBlockSize + { + static constexpr size_t this_value = AlignmentOr8Bytes::value; + static constexpr size_t base_value = CalculateBytellBlockSize::value; + static constexpr size_t value = this_value > base_value ? this_value : base_value; + }; + template<> + struct CalculateBytellBlockSize<> + { + static constexpr size_t value = 8; + }; + } + + template, typename E = std::equal_to, typename A = std::allocator > > + class bytell_hash_map + : public detailv8::sherwood_v8_table + < + std::pair, + K, + H, + detailv8::KeyOrValueHasher, H>, + E, + detailv8::KeyOrValueEquality, E>, + A, + typename std::allocator_traits::template rebind_alloc, + detailv8::CalculateBytellBlockSize::value + > + { + using Table = detailv8::sherwood_v8_table + < + std::pair, + K, + H, + detailv8::KeyOrValueHasher, H>, + E, + detailv8::KeyOrValueEquality, E>, + A, + typename std::allocator_traits::template rebind_alloc, + detailv8::CalculateBytellBlockSize::value + >; + public: + + using key_type = K; + using mapped_type = V; + + using Table::Table; + bytell_hash_map() + { + } + + inline V& operator[](const K& key) + { + return emplace(key, convertible_to_value()).first->second; + } + inline V& operator[](K&& key) + { + return emplace(std::move(key), convertible_to_value()).first->second; + } + V& at(const K& key) + { + auto found = this->find(key); + if (found == this->end()) + throw std::out_of_range("Argument passed to at() was not in the map."); + return found->second; + } + const V& at(const K& key) const + { + auto found = this->find(key); + if (found == this->end()) + throw std::out_of_range("Argument passed to at() was not in the map."); + return found->second; + } + + using Table::emplace; + std::pair emplace() + { + return emplace(key_type(), convertible_to_value()); + } + template + std::pair insert_or_assign(const key_type& key, M&& m) + { + auto emplace_result = emplace(key, std::forward(m)); + if (!emplace_result.second) + emplace_result.first->second = std::forward(m); + return emplace_result; + } + template + std::pair insert_or_assign(key_type&& key, M&& m) + { + auto emplace_result = emplace(std::move(key), std::forward(m)); + if (!emplace_result.second) + emplace_result.first->second = std::forward(m); + return emplace_result; + } + template + typename Table::iterator insert_or_assign(typename Table::const_iterator, const key_type& key, M&& m) + { + return insert_or_assign(key, std::forward(m)).first; + } + template + typename Table::iterator insert_or_assign(typename Table::const_iterator, key_type&& key, M&& m) + { + return insert_or_assign(std::move(key), std::forward(m)).first; + } + + friend bool operator==(const bytell_hash_map& lhs, const bytell_hash_map& rhs) + { + if (lhs.size() != rhs.size()) + return false; + for (const typename Table::value_type& value : lhs) + { + auto found = rhs.find(value.first); + if (found == rhs.end()) + return false; + else if (value.second != found->second) + return false; + } + return true; + } + friend bool operator!=(const bytell_hash_map& lhs, const bytell_hash_map& rhs) + { + return !(lhs == rhs); + } + + private: + struct convertible_to_value + { + operator V() const + { + return V(); + } + }; + }; + + template, typename E = std::equal_to, typename A = std::allocator > + class bytell_hash_set + : public detailv8::sherwood_v8_table + < + T, + T, + H, + detailv8::functor_storage, + E, + detailv8::functor_storage, + A, + typename std::allocator_traits::template rebind_alloc, + detailv8::CalculateBytellBlockSize::value + > + { + using Table = detailv8::sherwood_v8_table + < + T, + T, + H, + detailv8::functor_storage, + E, + detailv8::functor_storage, + A, + typename std::allocator_traits::template rebind_alloc, + detailv8::CalculateBytellBlockSize::value + >; + public: + + using key_type = T; + + using Table::Table; + bytell_hash_set() + { + } + + template + std::pair emplace(Args &&... args) + { + return Table::emplace(T(std::forward(args)...)); + } + std::pair emplace(const key_type& arg) + { + return Table::emplace(arg); + } + std::pair emplace(key_type& arg) + { + return Table::emplace(arg); + } + std::pair emplace(const key_type&& arg) + { + return Table::emplace(std::move(arg)); + } + std::pair emplace(key_type&& arg) + { + return Table::emplace(std::move(arg)); + } + + friend bool operator==(const bytell_hash_set& lhs, const bytell_hash_set& rhs) + { + if (lhs.size() != rhs.size()) + return false; + for (const T& value : lhs) + { + if (rhs.find(value) == rhs.end()) + return false; + } + return true; + } + friend bool operator!=(const bytell_hash_set& lhs, const bytell_hash_set& rhs) + { + return !(lhs == rhs); + } + }; + +} // end namespace ska diff --git a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp new file mode 100644 index 00000000000..77f9692b40c --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp @@ -0,0 +1,1511 @@ +#pragma once +#include +#include +#include "types.h" +#include "gates.h" +#include +#include +#include +#include "basic_quantum_state.hpp" +#include "quantum_hash_map.hpp" +#include +#include +#include + +#include +#include + +#ifdef _OPENMP + #include +#endif + + +using namespace std::literals::complex_literals; + +namespace Microsoft +{ +namespace Quantum +{ +namespace 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 poppar(std::bitset 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 = std::bitset(); + 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); }; + + _initialize_threads(); + } + + ~QuantumState() { + complete_threads(); + } + + // 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); + } + // Create local threads for this state + _initialize_threads(); + } + + 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){ + std::cout << spacing << " " << label.to_string() << ": "; + std::cout << val.real(); + std::cout << (val.imag() < 0 ? " - " : " + ") << std::abs(val.imag()) << "i\n"; + }; + _DumpWavefunction_base(wfn, line_dump); + std::cout << spacing << "--end wavefunction\n"; + } + + + void set_random_seed(unsigned 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; + _precision_squared = _precision *_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_squared ){ + if (std::norm(id_coeff) > _rotation_precision_squared){ + // 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 *= (poppar(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 (!poppar(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 (poppar(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 * (poppar(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 = poppar(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_squared) { + 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_squared) { + 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_squared ){ + if (std::norm(id_coeff) > _rotation_precision_squared){ + // 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 *= (poppar(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 (!poppar(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 (poppar(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 * (poppar(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 = poppar(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_squared) { + 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_squared) { + 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); + } + } + + + bool M(logical_qubit_id target) { + qubit_label flip = qubit_label(); + flip.set(target); + + bool result = _qubit_data.begin()->first[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 + 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); + + return result; + } + + void Reset(logical_qubit_id target) { + qubit_label flip = qubit_label(0); + flip.set(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) { + if (std::norm(_qubit_data.find(current_state->first ^ XYs)->second - current_state->second * (poppar(current_state->first & YZs) ? -phaseShift : phaseShift)) > _precision_squared) { + qubit_label label = current_state->first; + amplitude val = current_state->second; + std::cout << "Problematic state: " << label << "\n"; + std::cout << "Expected " << val * (poppar(label & YZs) ? -phaseShift : phaseShift); + std::cout << ", got " << _qubit_data.find(label ^ XYs)->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)) * (poppar(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(); + } + + bool 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; + 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 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 label) { + qubit_label bit_label = qubit_label(label); + return probe(bit_label); + } + + // 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 qubits, void (*callback)(char*, double, double)) { + // 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){ + std::string label_string(qubits.size(), '0'); + for (size_t i=0; i < qubits.size(); i++){ + label_string[i] = label[qubits[i]] ? '1' : '0'; + } + callback(const_cast(label_string.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, void (*callback)(char*, double, double)) { + _DumpWavefunction_base(_qubit_data, [max_qubit_id, callback](qubit_label label, amplitude val){ + callback(const_cast(label.to_string().substr(num_qubits - 1 - max_qubit_id, max_qubit_id + 1).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 + _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; + case OP::Assert: + _operation_vector.push_back(internal_operation(op.gate_type, _get_mask(op.controls), op.result)); + break; + default: + throw std::runtime_error("Unsupported operation"); + break; + } + } + + _new_qubit_data = make_wavefunction(); + + // Threading introduces a lot of overhead so it + // is only worthwhile for large instances + // 64 and 4096 are empirical values + if (_operation_vector.size() < 64 || _qubit_data.size() < 4096){ // small; do not thread + // 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; + case OP::Assert: + if (poppar(label & op.controls) != op.result && std::norm(val) > _precision_squared){ + std::cout << "Problematic state: " << label << "\n"; + std::cout << "Amplitude: " << val << "\n"; + std::cout << "Wavefunction size: " << _qubit_data.size() << "\n"; + throw std::runtime_error("Assert failed"); + } + 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); + _new_qubit_data.clear(); + _operation_vector.clear(); + } else { // Large enough to multi-thread + #ifndef DOMP_GE_V3 // OMP version is too low; uses condition variables + // Lock the mutex so the threads stay asleep during prep + std::unique_lock state_lock(_state_mtx); + _new_qubit_data = wavefunction(_qubit_data.size()); + // jump_size gives a rough guess for how many states each thread should process + // to minimize contention for current_state + _jump_size = std::max((size_t)1 , (size_t)(_qubit_data.size() / (8*_thread_pool.size()))); + // Set the _current state (this allows the threads to wake up) + _current_state = _qubit_data.begin(); + + // Wake up all threads + cv.notify_all(); + // Wait for the number of running threads to be 0 + // This will spuriously wake many times + cv.wait(state_lock, [&](){ + return _current_state == _qubit_data.end() && _running_threads == 0; + }); + // Here all threads are finished + #else + #pragma omp parallel + { + _jump_size = std::max((size_t)1 , (size_t)(_qubit_data.size() / (8*omp_get_num_threads()))); + #pragma omp single + { + auto internal_state = _qubit_data.begin(); + auto internal_end = _qubit_data.end(); + for (auto current_state = _qubit_data.begin(); current_state != _qubit_data.end();){ + internal_state = current_state; + current_state.jump_forward(_jump_size); + internal_end = current_state; + #pragma omp task firstprivate(internal_state) firstprivate(internal_end) + { + qubit_label label; + amplitude val; + for (; internal_state != internal_end; ++internal_state){ + label = internal_state->first; + val = internal_state->second; + 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; + case OP::Assert: + if (poppar(label & op.controls) != op.result && std::norm(val) > _precision_squared){ + std::cout << "Problematic state: " << label << "\n"; + std::cout << "Amplitude: " << val << "\n"; + std::cout << "Wavefunction size: " << _qubit_data.size() << "\n"; + throw std::runtime_error("Assert failed"); + } + break; + default: + throw std::runtime_error("Unsupported operation"); + break; + } + } + #pragma omp critical + { + _new_qubit_data.emplace(label, val); + } + } + } + + } + } + #pragma omp barrier + } + #endif + _qubit_data = std::move(_new_qubit_data); + _operation_vector.clear(); + _new_qubit_data.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 = amplitude(std::cos(phi / 2.0), -std::sin(phi / 2.0)); + amplitude exp_1 = amplitude(std::cos(phi / 2.0), std::sin(phi / 2.0)); + 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 = -std::sin(phi / 2.0) * (b == Gates::Basis::PauliY ? 1 : 1i); + if (std::norm(M00) < _rotation_precision_squared){ + // 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_squared){ + // just an identity + return; + } + + amplitude M10 = M01 * amplitude(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_squared ) { + 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_squared) { + 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 = amplitude(std::cos(phi / 2.0), -std::sin(phi / 2.0)); + amplitude exp_1 = amplitude(std::cos(phi / 2.0), std::sin(phi / 2.0)); + 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(phi / 2.0); + amplitude M01 = -std::sin(phi / 2.0) * (b == Gates::Basis::PauliY ? 1 : 1i); + amplitude M10 = (b == Gates::Basis::PauliY ? -1.0 : 1.0) * M01; + + if (std::norm(M00) < _rotation_precision_squared){ + // 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/M01; + phase_and_permute(std::list{ + operation(OP::MCPhase, controls[0], controls, phase), + operation(OP::MCY, target, controls) + }); + } else { + amplitude phase = 1.0/M01; + phase_and_permute(std::list{ + operation(OP::MCPhase, controls[0], controls, phase), + operation(OP::MCY, target, controls) + }); + } + return; + } else if (std::norm(M01) < _rotation_precision_squared){ + // This is equivalent to a multi-controlled Z if the rotation is -1 + if (std::norm(M01 + 1.0) < _rotation_precision_squared){ + phase_and_permute(std::list{operation(OP::MCZ, controls[0], controls)}); + } + 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_squared) { + 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_squared) { + new_qubit_data.emplace(current_state->first | flip, 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 _qubut_date + 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_squared) { + 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_squared) { + 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_squared) { + 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_squared) { + 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_squared) { + return false; + } + } + return true; + } + + // 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; } + + // Finishes the threads which wait for the queue + void complete_threads() { + in_use=false; + // If this spuriously wakes up threads, + // they will still end because in_use is false + _current_state = _qubit_data.begin(); + cv.notify_all(); + + for (auto &thread : _thread_pool){ + thread.join(); + } + } + + int get_num_threads() { + #ifndef DOMP_GE_V3 + return static_cast(_thread_pool.size()); + #else + int threads=0; + #pragma omp parallel + { + #pragma omp master + threads = omp_get_num_threads(); + } + return threads; + #endif + } + + +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-5; + // Threshold at which something is zero when + // deciding whether to add it into the superposition + double _rotation_precision = 1e-6; + // Often we compare to norms, so the precision must be squared + double _precision_squared = _precision * _precision; + double _rotation_precision_squared = _rotation_precision*_rotation_precision; + + // Normalizer for H and T gates (1/sqrt(2) as an amplitude) + const amplitude _normalizer = amplitude(1.0, 0.0) / std::sqrt(2.0); + + // The default for bytell_hash_map + // 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); + } + + //********* Member variables for multithreading permutations ******// + // Shared iterator through the wavefunction + // Should point to _qubit_data.end() unless in use + decltype(_qubit_data.begin()) _current_state; + // Vector of waiting threads + std::vector _thread_pool; + // Condition variable, locked on _state_mtx + std::condition_variable cv; + // Mutex to prevent concurrent access to _current_state + std::mutex _state_mtx; + // Mutex to prevent concurrent access to _new_qubit_data + std::mutex _wfn_mtx; + // Used as a flag to decide when the thread pool is necessary, + // i.e., it is only set to false when the simulator is being destroyed + std::atomic in_use = true; + // Counts running threads, so controlling thread knows when the permutation is finished + std::atomic _running_threads = 0; + // A vector of operations that all threads point to to read which operations to apply + std::vector _operation_vector; + // New wavefunction which will replace the old one + wavefunction _new_qubit_data; + // The number of elements of the hash map that each thread will handle in one iteration + size_t _jump_size; + // Max number of threads + int max_num_threads = std::thread::hardware_concurrency(); + + // Called on creation, starts the thread pool and waits for all threads to + // finish initialization tasks + void _initialize_threads() { + // Unnecessary if OpenMP Is available + #ifndef DOMP_GE_V3 + // Prevents spurious wake-ups + _current_state = _qubit_data.end(); + _thread_pool = std::vector(); + #ifndef _OPENMP + int num_threads = std::thread::hardware_concurrency(); + #else + int num_threads = 1; + #pragma omp parallel + { + #pragma omp single + num_threads = omp_get_num_threads(); + } + #endif + // Lock the state, so that all the new threads get stopped during their execution + std::unique_lock state_lock(_state_mtx); + for (int i = 0; i < num_threads; i++){ + // Each running thread will decrement this just before it waits on the condition variable + ++_running_threads; + _thread_pool.push_back(std::thread([this](){this->_wait_to_permute();})); + } + // Waits until all the running threads have finished initializing + // This ensures the simulation does not actually start + // until the thread pool has finished initializing + cv.wait(state_lock, [&](){return _running_threads==0;}); + #endif + } + + // Function that all threads in _thread_pool run + // Waits on the condition variable, then applies _operation_vector + // to the qubit data + void _wait_to_permute(){ + // Prep local variables + auto local_state = _qubit_data.begin(); + auto local_end = _qubit_data.end(); + // Initialize a wfn lock to avoid recreating this object in every loop + std::unique_lock wfn_lock(_wfn_mtx); + wfn_lock.unlock(); + // First lock the state so we can wait on it + std::unique_lock state_lock(_state_mtx); + // Set the _current_state to ensure it waits + _current_state = _qubit_data.end(); + // Reduce number of running threads + _running_threads--; + // Notify all (i.e., the constructor thread) + // which will wait until the lock is released to check the number of running threads + cv.notify_all(); + // Wait until we set the current state to the start of the wavefunction + cv.wait(state_lock, [&](){ + return _current_state != _qubit_data.end(); + }); + + // Loop continuously + while (in_use){ + ++_running_threads; + // Loop through the wavefunction + while (_current_state != _qubit_data.end()){ + // Update local variables + local_state = _current_state; + _current_state.jump_forward(_jump_size); + local_end = _current_state; + // Unlock state to allow other threads to modify their state + state_lock.unlock(); + // Loop through the chunk that this thread has taken + for (; local_state != local_end; ++local_state){ + qubit_label label = local_state->first; + amplitude val = local_state->second; + + for (auto op : _operation_vector){ + 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; + case OP::Assert: + if (poppar(label & op.controls) != op.result && std::norm(val) > _precision_squared){ + std::cout << "Problematic state: " << label << "\n"; + std::cout << "Amplitude: " << val << "\n"; + std::cout << "Wavefunction size: " << _qubit_data.size() << "\n"; + throw std::runtime_error("Assert failed"); + } + break; + default: + throw std::runtime_error("Unsupported operation"); + break; + } + } + + wfn_lock.lock(); + _new_qubit_data.emplace(label, val); + wfn_lock.unlock(); + } + // Lock before checking and modifying the current state + state_lock.lock(); + } + // This thread has finished iterating through the wavefunction + --_running_threads; + // Notify all other threads (i.e., the controlling thread) + // if this is the last thread to finish + // The check on _running_threads avoids spurious wake-ups to + // the controlling thread, but takes a bit of time as _running_threads + // is atomic + if (_running_threads ==0) + cv.notify_all(); + // Wait on the state lock before repeating the loop + // The wait unlocks state_lock + cv.wait(state_lock, [&](){return _current_state != _qubit_data.end();}); + } + } + + + // 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. + // This means the coefficients of wfn1 are all of the form (c_x/c_b); + // Thus, the norm of wfn1 will be 1/|c_b|^2; thus the norm of wfn2 is 1/|d_b|^2 = |c_b|^2/|a_bb|^2 + // So we iterate through the smaller wavefunction, to get the normalizing constant, + // then normalize both + bool _split_wavefunction(qubit_label 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(); + 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 normalizer_1 = 0.0; + double normalizer_2 = 0.0; + // 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_squared*_precision_squared){ + return false; + } else { + // Not entangled so far, save the two states, with amplitudes a_xx/a_bx and a_xx/a_xb, respectively + wfn1[label_1] = base_state->second / second_state->second; + wfn2[label_2] = base_state->second / first_state->second; + } + } + } + // Normalize + // This cannot be done in the previous loop, as that loop will encounter the same data several times + wavefunction &smaller_wfn = (wfn1.size() < wfn2.size()) ? wfn1 : wfn2; + wavefunction &larger_wfn = (wfn1.size() < wfn2.size()) ? wfn2 : wfn1; + for (auto current_state = smaller_wfn.begin(); current_state != smaller_wfn.end(); ++current_state){ + normalizer_1 += std::norm(current_state->second); + } + normalizer_2 = normalizer_1/std::norm(base_val); + normalizer_1 = 1.0/normalizer_1; + for (auto current_state = smaller_wfn.begin(); current_state != smaller_wfn.end(); ++current_state){ + current_state->second *= normalizer_1; + } + for (auto current_state = larger_wfn.begin(); current_state != larger_wfn.end(); ++current_state){ + current_state->second *= normalizer_2; + } + 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; } + std::vector sortedLabels; + sortedLabels.reserve(wfn.size()); + for (auto current_state = (wfn).begin(); current_state != (wfn).end(); ++current_state) { + sortedLabels.push_back(current_state->first); + } + std::sort( + sortedLabels.begin(), + sortedLabels.end(), + [](const qubit_label& lhs, const qubit_label& rhs){return lhs < rhs;}); + amplitude val; + for (qubit_label label : sortedLabels){ + output(label, _qubit_data.find(label)->second); + + } + } + +}; + + +} // namespace SPARSESIMULATOR +} // namespace Quantum +} // namespace Microsoft diff --git a/src/Simulation/Simulators/SparseSimulator/Native/types.h b/src/Simulation/Simulators/SparseSimulator/Native/types.h new file mode 100644 index 00000000000..17116a51bc5 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/Native/types.h @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +#pragma once + + +#include +#include +#include +#include +#include "quantum_hash_map.hpp" +#include +#include + +namespace Microsoft +{ + namespace Quantum + { + namespace SPARSESIMULATOR + { + using mutex_type = std::mutex; + using recursive_mutex_type = std::recursive_mutex; + using lock_type = std::lock_guard; + using recursive_lock_type = std::lock_guard; + + #ifndef USE_SINGLE_PRECISION + using RealType = double; + #else + using RealType = float; + #endif + + + + // Logical qubit id is visible to the clients and is immutable during the lifetime of the qubit. + using logical_qubit_id = unsigned; + + 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 = ska::bytell_hash_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 SPARSESIMULATOR + } // namespace Quantum +} // namespace Microsoft diff --git a/src/Simulation/Simulators/SparseSimulator/README.md b/src/Simulation/Simulators/SparseSimulator/README.md new file mode 100644 index 00000000000..2705828bee0 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/README.md @@ -0,0 +1,155 @@ +# Introduction +This is a an alternative quantum simulator, compatible with Q\# and Microsoft's quantum development kit. It is efficient for algorithms with a smaller number of states in superposition, regardless of the number of qubits. + +# Repository Structure + +/SparseQuantumSimulator/ +|--/Native/: C++ code for the simulator +|--/SparseSimulatorCS/: C# library for the simulator +|--/SparseSimQSharpTests/: Several Q# tests of the simulator +|--/SparseSimulatorTests/: C++ tests of the simulator + +# Dependencies + - CMake v1.2.0 + - Q\# v0.15.2101126940 + - C++11 + - Dotnet core v 3.1 + - Dotnet cake v1.1.0 + +# Setup +To build the Sparse Simulator, call `dotnet restore` from the main `SparseSimulator` folder, then `dotnet cake`. This builds the C++ backend and the C\# interface. + +To use the SparseSimulator in a Q\# project, ensure that it includes +`` +` ` +`` +in the `.csproj` file, to import the sparse simulator. + +The C\# sparse simulator class and all its Q\# functions are in the `Microsoft.Quantum.SparseSimulation` namespace. + +A sparse simulator object can be initialized in C\# by calling `SparseSimulator()`. In an executable Q\# project, add +`Microsoft.Quantum.SparseSimulation.SparseSimulator` +to the property group in the `.csproj` file. + +To run a test with the sparse simulator, add `@Test("Microsoft.Quantum.SparseSimulation.SparseSimulator")` above the test operation. + +# Other + +## Qubit Capacity +The simulator has a qubit capacity which can be either 64, 128, 256, 512, or 1024. The capacity is only related to the internal data structure of quantum states, and only needs to be at least as large as the number of qubits actually in use. By default it starts with 64 qubits, and allocating more than 64 qubits will simply increase its capacity to accommodate this. It can be initialized with more qubits with an optional argument to C\#, i.e., `var sim = SparseSimulator(128)` will initialize with capacity for 128 qubits. + +## Emulation +Currently the simulator only emulates AND and adjoint AND gates. It asserts that the target qubit is |0> before the AND and after the adjoint AND. + +## Statistics +By default, the simulator tracks the total number of qubits used. It also tracks the number of occupied qubits, which is based on whether they are ever in a non-zero state. That is, if one allocates a qubit and applies Z to it, the simulator will still regard it as unoccupied because it wil still be in the |0> state. + +## Assertion optimizations +Z-assertions can be treated as phase/permutation gates, and thus in a Release build, they are added to the queue and not immediately run. This means if such an assertion fails, it might not give an error until much later in the execution. In a Debug build, they are executed immediately (which will slow down execution, but be more helpful for debugging). + + +# Special Operations +The sparse simulator has some extra C\# Q\# operations for specific purposes. + +## Member functions of `SparseSimulator` +`SetLogging(bool)`: Logging is set to `false` by default; if set to true, the simulator will output the name of each operation as it executes them. + +## Q\# Functions + +`GetAmplitudeFromInt(qubit : Qubit[], label : Int)`: Uses `label` as a bitstring to index the qubits in `qubit`. It returns the amplitude of the state with that label, where the value of all other qubits is 0. + +`GetAmplitude(qubit : Qubit[], label : BigInt) : Complex` : Same as `GetAmplitudeFromInt`, but uses a BigInt as an index. + +`AssertProbBigInt (stateIndex : BigInt, expected : Double, qubits : Microsoft.Quantum.Arithmetic.LittleEndian, tolerance : Double) : Unit`: Asserts that the probability to measure the qubits in `Qubits` and find the value `stateIndex` is within `tolerance` of the expected probability `expected`. + +`Sample(register : qubit [])`: This acts like measuring `register` in the Pauli-Z basis, and returns a boolean array of the result of that measurement, except it is non-destructive. + +# Adding Gates + +To add a gate to the simulator, you will need to add it to: +- SparseSimulatorCS/SparseSimulator.cs +- Native/capi.cpp +- Native/quantum_state.hpp +- Native/basic_quantum_state.hpp +- Native/SparseSimulator.h + +Optionally, you may need to add a Q\# and C\# file to the SparseSimulatorCS folder, to create a Q\# operation to call the gate if it does not already exist. + +## Quantum State (quantum_state.hpp and basec_quantum_state.hpp) +This code will modify the actual wavefunction data structure. The code for the `H` gate will be a good template. Typically, such a function will create a new wavefunction object, then iterate through the existing wavefunction to compute new (label, amplitude) pairs, which are inserted into the new wavefunction. Finally, it moves the new data into the old wavefunction object. + +Since the hash map does not allow modifications while iterating through it, this is the only way to implement most gates. + +Finally, the `BasicQuantumState` class needs to implement the same function virtually, so the `SparseSimulator` class can call it. + +## SparseSimulator (SparseSimulator.h) +The `SparseSimulator` class will also need a wrapper function for the new gate. This will call `_quantum_state->your_new_gate(args...);`. However, `SparseSimulator` also manages gate queues, so you will need to decide how the new gate commutes with existing ones. The simplest approach will be to start by calling `_execute_queued_ops();`, which will flush all the gate queues and allow you to run your gate without needing any commutation relations. + +## C API (capi.cpp) +Within capi.cpp, you need to add a new wrapper to call the function in `SparseSimulator`. The first argument must be an unsigned integer, representing the ID of the simulator, so you will call it as +`getSimulator(sim_id)->your_new_gate(\# args \#);` +For more complicated arguments from C\#, the other functions provide a template. For consistency, append "\_cpp" to the end of the function name in capi.cpp. + + +## SparseSimulator (SparseSimulator.cs) +Here the C\# code will call your new gate. The wrapper must go in the `SparseSimulatorProcessor` class, and must have the following form: +`private static extern void your_new_gate_cpp(uint sim, \# args\#);` +`public override void Your_new_gate(\#args\#) +{ + your_new_gate_cpp(Id, \# arguments, parsed into a format that C++ can read\#); +}` +Here `Id` is an internal variable for the simulator's ID, which must be passed to C++. + +## (Optional) Q\# Code +If you are implementing a gate that Q\# already expects (i.e., it is initialized in the `QuantumProcessorBase` class) then the previous steps will be enough. However, if you want to create an entirely new gate, you will need to create a Q\# operation to call it. + +"Probes.qs" and "Probes.cs" provide a template for how this code will look. In Q\#, declare the operation `YourNewGate` with code `body intrinsic;`. Then in a separate C\# file, use the following template: +`public partial class YourNewGate +{ + public class Native : YourNewGate + { + private SparseSimulator sim = null; + public Native(IOperationFactory m) : base(m) + { + sim = m as SparseSimulator; + } + public override Func<\#input types from Q\#\#, \#output types to Q\#\#> __Body__ => sim == null ? base.__Body__ : (args) => { + return sim.Your_new_gate(args.Item1, args.Item2,...); + }; + } +}` + +You will also need to add +`public partial class SparseSimulator : QuantumProcessorDispatcher, IDisposable +{ + public \# Q\# return type\# Your_new_gate(\#args\#) + { + return ((SparseSimulatorProcessor)this.QuantumProcessor).Your_new_gate(\#args\#); + } +} +` +which tells the `SparseSimulator` class to forward the call to its internal `SparseSimulatorProcessor` class. + + +# Internal Logic +We desribe the main data structure strategies and optimizations in the paper at https://arxiv.org/abs/2105.01533. + +## Threading Logic +The multithreading uses OpenMP where possible, but the Visual Studio compiler's version is too low, and hence it relies on std::thread. The build detects the version of OpenMP: + - If OpenMP is not available, it does not set the flags `DOMP_GE_V3` nor `_OPENMP`. This will select std::thread and use `std::hardware_concurrency` to decide on the number of threads. + - If OpenMP is available but the version is too low (i.e., Visual Studio), it does not set `DOMP_GE_V3` but does set `_OPENMP`. Here it will use std::thread but use `omp_get_num_threads` to decide on the number of threads. + - If OpenMP is availalble and the version is at least 3, it will set both flags to 1 and use OpenMP parallelism + + +# Future Optimizations + +## Delayed Release +Currently the simulator executes any queued operations on any qubits it needs to release. This is not strictly necessary: it could add an assertion that the qubits are zero, and continue on. However, it's important to set the `_occupied_qubits` vector to be `0` for that qubit after it is released, but this will not necessarily be true if the gates are not executed. Hence, to delay the release of qubits, it will need a more involved method to track occupied qubits, and qubits which are actually 0. + + +# Licence +The wavefunction uses the bytell hash map written by Malte Skarupke, which has the following licence: + +Copyright Malte Skarupke 2017. +Distributed under the Boost Software License, Version 1.0. + (See http://www.boost.org/LICENSE_1_0.txt) \ No newline at end of file diff --git a/src/Simulation/Simulators/SparseSimulator/SparseQuantumSimulator.sln b/src/Simulation/Simulators/SparseSimulator/SparseQuantumSimulator.sln new file mode 100644 index 00000000000..05a785cd491 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/SparseQuantumSimulator.sln @@ -0,0 +1,65 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30907.101 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SparseSimulatorTests", "SparseSimulatorTests\SparseSimulatorTests.vcxproj", "{E1663845-BBA8-41EA-AC31-50A092EEA728}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SparseSimulator", "SparseSimulatorCS\SparseSimulator.csproj", "{1E901047-6E72-4B99-8683-A5B64D755400}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1CECBF17-34F0-41FF-BE04-88659415074A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SparseSimQSharpTests", "SparseSimQSharpTests\SparseSimQSharpTests.csproj", "{2D6F4915-A10D-4109-8B83-DA6E5C36AEA4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E1663845-BBA8-41EA-AC31-50A092EEA728}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {E1663845-BBA8-41EA-AC31-50A092EEA728}.Debug|x64.ActiveCfg = Debug|x64 + {E1663845-BBA8-41EA-AC31-50A092EEA728}.Debug|x64.Build.0 = Debug|x64 + {E1663845-BBA8-41EA-AC31-50A092EEA728}.Debug|x86.ActiveCfg = Debug|Win32 + {E1663845-BBA8-41EA-AC31-50A092EEA728}.Debug|x86.Build.0 = Debug|Win32 + {E1663845-BBA8-41EA-AC31-50A092EEA728}.Release|Any CPU.ActiveCfg = Release|Win32 + {E1663845-BBA8-41EA-AC31-50A092EEA728}.Release|x64.ActiveCfg = Release|x64 + {E1663845-BBA8-41EA-AC31-50A092EEA728}.Release|x64.Build.0 = Release|x64 + {E1663845-BBA8-41EA-AC31-50A092EEA728}.Release|x86.ActiveCfg = Release|Win32 + {E1663845-BBA8-41EA-AC31-50A092EEA728}.Release|x86.Build.0 = Release|Win32 + {1E901047-6E72-4B99-8683-A5B64D755400}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E901047-6E72-4B99-8683-A5B64D755400}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E901047-6E72-4B99-8683-A5B64D755400}.Debug|x64.ActiveCfg = Debug|Any CPU + {1E901047-6E72-4B99-8683-A5B64D755400}.Debug|x64.Build.0 = Debug|Any CPU + {1E901047-6E72-4B99-8683-A5B64D755400}.Debug|x86.ActiveCfg = Debug|Any CPU + {1E901047-6E72-4B99-8683-A5B64D755400}.Debug|x86.Build.0 = Debug|Any CPU + {1E901047-6E72-4B99-8683-A5B64D755400}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E901047-6E72-4B99-8683-A5B64D755400}.Release|Any CPU.Build.0 = Release|Any CPU + {1E901047-6E72-4B99-8683-A5B64D755400}.Release|x64.ActiveCfg = Release|Any CPU + {1E901047-6E72-4B99-8683-A5B64D755400}.Release|x64.Build.0 = Release|Any CPU + {1E901047-6E72-4B99-8683-A5B64D755400}.Release|x86.ActiveCfg = Release|Any CPU + {1E901047-6E72-4B99-8683-A5B64D755400}.Release|x86.Build.0 = Release|Any CPU + {2D6F4915-A10D-4109-8B83-DA6E5C36AEA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D6F4915-A10D-4109-8B83-DA6E5C36AEA4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D6F4915-A10D-4109-8B83-DA6E5C36AEA4}.Debug|x64.ActiveCfg = Debug|Any CPU + {2D6F4915-A10D-4109-8B83-DA6E5C36AEA4}.Debug|x64.Build.0 = Debug|Any CPU + {2D6F4915-A10D-4109-8B83-DA6E5C36AEA4}.Debug|x86.ActiveCfg = Debug|Any CPU + {2D6F4915-A10D-4109-8B83-DA6E5C36AEA4}.Debug|x86.Build.0 = Debug|Any CPU + {2D6F4915-A10D-4109-8B83-DA6E5C36AEA4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D6F4915-A10D-4109-8B83-DA6E5C36AEA4}.Release|Any CPU.Build.0 = Release|Any CPU + {2D6F4915-A10D-4109-8B83-DA6E5C36AEA4}.Release|x64.ActiveCfg = Release|Any CPU + {2D6F4915-A10D-4109-8B83-DA6E5C36AEA4}.Release|x64.Build.0 = Release|Any CPU + {2D6F4915-A10D-4109-8B83-DA6E5C36AEA4}.Release|x86.ActiveCfg = Release|Any CPU + {2D6F4915-A10D-4109-8B83-DA6E5C36AEA4}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {756B5CCE-BACE-4478-A9A1-25027C14669B} + EndGlobalSection +EndGlobal diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/Program.qs b/src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/Program.qs new file mode 100644 index 00000000000..0893c4440b3 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/Program.qs @@ -0,0 +1,643 @@ +namespace Microsoft.Quantum.SparseSimulatorTests { + + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Convert; + open Microsoft.Quantum.Arithmetic; + open Microsoft.Quantum.Arrays; + open Microsoft.Quantum.Preparation; + open Microsoft.Quantum.SparseSimulation; + open Microsoft.Quantum.Math; + + @Test("Microsoft.Quantum.SparseSimulation.SparseSimulator") + operation ControlledHTest() : Unit { + use qubits = Qubit[4]{ + H(qubits[0]); + H(qubits[1]); + CNOT(qubits[0], qubits[2]); + CNOT(qubits[1], qubits[3]); + (Controlled H)([qubits[2]], (qubits[3])); + DumpMachine("Test file"); + let expected = [ + (0.5, 0L), + (0.353553,5L), + (0.5,10L), + (0.353553,7L), + (0.353553,13L), + (-0.353553,15L)]; + mutable results = new Complex[Length(expected)]; + let expBasicC = Complex(2.0,0.0); + let basicC = GetAmplitude(qubits, 0L); + let invC = Complex(basicC::Real/AbsSquaredComplex(basicC), basicC::Imag/AbsSquaredComplex(basicC)); + for (value, label) in expected { + let c = TimesC(GetAmplitude(qubits, label), invC); + Fact(AbsD(c::Real - expBasicC::Real*value) + AbsD(c::Imag - expBasicC::Imag*value) < 0.001, "Controlled H failed"); + } + ResetAll(qubits); + } + } + + + operation DumpIdRotation() : Unit { + use qubits = Qubit[2] { + H(qubits[0]); + H(qubits[1]); + (Controlled Exp)([qubits[0]], ([PauliI], 0.5, [qubits[1]])); + DumpMachine(); + ResetAll(qubits); + } + } + + 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("Microsoft.Quantum.SparseSimulation.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("Microsoft.Quantum.SparseSimulation.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, _)); + } + } + + operation DumpMultiplexZ() : Unit { + use qubits = Qubit[12] { + ApplyToEach(H, qubits[0..5]); + ApplyToEach(CNOT, Zipped(qubits[0..5], qubits[6..11])); + ApproximatelyMultiplexZ(0.001, [0.12, 0.34, -0.26, 0.5, 1.8], LittleEndian(qubits[6..10]), qubits[11]); + DumpMachine(); + ResetAll(qubits); + } + } + + + + internal operation ControlledRz(angle : Double, (control : Qubit, target : Qubit)) : Unit is Adj { + Controlled Rz([control], (angle, target)); + DumpMachine(); + } + + internal operation ControlledRzAsR1(angle : Double, (control : Qubit, target : Qubit)) : Unit is Adj { + Controlled R1([control], (angle, target)); + R1(-angle / 2.0, control); + DumpMachine(); + } + @Test("Microsoft.Quantum.SparseSimulation.SparseSimulator") + operation TestEqualityOfControlledRz() : Unit { + for _ in 1..10 { + let angle = Microsoft.Quantum.Random.DrawRandomDouble(0.0, 2.0 * PI()); + AssertOperationsEqualReferenced(2, ApplyToFirstTwoQubits(ControlledRzAsR1(angle, _), _), ApplyToFirstTwoQubitsA(ControlledRz(angle, _), _)); + } + } + + @Test("Microsoft.Quantum.SparseSimulation.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(AndChainTest, nqubits); + LargeStateTestWrapper(CZTest, 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("Microsoft.Quantum.SparseSimulation.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); + } + + operation MCXTime(nqubits : Int, ngates : Int) : Unit { + use qubits = Qubit[nqubits]; + for idx in 0..nqubits - 1 { + H(qubits[idx]); + } + for idx in 0..nqubits - 1 { + CNOT(qubits[idx], qubits[(idx+1)% nqubits]); + } + for idy in 1..ngates { + CNOT(qubits[idy %nqubits], qubits[(idy+1)%nqubits]); + } + ResetAll(qubits); + } + + @Test("Microsoft.Quantum.SparseSimulation.SparseSimulator") + operation PartialDumpTest() : Unit { + use qubits = Qubit[4] { + ApplyToEach(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); + } + } + + + 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]); + } + + operation R1FracWithArray(numerator : Int, denominator : Int, qubit : Qubit[]) : Unit is Adj + Ctl { + R1Frac(numerator, denominator, qubit[0]); + } + + operation _FakeR1(angle : Double, qubit : Qubit[]) : Unit is Adj + Ctl { + R(PauliZ, angle, qubit[0]); + R(PauliI, -angle, qubit[0]); + } + operation R1WithArray(angle : Double, qubit : Qubit[]) : Unit is Adj + Ctl { + R1(angle, qubit[0]); + } + @Test("Microsoft.Quantum.SparseSimulation.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, _)); + } + } + + operation RFracWithArray(axis : Pauli, num : Int, denom : Int, qubit : Qubit[]) : Unit is Adj + Ctl { + RFrac(axis, num, denom, qubit[0]); + } + operation RWithArray(axis : Pauli, angle : Double, qubit : Qubit[]) : Unit is Adj + Ctl { + R(axis, angle, qubit[0]); + } + @Test("Microsoft.Quantum.SparseSimulation.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("Microsoft.Quantum.SparseSimulation.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("Microsoft.Quantum.SparseSimulation.SparseSimulator") + operation AllocationTest() : Unit { + use qubits = Qubit[512]{ + for idx in 0..511 { + X(qubits[idx]); + } + ResetAll(qubits); + } + } + + // Taken from the PurifiedMixedState documentation + @Test("Microsoft.Quantum.SparseSimulation.SparseSimulator") + operation QROMPrepareTest() : Unit { + let coefficients = [1.0, 2.0, 3.0, 4.0, 5.0]; + let targetError = 1e-3; + let purifiedState = PurifiedMixedState(targetError, coefficients); + use indexRegister = Qubit[purifiedState::Requirements::NIndexQubits] { + use garbageRegister = Qubit[purifiedState::Requirements::NGarbageQubits] { + purifiedState::Prepare(LittleEndian(indexRegister), new Qubit[0], garbageRegister); + ResetAll(garbageRegister); + } + ResetAll(indexRegister); + } + } + + + @Test("Microsoft.Quantum.SparseSimulation.SparseSimulator") + operation CZTest() : Unit { + let num_qubits = 5; + use qubits = Qubit[num_qubits]{ + H(qubits[0]); + for idx in 0..(2^(num_qubits-1) - 1) { + let result = (idx == 2^(num_qubits - 1) - 1); + within { + ApplyXorInPlace(idx, LittleEndian(qubits[1..num_qubits - 1])); + (Controlled Z)(qubits[1..num_qubits -1], (qubits[0])); + } apply { + if (result){ + AssertMeasurement([PauliX], qubits[0..0], One, "CZ failed to add phase"); + } else { + AssertMeasurement([PauliX], qubits[0..0], Zero, "CZ added unexpected phase"); + } + } + } + H(qubits[0]); + } + } + + operation ApplyAndChain(andOp : ((Qubit, Qubit, Qubit)=>Unit is Adj + Ctl), auxRegister : Qubit[], ctrlRegister : Qubit[], target : Qubit) + : Unit is Adj { + if (Length(ctrlRegister) == 0) { + X(target); + } elif (Length(ctrlRegister) == 1) { + CNOT(Head(ctrlRegister), target); + } else { + EqualityFactI(Length(auxRegister), Length(ctrlRegister) - 2, "Unexpected number of auxiliary qubits"); + let controls1 = ctrlRegister[0..0] + auxRegister; + let controls2 = Rest(ctrlRegister); + let targets = auxRegister + [target]; + ApplyToEachA(andOp, Zipped3(controls1, controls2, targets)); + } + } + + operation AndChainDump() : Unit { + let num_qubits = 5; + use qubits = Qubit[num_qubits]{ + use aux = Qubit[num_qubits - 3]{ + within{ + for idx in 1..num_qubits - 1 { + H(qubits[idx]); + if (idx % 3 == 0){ Z(qubits[idx]);} + } + ApplyAndChain(ApplyAnd, aux, qubits[1..num_qubits -1], qubits[0]); + } apply { + DumpMachine(); + } + } + } + } + @Test("Microsoft.Quantum.SparseSimulation.SparseSimulator") + operation AndChainTest() : Unit { + let num_qubits = 5; + use qubits = Qubit[num_qubits]{ + use aux = Qubit[num_qubits - 3]{ + for idx in 0..(2^(num_qubits-1) - 1) { + let result = (idx == 2^(num_qubits - 1) - 1); + within { + ApplyXorInPlace(idx, LittleEndian(qubits[1..num_qubits - 1])); + ApplyAndChain(ApplyAnd, aux, qubits[1..num_qubits -1], qubits[0]); + } apply { + let after = M(qubits[0]); + if (result){ + Fact(after == One, "Did not apply AND"); + } else { + Fact(after == Zero, "Applied AND unexpectedly"); + } + } + } + } + } + } + + + 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("Microsoft.Quantum.SparseSimulation.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("Microsoft.Quantum.SparseSimulation.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("Microsoft.Quantum.SparseSimulation.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("Microsoft.Quantum.SparseSimulation.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("Microsoft.Quantum.SparseSimulation.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("Microsoft.Quantum.SparseSimulation.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("Microsoft.Quantum.SparseSimulation.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("Microsoft.Quantum.SparseSimulation.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/SparseSimulator/SparseSimQSharpTests/SparseSimQSharpTests.csproj b/src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/SparseSimQSharpTests.csproj new file mode 100644 index 00000000000..4b8e18a115c --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/SparseSimQSharpTests.csproj @@ -0,0 +1,15 @@ + + + + Exe + netcoreapp3.1 + Microsoft.Quantum.SparseSimulation.SparseSimulator + + + + + + + + + diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.cs b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.cs new file mode 100644 index 00000000000..d058eb86c5d --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.cs @@ -0,0 +1,357 @@ +// Functions that access the internal wavefunction for diagnostic purposes + +using Microsoft.Quantum.Simulation; +using Microsoft.Quantum.Simulation.Common; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.QuantumProcessor; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace Microsoft.Quantum.SparseSimulation +{ + + public partial class GetAmplitudeFromInt + { + public class Native : GetAmplitudeFromInt + { + private SparseSimulator sim = null; + public Native(IOperationFactory m) : base(m) + { + sim = m as SparseSimulator; + } + + public override Func<(IQArray, long), Microsoft.Quantum.Math.Complex> __Body__ => sim == null ? base.__Body__ : (args) => { + return sim.GetAmplitude(args.Item1, args.Item2); + }; + + } + } + + public partial class GetAmplitude + { + public class Native : GetAmplitude + { + private SparseSimulator sim = null; + public Native(IOperationFactory m) : base(m) + { + sim = m as SparseSimulator; + } + + public override Func<(IQArray, System.Numerics.BigInteger), Microsoft.Quantum.Math.Complex> __Body__ => sim == null ? base.__Body__ : args => { + return sim.GetAmplitude(args.Item1, args.Item2); + }; + + } + } + public partial class SparseSimulator : QuantumProcessorDispatcher, IDisposable + { + // // Converts a big integer into a string label + // where the label is read over the qubits passed as an argument, in order + // The label for all other qubits is assumed to be 0 + // That is: Suppose we have 11 = 1011 on qubits 0, 6,9,3. + // That will output the string 0001001001 + public static string InterleaveLabelByArray(IQArray qubits, System.Numerics.BigInteger label) + { + // Converts a big integer into a string label + // where the label is read over the qubits passed as an argument, in order + // The label for all other qubits is assumed to be 0 + char[] newlabel = "".PadRight((int)qubits.GetIds().Max() + 1, '0').ToCharArray(); + string test = new string(newlabel); + for (int idx = 0; idx < (int)qubits.Length; idx++) + { + if (((label >> idx) & 1) == 1) + { + int id = qubits[idx].Id; + newlabel[id] = '1'; + } + } + // std::bitset parses strings backwards + Array.Reverse(newlabel); + return new string(newlabel); + } + + // Returns the amplitude of a single state + public Microsoft.Quantum.Math.Complex GetAmplitude(string label) + { + return ((SparseSimulatorProcessor)this.QuantumProcessor).GetAmplitude(label); + } + public Microsoft.Quantum.Math.Complex GetAmplitude(uint label) + { + return GetAmplitude(label.ToString()); + } + public Microsoft.Quantum.Math.Complex GetAmplitude(System.Numerics.BigInteger label) + { + return GetAmplitude(label.ToString()); + } + + public Microsoft.Quantum.Math.Complex GetAmplitude(IQArray qubits, System.Numerics.BigInteger label) + { + return GetAmplitude(InterleaveLabelByArray(qubits, label)); + } + + + // Dumps the state in qubits to `target` (a filename), if the qubits are + // either null or not entangled to the rest of the machine; + // prints to console if `target` is null + protected virtual QVoid Dump(T target, IQArray qubits = null) + { + + var filename = (target is QVoid) ? "" : target.ToString(); + var logMessage = this.Get, Microsoft.Quantum.Intrinsic.Message>(); + // Once it turns `target` into a string action, it calls this process to push output + // into the string channel + QVoid process(Action channel) + { + // Just to output the ids + var ids = qubits?.Select(q => q.Id).ToArray() ?? this.QubitManager.AllocatedIds().Select(q => (int)q).ToArray(); + + // This will get passed, more or less, to the C++ + SparseSimulatorProcessor.StringCallback callback = (string label, double real, double imag) => + { + channel(Format(label, real, imag)); + }; + + channel($"# wave function for qubits with ids (least to most significant): {string.Join(";", ids)}"); + + // Calls the internal SparseSimulatorProcessor, which calls C++. + // Returns false if the state was entangled. + if (!((SparseSimulatorProcessor)this.QuantumProcessor).Dump(callback, ids.Max(), qubits)) + { + channel("## Qubits were entangled with an external qubit. Cannot dump corresponding wave function. ##"); + } + + return QVoid.Instance; + } + + // This generates the channel, then calls it + // If no file provided, use `Message` to generate the message into the console; + if (string.IsNullOrWhiteSpace(filename)) + { + var op = this.Get, Microsoft.Quantum.Intrinsic.Message>(); + return process((msg) => op.Apply(msg)); + } + else + { + try + { + using (var file = new StreamWriter(filename)) + { + return process(file.WriteLine); + } + } + catch (Exception e) + { + logMessage.Apply($"[warning] Unable to write state to '{filename}' ({e.Message})"); + return QVoid.Instance; + } + } + } + + + // Called by the diagnostics functions + public class SparseSimDumpMachine : Quantum.Diagnostics.DumpMachine + { + private SparseSimulator Simulator { get; } + + public SparseSimDumpMachine(SparseSimulator m) : base(m) + { + this.Simulator = m; + } + + public override Func __Body__ => (location) => + { + if (location == null) { throw new ArgumentNullException(nameof(location)); } + + return Simulator.Dump(location); + }; + } + + // Called by the diagnostics functions + public class QSimDumpRegister : Quantum.Diagnostics.DumpRegister + { + private SparseSimulator Simulator { get; } + + + public QSimDumpRegister(SparseSimulator m) : base(m) + { + this.Simulator = m; + } + + public override Func<(T, IQArray), QVoid> __Body__ => (__in) => + { + var (location, qubits) = __in; + + if (location == null) { throw new ArgumentNullException(nameof(location)); } + + return Simulator.Dump(location, qubits); + }; + } + + private string FormatMagnitude(double magnitude, double phase) => + (new String('*', (int)System.Math.Ceiling(20.0 * magnitude))).PadRight(20) + $" [ {magnitude:F6} ]"; + + /// + /// Returns a string that represents the phase of the amplitude. + /// + private string FormatAngle(double magnitude, double angle) + { + var PI = System.Math.PI; + var offset = PI / 16.0; + if (magnitude == 0.0) + { + return " "; + } + + var chart = " ---"; + if (angle > 0) + { + if (angle >= (0 * PI / 8) + offset && angle < ((1 * PI / 8) + offset)) { chart = " /-"; } + if (angle >= (1 * PI / 8) + offset && angle < ((2 * PI / 8) + offset)) { chart = " / "; } + if (angle >= (2 * PI / 8) + offset && angle < ((3 * PI / 8) + offset)) { chart = " +/ "; } + if (angle >= (3 * PI / 8) + offset && angle < ((4 * PI / 8) + offset)) { chart = " ↑ "; } + if (angle >= (4 * PI / 8) + offset && angle < ((5 * PI / 8) + offset)) { chart = " \\- "; } + if (angle >= (5 * PI / 8) + offset && angle < ((6 * PI / 8) + offset)) { chart = " \\ "; } + if (angle >= (6 * PI / 8) + offset && angle < ((7 * PI / 8) + offset)) { chart = "+\\ "; } + if (angle >= (7 * PI / 8) + offset) { chart = "--- "; } + } + else if (angle < 0) + { + var abs_angle = System.Math.Abs(angle); + if (abs_angle >= (0 * PI / 8) + offset && abs_angle < ((1 * PI / 8) + offset)) { chart = " \\+"; } + if (abs_angle >= (1 * PI / 8) + offset && abs_angle < ((2 * PI / 8) + offset)) { chart = " \\ "; } + if (abs_angle >= (2 * PI / 8) + offset && abs_angle < ((3 * PI / 8) + offset)) { chart = " -\\ "; } + if (abs_angle >= (3 * PI / 8) + offset && abs_angle < ((4 * PI / 8) + offset)) { chart = " ↓ "; } + if (abs_angle >= (4 * PI / 8) + offset && abs_angle < ((5 * PI / 8) + offset)) { chart = " /+ "; } + if (abs_angle >= (5 * PI / 8) + offset && abs_angle < ((6 * PI / 8) + offset)) { chart = " / "; } + if (abs_angle >= (6 * PI / 8) + offset && abs_angle < ((7 * PI / 8) + offset)) { chart = "-/ "; } + } + + return $" {chart} [ {angle,8:F5} rad ]"; + } + + /// + /// Returns a string for the amplitude's polar representation (magnitude/angle). + /// + private string FormatPolar(double magnitude, double angle) => + $"{FormatMagnitude(magnitude, angle)}{FormatAngle(magnitude, angle)}"; + + /// + /// Returns a string for the amplitude's cartesian representation (real + imagnary). + /// + private string FormatCartesian(double real, double img) => + $"{real,9:F6} + {img,9:F6} i"; + + /// + /// The method to use to format the amplitude into a string. + /// + private string Format(string label, double real, double img) + { + var amplitude = (real * real) + (img * img); + var angle = System.Math.Atan2(img, real); + + return $"{label}:\t" + + $"{FormatCartesian(real, img)}\t == \t" + + $"{FormatPolar(amplitude, angle)}"; + + } + + } + + + /// + /// Samples a random label from the wavefunction, proportional to its amplitude squared, + /// and returns it as a boolean array + /// + public partial class Sample + { + private SparseSimulator simulator; + public class Native : Sample + { + public Native(IOperationFactory m) : base(m) + { + simulator = m as SparseSimulator; + } + public override Func, IQArray> __Body__ => (__in__) => + { + if (simulator != null) + { + return new QArray(((SparseSimulatorProcessor)simulator.QuantumProcessor).Sample(__in__)); + } + return new QArray( new bool[0]); + }; + } + } + + public partial class SparseSimulatorProcessor : QuantumProcessorBase { + + + // These two delegates exist so that C# can use StringCallback (which has a nice + // string object) and C++ can use DumpCallback (which can handle char*) + public delegate void StringCallback(string label, double real, double img); + + private delegate void DumpCallback(StringBuilder label, double real, double img); + [DllImport(simulator_dll)] + private static extern void Dump_cpp(uint sim, uint max_qubits, DumpCallback callback); + + // This gets called by the base class + public bool Dump(StringCallback callback, int max_id, IQArray qubits = null) + { + DumpCallback _callback = (StringBuilder label_builder, double real, double imag) => + { + callback(label_builder.ToString(), real, imag); + }; + + // It's expensive for the C++ simulator to separate two parts of a wavefunction, + // so if we just want the full state, it calls a different function + if (qubits == null) + { + Dump_cpp(Id, (uint)max_id, _callback); + return true; + } else + { + return DumpQubits_cpp(Id, qubits.Count(), qubits.Select(x => x.Id).ToArray(), _callback) ; + } + } + + [DllImport(simulator_dll)] + private static extern bool DumpQubits_cpp(uint sim, int length, int[] qubit_ids, DumpCallback callback); + + [DllImport(simulator_dll)] + private static extern uint num_qubits_cpp(uint sim); + + // Returns the amplitude of a specific state, given by a string representing its label + [DllImport(simulator_dll)] + private static extern void GetAmplitude_cpp(uint sim, uint label_length, char[] label, ref double real, ref double imag); + + public Microsoft.Quantum.Math.Complex GetAmplitude(string label) + { + double real = 0; + double imag = 0; + GetAmplitude_cpp(Id, (uint)label.Length, label.ToCharArray(), ref real, ref imag); + return new Microsoft.Quantum.Math.Complex((real, imag)); + } + + [DllImport(simulator_dll)] + private static extern IntPtr Sample_cpp(uint sim); + + public bool[] Sample(IQArray register) + { + var result = Marshal.PtrToStringAnsi(Sample_cpp(Id)).ToCharArray(); + Array.Reverse(result); + var bool_result = new bool[register.Length]; + for (int i=0; i < register.Length; i++) + { + bool_result[i] = (result[register[i].Id] == '1' ? true : false); + } + return bool_result; + } + } + + + + +} diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.qs b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.qs new file mode 100644 index 00000000000..8c0f5ee3c6e --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.qs @@ -0,0 +1,33 @@ +namespace Microsoft.Quantum.SparseSimulation { + + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Diagnostics; + + // These operations are physically unrealistic diagnostic + // tools that can read amplitudes and probabilities + // from a quantum state directly without modifyng it. + + // Returns an output with the same distribution + // as measuring all the qubits, but does not + // modify the quantum state. + function Sample(register : Qubit[]) : Bool[] { + body intrinsic; + } + + // Returns the amplitude of the quantum state which has a specific qubit + // label (e.g., "|101>" has label 5) + function GetAmplitudeFromInt(qubit : Qubit[], label : Int) : Complex { + fail "Only implemented for SparseSimulator"; + } + function GetAmplitude(qubit : Qubit[], label : BigInt) : Complex { + fail "Only implemented for SparseSimulator"; + } + + // Asserts (within some tolerance) that the probability of measuring the result passed as + // stateIndex is a specific value. + operation AssertProbBigInt (stateIndex : BigInt, expected : Double, qubits : Microsoft.Quantum.Arithmetic.LittleEndian, tolerance : Double) : Unit { + Fact(AbsD(AbsSquaredComplex (GetAmplitude(qubits!, stateIndex)) - expected) < tolerance, "Probability failed"); + } +} diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Simulator.ApplyAnd.cs b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Simulator.ApplyAnd.cs new file mode 100644 index 00000000000..466d8d6b2a0 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Simulator.ApplyAnd.cs @@ -0,0 +1,59 @@ +// Overrides the basic AND gate for faster execution + +using Microsoft.Quantum.Simulation.Core; +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; +using Microsoft.Quantum.Canon; + +namespace Microsoft.Quantum.SparseSimulation +{ + public class ApplyAndWrapper : ApplyAnd + { + private SparseSimulator simulator; + public ApplyAndWrapper(IOperationFactory m) : base(m) + { + simulator = m as SparseSimulator; + } + + public override Func<(Qubit, Qubit, Qubit), QVoid> __Body__ => simulator == null ? base.__Body__ : (args) => + { + simulator.And(new QArray(args.Item1, args.Item2), args.Item3); + return QVoid.Instance; + }; + + public override Func<(Qubit, Qubit, Qubit), QVoid> __AdjointBody__ => simulator == null ? base.__AdjointBody__ : (args) => + { + simulator.AdjointAnd(new QArray(args.Item1, args.Item2), args.Item3); + return QVoid.Instance; + }; + + public override Func<(IQArray, (Qubit, Qubit, Qubit)), QVoid> __ControlledBody__ => simulator == null ? base.__ControlledBody__ : (args) => + { + simulator.And(new QArray(args.Item1.Concat(new QArray(args.Item2.Item1, args.Item2.Item2))), args.Item2.Item3); + return QVoid.Instance; + }; + + public override Func<(IQArray, (Qubit, Qubit, Qubit)), QVoid> __ControlledAdjointBody__ => simulator == null ? base.__ControlledAdjointBody__ : (args) => + { + simulator.AdjointAnd(new QArray(args.Item1.Concat(new QArray(args.Item2.Item1, args.Item2.Item2))), args.Item2.Item3); + return QVoid.Instance; + }; + } + + public partial class SparseSimulator + { + // Wrappers for the relevant functions of SparseSimulatorProcessor + // Since And/AdjointAnd are inherently multi-controlled, there is only a multi-controlled + // emulator + public void And(IQArray controls, Qubit target) + { + ((SparseSimulatorProcessor)this.QuantumProcessor).MCAnd(controls, target); ; + } + public void AdjointAnd(IQArray controls, Qubit target) + { + ((SparseSimulatorProcessor)this.QuantumProcessor).MCAdjointAnd(controls, target); + } + } +} \ No newline at end of file diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.cs b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.cs new file mode 100644 index 00000000000..04ad05ca62f --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.cs @@ -0,0 +1,385 @@ +using System; +using System.Collections.Generic; +using Microsoft.Quantum.Simulation.QuantumProcessor; +using Microsoft.Quantum.Simulation.Common; +using Microsoft.Quantum.Simulation.Core; +using System.Runtime.InteropServices; +using System.Runtime.ExceptionServices; +using System.Threading.Tasks; +using Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime; +using Newtonsoft.Json.Serialization; +using System.Reflection.Metadata.Ecma335; +using Microsoft.VisualBasic.FileIO; +using Microsoft.Quantum.Simulation.Simulators; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Linq; +using Newtonsoft.Json.Converters; +using Microsoft.Quantum.Diagnostics; +using Microsoft.Quantum.Extensions.Math; +using Microsoft.Quantum.Math; +using System.Numerics; +using Microsoft.Quantum.Canon; +using Microsoft.Quantum.Measurement; + +using Microsoft.Quantum.SparseSimulation; + +namespace Microsoft.Quantum.SparseSimulation +{ + + // Class that can be used as a simulator, but is mostly a wrapper + // for the underlying SparseSimulatorProcessor + public partial class SparseSimulator : QuantumProcessorDispatcher, IDisposable + { + public uint Id { get; } + + // If set to true, the simulator will output the name of all operations + // it executes to the console, as it executes them + public void SetLogging(bool isLogging) + { + ((SparseSimulatorProcessor)this.QuantumProcessor).isLogging = isLogging; + } + + public SparseSimulator() : base(new SparseSimulatorProcessor() ) + { + // Emulates AND + Register(typeof(ApplyAnd), typeof(ApplyAndWrapper), typeof(IUnitary)); + Id = ((SparseSimulatorProcessor)this.QuantumProcessor).Id; + } + + public void Dispose() + { + Dispose(true); + } + + // Clears memory, specifically by telling the C++ to clear the memory + public void Dispose(bool Disposing) + { + ((SparseSimulatorProcessor)this.QuantumProcessor).Dispose(); + } + + // Sets the random seed in the C++ code, used for the randomness + // of measurements. + // Default value is the default for std::mt19937 + public void SetSeed(uint newSeed = 5489) + { + ((SparseSimulatorProcessor)this.QuantumProcessor).SetSeed(newSeed); + } + + } + + + // Class that performs all gate operations + // Mostly a wrapper for C++ code + partial class SparseSimulatorProcessor : QuantumProcessorBase + { + // References the C++ dll that is copied in during the build + private const string simulator_dll = "SparseQuantumSimulator.dll"; + // Controls whether it outputs all operations for debugging + public bool isLogging = false; + // For debugging + private string stackDepth; + + // C++ code uses the Id to refer to this simulator in a vector of simulators + public uint Id { get; } + + public string Name => "Sparse Simulator"; + + [DllImport(simulator_dll)] + private static extern uint init_cpp(uint num_qubits); + public SparseSimulatorProcessor(uint num_qubits = 64) + { + Id = init_cpp(num_qubits); + stackDepth = ""; + } + + + [DllImport(simulator_dll)] + private static extern void seed_cpp(uint sim, uint new_seed); + public void SetSeed(uint newSeed = 5489) + { + seed_cpp(Id, newSeed); + } + + + + // Basic gates + [DllImport(simulator_dll)] + private static extern void MCAnd_cpp(uint sim, int length, int[] controls, int target); + public void MCAnd(IQArray controls, Qubit qubit) + { + MCAnd_cpp(Id, controls.Count(), controls.Select(x => x.Id).ToArray(), qubit.Id); + } + + [DllImport(simulator_dll)] + private static extern void MCAdjointAnd_cpp(uint sim, int length, int[] controls, int target); + public void MCAdjointAnd(IQArray controls, Qubit qubit) + { + MCAdjointAnd_cpp(Id, controls.Count(), controls.Select(x => x.Id).ToArray(), qubit.Id); + } + + + [DllImport(simulator_dll)] + private static extern void X_cpp(uint sim, int qubit_id); + public override void X(Qubit qubit) + { X_cpp(Id, qubit.Id); } + [DllImport(simulator_dll)] + private static extern void MCX_cpp(uint sim, int length, int[] controls, int target); + public override void ControlledX(IQArray controls, Qubit qubit) + { + MCX_cpp(Id, controls.Count(), controls.Select(x => x.Id).ToArray(), qubit.Id); + } + [DllImport(simulator_dll)] + private static extern void Z_cpp(uint sim, int qubit_id); + public override void Z(Qubit qubit) + { Z_cpp(Id, qubit.Id); } + [DllImport(simulator_dll)] + private static extern void MCZ_cpp(uint sim, int length, int[] controls, int target); + public override void ControlledZ(IQArray controls, Qubit qubit) + { + MCZ_cpp(Id, controls.Count(), controls.Select(x => x.Id).ToArray(), qubit.Id); + } + [DllImport(simulator_dll)] + private static extern void Y_cpp(uint sim, int qubit_id); + public override void Y(Qubit qubit) + { Y_cpp(Id, qubit.Id); } + [DllImport(simulator_dll)] + private static extern void MCY_cpp(uint sim, int length, int[] controls, int target); + public override void ControlledY(IQArray controls, Qubit qubit) + { + MCY_cpp(Id, controls.Count(), controls.Select(x => x.Id).ToArray(), qubit.Id); + } + [DllImport(simulator_dll)] + private static extern int H_cpp(uint sim, int qubit_id); + public override void H(Qubit qubit) + { H_cpp(Id, qubit.Id); } + [DllImport(simulator_dll)] + private static extern void MCH_cpp(uint sim, int length, int[] controls, int target); + public override void ControlledH(IQArray controls, Qubit qubit) + { + MCH_cpp(Id, controls.Count(), controls.Select(x => x.Id).ToArray(), qubit.Id); + } + [DllImport(simulator_dll)] + private static extern void S_cpp(uint sim, int qubit_id); + public override void S(Qubit qubit) + { S_cpp(Id, qubit.Id); } + [DllImport(simulator_dll)] + private static extern void AdjS_cpp(uint sim, int qubit_id); + public override void SAdjoint(Qubit qubit) + { AdjS_cpp(Id, qubit.Id); } + [DllImport(simulator_dll)] + private static extern void T_cpp(uint sim, int qubit_id); + public override void T(Qubit qubit) + { T_cpp(Id, qubit.Id); } + [DllImport(simulator_dll)] + private static extern void AdjT_cpp(uint sim, int qubit_id); + public override void TAdjoint(Qubit qubit) + { AdjT_cpp(Id, qubit.Id); } + [DllImport(simulator_dll)] + [return: MarshalAs(UnmanagedType.I1)] // necessary because C++ and C# represent bools differently + private static extern bool M_cpp(uint sim, int qubit_id); + public override Result M(Qubit qubit) + { + if (M_cpp(Id, qubit.Id)) + { + return Result.One; + } + else + { + return Result.Zero; + } + } + [DllImport(simulator_dll)] + [return: MarshalAs(UnmanagedType.I1)] // necessary because C++ and C# represent bools differently + private static extern bool Measure_cpp(uint sim, int length, int[] basis, int[] qubits); + public override Result Measure(IQArray bases, IQArray qubits) + { + if (Measure_cpp(Id, bases.Count(), bases.Select(x => (int)x).ToArray(), qubits.Select(x => x.Id).ToArray())) { + return Result.One; + } else { return Result.Zero; } + } + + [DllImport(simulator_dll)] + private static extern void Reset_cpp(uint sim, int qubit_id); + public override void Reset(Qubit qubit) + { + Reset_cpp(Id, qubit.Id); + } + + + [DllImport(simulator_dll)] + private static extern void R_cpp(uint sim, int axis, double theta, int qubit_id); + public override void R(Pauli axis, double theta, Qubit qubit) + { + R_cpp(Id, (int)axis, theta, qubit.Id); + } + [DllImport(simulator_dll)] + private static extern void MCR_cpp(uint sim, int basis, double angle, int length, int[] controls, int target); + public override void ControlledR(IQArray controls, Pauli axis, double theta, Qubit qubit) + { + MCR_cpp(Id, (int)axis, theta, controls.Count(), controls.Select(x => x.Id).ToArray(), qubit.Id); + } + [DllImport(simulator_dll)] + private static extern void Rfrac_cpp(uint sim, int axis, long numerator, long power, int qubit_id); + public override void RFrac(Pauli axis, long numerator, long power, Qubit qubit) + { + Rfrac_cpp(Id, (int)axis, numerator, power, qubit.Id); + } + + [DllImport(simulator_dll)] + private static extern void MCRFrac_cpp(uint sim, int basis, long numerator, long power, int length, int[] controls, int target); + public override void ControlledRFrac(IQArray controls, Pauli axis, long numerator, long power, Qubit qubit) + { + MCRFrac_cpp(Id, (int)axis, numerator, power, controls.Count(), controls.Select(x => x.Id).ToArray(), qubit.Id); + } + [DllImport(simulator_dll)] + private static extern void R1_cpp(uint sim, double theta, int qubit_id); + public override void R1(double theta, Qubit qubit) + { + R1_cpp(Id, theta, qubit.Id); + } + [DllImport(simulator_dll)] + private static extern void MCR1_cpp(uint sim, double angle, int length, int[] controls, int target); + public override void ControlledR1(IQArray controls, double theta, Qubit qubit) + { + MCR1_cpp(Id, theta, controls.Count(), controls.Select(x => x.Id).ToArray(), qubit.Id); + } + [DllImport(simulator_dll)] + private static extern void R1frac_cpp(uint sim, long numerator, long power, int qubit_id); + public override void R1Frac(long numerator, long power, Qubit qubit) + { + R1frac_cpp(Id, numerator, power, qubit.Id); + } + [DllImport(simulator_dll)] + private static extern void MCR1Frac_cpp(uint sim, long numerator, long power, int length, int[] controls, int target); + public override void ControlledR1Frac(IQArray controls, long numerator, long power, Qubit qubit) + { + MCR1Frac_cpp(Id, numerator, power, controls.Count(), controls.Select(x => x.Id).ToArray(), qubit.Id); + } + + [DllImport(simulator_dll)] + private static extern void SWAP_cpp(uint sim, int qubit_id_1, int qubit_id_2); + public override void SWAP(Qubit qubit1, Qubit qubit2) + { + SWAP_cpp(Id, qubit1.Id, qubit2.Id); + } + [DllImport(simulator_dll)] + private static extern void MCSWAP_cpp(uint sim, int length, int[] controls, int qubit_id_1, int qubit_id_2); + public override void ControlledSWAP(IQArray controls, Qubit qubit1, Qubit qubit2) + { + MCSWAP_cpp(Id, controls.Count(), controls.Select(x => x.Id).ToArray(), qubit1.Id, qubit2.Id); + } + + [DllImport(simulator_dll)] + private static extern void Exp_cpp(uint sim, int length, int[] b, double phi, int[] q); + public override void Exp(IQArray paulis, double theta, IQArray qubits) + { + Exp_cpp(Id, paulis.Count(), paulis.Select(x => (int)x).ToArray(), theta, qubits.Select(x => x.Id).ToArray()); + } + [DllImport(simulator_dll)] + private static extern void MCExp_cpp(uint sim, int controls_length, int length, int[] c, int[] b, double phi, int[] q); + public override void ControlledExp(IQArray controls, IQArray paulis, double theta, IQArray qubits) + { + MCExp_cpp(Id, controls.Count(), paulis.Count(), controls.Select(x => x.Id).ToArray(), paulis.Select(x => (int)x).ToArray(), theta, qubits.Select(x => x.Id).ToArray()); + } + + [DllImport(simulator_dll)] + [return: MarshalAs(UnmanagedType.I1)] // necessary because C++ and C# represent bools differently + private static extern bool Assert_cpp(uint sim, int length, int[] b, int[] q, bool result); + public override void Assert(IQArray bases, IQArray qubits, Result result, string msg) + { + // Relies on C++ catching any assertion errors and returning false + if (!Assert_cpp( + Id, + bases.Count(), + bases.Select(x => (int)x).ToArray(), + qubits.Select(x => x.Id).ToArray(), + result.Equals(Result.One))) + { + throw new Exception(msg); + } + } + + [DllImport(simulator_dll)] + private static extern double JointEnsembleProbability_cpp(uint sim, int length, int[] basis, int[] qubits); + public override void AssertProb(IQArray bases, IQArray qubits, double probabilityOfZero, string msg, double tol) + { + double result = JointEnsembleProbability_cpp(Id, bases.Count(), bases.Select(x => (int)x).ToArray(), qubits.Select(x => x.Id).ToArray()); + if (System.Math.Abs(1.0 - result - probabilityOfZero) > tol) + { + var extendedMsg = $"{msg}\n\tExpected:\t{probabilityOfZero}\n\tActual:\t{result}"; + IgnorableAssert.Assert(false, extendedMsg); + throw new ExecutionFailException(extendedMsg); + } + } + + + [DllImport(simulator_dll)] + private static extern void allocateQubit_cpp(uint sim, int qubit_id); + public override void OnAllocateQubits(IQArray qubits) + { + try + { + foreach (Qubit qubit in qubits) + { + allocateQubit_cpp(Id, qubit.Id); + } + } catch (ExternalException ex) + { + // Possibly C++ errors: qubit already occupied, or not enough qubits + throw ex; + } + } + + [DllImport(simulator_dll)] + private static extern void releaseQubit_cpp(uint sim, int qubit_id); + public override void OnReleaseQubits(IQArray qubits) + { + try + { + foreach (Qubit qubit in qubits) + { + releaseQubit_cpp(Id, qubit.Id); + } + } catch (ExternalException) + { + // Possible C++ errors: Qubit not in zero state + throw new Microsoft.Quantum.Simulation.Simulators.Exceptions.ReleasedQubitsAreNotInZeroState(); + } + } + + // Tells C++ to delete any objects, to manage memory + public void Dispose() + { + Dispose(true); + } + + [DllImport(simulator_dll)] + private static extern void destroy_cpp(uint sim); + public void Dispose(bool Disposing) + { + destroy_cpp(Id); + } + + public override void OnOperationStart(ICallable operation, IApplyData arguments) + { + // Writes the operation to console if it is logging + if (isLogging) + { + Console.WriteLine(stackDepth + operation.FullName + "{"); + stackDepth += " "; + } + } + + public override void OnOperationEnd(ICallable operation, IApplyData arguments) + { + + if (isLogging) + { + stackDepth = stackDepth[0..(stackDepth.Length - 1)]; + Console.WriteLine(stackDepth + "}"); + } + } + + + } +} diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.csproj b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.csproj new file mode 100644 index 00000000000..ce7ad730387 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.csproj @@ -0,0 +1,28 @@ + + + + Library + netcoreapp3.1 + true + + + + + ..\Native\build\libSparseQuantumSimulator.dylib + ..\Native\build\libSparseQuantumSimulator.so + ..\Native\build\$(Configuration)\SparseQuantumSimulator.dll + $(QsimDllMac) + $(QsimDllLinux) + $(QsimDllWindows) + + + + + SparseQuantumSimulator.dll + PreserveNewest + true + + + + + diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CMakeLists.txt b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CMakeLists.txt new file mode 100644 index 00000000000..cd8079e38f1 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.10) +project(SparseQuantumSimulator) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +include(CTest) +enable_testing() + +set(CMAKE_MACOSX_RPATH 1) +add_executable(SparseSimulatorTests SparseSimulatorTests.cpp) +add_test(SparseSimulatorTests SparseSimulatorTests) + +#target_compile_options(SparseQuantumSimulator PUBLIC -fdeclspec) + +#add_executable(SparseQuantumSimulatorExe SparseQuantumSimulator.cpp) +# target_include_directories(${EXE} PUBLIC ${OSSIM_INCLUDE_DIRS}) +# target_link_libraries(${EXE} PRIVATE ${OPENMP_LINKER_FLAG}) \ No newline at end of file diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CSharpIntegrationTests.cpp b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CSharpIntegrationTests.cpp new file mode 100644 index 00000000000..f94750c84e5 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CSharpIntegrationTests.cpp @@ -0,0 +1,306 @@ +#pragma once + +#include "../SparseQuantumSimulator/SparseSimulator.h" +#include "../SparseQuantumSimulator/capi.hpp" +#include "../SparseQuantumSimulator/capi.cpp" // yes really +#include "../SparseQuantumSimulator/factory.hpp" +#include "../SparseQuantumSimulator/factory.cpp" +#include "TestHelpers.hpp" + +#include "CppUnitTest.h" +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Microsoft::Quantum::SPARSESIMULATOR; +using namespace SparseSimulatorTestHelpers; + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace CSharpIntegrationTests +{ + TEST_CLASS(CSharpIntegrationTests) + { + 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) { + unsigned sim = init_cpp(32); + qubit_prep(sim); + + std::vector vector_rep(8, 0.0); + for (unsigned 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{ 0, 1, 2 }); + for (unsigned 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); + } + } + } + public: + TEST_METHOD(initializationTest) { + unsigned sim = init_cpp(32); + } + + TEST_METHOD(AllocationTest) { + unsigned sim = init_cpp(32); + allocateQubit_cpp(sim, 0); + releaseQubit_cpp(sim, 0); + } + TEST_METHOD(AllocateRebuildTest) { + unsigned 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_METHOD(XTest) { + unsigned 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_METHOD(ZTest) { + unsigned 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_METHOD(HTest) { + unsigned 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_METHOD(TGateTest) { + unsigned 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_METHOD(HCancellationTest) + { + int n_qubits = 16; + unsigned 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_METHOD(HXZCommutationTest) + { + const int n_qubits = 16; + unsigned 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 (__int64 i = 0; i< pow(2, 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_METHOD(ResetTest) + { + const int n_qubits = 16; + unsigned 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_METHOD(MultiExpWithHTest) { + const int num_qubits = 32; + auto qubit_prep = [](unsigned sim ) { + H_cpp(sim, 0); + H_cpp(sim, 1); + H_cpp(sim, 2); + }; + auto qubit_clear = [](unsigned 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_METHOD(MultiExpBasisTest) { + const int num_qubits = 32; + auto qubit_prep = [](unsigned 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 = [](unsigned 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([=](unsigned sim) {qubit_prep(sim, i); }, [=](unsigned sim) {qubit_clear(sim, i); }); + } + } + + TEST_METHOD(R1Test) { + const int num_qubits = 32; + amplitude result0; + amplitude result1; + for (double angle = 0.0; angle < M_PI / 2.0; angle += 0.1) { + unsigned 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/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp new file mode 100644 index 00000000000..c7b61dd96ad --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp @@ -0,0 +1,772 @@ +#pragma once + +#include "pch.h" +#include "CppUnitTest.h" +#include "../SparseQuantumSimulator/SparseSimulator.h" +#include "TestHelpers.hpp" +#include +#include +#include // necessary for string conversions for logging +#include // necessary for string conversions for logging +#include // necessary for string conversions for logging + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Microsoft::Quantum::SPARSESIMULATOR; +using namespace SparseSimulatorTestHelpers; + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + + + + +namespace SparseSimulatorTests +{ + TEST_CLASS(SparseSimulatorTests) + { + + 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 (unsigned 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 (unsigned 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); + } + } + } + + public: + // Tests comparisons of bitstrings + TEST_METHOD(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); + std::wstring_convert> converter; + 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); + Assert::AreEqual(k < m, label1 < label2, converter.from_bytes("Comparing " + std::to_string(k) + " to " + std::to_string(m) + "\n").c_str()); + } + } + } + // Tests that the X gate flips the computational basis states + TEST_METHOD(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_METHOD(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_METHOD(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_METHOD(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_METHOD(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_METHOD(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_METHOD(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_METHOD(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_METHOD(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_METHOD(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_METHOD(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_METHOD(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_METHOD(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_METHOD(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_METHOD(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_METHOD(MTest) { + const logical_qubit_id num_qubits = 32; + const qubit_label_type zero(0); + const int 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; + unsigned long total_tests = 0; + unsigned long 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 = 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_METHOD(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 + Assert::ExpectException([&]() {sim.Assert(basis, qubits, true); sim.update_state(); }); + basis = { Basis::PauliZ, Basis::PauliZ, Basis:: PauliI }; + sim.Assert(basis, qubits, false); + Assert::ExpectException([&]() {sim.Assert(basis, qubits, true); sim.update_state(); }); + basis = { Basis::PauliX, Basis::PauliI, Basis::PauliI }; + Assert::ExpectException([&]() {sim.Assert(basis, qubits, false); sim.update_state(); }); + Assert::ExpectException([&]() {sim.Assert(basis, qubits, true); sim.update_state(); }); + basis = { Basis::PauliY, Basis::PauliI, Basis::PauliI }; + Assert::ExpectException([&]() {sim.Assert(basis, qubits, false); sim.update_state(); }); + Assert::ExpectException([&]() {sim.Assert(basis, qubits, true); sim.update_state(); }); + } + + // Tests an assortment of assertions on GHZ states + TEST_METHOD(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); + Assert::ExpectException([&]() {sim.Assert(basis, qubits, true); }); + sim.Z(0); + sim.Assert(basis, qubits, true); + Assert::ExpectException([&]() {sim.Assert(basis, qubits, false); }); + sim.S(0); + basis = { Basis::PauliY, Basis::PauliY, Basis::PauliY }; + sim.Assert(basis, qubits, false); + Assert::ExpectException([&]() {sim.Assert(basis, qubits, true); }); + sim.Z(0); + sim.Assert(basis, qubits, true); + Assert::ExpectException([&]() {sim.Assert(basis, qubits, false); }); + sim.probe("0"); + } + + // Basic test of quantum teleportation + TEST_METHOD(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"); + Assert::AreEqual((float)cos(test_angle / 2.0), (float)teleported_qubit_0.real()); + Assert::AreEqual((float)0.0, (float)teleported_qubit_0.imag()); + Assert::AreEqual((float)sin(test_angle / 2.0), (float)teleported_qubit_1.real()); + Assert::AreEqual((float)0.0, (float)teleported_qubit_1.imag()); + } + } + + + // Tests that H gates properly cancel when executed + TEST_METHOD(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_METHOD(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 (__int64 i = 0; i < pow(2, 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/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj new file mode 100644 index 00000000000..37fe5ddeac1 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj @@ -0,0 +1,176 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {E1663845-BBA8-41EA-AC31-50A092EEA728} + Win32Proj + SparseSimulatorTests + 10.0 + NativeUnitTestProject + + + + DynamicLibrary + true + v142 + Unicode + false + + + DynamicLibrary + false + v142 + true + Unicode + false + + + DynamicLibrary + true + v142 + Unicode + false + + + DynamicLibrary + false + v142 + true + Unicode + false + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + NotUsing + Level3 + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;%(PreprocessorDefinitions) + true + pch.h + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + NotUsing + Level3 + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + _DEBUG;%(PreprocessorDefinitions) + true + pch.h + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + NotUsing + Level3 + true + true + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;%(PreprocessorDefinitions) + true + pch.h + + + Windows + true + true + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + NotUsing + Level3 + true + true + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + NDEBUG;%(PreprocessorDefinitions) + true + pch.h + + + Windows + true + true + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + + Create + Create + Create + Create + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj.filters b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj.filters new file mode 100644 index 00000000000..5947f4294c2 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Source Files + + + \ No newline at end of file diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.cpp b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.cpp new file mode 100644 index 00000000000..539612fd9fa --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.cpp @@ -0,0 +1,159 @@ +#pragma once + +#include "pch.h" +#include "CppUnitTest.h" +#include "../SparseQuantumSimulator/SparseSimulator.h" +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace Microsoft::Quantum::SPARSESIMULATOR; + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#define TEST_TOLERANCE 0.00000001 + + +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) + { + unsigned 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; } + unsigned 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)); + +#pragma omp parallel for schedule(static) + 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 (unsigned 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) { + if (value1 > value2) { + value1 = std::max(value1 - TEST_TOLERANCE, value2); + } + else { + value1 = std::min(value1 + TEST_TOLERANCE, value2); + } + Assert::AreEqual(value1, value2); + } + + 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/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.hpp b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.hpp new file mode 100644 index 00000000000..433a97413b4 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "pch.h" +#include "CppUnitTest.h" +#include "../SparseQuantumSimulator/SparseSimulator.h" +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +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 + +} \ No newline at end of file diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/pch.cpp b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/pch.cpp new file mode 100644 index 00000000000..64b7eef6d6b --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/pch.h b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/pch.h new file mode 100644 index 00000000000..61302cbcf59 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/pch.h @@ -0,0 +1,13 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +// add headers that you want to pre-compile here + + +#endif //PCH_H diff --git a/src/Simulation/Simulators/SparseSimulator/build.cake b/src/Simulation/Simulators/SparseSimulator/build.cake new file mode 100644 index 00000000000..9b923a0f297 --- /dev/null +++ b/src/Simulation/Simulators/SparseSimulator/build.cake @@ -0,0 +1,72 @@ +#addin nuget:?package=Cake.CMake&version=1.2.0 + +var release_configuration = Argument("configuration", "Release"); +var debug_configuration = Argument("configuration", "Debug"); +var target = Argument("target", "Build"); + +////////////////////////////////////////////////////////////////////// +// TASKS +////////////////////////////////////////////////////////////////////// + +Task("Clean") + .WithCriteria(c => HasArgument("rebuild")) + .Does(() => +{ + CleanDirectory($"./{release_configuration}"); + CleanDirectory($"./{debug_configuration}"); +}); + +Task("CMake" +) .IsDependentOn("Clean") + .Does(() => +{ + CMake(new CMakeSettings { + OutputPath = Directory("Native/build"), + SourcePath = Directory("Native/"), + }); + + CMakeBuild(new CMakeBuildSettings { + BinaryPath = Directory("Native/build"), + Configuration = release_configuration + }); +}); + +Task("Build") + .IsDependentOn("CMake") + .Does(() => +{ + DotNetCoreBuild(".", new DotNetCoreBuildSettings + { + Configuration = release_configuration, + }); +}); + +Task("CMake_debug" +) .IsDependentOn("Clean") + .Does(() => +{ + CMake(new CMakeSettings { + OutputPath = Directory("Native/build"), + SourcePath = Directory("Native/"), + }); + + CMakeBuild(new CMakeBuildSettings { + BinaryPath = Directory("Native/build"), + Configuration = debug_configuration + }); +}); + +Task("Build_debug") + .IsDependentOn("CMake_debug") + .Does(() => +{ + DotNetCoreBuild(".", new DotNetCoreBuildSettings + { + Configuration = debug_configuration, + }); +}); + + +RunTarget(target); +RunTarget(Argument("target", "Build_debug")); + From dc11ee9d70a84136a5217e22018bb5123ed7f7fe Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Wed, 7 Jul 2021 15:13:41 -0700 Subject: [PATCH 02/25] Updated copyright message --- .../Simulators/SparseSimulator/Native/SparseSimulator.h | 3 +++ .../SparseSimulator/Native/basic_quantum_state.hpp | 3 +++ src/Simulation/Simulators/SparseSimulator/Native/capi.cpp | 2 +- src/Simulation/Simulators/SparseSimulator/Native/capi.hpp | 2 +- src/Simulation/Simulators/SparseSimulator/Native/factory.cpp | 2 +- src/Simulation/Simulators/SparseSimulator/Native/factory.hpp | 2 +- src/Simulation/Simulators/SparseSimulator/Native/gates.h | 3 +++ .../Simulators/SparseSimulator/Native/quantum_state.hpp | 3 +++ src/Simulation/Simulators/SparseSimulator/Native/types.h | 3 ++- .../SparseSimulator/SparseSimQSharpTests/Program.qs | 5 ++++- .../Simulators/SparseSimulator/SparseSimulatorCS/Probes.cs | 5 ++++- .../Simulators/SparseSimulator/SparseSimulatorCS/Probes.qs | 5 ++++- .../SparseSimulator/SparseSimulatorCS/Simulator.ApplyAnd.cs | 5 ++++- .../SparseSimulator/SparseSimulatorCS/SparseSimulator.cs | 5 ++++- .../SparseSimulatorTests/CSharpIntegrationTests.cpp | 3 +++ .../SparseSimulatorTests/SparseSimulatorTests.cpp | 3 +++ .../SparseSimulator/SparseSimulatorTests/TestHelpers.cpp | 3 +++ .../SparseSimulator/SparseSimulatorTests/TestHelpers.hpp | 3 +++ .../Simulators/SparseSimulator/SparseSimulatorTests/pch.cpp | 3 +++ .../Simulators/SparseSimulator/SparseSimulatorTests/pch.h | 3 +++ 20 files changed, 56 insertions(+), 10 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h index 809cd8f16a5..cda2f03e17e 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #pragma once #include #include "types.h" diff --git a/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp index 330261881f5..72b9a531e65 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #pragma once #include "types.h" #include "gates.h" diff --git a/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp b/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp index 24bd58358d2..7ff43f5c8f0 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // Wrapper functions for basic C++ functions diff --git a/src/Simulation/Simulators/SparseSimulator/Native/capi.hpp b/src/Simulation/Simulators/SparseSimulator/Native/capi.hpp index 85add5e5b84..3bd34868977 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/capi.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/capi.hpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once diff --git a/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp b/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp index fa42fd3c5fe..c77e59bae59 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // Manages simulators in a vector of pointers to simulators diff --git a/src/Simulation/Simulators/SparseSimulator/Native/factory.hpp b/src/Simulation/Simulators/SparseSimulator/Native/factory.hpp index 52b95f5c64a..1a1ac12c735 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/factory.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/factory.hpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // Manages simulators in a vector of pointers to simulators diff --git a/src/Simulation/Simulators/SparseSimulator/Native/gates.h b/src/Simulation/Simulators/SparseSimulator/Native/gates.h index 5b05870fa21..41591f2d4f0 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/gates.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/gates.h @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #pragma once diff --git a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp index 77f9692b40c..2656666f53b 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #pragma once #include #include diff --git a/src/Simulation/Simulators/SparseSimulator/Native/types.h b/src/Simulation/Simulators/SparseSimulator/Native/types.h index 17116a51bc5..63e3b7c6a81 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/types.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/types.h @@ -1,5 +1,6 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. + #pragma once diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/Program.qs b/src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/Program.qs index 0893c4440b3..24712e556bb 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/Program.qs +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/Program.qs @@ -1,4 +1,7 @@ -namespace Microsoft.Quantum.SparseSimulatorTests { +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.SparseSimulatorTests { open Microsoft.Quantum.Canon; open Microsoft.Quantum.Intrinsic; diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.cs b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.cs index d058eb86c5d..a9248bdf84f 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.cs +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.cs @@ -1,4 +1,7 @@ -// Functions that access the internal wavefunction for diagnostic purposes +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Functions that access the internal wavefunction for diagnostic purposes using Microsoft.Quantum.Simulation; using Microsoft.Quantum.Simulation.Common; diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.qs b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.qs index 8c0f5ee3c6e..94304b5407f 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.qs +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.qs @@ -1,4 +1,7 @@ -namespace Microsoft.Quantum.SparseSimulation { +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.SparseSimulation { open Microsoft.Quantum.Canon; open Microsoft.Quantum.Intrinsic; diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Simulator.ApplyAnd.cs b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Simulator.ApplyAnd.cs index 466d8d6b2a0..8512ef6853b 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Simulator.ApplyAnd.cs +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Simulator.ApplyAnd.cs @@ -1,4 +1,7 @@ -// Overrides the basic AND gate for faster execution +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Overrides the basic AND gate for faster execution using Microsoft.Quantum.Simulation.Core; using System; diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.cs b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.cs index 04ad05ca62f..2478e2d8e26 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.cs +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; using System.Collections.Generic; using Microsoft.Quantum.Simulation.QuantumProcessor; using Microsoft.Quantum.Simulation.Common; diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CSharpIntegrationTests.cpp b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CSharpIntegrationTests.cpp index f94750c84e5..60dfe5fed93 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CSharpIntegrationTests.cpp +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CSharpIntegrationTests.cpp @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #pragma once #include "../SparseQuantumSimulator/SparseSimulator.h" diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp index c7b61dd96ad..bf5d3206635 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #pragma once #include "pch.h" diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.cpp b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.cpp index 539612fd9fa..b92e52aed56 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.cpp +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.cpp @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #pragma once #include "pch.h" diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.hpp b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.hpp index 433a97413b4..5bec3a92001 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.hpp +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.hpp @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #pragma once #include "pch.h" diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/pch.cpp b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/pch.cpp index 64b7eef6d6b..2d641a7e3b9 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/pch.cpp +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/pch.cpp @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + // pch.cpp: source file corresponding to the pre-compiled header #include "pch.h" diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/pch.h b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/pch.h index 61302cbcf59..2cd3c85c965 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/pch.h +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/pch.h @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + // pch.h: This is a precompiled header file. // Files listed below are compiled only once, improving build performance for future builds. // This also affects IntelliSense performance, including code completion and many code browsing features. From 15345ac32c8413dd17c880ea58ebee1d4f7610c9 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Wed, 7 Jul 2021 15:23:51 -0700 Subject: [PATCH 03/25] Formatted readme.md --- src/Simulation/Simulators/SparseSimulator/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/README.md b/src/Simulation/Simulators/SparseSimulator/README.md index 2705828bee0..4107ebb7609 100644 --- a/src/Simulation/Simulators/SparseSimulator/README.md +++ b/src/Simulation/Simulators/SparseSimulator/README.md @@ -3,11 +3,11 @@ This is a an alternative quantum simulator, compatible with Q\# and Microsoft's # Repository Structure -/SparseQuantumSimulator/ -|--/Native/: C++ code for the simulator -|--/SparseSimulatorCS/: C# library for the simulator -|--/SparseSimQSharpTests/: Several Q# tests of the simulator -|--/SparseSimulatorTests/: C++ tests of the simulator +/SparseQuantumSimulator/ +|--/Native/: C++ code for the simulator +|--/SparseSimulatorCS/: C# library for the simulator +|--/SparseSimQSharpTests/: Several Q# tests of the simulator +|--/SparseSimulatorTests/: C++ tests of the simulator # Dependencies - CMake v1.2.0 From 1d5be64014b1709e10f8cfe66145161d6dbac2e7 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Wed, 7 Jul 2021 15:38:35 -0700 Subject: [PATCH 04/25] Formatted readme.md and fixed typo --- .../Simulators/SparseSimulator/README.md | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/README.md b/src/Simulation/Simulators/SparseSimulator/README.md index 4107ebb7609..4b5a42eb3f0 100644 --- a/src/Simulation/Simulators/SparseSimulator/README.md +++ b/src/Simulation/Simulators/SparseSimulator/README.md @@ -75,7 +75,7 @@ To add a gate to the simulator, you will need to add it to: Optionally, you may need to add a Q\# and C\# file to the SparseSimulatorCS folder, to create a Q\# operation to call the gate if it does not already exist. -## Quantum State (quantum_state.hpp and basec_quantum_state.hpp) +## Quantum State (quantum_state.hpp and basic_quantum_state.hpp) This code will modify the actual wavefunction data structure. The code for the `H` gate will be a good template. Typically, such a function will create a new wavefunction object, then iterate through the existing wavefunction to compute new (label, amplitude) pairs, which are inserted into the new wavefunction. Finally, it moves the new data into the old wavefunction object. Since the hash map does not allow modifications while iterating through it, this is the only way to implement most gates. @@ -104,7 +104,8 @@ Here `Id` is an internal variable for the simulator's ID, which must be passed t If you are implementing a gate that Q\# already expects (i.e., it is initialized in the `QuantumProcessorBase` class) then the previous steps will be enough. However, if you want to create an entirely new gate, you will need to create a Q\# operation to call it. "Probes.qs" and "Probes.cs" provide a template for how this code will look. In Q\#, declare the operation `YourNewGate` with code `body intrinsic;`. Then in a separate C\# file, use the following template: -`public partial class YourNewGate +```C# +public partial class YourNewGate { public class Native : YourNewGate { @@ -113,21 +114,24 @@ If you are implementing a gate that Q\# already expects (i.e., it is initialized { sim = m as SparseSimulator; } - public override Func<\#input types from Q\#\#, \#output types to Q\#\#> __Body__ => sim == null ? base.__Body__ : (args) => { + public override Func<'input types from Q#', 'output types to Q#'> __Body__ => sim == null ? base.__Body__ : (args) => { return sim.Your_new_gate(args.Item1, args.Item2,...); }; } -}` +} +``` You will also need to add -`public partial class SparseSimulator : QuantumProcessorDispatcher, IDisposable +```c# +public partial class SparseSimulator : QuantumProcessorDispatcher, IDisposable { - public \# Q\# return type\# Your_new_gate(\#args\#) + public 'Q# return type' Your_new_gate('args') { - return ((SparseSimulatorProcessor)this.QuantumProcessor).Your_new_gate(\#args\#); + return ((SparseSimulatorProcessor)this.QuantumProcessor).Your_new_gate('args'); } } -` +``` + which tells the `SparseSimulator` class to forward the call to its internal `SparseSimulatorProcessor` class. From 42e8fc1ae23d524afef5cb19de16cd83bb812fe8 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Thu, 8 Jul 2021 13:41:16 -0700 Subject: [PATCH 05/25] Removed #pragma once in .cpp files --- .../SparseSimulatorTests/CSharpIntegrationTests.cpp | 2 -- .../SparseSimulatorTests/SparseSimulatorTests.cpp | 2 -- .../SparseSimulator/SparseSimulatorTests/TestHelpers.cpp | 2 -- 3 files changed, 6 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CSharpIntegrationTests.cpp b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CSharpIntegrationTests.cpp index 60dfe5fed93..e58ab774839 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CSharpIntegrationTests.cpp +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CSharpIntegrationTests.cpp @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -#pragma once - #include "../SparseQuantumSimulator/SparseSimulator.h" #include "../SparseQuantumSimulator/capi.hpp" #include "../SparseQuantumSimulator/capi.cpp" // yes really diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp index bf5d3206635..ec306be97dc 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -#pragma once - #include "pch.h" #include "CppUnitTest.h" #include "../SparseQuantumSimulator/SparseSimulator.h" diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.cpp b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.cpp index b92e52aed56..23627d3bcd1 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.cpp +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.cpp @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -#pragma once - #include "pch.h" #include "CppUnitTest.h" #include "../SparseQuantumSimulator/SparseSimulator.h" From 949aa01d0dfb0ba9861f82dbe13c64f9a0fb7f07 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Thu, 8 Jul 2021 14:11:57 -0700 Subject: [PATCH 06/25] Collapsed nested namespace definitions using :: --- .../SparseSimulator/Native/SparseSimulator.h | 1422 ++++++++--------- .../Native/basic_quantum_state.hpp | 85 +- .../SparseSimulator/Native/factory.cpp | 12 +- .../SparseSimulator/Native/factory.hpp | 12 +- .../Simulators/SparseSimulator/Native/gates.h | 536 +++---- .../SparseSimulator/Native/quantum_state.hpp | 11 +- .../Simulators/SparseSimulator/Native/types.h | 52 +- 7 files changed, 1039 insertions(+), 1091 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h index cda2f03e17e..ad60fc4fb2d 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h @@ -23,17 +23,13 @@ using namespace std::literals::complex_literals; -namespace Microsoft -{ -namespace Quantum -{ -namespace SPARSESIMULATOR +namespace Microsoft::Quantum::SPARSESIMULATOR { + #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 @@ -51,799 +47,793 @@ std::shared_ptr expand_wfn_helper(std::shared_ptr max_num_bits / 2) ? std::shared_ptr(new QuantumState(old_sim)): expand_wfn_helper(old_sim, nqubits); } - - - - class SparseSimulator - { - public: - - std::set operations_done; +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; + + _Ry_queue = std::vector(num_qubits, 0); + _Rx_queue = std::vector(num_qubits, 0); + _H_queue = std::vector(num_qubits, 0); + _Rx_angles = std::vector(num_qubits, 0.0); + _Ry_angles = std::vector(num_qubits, 0.0); + + } + + + ~SparseSimulator() { + _execute_queued_ops(); + } + + // 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(unsigned 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) { + size_t 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); - 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; - - _Ry_queue = std::vector(num_qubits, 0); - _Rx_queue = std::vector(num_qubits, 0); - _H_queue = std::vector(num_qubits, 0); - _Rx_angles = std::vector(num_qubits, 0.0); - _Ry_angles = std::vector(num_qubits, 0.0); - + _occupied_qubits.resize(num_qubits, 0); + _Ry_queue.resize(num_qubits, 0); + _Rx_queue.resize(num_qubits, 0); + _H_queue.resize(num_qubits, 0); + _Rx_angles.resize(num_qubits, 0.0); + _Ry_angles.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 + } + - ~SparseSimulator() { - _execute_queued_ops(); - } + // 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); + + if (!_quantum_state->is_qubit_zero(qubit_id)){ + throw std::runtime_error("Released qubit not in zero state"); + } - // Outputs the wavefunction to the console, after - // executing any queued operations - void DumpWavefunction(size_t indent = 0){ - _execute_queued_ops(); - _quantum_state->DumpWavefunction(indent); } + _set_qubit_to_zero(qubit_id); + return true; + } - // 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(unsigned seed = std::mt19937::default_seed){ - _quantum_state->set_random_seed(seed); + void X(logical_qubit_id index) { + // XY = - YX + if (_Ry_queue[index]){ + _Ry_angles[index] *= -1.0; } - - // 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(); + // Rx trivially commutes + if (_H_queue[index]) { + _queued_operations.push_back(operation(OP::Z, index)); + return; } + _queued_operations.push_back(operation(OP::X, index)); + _set_qubit_to_nonzero(index); + } - // 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) { - size_t 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); - _Ry_queue.resize(num_qubits, 0); - _Rx_queue.resize(num_qubits, 0); - _H_queue.resize(num_qubits, 0); - _Rx_angles.resize(num_qubits, 0.0); - _Ry_angles.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); - - if (!_quantum_state->is_qubit_zero(qubit_id)){ - throw std::runtime_error("Released qubit not in zero state"); - } + // 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) { + // 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 (_Ry_queue[controls[0]] || _Rx_queue[controls[0]] || (_H_queue[controls[0]] && !_H_queue[target])){ + _execute_queued_ops(controls, OP::Ry); } - _set_qubit_to_zero(qubit_id); - return true; } - - - void X(logical_qubit_id index) { - // XY = - YX - if (_Ry_queue[index]){ - _Ry_angles[index] *= -1.0; - } - // Rx trivially commutes - if (_H_queue[index]) { - _queued_operations.push_back(operation(OP::Z, index)); - return; - } - _queued_operations.push_back(operation(OP::X, index)); - _set_qubit_to_nonzero(index); + // Ry on the target causes issues + if (_Ry_queue[target]){ + _execute_queued_ops(target, OP::Ry); } + // Rx on the target trivially commutes - - // 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) { - // Check for anything on the controls - if (controls.size() > 1){ - _execute_if(controls); + // An H on the target flips the operation + if (_H_queue[target]){ + // If it is a CNOT and there is also an H on the control, we swap control and target + if (controls.size() == 1 && _H_queue[controls[0]]){ + _queued_operations.push_back(operation(OP::MCX, controls[0], std::vector{target})); + _set_qubit_to_nonzero(controls[0]); } else { - // An H on the control but not the target forces execution - if (_Ry_queue[controls[0]] || _Rx_queue[controls[0]] || (_H_queue[controls[0]] && !_H_queue[target])){ - _execute_queued_ops(controls, OP::Ry); - } - } - // Ry on the target causes issues - if (_Ry_queue[target]){ - _execute_queued_ops(target, OP::Ry); - } - // Rx on the target trivially commutes - - // An H on the target flips the operation - if (_H_queue[target]){ - // If it is a CNOT and there is also an H on the control, we swap control and target - if (controls.size() == 1 && _H_queue[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); - } + _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 AND(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 AdjAND(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 (_Rx_queue[index]){ + _Rx_angles[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) { + _execute_if(controls); + // Commutes with Ry on the target, not Rx + if (_Rx_queue[target]){ + _execute_queued_ops(target, OP::Rx); + } + // YH = -YH, so we add a phase to track this + if (_H_queue[target]){ + // The phase added does not depend on the target + // Thus we use one of the controls as a target + _queued_operations.push_back(operation(OP::MCZ, controls[0], controls)); + } + _queued_operations.push_back(operation(OP::MCY, target, controls)); + _set_qubit_to_nonzero(target); + } - // Same as MCX, but we assert that the target is 0 before execution - void AND(std::vector const& controls, logical_qubit_id target) { - Assert(std::vector{Gates::Basis::PauliZ}, std::vector{target}, 0); - MCX(controls, target); + + void Z(logical_qubit_id index) { + // ZY = -YZ + if (_Ry_queue[index]){ + _Ry_angles[index] *= -1; } - // Same as MCX, but we assert that the target is 0 after execution - void AdjAND(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); + // XZ = -ZX + if (_Rx_queue[index]){ + _Rx_angles[index] *= -1; } - - void Y(logical_qubit_id index) { - // XY = -YX - if (_Rx_queue[index]){ - _Rx_angles[index] *= -1.0; - } - // commutes with H up to phase, so we ignore the H queue - _queued_operations.push_back(operation(OP::Y, index)); + // HZ = XH + if (_H_queue[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 MCY(std::vector const& controls, logical_qubit_id target) { - _execute_if(controls); - // Commutes with Ry on the target, not Rx - if (_Rx_queue[target]){ - _execute_queued_ops(target, OP::Rx); + void MCZ(std::vector const& controls, logical_qubit_id target) { + // 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 (_Ry_queue[control] || _Rx_queue[control]){ + count += 2; } - // YH = -YH, so we add a phase to track this - if (_H_queue[target]){ - // The phase added does not depend on the target - // Thus we use one of the controls as a target - _queued_operations.push_back(operation(OP::MCZ, controls[0], controls)); + if (_H_queue[control]){ + count++; } - _queued_operations.push_back(operation(OP::MCY, target, controls)); - _set_qubit_to_nonzero(target); } - - - void Z(logical_qubit_id index) { - // ZY = -YZ - if (_Ry_queue[index]){ - _Ry_angles[index] *= -1; - } - // XZ = -ZX - if (_Rx_queue[index]){ - _Rx_angles[index] *= -1; - } - // HZ = XH - if (_H_queue[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 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 (_Ry_queue[control] || _Rx_queue[control]){ - count += 2; - } + if (_Ry_queue[target] || _Rx_queue[target]){ + count +=2; + } + if (_H_queue[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 + std::vector new_controls(controls); + for (logical_qubit_id control : controls){ if (_H_queue[control]){ - count++; + std::swap(new_controls[control], target); } } - if (_Ry_queue[target] || _Rx_queue[target]){ - count +=2; - } - if (_H_queue[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 - std::vector new_controls(controls); - for (logical_qubit_id control : controls){ - if (_H_queue[control]){ - std::swap(new_controls[control], target); - } - } - _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 (_Ry_queue[index] || _Ry_queue[index] || _H_queue[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){ - _execute_if(controls); - _execute_if(target); - _queued_operations.push_back(operation(OP::MCPhase, target, controls, phase)); + _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)); + } - 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); + // 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 (_Ry_queue[index] || _Ry_queue[index] || _H_queue[index]){ + _execute_queued_ops(index, OP::Ry); } - + _queued_operations.push_back(operation(OP::Phase, index, phase)); + } - void R1(double const& angle, logical_qubit_id index) { - Phase(amplitude(std::cos(angle), std::sin(angle)), index); - } + void MCPhase(std::vector const& controls, amplitude const& phase, logical_qubit_id target){ + _execute_if(controls); + _execute_if(target); + _queued_operations.push_back(operation(OP::MCPhase, target, controls, phase)); + } - void MCR1(std::vector const& controls, double const& angle, logical_qubit_id target){ - MCPhase(controls, amplitude(std::cos(angle), std::sin(angle)), target); - } + void T(logical_qubit_id index) { + Phase(amplitude(_normalizer_double, _normalizer_double), index); + } - 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 AdjT(logical_qubit_id index) { + Phase(amplitude(_normalizer_double, -_normalizer_double), index); + } + - void MCR1Frac(std::vector const& controls, std::int64_t numerator, std::int64_t power, logical_qubit_id target){ - MCR1(controls, (double)numerator * pow(0.5, power) * M_PI, target); - } + void R1(double const& angle, logical_qubit_id index) { + Phase(amplitude(std::cos(angle), std::sin(angle)), index); + } - void S(logical_qubit_id index) { - Phase(1i, index); - } + void MCR1(std::vector const& controls, double const& angle, logical_qubit_id target){ + MCPhase(controls, amplitude(std::cos(angle), std::sin(angle)), target); + } - void AdjS(logical_qubit_id index) { - Phase(-1i, index); - } + 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){ + MCR1(controls, (double)numerator * pow(0.5, power) * M_PI, target); + } + void S(logical_qubit_id index) { + Phase(1i, index); + } - void R(Gates::Basis b, double phi, logical_qubit_id index) - { - if (b == Gates::Basis::PauliI){ - return; - } + void AdjS(logical_qubit_id index) { + Phase(-1i, index); + } - // 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){ - _Ry_queue[index] = true; - _Ry_angles[index] += phi; - _set_qubit_to_nonzero(index); - return; - } else if (_Ry_queue[index]) { - _execute_queued_ops(index, OP::Ry); - } - - if (b == Gates::Basis::PauliX){ - _Rx_queue[index] = true; - _Rx_angles[index] += phi; - _set_qubit_to_nonzero(index); - return; - } else if (_Rx_queue[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 (_H_queue[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 (b == Gates::Basis::PauliI){ - // Controlled I rotations are equivalent to controlled phase gates - if (controls.size() > 1){ - MCPhase(controls, amplitude(std::cos(phi),std::sin(phi)), controls[0]); - } else { - Phase(amplitude(std::cos(phi),std::sin(phi)), controls[0]); - } - return; - } - _execute_if(controls); - // The target can commute with rotations of the same type - if (_Ry_queue[target] && b != Gates::Basis::PauliY){ - _execute_queued_ops(target, OP::Ry); - } - if (_Rx_queue[target] && b != Gates::Basis::PauliX){ - _execute_queued_ops(target, OP::Rx); - } - if (_H_queue[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 * 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){ - 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 R(Gates::Basis b, double phi, logical_qubit_id index) + { + if (b == Gates::Basis::PauliI){ + return; } - - - void H(logical_qubit_id index) { - // YH = -HY - _Ry_angles[index] *= (_Ry_queue[index] ? -1.0 : 1.0); - // Commuting with Rx creates a phase, but on the wrong side - // So we execute any Rx immediately - if (_Rx_queue[index]){ - _execute_queued_ops(index, OP::Rx); - } - _H_queue[index] = !_H_queue[index]; + // 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){ + _Ry_queue[index] = true; + _Ry_angles[index] += phi; _set_qubit_to_nonzero(index); + return; + } else if (_Ry_queue[index]) { + _execute_queued_ops(index, OP::Ry); } - void MCH(std::vector const& controls, logical_qubit_id target) { - // No commutation on controls - _execute_if(controls); - // No Ry or Rx commutation on target - if (_Ry_queue[target] || _Rx_queue[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); + if (b == Gates::Basis::PauliX){ + _Rx_queue[index] = true; + _Rx_angles[index] += phi; + _set_qubit_to_nonzero(index); + return; + } else if (_Rx_queue[index]){ + _execute_queued_ops(index, OP::Rz); } - - - - 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 - _Ry_queue.swap(_Ry_queue[index_1], _Ry_queue[index_2]); - std::swap(_Ry_angles[index_1], _Ry_angles[index_2]); - _Rx_queue.swap(_Rx_queue[index_1], _Rx_queue[index_2]); - std::swap(_Rx_angles[index_1], _Rx_angles[index_2]); - _H_queue.swap(_H_queue[index_1], _H_queue[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 (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); + // 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 (_H_queue[index]){ + _execute_queued_ops(index, OP::H); } + // Rz(phi) = RI(phi)*R1(-2*phi) + // Global phase from RI is ignored + R1(phi, index); } + } - bool M(logical_qubit_id target) { - // Do nothing if the qubit is known to be 0 - if (!_occupied_qubits[target]){ - return false; - } - // 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 - if (_quantum_state->M(target)){ - _set_qubit_to_nonzero(target); + void MCR (std::vector const& controls, Gates::Basis b, double phi, logical_qubit_id target) { + if (b == Gates::Basis::PauliI){ + // Controlled I rotations are equivalent to controlled phase gates + if (controls.size() > 1){ + MCPhase(controls, amplitude(std::cos(phi),std::sin(phi)), controls[0]); } else { - _set_qubit_to_zero(target); - } - return _occupied_qubits[target]; - - } - - void Reset(logical_qubit_id target) { - if (!_occupied_qubits[target]){ return; } - // If we get a measurement, we take it as soon as we can - _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 (_Rx_queue[qubit] || _Ry_queue[qubit]){ - _execute_queued_ops(qubits, OP::Ry); - } + Phase(amplitude(std::cos(phi),std::sin(phi)), controls[0]); } - bool isAllZ = true; - 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 (_H_queue[qubits[i]]){ - result ^= _H_queue[qubits[i]]; - } - isAllZ = false; - isEmpty = false; - break; - case Gates::Basis::PauliX: - // HX = ZH - if (_H_queue[qubits[i]]){ - axes[i] = Gates::Basis::PauliZ; - } else { - isAllZ = false; - } - isEmpty = false; - break; - case Gates::Basis::PauliZ: - // HZ = XH - if (_H_queue[qubits[i]]){ - axes[i] = Gates::Basis::PauliX; - isAllZ = false; - } - isEmpty = false; - break; - default: - break; - } - } - if (isEmpty) { - return; - } - // Z assertions are like phase gates - // If it's in release mode, it will queue them - // as a phase/permutation gate - // This means if an assert fails, it will fail - // at some future point, not at the point of failure - #if NDEBUG - if (isAllZ) { - _queued_operations.push_back(operation(OP::Assert, qubits, result)); - return; - } - #endif - // X or Y assertions require execution - _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); - } - - - - bool Measure(std::vector const& axes, std::vector const& qubits){ - _execute_queued_ops(qubits, OP::Ry); - bool 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 label) { - _execute_queued_ops(); - return _quantum_state->probe(label); - } - - std::string Sample() { - _execute_queued_ops(); - return _quantum_state->Sample(); + return; } - // 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 qubits, void (*callback)(char*, double, double)) { - _execute_queued_ops(qubits, OP::Ry); - return _quantum_state->dump_qubits(qubits, callback); - } - - // Dumps all the states in superposition via a callback function - void dump_all(logical_qubit_id max_qubit_id, void (*callback)(char*, double, double)) { - _execute_queued_ops(); - _quantum_state->dump_all(max_qubit_id, callback); - } - - // 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 _H_queue; - std::vector _Rx_queue; - std::vector _Ry_queue; + _execute_if(controls); + // The target can commute with rotations of the same type + if (_Ry_queue[target] && b != Gates::Basis::PauliY){ + _execute_queued_ops(target, OP::Ry); + } + if (_Rx_queue[target] && b != Gates::Basis::PauliX){ + _execute_queued_ops(target, OP::Rx); + } + if (_H_queue[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 * 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){ + 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); + } + } - std::vector _Rx_angles; - std::vector _Ry_angles; + + + void H(logical_qubit_id index) { + // YH = -HY + _Ry_angles[index] *= (_Ry_queue[index] ? -1.0 : 1.0); + // Commuting with Rx creates a phase, but on the wrong side + // So we execute any Rx immediately + if (_Rx_queue[index]){ + _execute_queued_ops(index, OP::Rx); + } + _H_queue[index] = !_H_queue[index]; + _set_qubit_to_nonzero(index); + } + + void MCH(std::vector const& controls, logical_qubit_id target) { + // No commutation on controls + _execute_if(controls); + // No Ry or Rx commutation on target + if (_Ry_queue[target] || _Rx_queue[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); + } - // 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; + 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 + _Ry_queue.swap(_Ry_queue[index_1], _Ry_queue[index_2]); + std::swap(_Ry_angles[index_1], _Ry_angles[index_2]); + _Rx_queue.swap(_Rx_queue[index_1], _Rx_queue[index_2]); + std::swap(_Rx_angles[index_1], _Rx_angles[index_2]); + _H_queue.swap(_H_queue[index_1], _H_queue[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 (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); + } + } + + bool M(logical_qubit_id target) { + // Do nothing if the qubit is known to be 0 + if (!_occupied_qubits[target]){ + return false; + } + // 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 + if (_quantum_state->M(target)){ + _set_qubit_to_nonzero(target); + } else { + _set_qubit_to_zero(target); } + return _occupied_qubits[target]; - // 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 (_H_queue[index]){ - _quantum_state->H(index); - _H_queue[index] = false; - } - if (_Rx_queue[index]){ - _quantum_state->R(Gates::Basis::PauliX, _Rx_angles[index], index); - _Rx_angles[index] = 0.0; - _Rx_queue[index] = false; - } - if (_Ry_queue[index]){ - _quantum_state->R(Gates::Basis::PauliY, _Ry_angles[index], index); - _Ry_angles[index] = 0.0; - _Ry_queue[index] = false; + void Reset(logical_qubit_id target) { + if (!_occupied_qubits[target]){ return; } + // If we get a measurement, we take it as soon as we can + _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 (_Rx_queue[qubit] || _Ry_queue[qubit]){ + _execute_queued_ops(qubits, OP::Ry); + } + } + bool isAllZ = true; + 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 (_H_queue[qubits[i]]){ + result ^= _H_queue[qubits[i]]; + } + isAllZ = false; + isEmpty = false; + break; + case Gates::Basis::PauliX: + // HX = ZH + if (_H_queue[qubits[i]]){ + axes[i] = Gates::Basis::PauliZ; + } else { + isAllZ = false; + } + isEmpty = false; + break; + case Gates::Basis::PauliZ: + // HZ = XH + if (_H_queue[qubits[i]]){ + axes[i] = Gates::Basis::PauliX; + isAllZ = false; + } + isEmpty = false; + break; + default: + break; } } - - void _execute_RxH_single_qubit(logical_qubit_id const &index){ - if (_H_queue[index]){ - _quantum_state->H(index); - _H_queue[index] = false; - } - if (_Rx_queue[index]){ - _quantum_state->R(Gates::Basis::PauliX, _Rx_angles[index], index); - _Rx_angles[index] = 0.0; - _Rx_queue[index] = false; - } + if (isEmpty) { + return; } - - void _execute_H_single_qubit(logical_qubit_id const &index){ - if (_H_queue[index]){ - _quantum_state->H(index); - _H_queue[index] = false; + // Z assertions are like phase gates + // If it's in release mode, it will queue them + // as a phase/permutation gate + // This means if an assert fails, it will fail + // at some future point, not at the point of failure + #if NDEBUG + if (isAllZ) { + _queued_operations.push_back(operation(OP::Assert, qubits, result)); + return; } + #endif + // X or Y assertions require execution + _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); + } + + + + bool Measure(std::vector const& axes, std::vector const& qubits){ + _execute_queued_ops(qubits, OP::Ry); + bool 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]); + } } - - // 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(); - } + _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]); + } } - - // 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++){ + return result; + } + + // Returns the amplitude of a given bitstring + amplitude probe(std::string label) { + _execute_queued_ops(); + return _quantum_state->probe(label); + } + + std::string Sample() { + _execute_queued_ops(); + return _quantum_state->Sample(); + } + + // 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 qubits, void (*callback)(char*, double, double)) { + _execute_queued_ops(qubits, OP::Ry); + return _quantum_state->dump_qubits(qubits, callback); + } + + // Dumps all the states in superposition via a callback function + void dump_all(logical_qubit_id max_qubit_id, void (*callback)(char*, double, double)) { + _execute_queued_ops(); + _quantum_state->dump_all(max_qubit_id, callback); + } + + // 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 _H_queue; + std::vector _Rx_queue; + std::vector _Ry_queue; + + std::vector _Rx_angles; + std::vector _Ry_angles; + + // 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 (_H_queue[index]){ + _quantum_state->H(index); + _H_queue[index] = false; + } + if (_Rx_queue[index]){ + _quantum_state->R(Gates::Basis::PauliX, _Rx_angles[index], index); + _Rx_angles[index] = 0.0; + _Rx_queue[index] = false; + } + if (_Ry_queue[index]){ + _quantum_state->R(Gates::Basis::PauliY, _Ry_angles[index], index); + _Ry_angles[index] = 0.0; + _Ry_queue[index] = false; + } + } + + void _execute_RxH_single_qubit(logical_qubit_id const &index){ + if (_H_queue[index]){ + _quantum_state->H(index); + _H_queue[index] = false; + } + if (_Rx_queue[index]){ + _quantum_state->R(Gates::Basis::PauliX, _Rx_angles[index], index); + _Rx_angles[index] = 0.0; + _Rx_queue[index] = false; + } + } + + void _execute_H_single_qubit(logical_qubit_id const &index){ + if (_H_queue[index]){ + _quantum_state->H(index); + _H_queue[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); - } - } - - // 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: + 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 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: + } + break; + case OP::Rx: + for (auto index : indices){ _execute_RxH_single_qubit(index); - break; - case OP::H: + } + break; + case OP::H: + for (auto index : indices){ _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 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; - } + } + 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 (_Ry_queue[target] || _Rx_queue[target] || _H_queue[target]){ - _execute_queued_ops(target, OP::Ry); - } + // 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 (_Ry_queue[target] || _Rx_queue[target] || _H_queue[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 (_Ry_queue[control] || _Rx_queue[control] || _H_queue[control]){ - _execute_queued_ops(controls, OP::Ry); - return; - } + // 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 (_Ry_queue[control] || _Rx_queue[control] || _H_queue[control]){ + _execute_queued_ops(controls, OP::Ry); + return; } } + } - }; - +}; -} // namespace SPARSESIMULATOR -} // namespace Quantum -} // namespace Microsoft +} // namespace Microsoft::Quantum::SPARSESIMULATOR diff --git a/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp index 72b9a531e65..137ed95f170 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp @@ -7,79 +7,70 @@ #include -namespace Microsoft +namespace Microsoft::Quantum::SPARSESIMULATOR { -namespace Quantum -{ -namespace SPARSESIMULATOR -{ - - - // Virtual class for QuantumState - // This is not templated, so it allows SparseSimulator types to avoid templates - class BasicQuantumState - { - public: - - BasicQuantumState() {} +// 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; + BasicQuantumState() {} - virtual void DumpWavefunction(size_t indent = 0) = 0; + virtual logical_qubit_id get_num_qubits() = 0; - virtual void set_random_seed(unsigned seed = std::mt19937::default_seed) = 0; + virtual void DumpWavefunction(size_t indent = 0) = 0; - virtual void set_precision(double new_precision) = 0; + virtual void set_random_seed(unsigned seed = std::mt19937::default_seed) = 0; - virtual float get_load_factor() = 0; + virtual void set_precision(double new_precision) = 0; - virtual void set_load_factor(float new_load_factor) = 0; + virtual float get_load_factor() = 0; - virtual size_t get_wavefunction_size() = 0; + virtual void set_load_factor(float new_load_factor) = 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 size_t get_wavefunction_size() = 0; - virtual bool M(logical_qubit_id) = 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 void Reset(logical_qubit_id) = 0; + virtual bool 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 bool Measure(std::vector const&, std::vector const&) = 0; + virtual void Assert(std::vector const&, std::vector const&, bool) = 0; + virtual double MeasurementProbability(std::vector const&, std::vector const&) = 0; + virtual bool Measure(std::vector const&, std::vector const&) = 0; - virtual amplitude probe(std::string label) = 0; - virtual bool dump_qubits(std::vector qubits, void (*callback)(char*, double, double)) = 0; + virtual amplitude probe(std::string label) = 0; - virtual void dump_all(logical_qubit_id max_qubit_id, void (*callback)(char*, double, double)) = 0; + virtual bool dump_qubits(std::vector qubits, void (*callback)(char*, double, double)) = 0; - virtual void phase_and_permute(std::listconst &) = 0; + virtual void dump_all(logical_qubit_id max_qubit_id, void (*callback)(char*, double, double)) = 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 phase_and_permute(std::listconst &) = 0; - virtual void H(logical_qubit_id index) = 0; - virtual void MCH(std::vector const& controls, logical_qubit_id index) = 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 bool is_qubit_zero(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 universal_wavefunction get_universal_wavefunction() = 0; + virtual bool is_qubit_zero(logical_qubit_id) = 0; - virtual std::function get_rng() = 0; + virtual universal_wavefunction get_universal_wavefunction() = 0; - virtual void complete_threads() = 0; - virtual int get_num_threads() = 0; + virtual std::function get_rng() = 0; - virtual std::string Sample() = 0; - }; + virtual void complete_threads() = 0; + virtual int get_num_threads() = 0; + virtual std::string Sample() = 0; +}; -} // namespace SPARSESIMULATOR -} // namespace Quantum -} // namespace Microsoft +} // namespace Microsoft::Quantum::SPARSESIMULATOR diff --git a/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp b/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp index c77e59bae59..d7c7b08cb88 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp @@ -9,13 +9,9 @@ #include #include - -namespace Microsoft -{ -namespace Quantum -{ -namespace SPARSESIMULATOR +namespace Microsoft::Quantum::SPARSESIMULATOR { + // Ensures exclusive access to _simulators, the vector of simulators std::shared_mutex _mutex; std::vector> _simulators; @@ -63,6 +59,4 @@ std::shared_ptr& getSimulator(unsigned id) return _simulators[id]; } -} // namespace Simulator -} // namespace Quantum -} // namespace Microsoft +} // namespace Microsoft::Quantum::SPARSESIMULATOR diff --git a/src/Simulation/Simulators/SparseSimulator/Native/factory.hpp b/src/Simulation/Simulators/SparseSimulator/Native/factory.hpp index 1a1ac12c735..d624754e43d 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/factory.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/factory.hpp @@ -7,16 +7,12 @@ #include "types.h" #include "SparseSimulator.h" -namespace Microsoft -{ -namespace Quantum -{ -namespace SPARSESIMULATOR +namespace Microsoft::Quantum::SPARSESIMULATOR { + unsigned createSimulator(logical_qubit_id); void destroySimulator(unsigned); std::shared_ptr& getSimulator(unsigned); -} // namespace Simulator -} // namespace Quantum -} // namespace Microsoft + +} // namespace Microsoft::Quantum::SPARSESIMULATOR diff --git a/src/Simulation/Simulators/SparseSimulator/Native/gates.h b/src/Simulation/Simulators/SparseSimulator/Native/gates.h index 41591f2d4f0..6871f2a9e2d 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/gates.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/gates.h @@ -3,285 +3,277 @@ #pragma once - -namespace Microsoft +namespace Microsoft::Quantum::SPARSESIMULATOR { - namespace Quantum - { - namespace SPARSESIMULATOR - { - // Used to track differnet kind of gates, - // mainly for timing summary data - enum 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, - Assert, - 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::Assert: - return "Assert"; - 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 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, + Assert, + PermuteSmall, + PermuteLarge, + Proj, + MCProj, + Allocate, + Release, + NUM_OPS // counts gate types; do not add gates after this! +}; - // 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) {} +// 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::Assert: + return "Assert"; + 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"; + } +} - 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){} +// 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) {} - 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) - {} - bool result; - // Assert - operation(OP gate_type_arg, - std::vector Zs_arg, - bool result_arg - ) : target(0), - gate_type(gate_type_arg), - controls(Zs_arg), - result(result_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){} - // 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) - {} + 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) + {} + bool result; + // Assert + operation(OP gate_type_arg, + std::vector Zs_arg, + bool result_arg + ) : target(0), + gate_type(gate_type_arg), + controls(Zs_arg), + result(result_arg) + {} +}; - std::bitset controls; - condensed_operation(OP gate_type_arg, - logical_qubit_id target_arg, - std::bitset controls_arg - ) : gate_type(gate_type_arg), - target(target_arg), - controls(controls_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) + {} - 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 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 controls_arg, - amplitude phase_arg - ) : gate_type(gate_type_arg), - target(target_arg), - controls(controls_arg), - phase(phase_arg) - {} - bool result; - // Assert - condensed_operation(OP gate_type_arg, - std::bitset Zs_arg, - bool result_arg - ) : target(0), - gate_type(gate_type_arg), - controls(Zs_arg), - result(result_arg){} - }; - - namespace Gates - { - /// a type for runtime basis specification - enum class Basis - { - PauliI = 0, - PauliX = 1, - PauliY = 3, - PauliZ = 2 - }; - + std::bitset controls; + condensed_operation(OP gate_type_arg, + logical_qubit_id target_arg, + std::bitset 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 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 controls_arg, + amplitude phase_arg + ) : gate_type(gate_type_arg), + target(target_arg), + controls(controls_arg), + phase(phase_arg) + {} + bool result; + // Assert + condensed_operation(OP gate_type_arg, + std::bitset Zs_arg, + bool result_arg + ) : target(0), + gate_type(gate_type_arg), + controls(Zs_arg), + result(result_arg){} +}; - } - } - } -} \ No newline at end of file +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/Simulators/SparseSimulator/Native/quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp index 2656666f53b..cf20c16bd41 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp @@ -25,13 +25,8 @@ using namespace std::literals::complex_literals; -namespace Microsoft +namespace Microsoft::Quantum::SPARSESIMULATOR { -namespace Quantum -{ -namespace SPARSESIMULATOR -{ - // power of square root of -1 inline amplitude iExp(int power) @@ -1509,6 +1504,4 @@ class QuantumState : public BasicQuantumState }; -} // namespace SPARSESIMULATOR -} // namespace Quantum -} // namespace Microsoft +} // namespace Microsoft::Quantum::SPARSESIMULATOR diff --git a/src/Simulation/Simulators/SparseSimulator/Native/types.h b/src/Simulation/Simulators/SparseSimulator/Native/types.h index 63e3b7c6a81..7e095af3ad2 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/types.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/types.h @@ -3,7 +3,6 @@ #pragma once - #include #include #include @@ -12,41 +11,34 @@ #include #include -namespace Microsoft +namespace Microsoft::Quantum::SPARSESIMULATOR { - namespace Quantum - { - namespace SPARSESIMULATOR - { - using mutex_type = std::mutex; - using recursive_mutex_type = std::recursive_mutex; - using lock_type = std::lock_guard; - using recursive_lock_type = std::lock_guard; - - #ifndef USE_SINGLE_PRECISION - using RealType = double; - #else - using RealType = float; - #endif +using mutex_type = std::mutex; +using recursive_mutex_type = std::recursive_mutex; +using lock_type = std::lock_guard; +using recursive_lock_type = std::lock_guard; +#ifndef USE_SINGLE_PRECISION + using RealType = double; +#else + using RealType = float; +#endif - // Logical qubit id is visible to the clients and is immutable during the lifetime of the qubit. - using logical_qubit_id = unsigned; +// Logical qubit id is visible to the clients and is immutable during the lifetime of the qubit. +using logical_qubit_id = unsigned; - using amplitude = std::complex; +using amplitude = std::complex; - template - using qubit_label_type = std::bitset; +template +using qubit_label_type = std::bitset; - // Wavefunctions are hash maps of some key (std::bitset or a string) - template - using abstract_wavefunction = ska::bytell_hash_map>; +// Wavefunctions are hash maps of some key (std::bitset or a string) +template +using abstract_wavefunction = ska::bytell_hash_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; +// 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 SPARSESIMULATOR - } // namespace Quantum -} // namespace Microsoft +} // namespace Microsoft::Quantum::SPARSESIMULATOR From 809d9f389a416c1a1a38e4315283832736d59b90 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Fri, 9 Jul 2021 18:04:22 -0700 Subject: [PATCH 07/25] Replaced defines with constexpr --- .../Simulators/SparseSimulator/Native/SparseSimulator.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h index ad60fc4fb2d..0287fafa211 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h @@ -17,15 +17,14 @@ #include - -#define MAX_QUBITS 1024 -#define MIN_QUBITS 64 - 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 From fe83d37a2b580b5406d0e83136617f35d09d3562 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Fri, 9 Jul 2021 18:23:20 -0700 Subject: [PATCH 08/25] Replaced tabs with spaces --- .../SparseSimulator/Native/SparseSimulator.h | 11 +- .../Native/basic_quantum_state.hpp | 60 +- .../SparseSimulator/Native/quantum_state.hpp | 2882 ++++++++--------- .../Simulators/SparseSimulator/Native/types.h | 4 +- 4 files changed, 1479 insertions(+), 1478 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h index 0287fafa211..957d3de16e8 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h @@ -2,6 +2,7 @@ // Licensed under the MIT License. #pragma once + #include #include "types.h" #include "gates.h" @@ -33,17 +34,17 @@ constexpr logical_qubit_id MIN_QUBITS = 64; // 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())); + 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); + return (nqubits > max_num_bits / 2) ? std::shared_ptr(new QuantumState(old_sim)): expand_wfn_helper(old_sim, nqubits); } class SparseSimulator diff --git a/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp index 137ed95f170..38d41f4514c 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp @@ -16,61 +16,61 @@ class BasicQuantumState { public: - BasicQuantumState() {} + BasicQuantumState() {} - virtual logical_qubit_id get_num_qubits() = 0; + virtual logical_qubit_id get_num_qubits() = 0; - virtual void DumpWavefunction(size_t indent = 0) = 0; + virtual void DumpWavefunction(size_t indent = 0) = 0; - virtual void set_random_seed(unsigned seed = std::mt19937::default_seed) = 0; + virtual void set_random_seed(unsigned seed = std::mt19937::default_seed) = 0; - virtual void set_precision(double new_precision) = 0; + virtual void set_precision(double new_precision) = 0; - virtual float get_load_factor() = 0; + virtual float get_load_factor() = 0; - virtual void set_load_factor(float new_load_factor) = 0; + virtual void set_load_factor(float new_load_factor) = 0; - virtual size_t get_wavefunction_size() = 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 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 bool M(logical_qubit_id) = 0; + virtual bool M(logical_qubit_id) = 0; - virtual void Reset(logical_qubit_id) = 0; + virtual void Reset(logical_qubit_id) = 0; - + - virtual void Assert(std::vector const&, std::vector const&, bool) = 0; + virtual void Assert(std::vector const&, std::vector const&, bool) = 0; - virtual double MeasurementProbability(std::vector const&, std::vector const&) = 0; - virtual bool Measure(std::vector const&, std::vector const&) = 0; + virtual double MeasurementProbability(std::vector const&, std::vector const&) = 0; + virtual bool Measure(std::vector const&, std::vector const&) = 0; - virtual amplitude probe(std::string label) = 0; + virtual amplitude probe(std::string label) = 0; - virtual bool dump_qubits(std::vector qubits, void (*callback)(char*, double, double)) = 0; + virtual bool dump_qubits(std::vector qubits, void (*callback)(char*, double, double)) = 0; - virtual void dump_all(logical_qubit_id max_qubit_id, void (*callback)(char*, double, double)) = 0; + virtual void dump_all(logical_qubit_id max_qubit_id, void (*callback)(char*, double, double)) = 0; - virtual void phase_and_permute(std::listconst &) = 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 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 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 bool is_qubit_zero(logical_qubit_id) = 0; - virtual universal_wavefunction get_universal_wavefunction() = 0; + virtual universal_wavefunction get_universal_wavefunction() = 0; - virtual std::function get_rng() = 0; + virtual std::function get_rng() = 0; - virtual void complete_threads() = 0; - virtual int get_num_threads() = 0; + virtual void complete_threads() = 0; + virtual int get_num_threads() = 0; - virtual std::string Sample() = 0; + virtual std::string Sample() = 0; }; } // namespace Microsoft::Quantum::SPARSESIMULATOR diff --git a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp index cf20c16bd41..abccb97a00e 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp @@ -19,7 +19,7 @@ #include #ifdef _OPENMP - #include + #include #endif @@ -32,1474 +32,1474 @@ namespace Microsoft::Quantum::SPARSESIMULATOR 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; + 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 poppar(std::bitset bitstring){ - return bitstring.count() % 2; + 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(); + 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 = std::bitset(); - for (logical_qubit_id index : indices) { - mask.set(index); - } - return mask; + std::bitset mask = std::bitset(); + 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); }; - - _initialize_threads(); - } - - ~QuantumState() { - complete_threads(); - } - - // 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); - } - // Create local threads for this state - _initialize_threads(); - } - - 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){ - std::cout << spacing << " " << label.to_string() << ": "; - std::cout << val.real(); - std::cout << (val.imag() < 0 ? " - " : " + ") << std::abs(val.imag()) << "i\n"; - }; - _DumpWavefunction_base(wfn, line_dump); - std::cout << spacing << "--end wavefunction\n"; - } - - - void set_random_seed(unsigned 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; - _precision_squared = _precision *_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_squared ){ - if (std::norm(id_coeff) > _rotation_precision_squared){ - // 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 *= (poppar(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 (!poppar(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 (poppar(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 * (poppar(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 = poppar(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_squared) { - 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_squared) { - 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_squared ){ - if (std::norm(id_coeff) > _rotation_precision_squared){ - // 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 *= (poppar(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 (!poppar(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 (poppar(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 * (poppar(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 = poppar(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_squared) { - 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_squared) { - 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); - } - } - - - bool M(logical_qubit_id target) { - qubit_label flip = qubit_label(); - flip.set(target); - - bool result = _qubit_data.begin()->first[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 - 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); - - return result; - } - - void Reset(logical_qubit_id target) { - qubit_label flip = qubit_label(0); - flip.set(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) { - if (std::norm(_qubit_data.find(current_state->first ^ XYs)->second - current_state->second * (poppar(current_state->first & YZs) ? -phaseShift : phaseShift)) > _precision_squared) { - qubit_label label = current_state->first; - amplitude val = current_state->second; - std::cout << "Problematic state: " << label << "\n"; - std::cout << "Expected " << val * (poppar(label & YZs) ? -phaseShift : phaseShift); - std::cout << ", got " << _qubit_data.find(label ^ XYs)->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)) * (poppar(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(); - } - - bool 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; - 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 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 label) { - qubit_label bit_label = qubit_label(label); - return probe(bit_label); - } - - // 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 qubits, void (*callback)(char*, double, double)) { - // 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){ - std::string label_string(qubits.size(), '0'); - for (size_t i=0; i < qubits.size(); i++){ - label_string[i] = label[qubits[i]] ? '1' : '0'; - } - callback(const_cast(label_string.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, void (*callback)(char*, double, double)) { - _DumpWavefunction_base(_qubit_data, [max_qubit_id, callback](qubit_label label, amplitude val){ - callback(const_cast(label.to_string().substr(num_qubits - 1 - max_qubit_id, max_qubit_id + 1).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 - _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; - case OP::Assert: - _operation_vector.push_back(internal_operation(op.gate_type, _get_mask(op.controls), op.result)); - break; - default: - throw std::runtime_error("Unsupported operation"); - break; - } - } - - _new_qubit_data = make_wavefunction(); - - // Threading introduces a lot of overhead so it - // is only worthwhile for large instances - // 64 and 4096 are empirical values - if (_operation_vector.size() < 64 || _qubit_data.size() < 4096){ // small; do not thread - // 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; - case OP::Assert: - if (poppar(label & op.controls) != op.result && std::norm(val) > _precision_squared){ - std::cout << "Problematic state: " << label << "\n"; - std::cout << "Amplitude: " << val << "\n"; - std::cout << "Wavefunction size: " << _qubit_data.size() << "\n"; - throw std::runtime_error("Assert failed"); - } - 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); - _new_qubit_data.clear(); - _operation_vector.clear(); - } else { // Large enough to multi-thread - #ifndef DOMP_GE_V3 // OMP version is too low; uses condition variables - // Lock the mutex so the threads stay asleep during prep - std::unique_lock state_lock(_state_mtx); - _new_qubit_data = wavefunction(_qubit_data.size()); - // jump_size gives a rough guess for how many states each thread should process - // to minimize contention for current_state - _jump_size = std::max((size_t)1 , (size_t)(_qubit_data.size() / (8*_thread_pool.size()))); - // Set the _current state (this allows the threads to wake up) - _current_state = _qubit_data.begin(); - - // Wake up all threads - cv.notify_all(); - // Wait for the number of running threads to be 0 - // This will spuriously wake many times - cv.wait(state_lock, [&](){ - return _current_state == _qubit_data.end() && _running_threads == 0; - }); - // Here all threads are finished - #else - #pragma omp parallel - { - _jump_size = std::max((size_t)1 , (size_t)(_qubit_data.size() / (8*omp_get_num_threads()))); - #pragma omp single - { - auto internal_state = _qubit_data.begin(); - auto internal_end = _qubit_data.end(); - for (auto current_state = _qubit_data.begin(); current_state != _qubit_data.end();){ - internal_state = current_state; - current_state.jump_forward(_jump_size); - internal_end = current_state; - #pragma omp task firstprivate(internal_state) firstprivate(internal_end) - { - qubit_label label; - amplitude val; - for (; internal_state != internal_end; ++internal_state){ - label = internal_state->first; - val = internal_state->second; - 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; - case OP::Assert: - if (poppar(label & op.controls) != op.result && std::norm(val) > _precision_squared){ - std::cout << "Problematic state: " << label << "\n"; - std::cout << "Amplitude: " << val << "\n"; - std::cout << "Wavefunction size: " << _qubit_data.size() << "\n"; - throw std::runtime_error("Assert failed"); - } - break; - default: - throw std::runtime_error("Unsupported operation"); - break; - } - } - #pragma omp critical - { - _new_qubit_data.emplace(label, val); - } - } - } - - } - } - #pragma omp barrier - } - #endif - _qubit_data = std::move(_new_qubit_data); - _operation_vector.clear(); - _new_qubit_data.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 = amplitude(std::cos(phi / 2.0), -std::sin(phi / 2.0)); - amplitude exp_1 = amplitude(std::cos(phi / 2.0), std::sin(phi / 2.0)); - 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 = -std::sin(phi / 2.0) * (b == Gates::Basis::PauliY ? 1 : 1i); - if (std::norm(M00) < _rotation_precision_squared){ - // 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_squared){ - // just an identity - return; - } - - amplitude M10 = M01 * amplitude(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_squared ) { - 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_squared) { - 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 = amplitude(std::cos(phi / 2.0), -std::sin(phi / 2.0)); - amplitude exp_1 = amplitude(std::cos(phi / 2.0), std::sin(phi / 2.0)); - 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(phi / 2.0); - amplitude M01 = -std::sin(phi / 2.0) * (b == Gates::Basis::PauliY ? 1 : 1i); - amplitude M10 = (b == Gates::Basis::PauliY ? -1.0 : 1.0) * M01; - - if (std::norm(M00) < _rotation_precision_squared){ - // 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/M01; - phase_and_permute(std::list{ - operation(OP::MCPhase, controls[0], controls, phase), - operation(OP::MCY, target, controls) - }); - } else { - amplitude phase = 1.0/M01; - phase_and_permute(std::list{ - operation(OP::MCPhase, controls[0], controls, phase), - operation(OP::MCY, target, controls) - }); - } - return; - } else if (std::norm(M01) < _rotation_precision_squared){ - // This is equivalent to a multi-controlled Z if the rotation is -1 - if (std::norm(M01 + 1.0) < _rotation_precision_squared){ - phase_and_permute(std::list{operation(OP::MCZ, controls[0], controls)}); - } - 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_squared) { - 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_squared) { - new_qubit_data.emplace(current_state->first | flip, 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 _qubut_date - 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_squared) { - 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_squared) { - 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_squared) { - 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_squared) { - 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_squared) { - return false; - } - } - return true; - } - - // 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; - } + // 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); }; + + _initialize_threads(); + } + + ~QuantumState() { + complete_threads(); + } + + // 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); + } + // Create local threads for this state + _initialize_threads(); + } + + 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){ + std::cout << spacing << " " << label.to_string() << ": "; + std::cout << val.real(); + std::cout << (val.imag() < 0 ? " - " : " + ") << std::abs(val.imag()) << "i\n"; + }; + _DumpWavefunction_base(wfn, line_dump); + std::cout << spacing << "--end wavefunction\n"; + } + + + void set_random_seed(unsigned 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; + _precision_squared = _precision *_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_squared ){ + if (std::norm(id_coeff) > _rotation_precision_squared){ + // 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 *= (poppar(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 (!poppar(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 (poppar(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 * (poppar(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 = poppar(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_squared) { + 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_squared) { + 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_squared ){ + if (std::norm(id_coeff) > _rotation_precision_squared){ + // 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 *= (poppar(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 (!poppar(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 (poppar(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 * (poppar(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 = poppar(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_squared) { + 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_squared) { + 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); + } + } + + + bool M(logical_qubit_id target) { + qubit_label flip = qubit_label(); + flip.set(target); + + bool result = _qubit_data.begin()->first[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 + 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); + + return result; + } + + void Reset(logical_qubit_id target) { + qubit_label flip = qubit_label(0); + flip.set(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"); + } + } - // Returns the rng from this simulator - std::function get_rng() { return _rng; } - - // Finishes the threads which wait for the queue - void complete_threads() { - in_use=false; - // If this spuriously wakes up threads, - // they will still end because in_use is false - _current_state = _qubit_data.begin(); - cv.notify_all(); - - for (auto &thread : _thread_pool){ - thread.join(); - } - } - - int get_num_threads() { - #ifndef DOMP_GE_V3 - return static_cast(_thread_pool.size()); - #else - int threads=0; - #pragma omp parallel - { - #pragma omp master - threads = omp_get_num_threads(); - } - return threads; - #endif - } + 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) { + if (std::norm(_qubit_data.find(current_state->first ^ XYs)->second - current_state->second * (poppar(current_state->first & YZs) ? -phaseShift : phaseShift)) > _precision_squared) { + qubit_label label = current_state->first; + amplitude val = current_state->second; + std::cout << "Problematic state: " << label << "\n"; + std::cout << "Expected " << val * (poppar(label & YZs) ? -phaseShift : phaseShift); + std::cout << ", got " << _qubit_data.find(label ^ XYs)->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)) * (poppar(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(); + } + + bool 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; + 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 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 label) { + qubit_label bit_label = qubit_label(label); + return probe(bit_label); + } + + // 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 qubits, void (*callback)(char*, double, double)) { + // 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){ + std::string label_string(qubits.size(), '0'); + for (size_t i=0; i < qubits.size(); i++){ + label_string[i] = label[qubits[i]] ? '1' : '0'; + } + callback(const_cast(label_string.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, void (*callback)(char*, double, double)) { + _DumpWavefunction_base(_qubit_data, [max_qubit_id, callback](qubit_label label, amplitude val){ + callback(const_cast(label.to_string().substr(num_qubits - 1 - max_qubit_id, max_qubit_id + 1).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 + _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; + case OP::Assert: + _operation_vector.push_back(internal_operation(op.gate_type, _get_mask(op.controls), op.result)); + break; + default: + throw std::runtime_error("Unsupported operation"); + break; + } + } + + _new_qubit_data = make_wavefunction(); + + // Threading introduces a lot of overhead so it + // is only worthwhile for large instances + // 64 and 4096 are empirical values + if (_operation_vector.size() < 64 || _qubit_data.size() < 4096){ // small; do not thread + // 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; + case OP::Assert: + if (poppar(label & op.controls) != op.result && std::norm(val) > _precision_squared){ + std::cout << "Problematic state: " << label << "\n"; + std::cout << "Amplitude: " << val << "\n"; + std::cout << "Wavefunction size: " << _qubit_data.size() << "\n"; + throw std::runtime_error("Assert failed"); + } + 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); + _new_qubit_data.clear(); + _operation_vector.clear(); + } else { // Large enough to multi-thread + #ifndef DOMP_GE_V3 // OMP version is too low; uses condition variables + // Lock the mutex so the threads stay asleep during prep + std::unique_lock state_lock(_state_mtx); + _new_qubit_data = wavefunction(_qubit_data.size()); + // jump_size gives a rough guess for how many states each thread should process + // to minimize contention for current_state + _jump_size = std::max((size_t)1 , (size_t)(_qubit_data.size() / (8*_thread_pool.size()))); + // Set the _current state (this allows the threads to wake up) + _current_state = _qubit_data.begin(); + + // Wake up all threads + cv.notify_all(); + // Wait for the number of running threads to be 0 + // This will spuriously wake many times + cv.wait(state_lock, [&](){ + return _current_state == _qubit_data.end() && _running_threads == 0; + }); + // Here all threads are finished + #else + #pragma omp parallel + { + _jump_size = std::max((size_t)1 , (size_t)(_qubit_data.size() / (8*omp_get_num_threads()))); + #pragma omp single + { + auto internal_state = _qubit_data.begin(); + auto internal_end = _qubit_data.end(); + for (auto current_state = _qubit_data.begin(); current_state != _qubit_data.end();){ + internal_state = current_state; + current_state.jump_forward(_jump_size); + internal_end = current_state; + #pragma omp task firstprivate(internal_state) firstprivate(internal_end) + { + qubit_label label; + amplitude val; + for (; internal_state != internal_end; ++internal_state){ + label = internal_state->first; + val = internal_state->second; + 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; + case OP::Assert: + if (poppar(label & op.controls) != op.result && std::norm(val) > _precision_squared){ + std::cout << "Problematic state: " << label << "\n"; + std::cout << "Amplitude: " << val << "\n"; + std::cout << "Wavefunction size: " << _qubit_data.size() << "\n"; + throw std::runtime_error("Assert failed"); + } + break; + default: + throw std::runtime_error("Unsupported operation"); + break; + } + } + #pragma omp critical + { + _new_qubit_data.emplace(label, val); + } + } + } + + } + } + #pragma omp barrier + } + #endif + _qubit_data = std::move(_new_qubit_data); + _operation_vector.clear(); + _new_qubit_data.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 = amplitude(std::cos(phi / 2.0), -std::sin(phi / 2.0)); + amplitude exp_1 = amplitude(std::cos(phi / 2.0), std::sin(phi / 2.0)); + 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 = -std::sin(phi / 2.0) * (b == Gates::Basis::PauliY ? 1 : 1i); + if (std::norm(M00) < _rotation_precision_squared){ + // 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_squared){ + // just an identity + return; + } + + amplitude M10 = M01 * amplitude(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_squared ) { + 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_squared) { + 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 = amplitude(std::cos(phi / 2.0), -std::sin(phi / 2.0)); + amplitude exp_1 = amplitude(std::cos(phi / 2.0), std::sin(phi / 2.0)); + 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(phi / 2.0); + amplitude M01 = -std::sin(phi / 2.0) * (b == Gates::Basis::PauliY ? 1 : 1i); + amplitude M10 = (b == Gates::Basis::PauliY ? -1.0 : 1.0) * M01; + + if (std::norm(M00) < _rotation_precision_squared){ + // 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/M01; + phase_and_permute(std::list{ + operation(OP::MCPhase, controls[0], controls, phase), + operation(OP::MCY, target, controls) + }); + } else { + amplitude phase = 1.0/M01; + phase_and_permute(std::list{ + operation(OP::MCPhase, controls[0], controls, phase), + operation(OP::MCY, target, controls) + }); + } + return; + } else if (std::norm(M01) < _rotation_precision_squared){ + // This is equivalent to a multi-controlled Z if the rotation is -1 + if (std::norm(M01 + 1.0) < _rotation_precision_squared){ + phase_and_permute(std::list{operation(OP::MCZ, controls[0], controls)}); + } + 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_squared) { + 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_squared) { + new_qubit_data.emplace(current_state->first | flip, 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 _qubut_date + 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_squared) { + 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_squared) { + 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_squared) { + 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_squared) { + 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_squared) { + return false; + } + } + return true; + } + + // 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; } + + // Finishes the threads which wait for the queue + void complete_threads() { + in_use=false; + // If this spuriously wakes up threads, + // they will still end because in_use is false + _current_state = _qubit_data.begin(); + cv.notify_all(); + + for (auto &thread : _thread_pool){ + thread.join(); + } + } + + int get_num_threads() { + #ifndef DOMP_GE_V3 + return static_cast(_thread_pool.size()); + #else + int threads=0; + #pragma omp parallel + { + #pragma omp master + threads = omp_get_num_threads(); + } + return threads; + #endif + } 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-5; - // Threshold at which something is zero when - // deciding whether to add it into the superposition - double _rotation_precision = 1e-6; - // Often we compare to norms, so the precision must be squared - double _precision_squared = _precision * _precision; - double _rotation_precision_squared = _rotation_precision*_rotation_precision; - - // Normalizer for H and T gates (1/sqrt(2) as an amplitude) - const amplitude _normalizer = amplitude(1.0, 0.0) / std::sqrt(2.0); - - // The default for bytell_hash_map - // 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); - } - - //********* Member variables for multithreading permutations ******// - // Shared iterator through the wavefunction - // Should point to _qubit_data.end() unless in use - decltype(_qubit_data.begin()) _current_state; - // Vector of waiting threads - std::vector _thread_pool; - // Condition variable, locked on _state_mtx - std::condition_variable cv; - // Mutex to prevent concurrent access to _current_state - std::mutex _state_mtx; - // Mutex to prevent concurrent access to _new_qubit_data - std::mutex _wfn_mtx; - // Used as a flag to decide when the thread pool is necessary, - // i.e., it is only set to false when the simulator is being destroyed - std::atomic in_use = true; - // Counts running threads, so controlling thread knows when the permutation is finished - std::atomic _running_threads = 0; - // A vector of operations that all threads point to to read which operations to apply - std::vector _operation_vector; - // New wavefunction which will replace the old one - wavefunction _new_qubit_data; - // The number of elements of the hash map that each thread will handle in one iteration - size_t _jump_size; - // Max number of threads - int max_num_threads = std::thread::hardware_concurrency(); - - // Called on creation, starts the thread pool and waits for all threads to - // finish initialization tasks - void _initialize_threads() { - // Unnecessary if OpenMP Is available - #ifndef DOMP_GE_V3 - // Prevents spurious wake-ups - _current_state = _qubit_data.end(); - _thread_pool = std::vector(); - #ifndef _OPENMP - int num_threads = std::thread::hardware_concurrency(); - #else - int num_threads = 1; - #pragma omp parallel - { - #pragma omp single - num_threads = omp_get_num_threads(); - } - #endif - // Lock the state, so that all the new threads get stopped during their execution - std::unique_lock state_lock(_state_mtx); - for (int i = 0; i < num_threads; i++){ - // Each running thread will decrement this just before it waits on the condition variable - ++_running_threads; - _thread_pool.push_back(std::thread([this](){this->_wait_to_permute();})); - } - // Waits until all the running threads have finished initializing - // This ensures the simulation does not actually start - // until the thread pool has finished initializing - cv.wait(state_lock, [&](){return _running_threads==0;}); - #endif - } - - // Function that all threads in _thread_pool run - // Waits on the condition variable, then applies _operation_vector - // to the qubit data - void _wait_to_permute(){ - // Prep local variables - auto local_state = _qubit_data.begin(); - auto local_end = _qubit_data.end(); - // Initialize a wfn lock to avoid recreating this object in every loop - std::unique_lock wfn_lock(_wfn_mtx); - wfn_lock.unlock(); - // First lock the state so we can wait on it - std::unique_lock state_lock(_state_mtx); - // Set the _current_state to ensure it waits - _current_state = _qubit_data.end(); - // Reduce number of running threads - _running_threads--; - // Notify all (i.e., the constructor thread) - // which will wait until the lock is released to check the number of running threads - cv.notify_all(); - // Wait until we set the current state to the start of the wavefunction - cv.wait(state_lock, [&](){ - return _current_state != _qubit_data.end(); - }); - - // Loop continuously - while (in_use){ - ++_running_threads; - // Loop through the wavefunction - while (_current_state != _qubit_data.end()){ - // Update local variables - local_state = _current_state; - _current_state.jump_forward(_jump_size); - local_end = _current_state; - // Unlock state to allow other threads to modify their state - state_lock.unlock(); - // Loop through the chunk that this thread has taken - for (; local_state != local_end; ++local_state){ - qubit_label label = local_state->first; - amplitude val = local_state->second; - - for (auto op : _operation_vector){ - 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; - case OP::Assert: - if (poppar(label & op.controls) != op.result && std::norm(val) > _precision_squared){ - std::cout << "Problematic state: " << label << "\n"; - std::cout << "Amplitude: " << val << "\n"; - std::cout << "Wavefunction size: " << _qubit_data.size() << "\n"; - throw std::runtime_error("Assert failed"); - } - break; - default: - throw std::runtime_error("Unsupported operation"); - break; - } - } - - wfn_lock.lock(); - _new_qubit_data.emplace(label, val); - wfn_lock.unlock(); - } - // Lock before checking and modifying the current state - state_lock.lock(); - } - // This thread has finished iterating through the wavefunction - --_running_threads; - // Notify all other threads (i.e., the controlling thread) - // if this is the last thread to finish - // The check on _running_threads avoids spurious wake-ups to - // the controlling thread, but takes a bit of time as _running_threads - // is atomic - if (_running_threads ==0) - cv.notify_all(); - // Wait on the state lock before repeating the loop - // The wait unlocks state_lock - cv.wait(state_lock, [&](){return _current_state != _qubit_data.end();}); - } - } - - - // 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. - // This means the coefficients of wfn1 are all of the form (c_x/c_b); - // Thus, the norm of wfn1 will be 1/|c_b|^2; thus the norm of wfn2 is 1/|d_b|^2 = |c_b|^2/|a_bb|^2 - // So we iterate through the smaller wavefunction, to get the normalizing constant, - // then normalize both + // 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-5; + // Threshold at which something is zero when + // deciding whether to add it into the superposition + double _rotation_precision = 1e-6; + // Often we compare to norms, so the precision must be squared + double _precision_squared = _precision * _precision; + double _rotation_precision_squared = _rotation_precision*_rotation_precision; + + // Normalizer for H and T gates (1/sqrt(2) as an amplitude) + const amplitude _normalizer = amplitude(1.0, 0.0) / std::sqrt(2.0); + + // The default for bytell_hash_map + // 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); + } + + //********* Member variables for multithreading permutations ******// + // Shared iterator through the wavefunction + // Should point to _qubit_data.end() unless in use + decltype(_qubit_data.begin()) _current_state; + // Vector of waiting threads + std::vector _thread_pool; + // Condition variable, locked on _state_mtx + std::condition_variable cv; + // Mutex to prevent concurrent access to _current_state + std::mutex _state_mtx; + // Mutex to prevent concurrent access to _new_qubit_data + std::mutex _wfn_mtx; + // Used as a flag to decide when the thread pool is necessary, + // i.e., it is only set to false when the simulator is being destroyed + std::atomic in_use = true; + // Counts running threads, so controlling thread knows when the permutation is finished + std::atomic _running_threads = 0; + // A vector of operations that all threads point to to read which operations to apply + std::vector _operation_vector; + // New wavefunction which will replace the old one + wavefunction _new_qubit_data; + // The number of elements of the hash map that each thread will handle in one iteration + size_t _jump_size; + // Max number of threads + int max_num_threads = std::thread::hardware_concurrency(); + + // Called on creation, starts the thread pool and waits for all threads to + // finish initialization tasks + void _initialize_threads() { + // Unnecessary if OpenMP Is available + #ifndef DOMP_GE_V3 + // Prevents spurious wake-ups + _current_state = _qubit_data.end(); + _thread_pool = std::vector(); + #ifndef _OPENMP + int num_threads = std::thread::hardware_concurrency(); + #else + int num_threads = 1; + #pragma omp parallel + { + #pragma omp single + num_threads = omp_get_num_threads(); + } + #endif + // Lock the state, so that all the new threads get stopped during their execution + std::unique_lock state_lock(_state_mtx); + for (int i = 0; i < num_threads; i++){ + // Each running thread will decrement this just before it waits on the condition variable + ++_running_threads; + _thread_pool.push_back(std::thread([this](){this->_wait_to_permute();})); + } + // Waits until all the running threads have finished initializing + // This ensures the simulation does not actually start + // until the thread pool has finished initializing + cv.wait(state_lock, [&](){return _running_threads==0;}); + #endif + } + + // Function that all threads in _thread_pool run + // Waits on the condition variable, then applies _operation_vector + // to the qubit data + void _wait_to_permute(){ + // Prep local variables + auto local_state = _qubit_data.begin(); + auto local_end = _qubit_data.end(); + // Initialize a wfn lock to avoid recreating this object in every loop + std::unique_lock wfn_lock(_wfn_mtx); + wfn_lock.unlock(); + // First lock the state so we can wait on it + std::unique_lock state_lock(_state_mtx); + // Set the _current_state to ensure it waits + _current_state = _qubit_data.end(); + // Reduce number of running threads + _running_threads--; + // Notify all (i.e., the constructor thread) + // which will wait until the lock is released to check the number of running threads + cv.notify_all(); + // Wait until we set the current state to the start of the wavefunction + cv.wait(state_lock, [&](){ + return _current_state != _qubit_data.end(); + }); + + // Loop continuously + while (in_use){ + ++_running_threads; + // Loop through the wavefunction + while (_current_state != _qubit_data.end()){ + // Update local variables + local_state = _current_state; + _current_state.jump_forward(_jump_size); + local_end = _current_state; + // Unlock state to allow other threads to modify their state + state_lock.unlock(); + // Loop through the chunk that this thread has taken + for (; local_state != local_end; ++local_state){ + qubit_label label = local_state->first; + amplitude val = local_state->second; + + for (auto op : _operation_vector){ + 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; + case OP::Assert: + if (poppar(label & op.controls) != op.result && std::norm(val) > _precision_squared){ + std::cout << "Problematic state: " << label << "\n"; + std::cout << "Amplitude: " << val << "\n"; + std::cout << "Wavefunction size: " << _qubit_data.size() << "\n"; + throw std::runtime_error("Assert failed"); + } + break; + default: + throw std::runtime_error("Unsupported operation"); + break; + } + } + + wfn_lock.lock(); + _new_qubit_data.emplace(label, val); + wfn_lock.unlock(); + } + // Lock before checking and modifying the current state + state_lock.lock(); + } + // This thread has finished iterating through the wavefunction + --_running_threads; + // Notify all other threads (i.e., the controlling thread) + // if this is the last thread to finish + // The check on _running_threads avoids spurious wake-ups to + // the controlling thread, but takes a bit of time as _running_threads + // is atomic + if (_running_threads ==0) + cv.notify_all(); + // Wait on the state lock before repeating the loop + // The wait unlocks state_lock + cv.wait(state_lock, [&](){return _current_state != _qubit_data.end();}); + } + } + + + // 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. + // This means the coefficients of wfn1 are all of the form (c_x/c_b); + // Thus, the norm of wfn1 will be 1/|c_b|^2; thus the norm of wfn2 is 1/|d_b|^2 = |c_b|^2/|a_bb|^2 + // So we iterate through the smaller wavefunction, to get the normalizing constant, + // then normalize both bool _split_wavefunction(qubit_label 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(); - 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 normalizer_1 = 0.0; - double normalizer_2 = 0.0; - // 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_squared*_precision_squared){ - return false; - } else { - // Not entangled so far, save the two states, with amplitudes a_xx/a_bx and a_xx/a_xb, respectively - wfn1[label_1] = base_state->second / second_state->second; - wfn2[label_2] = base_state->second / first_state->second; - } - } - } - // Normalize - // This cannot be done in the previous loop, as that loop will encounter the same data several times - wavefunction &smaller_wfn = (wfn1.size() < wfn2.size()) ? wfn1 : wfn2; - wavefunction &larger_wfn = (wfn1.size() < wfn2.size()) ? wfn2 : wfn1; - for (auto current_state = smaller_wfn.begin(); current_state != smaller_wfn.end(); ++current_state){ - normalizer_1 += std::norm(current_state->second); - } - normalizer_2 = normalizer_1/std::norm(base_val); - normalizer_1 = 1.0/normalizer_1; - for (auto current_state = smaller_wfn.begin(); current_state != smaller_wfn.end(); ++current_state){ - current_state->second *= normalizer_1; - } - for (auto current_state = larger_wfn.begin(); current_state != larger_wfn.end(); ++current_state){ - current_state->second *= normalizer_2; - } - return true; + 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(); + 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 normalizer_1 = 0.0; + double normalizer_2 = 0.0; + // 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_squared*_precision_squared){ + return false; + } else { + // Not entangled so far, save the two states, with amplitudes a_xx/a_bx and a_xx/a_xb, respectively + wfn1[label_1] = base_state->second / second_state->second; + wfn2[label_2] = base_state->second / first_state->second; + } + } + } + // Normalize + // This cannot be done in the previous loop, as that loop will encounter the same data several times + wavefunction &smaller_wfn = (wfn1.size() < wfn2.size()) ? wfn1 : wfn2; + wavefunction &larger_wfn = (wfn1.size() < wfn2.size()) ? wfn2 : wfn1; + for (auto current_state = smaller_wfn.begin(); current_state != smaller_wfn.end(); ++current_state){ + normalizer_1 += std::norm(current_state->second); + } + normalizer_2 = normalizer_1/std::norm(base_val); + normalizer_1 = 1.0/normalizer_1; + for (auto current_state = smaller_wfn.begin(); current_state != smaller_wfn.end(); ++current_state){ + current_state->second *= normalizer_1; + } + for (auto current_state = larger_wfn.begin(); current_state != larger_wfn.end(); ++current_state){ + current_state->second *= normalizer_2; + } + 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; } - std::vector sortedLabels; - sortedLabels.reserve(wfn.size()); - for (auto current_state = (wfn).begin(); current_state != (wfn).end(); ++current_state) { - sortedLabels.push_back(current_state->first); - } - std::sort( - sortedLabels.begin(), - sortedLabels.end(), - [](const qubit_label& lhs, const qubit_label& rhs){return lhs < rhs;}); - amplitude val; - for (qubit_label label : sortedLabels){ - output(label, _qubit_data.find(label)->second); - - } - } + // 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; } + std::vector sortedLabels; + sortedLabels.reserve(wfn.size()); + for (auto current_state = (wfn).begin(); current_state != (wfn).end(); ++current_state) { + sortedLabels.push_back(current_state->first); + } + std::sort( + sortedLabels.begin(), + sortedLabels.end(), + [](const qubit_label& lhs, const qubit_label& rhs){return lhs < rhs;}); + amplitude val; + for (qubit_label label : sortedLabels){ + output(label, _qubit_data.find(label)->second); + + } + } }; diff --git a/src/Simulation/Simulators/SparseSimulator/Native/types.h b/src/Simulation/Simulators/SparseSimulator/Native/types.h index 7e095af3ad2..feb6ffe386c 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/types.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/types.h @@ -20,9 +20,9 @@ using lock_type = std::lock_guard; using recursive_lock_type = std::lock_guard; #ifndef USE_SINGLE_PRECISION - using RealType = double; + using RealType = double; #else - using RealType = float; + using RealType = float; #endif // Logical qubit id is visible to the clients and is immutable during the lifetime of the qubit. From 4e5c03204ab0685cdb91946945563d7c2189e1a6 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Fri, 9 Jul 2021 18:36:39 -0700 Subject: [PATCH 09/25] Updated message and comment --- src/Simulation/Simulators/SparseSimulator/Native/CMakeLists.txt | 2 +- src/Simulation/Simulators/SparseSimulator/Native/factory.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/CMakeLists.txt b/src/Simulation/Simulators/SparseSimulator/Native/CMakeLists.txt index 9540c4edb78..b26fae20dfd 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/CMakeLists.txt +++ b/src/Simulation/Simulators/SparseSimulator/Native/CMakeLists.txt @@ -5,7 +5,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -# Set OpenMp if it is available +# Set OpenMP if it is available find_package(OpenMP REQUIRED) if(OpenMP_CXX_FOUND) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") diff --git a/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp b/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp index d7c7b08cb88..cfa7c98ba1d 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp @@ -19,7 +19,7 @@ std::vector> _simulators; unsigned createSimulator(logical_qubit_id num_qubits) { if (num_qubits > MAX_QUBITS) - throw std::runtime_error("Max number of qubits is 1024!"); + throw std::runtime_error("Max number of qubits is exceeded!"); std::lock_guard lock(_mutex); size_t emptySlot = -1; for (auto const& s : _simulators) From 40f0ed45d1e174b1ef4c18bd45188e3cd6c963ba Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Fri, 9 Jul 2021 18:58:28 -0700 Subject: [PATCH 10/25] Rearranged headers, include path in tests --- .../SparseSimulator/Native/SparseSimulator.h | 10 +++++----- .../SparseSimulator/Native/basic_quantum_state.hpp | 5 +++-- .../Simulators/SparseSimulator/Native/capi.cpp | 11 +++++------ .../Simulators/SparseSimulator/Native/capi.hpp | 3 +-- .../Simulators/SparseSimulator/Native/factory.cpp | 5 +++-- .../Simulators/SparseSimulator/Native/factory.hpp | 1 + .../SparseSimulator/Native/quantum_state.hpp | 11 +++++------ .../Simulators/SparseSimulator/Native/types.h | 3 ++- .../SparseSimulatorTests/CSharpIntegrationTests.cpp | 10 +++++----- .../SparseSimulatorTests/SparseSimulatorTests.cpp | 2 +- .../SparseSimulatorTests/SparseSimulatorTests.vcxproj | 1 + .../SparseSimulatorTests/TestHelpers.cpp | 2 +- .../SparseSimulatorTests/TestHelpers.hpp | 2 +- 13 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h index 957d3de16e8..f76437579e7 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h @@ -4,20 +4,20 @@ #pragma once #include -#include "types.h" -#include "gates.h" #include #include #include -#include "quantum_state.hpp" -#include "basic_quantum_state.hpp" #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 diff --git a/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp index 38d41f4514c..3f67d6a5211 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp @@ -2,10 +2,11 @@ // Licensed under the MIT License. #pragma once -#include "types.h" -#include "gates.h" + #include +#include "types.h" +#include "gates.h" namespace Microsoft::Quantum::SPARSESIMULATOR { diff --git a/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp b/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp index 7ff43f5c8f0..999702d665b 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp @@ -6,18 +6,17 @@ // index into the vector of simulators, // then call a member function +#include +#include +#include +#include + #include "capi.hpp" #include "SparseSimulator.h" #include "factory.hpp" -#include -#include using namespace Microsoft::Quantum::SPARSESIMULATOR; - -#include -#include - std::string sample_string; extern "C" diff --git a/src/Simulation/Simulators/SparseSimulator/Native/capi.hpp b/src/Simulation/Simulators/SparseSimulator/Native/capi.hpp index 3bd34868977..8f4c3839041 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/capi.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/capi.hpp @@ -3,9 +3,8 @@ #pragma once - - #include + #include "types.h" // SAL only defined in windows. diff --git a/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp b/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp index cfa7c98ba1d..e8aa09a7a22 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp @@ -3,11 +3,12 @@ // Manages simulators in a vector of pointers to simulators +#include +#include + #include "factory.hpp" #include "SparseSimulator.h" #include "types.h" -#include -#include namespace Microsoft::Quantum::SPARSESIMULATOR { diff --git a/src/Simulation/Simulators/SparseSimulator/Native/factory.hpp b/src/Simulation/Simulators/SparseSimulator/Native/factory.hpp index d624754e43d..ffb54385f22 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/factory.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/factory.hpp @@ -4,6 +4,7 @@ // Manages simulators in a vector of pointers to simulators #pragma once + #include "types.h" #include "SparseSimulator.h" diff --git a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp index abccb97a00e..01a1b7d10f7 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp @@ -2,26 +2,25 @@ // Licensed under the MIT License. #pragma once + #include #include -#include "types.h" -#include "gates.h" #include #include #include -#include "basic_quantum_state.hpp" -#include "quantum_hash_map.hpp" #include #include #include - #include #include - #ifdef _OPENMP #include #endif +#include "basic_quantum_state.hpp" +#include "quantum_hash_map.hpp" +#include "types.h" +#include "gates.h" using namespace std::literals::complex_literals; diff --git a/src/Simulation/Simulators/SparseSimulator/Native/types.h b/src/Simulation/Simulators/SparseSimulator/Native/types.h index feb6ffe386c..888ebf2abd3 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/types.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/types.h @@ -7,10 +7,11 @@ #include #include #include -#include "quantum_hash_map.hpp" #include #include +#include "quantum_hash_map.hpp" + namespace Microsoft::Quantum::SPARSESIMULATOR { diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CSharpIntegrationTests.cpp b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CSharpIntegrationTests.cpp index e58ab774839..409e747a77e 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CSharpIntegrationTests.cpp +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CSharpIntegrationTests.cpp @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -#include "../SparseQuantumSimulator/SparseSimulator.h" -#include "../SparseQuantumSimulator/capi.hpp" -#include "../SparseQuantumSimulator/capi.cpp" // yes really -#include "../SparseQuantumSimulator/factory.hpp" -#include "../SparseQuantumSimulator/factory.cpp" +#include "../Native/SparseSimulator.h" +#include "../Native/capi.hpp" +#include "../Native/capi.cpp" // yes really +#include "../Native/factory.hpp" +#include "../Native/factory.cpp" #include "TestHelpers.hpp" #include "CppUnitTest.h" diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp index ec306be97dc..c43956686e5 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp @@ -3,7 +3,7 @@ #include "pch.h" #include "CppUnitTest.h" -#include "../SparseQuantumSimulator/SparseSimulator.h" +#include "../Native/SparseSimulator.h" #include "TestHelpers.hpp" #include #include diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj index 37fe5ddeac1..18ea6d5e2ad 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj @@ -96,6 +96,7 @@ WIN32;_DEBUG;%(PreprocessorDefinitions) true pch.h + stdcpp17 Windows diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.cpp b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.cpp index 23627d3bcd1..e2d8135a0df 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.cpp +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.cpp @@ -3,7 +3,7 @@ #include "pch.h" #include "CppUnitTest.h" -#include "../SparseQuantumSimulator/SparseSimulator.h" +#include "../Native/SparseSimulator.h" #include #include diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.hpp b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.hpp index 5bec3a92001..8eb71c0f7b1 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.hpp +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/TestHelpers.hpp @@ -5,7 +5,7 @@ #include "pch.h" #include "CppUnitTest.h" -#include "../SparseQuantumSimulator/SparseSimulator.h" +#include "../Native/SparseSimulator.h" #include #include From 748672b5664be9cb3111dc6bbb36d2b9e1620615 Mon Sep 17 00:00:00 2001 From: Sam Jaques Date: Sun, 11 Jul 2021 13:11:05 +0100 Subject: [PATCH 11/25] Marshall boolean C++ return to C# --- .../Simulators/SparseSimulator/SparseSimulatorCS/Probes.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.cs b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.cs index a9248bdf84f..9aab83113db 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.cs +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.cs @@ -321,6 +321,7 @@ public bool Dump(StringCallback callback, int max_id, IQArray qubits = n } [DllImport(simulator_dll)] + [return: MarshalAs(UnmanagedType.I1)] // necessary because C++ and C# represent bools differently private static extern bool DumpQubits_cpp(uint sim, int length, int[] qubit_ids, DumpCallback callback); [DllImport(simulator_dll)] From a53cbb001614e3ed7fbe9003f51c6069a7b71b03 Mon Sep 17 00:00:00 2001 From: Sam Jaques Date: Sun, 11 Jul 2021 13:42:07 +0100 Subject: [PATCH 12/25] Rename parity function --- .../SparseSimulator/Native/quantum_state.hpp | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp index 01a1b7d10f7..3533e670394 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp @@ -49,7 +49,7 @@ inline amplitude iExp(int power) } template -bool poppar(std::bitset bitstring){ +bool get_parity(std::bitset bitstring){ return bitstring.count() % 2; } @@ -225,14 +225,14 @@ class QuantumState : public BasicQuantumState if (std::norm(id_coeff) > _rotation_precision_squared){ // 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 *= (poppar(current_state->first & YZs) ? id_coeff : pauli_coeff); + 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 (!poppar(current_state->first & YZs)){ + if (!get_parity(current_state->first & YZs)){ new_qubit_data.emplace(current_state->first, current_state->second * pauli_coeff); } } @@ -242,7 +242,7 @@ class QuantumState : public BasicQuantumState // 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 (poppar(current_state->first & YZs)){ + if (get_parity(current_state->first & YZs)){ new_qubit_data.emplace(current_state->first, current_state->second * id_coeff); } } @@ -279,11 +279,11 @@ class QuantumState : public BasicQuantumState 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 * (poppar(current_state->first & YZs) ? -pauli_coeff : pauli_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 = poppar(current_state->first & YZs); + 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_squared) { new_qubit_data.emplace(current_state->first, new_state); @@ -350,7 +350,7 @@ class QuantumState : public BasicQuantumState // 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 *= (poppar(current_state->first & YZs) ? id_coeff : pauli_coeff); + current_state->second *= (get_parity(current_state->first & YZs) ? id_coeff : pauli_coeff); } } } else { @@ -358,7 +358,7 @@ class QuantumState : public BasicQuantumState // 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 (!poppar(current_state->first & YZs) && (current_state->first & cmask)==cmask){ + if (!get_parity(current_state->first & YZs) && (current_state->first & cmask)==cmask){ new_qubit_data.emplace(current_state->first, current_state->second * pauli_coeff); } } @@ -368,7 +368,7 @@ class QuantumState : public BasicQuantumState // 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 (poppar(current_state->first & YZs) && (current_state->first & cmask)==cmask){ + if (get_parity(current_state->first & YZs) && (current_state->first & cmask)==cmask){ new_qubit_data.emplace(current_state->first, current_state->second * id_coeff); } } @@ -405,11 +405,11 @@ class QuantumState : public BasicQuantumState 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 * (poppar(current_state->first & YZs) ? -pauli_coeff : pauli_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 = poppar(current_state->first & YZs); + 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_squared) { new_qubit_data.emplace(current_state->first, new_state); @@ -567,11 +567,11 @@ class QuantumState : public BasicQuantumState break; } for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { - if (std::norm(_qubit_data.find(current_state->first ^ XYs)->second - current_state->second * (poppar(current_state->first & YZs) ? -phaseShift : phaseShift)) > _precision_squared) { + if (std::norm(_qubit_data.find(current_state->first ^ XYs)->second - current_state->second * (get_parity(current_state->first & YZs) ? -phaseShift : phaseShift)) > _precision_squared) { qubit_label label = current_state->first; amplitude val = current_state->second; std::cout << "Problematic state: " << label << "\n"; - std::cout << "Expected " << val * (poppar(label & YZs) ? -phaseShift : phaseShift); + std::cout << "Expected " << val * (get_parity(label & YZs) ? -phaseShift : phaseShift); std::cout << ", got " << _qubit_data.find(label ^ XYs)->second << "\n"; std::cout << "Wavefunction size: " << _qubit_data.size() << "\n"; throw std::runtime_error("Not an eigenstate"); @@ -636,7 +636,7 @@ class QuantumState : public BasicQuantumState 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)) * (poppar(current_state->first & YZs) ? -phaseShift : phaseShift); + 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* @@ -799,7 +799,7 @@ class QuantumState : public BasicQuantumState } break; case OP::Assert: - if (poppar(label & op.controls) != op.result && std::norm(val) > _precision_squared){ + if (get_parity(label & op.controls) != op.result && std::norm(val) > _precision_squared){ std::cout << "Problematic state: " << label << "\n"; std::cout << "Amplitude: " << val << "\n"; std::cout << "Wavefunction size: " << _qubit_data.size() << "\n"; @@ -901,7 +901,7 @@ class QuantumState : public BasicQuantumState } break; case OP::Assert: - if (poppar(label & op.controls) != op.result && std::norm(val) > _precision_squared){ + if (get_parity(label & op.controls) != op.result && std::norm(val) > _precision_squared){ std::cout << "Problematic state: " << label << "\n"; std::cout << "Amplitude: " << val << "\n"; std::cout << "Wavefunction size: " << _qubit_data.size() << "\n"; @@ -1376,7 +1376,7 @@ class QuantumState : public BasicQuantumState } break; case OP::Assert: - if (poppar(label & op.controls) != op.result && std::norm(val) > _precision_squared){ + if (get_parity(label & op.controls) != op.result && std::norm(val) > _precision_squared){ std::cout << "Problematic state: " << label << "\n"; std::cout << "Amplitude: " << val << "\n"; std::cout << "Wavefunction size: " << _qubit_data.size() << "\n"; From 94ecce9b5c3b12ab7a17f9589fe92f703462f015 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Mon, 6 Sep 2021 17:41:30 -0700 Subject: [PATCH 13/25] Renamed reserved identifiers --- .../SparseSimulator/Native/SparseSimulator.h | 162 +++++++++--------- 1 file changed, 81 insertions(+), 81 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h index f76437579e7..eceb7140e16 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h @@ -64,11 +64,11 @@ class SparseSimulator _max_num_qubits_used = 0; _current_number_qubits_used = 0; - _Ry_queue = std::vector(num_qubits, 0); - _Rx_queue = std::vector(num_qubits, 0); - _H_queue = std::vector(num_qubits, 0); - _Rx_angles = std::vector(num_qubits, 0.0); - _Ry_angles = std::vector(num_qubits, 0.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); } @@ -113,11 +113,11 @@ class SparseSimulator num_qubits = _quantum_state->get_num_qubits(); _occupied_qubits.resize(num_qubits, 0); - _Ry_queue.resize(num_qubits, 0); - _Rx_queue.resize(num_qubits, 0); - _H_queue.resize(num_qubits, 0); - _Rx_angles.resize(num_qubits, 0.0); - _Ry_angles.resize(num_qubits, 0.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]) { @@ -150,11 +150,11 @@ class SparseSimulator void X(logical_qubit_id index) { // XY = - YX - if (_Ry_queue[index]){ - _Ry_angles[index] *= -1.0; + if (_queue_Ry[index]){ + _angles_Ry[index] *= -1.0; } // Rx trivially commutes - if (_H_queue[index]) { + if (_queue_H[index]) { _queued_operations.push_back(operation(OP::Z, index)); return; } @@ -172,20 +172,20 @@ class SparseSimulator _execute_if(controls); } else { // An H on the control but not the target forces execution - if (_Ry_queue[controls[0]] || _Rx_queue[controls[0]] || (_H_queue[controls[0]] && !_H_queue[target])){ + 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 (_Ry_queue[target]){ + 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 (_H_queue[target]){ + 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 && _H_queue[controls[0]]){ + 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 { @@ -212,8 +212,8 @@ class SparseSimulator void Y(logical_qubit_id index) { // XY = -YX - if (_Rx_queue[index]){ - _Rx_angles[index] *= -1.0; + 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)); @@ -223,11 +223,11 @@ class SparseSimulator void MCY(std::vector const& controls, logical_qubit_id target) { _execute_if(controls); // Commutes with Ry on the target, not Rx - if (_Rx_queue[target]){ + if (_queue_Rx[target]){ _execute_queued_ops(target, OP::Rx); } // YH = -YH, so we add a phase to track this - if (_H_queue[target]){ + if (_queue_H[target]){ // The phase added does not depend on the target // Thus we use one of the controls as a target _queued_operations.push_back(operation(OP::MCZ, controls[0], controls)); @@ -239,15 +239,15 @@ class SparseSimulator void Z(logical_qubit_id index) { // ZY = -YZ - if (_Ry_queue[index]){ - _Ry_angles[index] *= -1; + if (_queue_Ry[index]){ + _angles_Ry[index] *= -1; } // XZ = -ZX - if (_Rx_queue[index]){ - _Rx_angles[index] *= -1; + if (_queue_Rx[index]){ + _angles_Rx[index] *= -1; } // HZ = XH - if (_H_queue[index]) { + if (_queue_H[index]) { _queued_operations.push_back(operation(OP::X, index)); _set_qubit_to_nonzero(index); return; @@ -263,17 +263,17 @@ class SparseSimulator // must execute. size_t count = 0; for (auto control : controls) { - if (_Ry_queue[control] || _Rx_queue[control]){ + if (_queue_Ry[control] || _queue_Rx[control]){ count += 2; } - if (_H_queue[control]){ + if (_queue_H[control]){ count++; } } - if (_Ry_queue[target] || _Rx_queue[target]){ + if (_queue_Ry[target] || _queue_Rx[target]){ count +=2; } - if (_H_queue[target]) {count++;} + if (_queue_H[target]) {count++;} if (count > 1) { _execute_queued_ops(controls, OP::Ry); _execute_queued_ops(target, OP::Ry); @@ -282,7 +282,7 @@ class SparseSimulator // with the target std::vector new_controls(controls); for (logical_qubit_id control : controls){ - if (_H_queue[control]){ + if (_queue_H[control]){ std::swap(new_controls[control], target); } } @@ -297,7 +297,7 @@ class SparseSimulator // 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 (_Ry_queue[index] || _Ry_queue[index] || _H_queue[index]){ + if (_queue_Ry[index] || _queue_Ry[index] || _queue_H[index]){ _execute_queued_ops(index, OP::Ry); } _queued_operations.push_back(operation(OP::Phase, index, phase)); @@ -353,20 +353,20 @@ class SparseSimulator // 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){ - _Ry_queue[index] = true; - _Ry_angles[index] += phi; + _queue_Ry[index] = true; + _angles_Ry[index] += phi; _set_qubit_to_nonzero(index); return; - } else if (_Ry_queue[index]) { + } else if (_queue_Ry[index]) { _execute_queued_ops(index, OP::Ry); } if (b == Gates::Basis::PauliX){ - _Rx_queue[index] = true; - _Rx_angles[index] += phi; + _queue_Rx[index] = true; + _angles_Rx[index] += phi; _set_qubit_to_nonzero(index); return; - } else if (_Rx_queue[index]){ + } else if (_queue_Rx[index]){ _execute_queued_ops(index, OP::Rz); } @@ -374,7 +374,7 @@ class SparseSimulator if (b == Gates::Basis::PauliZ){ // HRz = RxH, but that's the wrong order for this structure // Thus we must execute the H queue - if (_H_queue[index]){ + if (_queue_H[index]){ _execute_queued_ops(index, OP::H); } // Rz(phi) = RI(phi)*R1(-2*phi) @@ -396,13 +396,13 @@ class SparseSimulator _execute_if(controls); // The target can commute with rotations of the same type - if (_Ry_queue[target] && b != Gates::Basis::PauliY){ + if (_queue_Ry[target] && b != Gates::Basis::PauliY){ _execute_queued_ops(target, OP::Ry); } - if (_Rx_queue[target] && b != Gates::Basis::PauliX){ + if (_queue_Rx[target] && b != Gates::Basis::PauliX){ _execute_queued_ops(target, OP::Rx); } - if (_H_queue[target]){ + if (_queue_H[target]){ _execute_queued_ops(target, OP::H); } // Execute any phase and permutation gates @@ -450,13 +450,13 @@ class SparseSimulator void H(logical_qubit_id index) { // YH = -HY - _Ry_angles[index] *= (_Ry_queue[index] ? -1.0 : 1.0); + _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 (_Rx_queue[index]){ + if (_queue_Rx[index]){ _execute_queued_ops(index, OP::Rx); } - _H_queue[index] = !_H_queue[index]; + _queue_H[index] = !_queue_H[index]; _set_qubit_to_nonzero(index); } @@ -464,7 +464,7 @@ class SparseSimulator // No commutation on controls _execute_if(controls); // No Ry or Rx commutation on target - if (_Ry_queue[target] || _Rx_queue[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 @@ -482,11 +482,11 @@ class SparseSimulator std::swap(index_2, index_1); } // Everything commutes nicely with a swap - _Ry_queue.swap(_Ry_queue[index_1], _Ry_queue[index_2]); - std::swap(_Ry_angles[index_1], _Ry_angles[index_2]); - _Rx_queue.swap(_Rx_queue[index_1], _Rx_queue[index_2]); - std::swap(_Rx_angles[index_1], _Rx_angles[index_2]); - _H_queue.swap(_H_queue[index_1], _H_queue[index_2]); + _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)); @@ -538,7 +538,7 @@ class SparseSimulator 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 (_Rx_queue[qubit] || _Ry_queue[qubit]){ + if (_queue_Rx[qubit] || _queue_Ry[qubit]){ _execute_queued_ops(qubits, OP::Ry); } } @@ -549,15 +549,15 @@ class SparseSimulator switch (axes[i]){ case Gates::Basis::PauliY: // HY=YH, so we switch the eigenvalue - if (_H_queue[qubits[i]]){ - result ^= _H_queue[qubits[i]]; + if (_queue_H[qubits[i]]){ + result ^= _queue_H[qubits[i]]; } isAllZ = false; isEmpty = false; break; case Gates::Basis::PauliX: // HX = ZH - if (_H_queue[qubits[i]]){ + if (_queue_H[qubits[i]]){ axes[i] = Gates::Basis::PauliZ; } else { isAllZ = false; @@ -566,7 +566,7 @@ class SparseSimulator break; case Gates::Basis::PauliZ: // HZ = XH - if (_H_queue[qubits[i]]){ + if (_queue_H[qubits[i]]){ axes[i] = Gates::Basis::PauliX; isAllZ = false; } @@ -670,12 +670,12 @@ class SparseSimulator // 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 _H_queue; - std::vector _Rx_queue; - std::vector _Ry_queue; + std::vector _queue_H; + std::vector _queue_Rx; + std::vector _queue_Ry; - std::vector _Rx_angles; - std::vector _Ry_angles; + std::vector _angles_Rx; + std::vector _angles_Ry; // Store which qubits are non-zero as a bitstring std::vector _occupied_qubits; @@ -715,38 +715,38 @@ class SparseSimulator // 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 (_H_queue[index]){ + if (_queue_H[index]){ _quantum_state->H(index); - _H_queue[index] = false; + _queue_H[index] = false; } - if (_Rx_queue[index]){ - _quantum_state->R(Gates::Basis::PauliX, _Rx_angles[index], index); - _Rx_angles[index] = 0.0; - _Rx_queue[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 (_Ry_queue[index]){ - _quantum_state->R(Gates::Basis::PauliY, _Ry_angles[index], index); - _Ry_angles[index] = 0.0; - _Ry_queue[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 (_H_queue[index]){ + if (_queue_H[index]){ _quantum_state->H(index); - _H_queue[index] = false; + _queue_H[index] = false; } - if (_Rx_queue[index]){ - _quantum_state->R(Gates::Basis::PauliX, _Rx_angles[index], index); - _Rx_angles[index] = 0.0; - _Rx_queue[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 (_H_queue[index]){ + if (_queue_H[index]){ _quantum_state->H(index); - _H_queue[index] = false; + _queue_H[index] = false; } } @@ -818,7 +818,7 @@ class SparseSimulator // 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 (_Ry_queue[target] || _Rx_queue[target] || _H_queue[target]){ + if (_queue_Ry[target] || _queue_Rx[target] || _queue_H[target]){ _execute_queued_ops(target, OP::Ry); } } @@ -827,7 +827,7 @@ class SparseSimulator // Used when queuing gates that do not commute well void _execute_if(std::vector const &controls) { for (auto control : controls){ - if (_Ry_queue[control] || _Rx_queue[control] || _H_queue[control]){ + if (_queue_Ry[control] || _queue_Rx[control] || _queue_H[control]){ _execute_queued_ops(controls, OP::Ry); return; } From 3375a20d62acf2193849ac3ee9942aca9bf665d6 Mon Sep 17 00:00:00 2001 From: Sam Jaques Date: Tue, 7 Sep 2021 13:04:11 -0600 Subject: [PATCH 14/25] Renamed AND, hashmap comments, fix cmake --- .../SparseSimulator/Native/CMakeLists.txt | 10 ++++------ .../SparseSimulator/Native/SparseSimulator.h | 4 ++-- .../Simulators/SparseSimulator/Native/capi.cpp | 8 ++++---- .../SparseSimulator/Native/quantum_state.hpp | 8 ++++---- .../SparseSimQSharpTests/Program.qs | 18 ++---------------- .../SparseSimulatorCS/Simulator.ApplyAnd.cs | 16 ++++++++-------- .../SparseSimulatorCS/SparseSimulator.cs | 14 +++++++------- .../SparseSimulatorTests/CMakeLists.txt | 8 +------- 8 files changed, 32 insertions(+), 54 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/CMakeLists.txt b/src/Simulation/Simulators/SparseSimulator/Native/CMakeLists.txt index b26fae20dfd..12ce6e42ce3 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/CMakeLists.txt +++ b/src/Simulation/Simulators/SparseSimulator/Native/CMakeLists.txt @@ -5,6 +5,10 @@ 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) + # Set OpenMP if it is available find_package(OpenMP REQUIRED) if(OpenMP_CXX_FOUND) @@ -14,12 +18,6 @@ if(OpenMP_CXX_FOUND) target_compile_definitions(SparseQuantumSimulator PRIVATE DOMP_GE_V3=1) endif() endif() - - - -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) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h index eceb7140e16..aff67c6b7fd 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h @@ -199,12 +199,12 @@ class SparseSimulator } // Same as MCX, but we assert that the target is 0 before execution - void AND(std::vector const& controls, logical_qubit_id target) { + 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 AdjAND(std::vector const& controls, logical_qubit_id target) { + 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); diff --git a/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp b/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp index 999702d665b..eaf1fc14715 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp @@ -100,12 +100,12 @@ extern "C" getSimulator(sim_id)->CSWAP(std::vector(c, c + n), q1, q2); } - MICROSOFT_QUANTUM_DECL void MCAnd_cpp(unsigned sim_id,_In_ int length, _In_reads_(length) logical_qubit_id* controls, _In_ logical_qubit_id target){ - getSimulator(sim_id)->AND(std::vector(controls, controls + length), target); + MICROSOFT_QUANTUM_DECL void MCApplyAnd_cpp(unsigned 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 MCAdjointAnd_cpp(unsigned sim_id,_In_ int length, _In_reads_(length) logical_qubit_id* controls, _In_ logical_qubit_id target){ - getSimulator(sim_id)->AdjAND(std::vector(controls, controls + length), target); + MICROSOFT_QUANTUM_DECL void MCAdjointApplyAnd(unsigned 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 diff --git a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp index 3533e670394..4438cc405d6 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp @@ -846,7 +846,7 @@ class QuantumState : public BasicQuantumState auto internal_end = _qubit_data.end(); for (auto current_state = _qubit_data.begin(); current_state != _qubit_data.end();){ internal_state = current_state; - current_state.jump_forward(_jump_size); + current_state.jump_forward(_jump_size); // Extra hash map functionality missing in STL internal_end = current_state; #pragma omp task firstprivate(internal_state) firstprivate(internal_end) { @@ -1076,7 +1076,7 @@ class QuantumState : public BasicQuantumState flip.set(index); // The amplitude for the new state amplitude new_state; - // Loops over all states in the wavefunction _qubut_date + // 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); @@ -1321,8 +1321,8 @@ class QuantumState : public BasicQuantumState // Loop through the wavefunction while (_current_state != _qubit_data.end()){ // Update local variables - local_state = _current_state; - _current_state.jump_forward(_jump_size); + local_state = _current_state; + _current_state.jump_forward(_jump_size); // Extra hash map functionality missing in STL local_end = _current_state; // Unlock state to allow other threads to modify their state state_lock.unlock(); diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/Program.qs b/src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/Program.qs index 24712e556bb..80d026e47a0 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/Program.qs +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/Program.qs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. namespace Microsoft.Quantum.SparseSimulatorTests { @@ -155,20 +155,6 @@ namespace Microsoft.Quantum.SparseSimulatorTests { ResetAll(qubits); } - operation MCXTime(nqubits : Int, ngates : Int) : Unit { - use qubits = Qubit[nqubits]; - for idx in 0..nqubits - 1 { - H(qubits[idx]); - } - for idx in 0..nqubits - 1 { - CNOT(qubits[idx], qubits[(idx+1)% nqubits]); - } - for idy in 1..ngates { - CNOT(qubits[idy %nqubits], qubits[(idy+1)%nqubits]); - } - ResetAll(qubits); - } - @Test("Microsoft.Quantum.SparseSimulation.SparseSimulator") operation PartialDumpTest() : Unit { use qubits = Qubit[4] { @@ -185,7 +171,7 @@ namespace Microsoft.Quantum.SparseSimulatorTests { } } - + 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]); diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Simulator.ApplyAnd.cs b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Simulator.ApplyAnd.cs index 8512ef6853b..c91e0789f98 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Simulator.ApplyAnd.cs +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Simulator.ApplyAnd.cs @@ -22,25 +22,25 @@ public ApplyAndWrapper(IOperationFactory m) : base(m) public override Func<(Qubit, Qubit, Qubit), QVoid> __Body__ => simulator == null ? base.__Body__ : (args) => { - simulator.And(new QArray(args.Item1, args.Item2), args.Item3); + simulator.ApplyAnd(new QArray(args.Item1, args.Item2), args.Item3); return QVoid.Instance; }; public override Func<(Qubit, Qubit, Qubit), QVoid> __AdjointBody__ => simulator == null ? base.__AdjointBody__ : (args) => { - simulator.AdjointAnd(new QArray(args.Item1, args.Item2), args.Item3); + simulator.AdjointApplyAnd(new QArray(args.Item1, args.Item2), args.Item3); return QVoid.Instance; }; public override Func<(IQArray, (Qubit, Qubit, Qubit)), QVoid> __ControlledBody__ => simulator == null ? base.__ControlledBody__ : (args) => { - simulator.And(new QArray(args.Item1.Concat(new QArray(args.Item2.Item1, args.Item2.Item2))), args.Item2.Item3); + simulator.ApplyAnd(new QArray(args.Item1.Concat(new QArray(args.Item2.Item1, args.Item2.Item2))), args.Item2.Item3); return QVoid.Instance; }; public override Func<(IQArray, (Qubit, Qubit, Qubit)), QVoid> __ControlledAdjointBody__ => simulator == null ? base.__ControlledAdjointBody__ : (args) => { - simulator.AdjointAnd(new QArray(args.Item1.Concat(new QArray(args.Item2.Item1, args.Item2.Item2))), args.Item2.Item3); + simulator.AdjointApplyAnd(new QArray(args.Item1.Concat(new QArray(args.Item2.Item1, args.Item2.Item2))), args.Item2.Item3); return QVoid.Instance; }; } @@ -50,13 +50,13 @@ public partial class SparseSimulator // Wrappers for the relevant functions of SparseSimulatorProcessor // Since And/AdjointAnd are inherently multi-controlled, there is only a multi-controlled // emulator - public void And(IQArray controls, Qubit target) + public void ApplyAnd(IQArray controls, Qubit target) { - ((SparseSimulatorProcessor)this.QuantumProcessor).MCAnd(controls, target); ; + ((SparseSimulatorProcessor)this.QuantumProcessor).MCApplyAnd(controls, target); ; } - public void AdjointAnd(IQArray controls, Qubit target) + public void AdjointApplyAnd(IQArray controls, Qubit target) { - ((SparseSimulatorProcessor)this.QuantumProcessor).MCAdjointAnd(controls, target); + ((SparseSimulatorProcessor)this.QuantumProcessor).MCAdjointApplyAnd(controls, target); } } } \ No newline at end of file diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.cs b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.cs index 2478e2d8e26..4bbff5babd4 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.cs +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; @@ -107,17 +107,17 @@ public void SetSeed(uint newSeed = 5489) // Basic gates [DllImport(simulator_dll)] - private static extern void MCAnd_cpp(uint sim, int length, int[] controls, int target); - public void MCAnd(IQArray controls, Qubit qubit) + private static extern void MCApplyAnd_cpp(uint sim, int length, int[] controls, int target); + public void MCApplyAnd(IQArray controls, Qubit qubit) { - MCAnd_cpp(Id, controls.Count(), controls.Select(x => x.Id).ToArray(), qubit.Id); + MCApplyAnd_cpp(Id, controls.Count(), controls.Select(x => x.Id).ToArray(), qubit.Id); } [DllImport(simulator_dll)] - private static extern void MCAdjointAnd_cpp(uint sim, int length, int[] controls, int target); - public void MCAdjointAnd(IQArray controls, Qubit qubit) + private static extern void MCAdjointApplyAnd_cpp(uint sim, int length, int[] controls, int target); + public void MCAdjointApplyAnd(IQArray controls, Qubit qubit) { - MCAdjointAnd_cpp(Id, controls.Count(), controls.Select(x => x.Id).ToArray(), qubit.Id); + MCAdjointApplyAnd_cpp(Id, controls.Count(), controls.Select(x => x.Id).ToArray(), qubit.Id); } diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CMakeLists.txt b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CMakeLists.txt index cd8079e38f1..a99693d94aa 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CMakeLists.txt +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CMakeLists.txt @@ -10,10 +10,4 @@ enable_testing() set(CMAKE_MACOSX_RPATH 1) add_executable(SparseSimulatorTests SparseSimulatorTests.cpp) -add_test(SparseSimulatorTests SparseSimulatorTests) - -#target_compile_options(SparseQuantumSimulator PUBLIC -fdeclspec) - -#add_executable(SparseQuantumSimulatorExe SparseQuantumSimulator.cpp) -# target_include_directories(${EXE} PUBLIC ${OSSIM_INCLUDE_DIRS}) -# target_link_libraries(${EXE} PRIVATE ${OPENMP_LINKER_FLAG}) \ No newline at end of file +add_test(SparseSimulatorTests SparseSimulatorTests) \ No newline at end of file From 550ad535fcca77b646b61e8b820cb6daf9f77360 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Thu, 9 Sep 2021 18:48:55 -0700 Subject: [PATCH 15/25] Made unit tests work. Added comments --- .../SparseSimulator/Native/SparseSimulator.h | 17 ++++++++++++++--- .../SparseSimulatorTests.cpp | 9 ++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h index aff67c6b7fd..5cce6e6b859 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h @@ -47,9 +47,17 @@ std::shared_ptr expand_wfn_helper(std::shared_ptr 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. +// Zero coefficients are simply not stored. +// It has good performance only when the number of non-zero coefficients is low. +// It employs hashtable structure (by Malte Skarupke) to store non-zero coefficients. +// Keys are basis vectors represented by std::bitset<>. +// Values are amplitudes represented by std::complex. +// 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: +public: std::set operations_done; @@ -584,13 +592,16 @@ class SparseSimulator // as a phase/permutation gate // This means if an assert fails, it will fail // at some future point, not at the point of failure - #if NDEBUG + #if defined(NDEBUG) if (isAllZ) { _queued_operations.push_back(operation(OP::Assert, qubits, result)); + // In release mode we queue Z assertion and return. return; } #endif - // X or Y assertions require execution + // X or Y assertions require immediate execution. + // We also execute Z assertion immediately in debug mode + // to provide feedback at the point of failure. _execute_queued_ops(qubits, OP::PermuteLarge); _quantum_state->Assert(axes, qubits, result); } diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp index c43956686e5..d3674644420 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.cpp @@ -7,9 +7,6 @@ #include "TestHelpers.hpp" #include #include -#include // necessary for string conversions for logging -#include // necessary for string conversions for logging -#include // necessary for string conversions for logging using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace Microsoft::Quantum::SPARSESIMULATOR; @@ -70,7 +67,7 @@ namespace SparseSimulatorTests uint64_t k = 0; qubit_label_type label1(0); qubit_label_type label2(1); - std::wstring_convert> converter; + for (i = 0; i < 500; i++){ k += i * i * i * i; uint64_t m = 0; @@ -78,7 +75,9 @@ namespace SparseSimulatorTests for (j = 0; j < 500; j++){ m += j * j * j * j; label2 = qubit_label_type(m); - Assert::AreEqual(k < m, label1 < label2, converter.from_bytes("Comparing " + std::to_string(k) + " to " + std::to_string(m) + "\n").c_str()); + wchar_t message[100]; + swprintf(message, sizeof(message)/sizeof(*message), L"Comparing %llu to %llu\n", k, m); + Assert::AreEqual(k < m, label1 < label2, message); } } } From 5667211d0836ec14e55c29a73248368ca9bffeea Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Thu, 9 Sep 2021 21:30:27 -0700 Subject: [PATCH 16/25] Updated SparseSimulator comment --- .../Simulators/SparseSimulator/Native/SparseSimulator.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h index 5cce6e6b859..ecdde7c5fe2 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h @@ -48,11 +48,12 @@ std::shared_ptr expand_wfn_helper(std::shared_ptr. -// Values are amplitudes represented by std::complex. +// 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 From a34d5c45bc2cb47d896f46b078ba2fa4ed7107e0 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Tue, 14 Sep 2021 13:59:19 -0700 Subject: [PATCH 17/25] Some build fixes --- src/Simulation/Simulators/SparseSimulator/Native/capi.cpp | 2 +- .../SparseSimulator/SparseSimulatorTests/CMakeLists.txt | 2 +- .../SparseSimulatorTests/SparseSimulatorTests.vcxproj | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp b/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp index eaf1fc14715..0543457cd67 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp @@ -104,7 +104,7 @@ extern "C" getSimulator(sim_id)->MCApplyAnd(std::vector(controls, controls + length), target); } - MICROSOFT_QUANTUM_DECL void MCAdjointApplyAnd(unsigned sim_id,_In_ int length, _In_reads_(length) logical_qubit_id* controls, _In_ logical_qubit_id target){ + MICROSOFT_QUANTUM_DECL void MCAdjointApplyAnd_cpp(unsigned 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); } diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CMakeLists.txt b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CMakeLists.txt index a99693d94aa..a208bb9df56 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CMakeLists.txt +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/CMakeLists.txt @@ -9,5 +9,5 @@ include(CTest) enable_testing() set(CMAKE_MACOSX_RPATH 1) -add_executable(SparseSimulatorTests SparseSimulatorTests.cpp) +add_executable(SparseSimulatorTests SparseSimulatorTests.cpp TestHelpers.cpp) add_test(SparseSimulatorTests SparseSimulatorTests) \ No newline at end of file diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj index 18ea6d5e2ad..fb1b867abfc 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj @@ -112,6 +112,7 @@ _DEBUG;%(PreprocessorDefinitions) true pch.h + stdcpp17 Windows From a8729e7eea600bd8de9de1935200bd2600778bb0 Mon Sep 17 00:00:00 2001 From: Sam Jaques Date: Thu, 16 Sep 2021 14:50:32 -0600 Subject: [PATCH 18/25] Changed amplitude probes to assertions, removed dependencies, used internal keyword --- .../SparseSimulator/Native/quantum_state.hpp | 1 - .../SparseSimQSharpTests/Program.qs | 51 +++++++++---------- .../SparseSimulatorCS/Probes.cs | 11 ++-- .../SparseSimulatorCS/Probes.qs | 35 ++++++++++--- .../SparseSimulatorCS/Simulator.ApplyAnd.cs | 2 - .../SparseSimulatorCS/SparseSimulator.cs | 14 ----- 6 files changed, 58 insertions(+), 56 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp index 4438cc405d6..4d3b61ec732 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp @@ -30,7 +30,6 @@ namespace Microsoft::Quantum::SPARSESIMULATOR // power of square root of -1 inline amplitude iExp(int power) { - int p = ((power % 4) + 8) % 4; switch (p) { diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/Program.qs b/src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/Program.qs index 80d026e47a0..c0886fe7ca1 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/Program.qs +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimQSharpTests/Program.qs @@ -21,22 +21,21 @@ namespace Microsoft.Quantum.SparseSimulatorTests { CNOT(qubits[0], qubits[2]); CNOT(qubits[1], qubits[3]); (Controlled H)([qubits[2]], (qubits[3])); - DumpMachine("Test file"); - let expected = [ - (0.5, 0L), - (0.353553,5L), - (0.5,10L), - (0.353553,7L), - (0.353553,13L), - (-0.353553,15L)]; - mutable results = new Complex[Length(expected)]; - let expBasicC = Complex(2.0,0.0); - let basicC = GetAmplitude(qubits, 0L); - let invC = Complex(basicC::Real/AbsSquaredComplex(basicC), basicC::Imag/AbsSquaredComplex(basicC)); - for (value, label) in expected { - let c = TimesC(GetAmplitude(qubits, label), invC); - Fact(AbsD(c::Real - expBasicC::Real*value) + AbsD(c::Imag - expBasicC::Imag*value) < 0.001, "Controlled H failed"); - } + let expected_vals = [ + Complex(0.5, 0.0), + Complex(0.353553,0.0), + Complex(0.5,0.0), + Complex(0.353553,0.0), + Complex(0.353553,0.0), + Complex(-0.353553,0.0)]; + let expected_labels = [ + 0L, + 5L, + 10L, + 7L, + 13L, + 15L]; + AssertAmplitudes(expected_labels, expected_vals, LittleEndian(qubits), 0.001); ResetAll(qubits); } } @@ -172,40 +171,40 @@ namespace Microsoft.Quantum.SparseSimulatorTests { } - operation _FakeR1Frac(numerator : Int, denominator : Int, qubit : Qubit[]) : Unit is Adj + Ctl { + 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]); } - operation R1FracWithArray(numerator : Int, denominator : Int, qubit : Qubit[]) : Unit is Adj + Ctl { + internal operation R1FracWithArray(numerator : Int, denominator : Int, qubit : Qubit[]) : Unit is Adj + Ctl { R1Frac(numerator, denominator, qubit[0]); } - operation _FakeR1(angle : Double, qubit : Qubit[]) : Unit is Adj + Ctl { + internal operation FakeR1(angle : Double, qubit : Qubit[]) : Unit is Adj + Ctl { R(PauliZ, angle, qubit[0]); R(PauliI, -angle, qubit[0]); } - operation R1WithArray(angle : Double, qubit : Qubit[]) : Unit is Adj + Ctl { + internal operation R1WithArray(angle : Double, qubit : Qubit[]) : Unit is Adj + Ctl { R1(angle, qubit[0]); } @Test("Microsoft.Quantum.SparseSimulation.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, _)); + 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, _)); + AssertOperationsEqualReferenced(1, R1WithArray(IntAsDouble(angle)/100.0, _), FakeR1(IntAsDouble(angle)/100.0, _)); + AssertOperationsEqualReferenced(1, FakeR1(IntAsDouble(angle)/100.0, _), R1WithArray(IntAsDouble(angle)/100.0, _)); } } - operation RFracWithArray(axis : Pauli, num : Int, denom : Int, qubit : Qubit[]) : Unit is Adj + Ctl { + internal operation RFracWithArray(axis : Pauli, num : Int, denom : Int, qubit : Qubit[]) : Unit is Adj + Ctl { RFrac(axis, num, denom, qubit[0]); } - operation RWithArray(axis : Pauli, angle : Double, qubit : Qubit[]) : Unit is Adj + Ctl { + internal operation RWithArray(axis : Pauli, angle : Double, qubit : Qubit[]) : Unit is Adj + Ctl { R(axis, angle, qubit[0]); } @Test("Microsoft.Quantum.SparseSimulation.SparseSimulator") diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.cs b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.cs index 9aab83113db..e8d54d0bffc 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.cs +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.cs @@ -8,7 +8,6 @@ using Microsoft.Quantum.Simulation.Core; using Microsoft.Quantum.Simulation.QuantumProcessor; using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -17,7 +16,7 @@ namespace Microsoft.Quantum.SparseSimulation { - public partial class GetAmplitudeFromInt + partial class GetAmplitudeFromInt { public class Native : GetAmplitudeFromInt { @@ -27,14 +26,14 @@ public Native(IOperationFactory m) : base(m) sim = m as SparseSimulator; } - public override Func<(IQArray, long), Microsoft.Quantum.Math.Complex> __Body__ => sim == null ? base.__Body__ : (args) => { + public override Func<(IQArray, long), Microsoft.Quantum.Math.Complex> __Body__ => (args) => { return sim.GetAmplitude(args.Item1, args.Item2); }; } } - public partial class GetAmplitude + partial class GetAmplitude { public class Native : GetAmplitude { @@ -44,7 +43,7 @@ public Native(IOperationFactory m) : base(m) sim = m as SparseSimulator; } - public override Func<(IQArray, System.Numerics.BigInteger), Microsoft.Quantum.Math.Complex> __Body__ => sim == null ? base.__Body__ : args => { + public override Func<(IQArray, System.Numerics.BigInteger), Microsoft.Quantum.Math.Complex> __Body__ => args => { return sim.GetAmplitude(args.Item1, args.Item2); }; @@ -269,7 +268,7 @@ private string Format(string label, double real, double img) /// Samples a random label from the wavefunction, proportional to its amplitude squared, /// and returns it as a boolean array /// - public partial class Sample + partial class Sample { private SparseSimulator simulator; public class Native : Sample diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.qs b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.qs index 94304b5407f..7d1f93e1139 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.qs +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Probes.qs @@ -8,29 +8,50 @@ namespace Microsoft.Quantum.SparseSimulation { open Microsoft.Quantum.Math; open Microsoft.Quantum.Diagnostics; - // These operations are physically unrealistic diagnostic + // These internal operations are physically unrealistic diagnostic // tools that can read amplitudes and probabilities // from a quantum state directly without modifyng it. // Returns an output with the same distribution // as measuring all the qubits, but does not // modify the quantum state. - function Sample(register : Qubit[]) : Bool[] { + internal function Sample(register : Qubit[]) : Bool[] { body intrinsic; } // Returns the amplitude of the quantum state which has a specific qubit // label (e.g., "|101>" has label 5) - function GetAmplitudeFromInt(qubit : Qubit[], label : Int) : Complex { - fail "Only implemented for SparseSimulator"; + internal function GetAmplitudeFromInt(qubit : Qubit[], label : Int) : Complex { + body intrinsic; } - function GetAmplitude(qubit : Qubit[], label : BigInt) : Complex { - fail "Only implemented for SparseSimulator"; + internal function GetAmplitude(qubit : Qubit[], label : BigInt) : Complex { + body intrinsic; } // Asserts (within some tolerance) that the probability of measuring the result passed as // stateIndex is a specific value. operation AssertProbBigInt (stateIndex : BigInt, expected : Double, qubits : Microsoft.Quantum.Arithmetic.LittleEndian, tolerance : Double) : Unit { - Fact(AbsD(AbsSquaredComplex (GetAmplitude(qubits!, stateIndex)) - expected) < tolerance, "Probability failed"); + Fact(AbsD(AbsSquaredComplex (GetAmplitude(qubits!, stateIndex)) - expected) < tolerance, "Probability failed"); + } + + // Asserts that the amplitudes of an array of states in superposition on a collection of qubits are equal + // (within tolerance) to a specified array, up to a global phase. + operation AssertAmplitudes(stateIndices : BigInt[], expected : Complex[], qubits : Microsoft.Quantum.Arithmetic.LittleEndian, tolerance : Double) : Unit { + let basicC = GetAmplitude(qubits!, stateIndices[0]); + let invC = Complex(basicC::Real/AbsSquaredComplex(basicC), basicC::Imag/AbsSquaredComplex(basicC)); + let expInv = Complex(expected[0]::Real/AbsSquaredComplex(expected[0]), expected[0]::Imag/AbsSquaredComplex(expected[0])); + for idx in 0..Length(stateIndices)-1 { + let c = TimesC(GetAmplitude(qubits!, stateIndices[idx]), invC); + let expC = TimesC(expected[idx], expInv); + Fact(AbsD(c::Real - expC::Real) + AbsD(c::Imag - expC::Imag) < 0.001, "Amplitude incorrect"); + } + } + + // Asserts that if the qubits in `register` were measured, they would pass some check. + // The measurement would product an array of boolean values, so the argument `assertion` + // should check whether an input array of boolean values, representing a measurement + // output, satisfies some criteria. + operation AssertSample(assertion : (Bool[] -> Bool), register : Qubit[]) : Unit { + Fact(assertion(Sample(register)), "Sampling failed"); } } diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Simulator.ApplyAnd.cs b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Simulator.ApplyAnd.cs index c91e0789f98..93e2345ff88 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Simulator.ApplyAnd.cs +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/Simulator.ApplyAnd.cs @@ -5,8 +5,6 @@ using Microsoft.Quantum.Simulation.Core; using System; -using System.Collections.Generic; -using System.Text; using System.Linq; using Microsoft.Quantum.Canon; diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.cs b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.cs index 4bbff5babd4..7b256c8cbcc 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.cs +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorCS/SparseSimulator.cs @@ -2,27 +2,13 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; using Microsoft.Quantum.Simulation.QuantumProcessor; using Microsoft.Quantum.Simulation.Common; using Microsoft.Quantum.Simulation.Core; using System.Runtime.InteropServices; -using System.Runtime.ExceptionServices; -using System.Threading.Tasks; -using Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime; -using Newtonsoft.Json.Serialization; -using System.Reflection.Metadata.Ecma335; -using Microsoft.VisualBasic.FileIO; using Microsoft.Quantum.Simulation.Simulators; -using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Linq; -using Newtonsoft.Json.Converters; -using Microsoft.Quantum.Diagnostics; -using Microsoft.Quantum.Extensions.Math; -using Microsoft.Quantum.Math; -using System.Numerics; using Microsoft.Quantum.Canon; -using Microsoft.Quantum.Measurement; using Microsoft.Quantum.SparseSimulation; From 955b9477a7056ba7d81b1bf34debf7b8194a2aed Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Tue, 21 Sep 2021 00:33:31 -0700 Subject: [PATCH 19/25] Replacing flat_hash_map with std::unordered_map --- .../SparseSimulator/Native/quantum_state.hpp | 29 +++++++++++++++++-- .../Simulators/SparseSimulator/Native/types.h | 7 ++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp index 4d3b61ec732..ca2d0157afb 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp @@ -18,7 +18,9 @@ #endif #include "basic_quantum_state.hpp" + #include "quantum_hash_map.hpp" + #include "types.h" #include "gates.h" @@ -566,6 +568,19 @@ class QuantumState : public BasicQuantumState break; } for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { + + // What happens if the key isn't present in the hashtable? + auto aaa = _qubit_data.find(current_state->first ^ XYs); + if (aaa == (_qubit_data).end()) { + // Here we shouldn't be able to dereference aaa! + // ska::flat_hash_map allows it and we get junk, but std::unordered_map doesn't allow it and we get exception. + auto bbb = aaa->second; + auto bbb1 = bbb; + } else { + auto ccc = aaa->second; + } + + if (std::norm(_qubit_data.find(current_state->first ^ XYs)->second - current_state->second * (get_parity(current_state->first & YZs) ? -phaseShift : phaseShift)) > _precision_squared) { qubit_label label = current_state->first; amplitude val = current_state->second; @@ -845,7 +860,12 @@ class QuantumState : public BasicQuantumState auto internal_end = _qubit_data.end(); for (auto current_state = _qubit_data.begin(); current_state != _qubit_data.end();){ internal_state = current_state; - current_state.jump_forward(_jump_size); // Extra hash map functionality missing in STL + + //current_state.jump_forward(_jump_size); // Extra hash map functionality missing in STL + for (size_t i=0; (i<_jump_size) && (_current_state != _qubit_data.end()); i++) { + ++_current_state; + } + internal_end = current_state; #pragma omp task firstprivate(internal_state) firstprivate(internal_end) { @@ -1321,7 +1341,12 @@ class QuantumState : public BasicQuantumState while (_current_state != _qubit_data.end()){ // Update local variables local_state = _current_state; - _current_state.jump_forward(_jump_size); // Extra hash map functionality missing in STL + + //_current_state.jump_forward(_jump_size); // Extra hash map functionality missing in STL + for (size_t i=0; (i<_jump_size) && (_current_state != _qubit_data.end()); i++) { + ++_current_state; + } + local_end = _current_state; // Unlock state to allow other threads to modify their state state_lock.unlock(); diff --git a/src/Simulation/Simulators/SparseSimulator/Native/types.h b/src/Simulation/Simulators/SparseSimulator/Native/types.h index 888ebf2abd3..051a4bd1fa1 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/types.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/types.h @@ -10,8 +10,10 @@ #include #include + #include "quantum_hash_map.hpp" + namespace Microsoft::Quantum::SPARSESIMULATOR { @@ -35,8 +37,11 @@ template using qubit_label_type = std::bitset; // Wavefunctions are hash maps of some key (std::bitset or a string) +//template +//using abstract_wavefunction = ska::bytell_hash_map>; + template -using abstract_wavefunction = ska::bytell_hash_map>; +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 From 7f87f68e25dddfd0cc4acb335add1531e96dd673 Mon Sep 17 00:00:00 2001 From: Sam Jaques Date: Tue, 21 Sep 2021 10:05:56 -0600 Subject: [PATCH 20/25] Iterator dereference fix --- .../SparseSimulator/Native/quantum_state.hpp | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp index ca2d0157afb..80054e6eb84 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp @@ -569,24 +569,30 @@ class QuantumState : public BasicQuantumState } for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { - // What happens if the key isn't present in the hashtable? - auto aaa = _qubit_data.find(current_state->first ^ XYs); - if (aaa == (_qubit_data).end()) { - // Here we shouldn't be able to dereference aaa! - // ska::flat_hash_map allows it and we get junk, but std::unordered_map doesn't allow it and we get exception. - auto bbb = aaa->second; - auto bbb1 = bbb; - } else { - auto ccc = aaa->second; - } - - - if (std::norm(_qubit_data.find(current_state->first ^ XYs)->second - current_state->second * (get_parity(current_state->first & YZs) ? -phaseShift : phaseShift)) > _precision_squared) { + // // What happens if the key isn't present in the hashtable? + // auto aaa = _qubit_data.find(current_state->first ^ XYs); + // if (aaa == (_qubit_data).end()) { + // // Here we shouldn't be able to dereference aaa! + // // ska::flat_hash_map allows it and we get junk, but std::unordered_map doesn't allow it and we get exception. + // auto bbb = aaa->second; + // auto bbb1 = bbb; + // } else { + // auto ccc = aaa->second; + // } + + + // 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_squared) { qubit_label label = current_state->first; amplitude val = current_state->second; std::cout << "Problematic state: " << label << "\n"; std::cout << "Expected " << val * (get_parity(label & YZs) ? -phaseShift : phaseShift); - std::cout << ", got " << _qubit_data.find(label ^ XYs)->second << "\n"; + 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"); } From 5b84247258e1c27b19ceaf54ef8e9ad3914c09fa Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Wed, 22 Sep 2021 16:23:47 -0700 Subject: [PATCH 21/25] Removed parallel execution --- .../Native/basic_quantum_state.hpp | 3 - .../SparseSimulator/Native/quantum_state.hpp | 509 +++--------------- 2 files changed, 76 insertions(+), 436 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp index 3f67d6a5211..94f57f6e53a 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp @@ -68,9 +68,6 @@ class BasicQuantumState virtual std::function get_rng() = 0; - virtual void complete_threads() = 0; - virtual int get_num_threads() = 0; - virtual std::string Sample() = 0; }; diff --git a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp index 80054e6eb84..b4159d7ceb4 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp @@ -11,11 +11,6 @@ #include #include #include -#include -#include -#ifdef _OPENMP - #include -#endif #include "basic_quantum_state.hpp" @@ -99,12 +94,6 @@ class QuantumState : public BasicQuantumState std::mt19937 gen(rd()); std::uniform_real_distribution dist(0, 1); _rng = [gen, dist]() mutable { return dist(gen); }; - - _initialize_threads(); - } - - ~QuantumState() { - complete_threads(); } // Copy data from an existing simulator @@ -122,8 +111,6 @@ class QuantumState : public BasicQuantumState 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); } - // Create local threads for this state - _initialize_threads(); } logical_qubit_id get_num_qubits() { @@ -568,19 +555,6 @@ class QuantumState : public BasicQuantumState break; } for (auto current_state = (_qubit_data).begin(); current_state != (_qubit_data).end(); ++current_state) { - - // // What happens if the key isn't present in the hashtable? - // auto aaa = _qubit_data.find(current_state->first ^ XYs); - // if (aaa == (_qubit_data).end()) { - // // Here we shouldn't be able to dereference aaa! - // // ska::flat_hash_map allows it and we get junk, but std::unordered_map doesn't allow it and we get exception. - // auto bbb = aaa->second; - // auto bbb1 = bbb; - // } else { - // auto ccc = aaa->second; - // } - - // 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 @@ -726,35 +700,38 @@ class QuantumState : public BasicQuantumState if (operation_list.size()==0){return;} // Condense the list into a memory-efficient vector with qubit labels - _operation_vector.reserve(operation_list.size()); + // 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)); + 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))); + 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))); + 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)); + 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)); + 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)); + 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)); + operation_vector.push_back(internal_operation(op.gate_type, op.target, _get_mask(op.controls), op.target_2)); break; case OP::Assert: - _operation_vector.push_back(internal_operation(op.gate_type, _get_mask(op.controls), op.result)); + operation_vector.push_back(internal_operation(op.gate_type, _get_mask(op.controls), op.result)); break; default: throw std::runtime_error("Unsupported operation"); @@ -762,198 +739,76 @@ class QuantumState : public BasicQuantumState } } - _new_qubit_data = make_wavefunction(); + wavefunction new_qubit_data = make_wavefunction(); - // Threading introduces a lot of overhead so it - // is only worthwhile for large instances - // 64 and 4096 are empirical values - if (_operation_vector.size() < 64 || _qubit_data.size() < 4096){ // small; do not thread - // 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: + // 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::MCX: - if ((op.controls & label) == op.controls){ - label.flip(op.target); - } - break; - case OP::Y: + } + 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::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; - case OP::Assert: - if (get_parity(label & op.controls) != op.result && std::norm(val) > _precision_squared){ - std::cout << "Problematic state: " << label << "\n"; - std::cout << "Amplitude: " << val << "\n"; - std::cout << "Wavefunction size: " << _qubit_data.size() << "\n"; - throw std::runtime_error("Assert failed"); - } - 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); - _new_qubit_data.clear(); - _operation_vector.clear(); - } else { // Large enough to multi-thread - #ifndef DOMP_GE_V3 // OMP version is too low; uses condition variables - // Lock the mutex so the threads stay asleep during prep - std::unique_lock state_lock(_state_mtx); - _new_qubit_data = wavefunction(_qubit_data.size()); - // jump_size gives a rough guess for how many states each thread should process - // to minimize contention for current_state - _jump_size = std::max((size_t)1 , (size_t)(_qubit_data.size() / (8*_thread_pool.size()))); - // Set the _current state (this allows the threads to wake up) - _current_state = _qubit_data.begin(); - - // Wake up all threads - cv.notify_all(); - // Wait for the number of running threads to be 0 - // This will spuriously wake many times - cv.wait(state_lock, [&](){ - return _current_state == _qubit_data.end() && _running_threads == 0; - }); - // Here all threads are finished - #else - #pragma omp parallel - { - _jump_size = std::max((size_t)1 , (size_t)(_qubit_data.size() / (8*omp_get_num_threads()))); - #pragma omp single - { - auto internal_state = _qubit_data.begin(); - auto internal_end = _qubit_data.end(); - for (auto current_state = _qubit_data.begin(); current_state != _qubit_data.end();){ - internal_state = current_state; - - //current_state.jump_forward(_jump_size); // Extra hash map functionality missing in STL - for (size_t i=0; (i<_jump_size) && (_current_state != _qubit_data.end()); i++) { - ++_current_state; - } - - internal_end = current_state; - #pragma omp task firstprivate(internal_state) firstprivate(internal_end) - { - qubit_label label; - amplitude val; - for (; internal_state != internal_end; ++internal_state){ - label = internal_state->first; - val = internal_state->second; - 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; - case OP::Assert: - if (get_parity(label & op.controls) != op.result && std::norm(val) > _precision_squared){ - std::cout << "Problematic state: " << label << "\n"; - std::cout << "Amplitude: " << val << "\n"; - std::cout << "Wavefunction size: " << _qubit_data.size() << "\n"; - throw std::runtime_error("Assert failed"); - } - break; - default: - throw std::runtime_error("Unsupported operation"); - break; - } - } - #pragma omp critical - { - _new_qubit_data.emplace(label, val); - } - } - } - } - } - #pragma omp barrier + 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; + case OP::Assert: + if (get_parity(label & op.controls) != op.result && std::norm(val) > _precision_squared){ + std::cout << "Problematic state: " << label << "\n"; + std::cout << "Amplitude: " << val << "\n"; + std::cout << "Wavefunction size: " << _qubit_data.size() << "\n"; + throw std::runtime_error("Assert failed"); + } + break; + default: + throw std::runtime_error("Unsupported operation"); + break; } - #endif - _qubit_data = std::move(_new_qubit_data); - _operation_vector.clear(); - _new_qubit_data.clear(); + } + // 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){ @@ -1185,34 +1040,6 @@ class QuantumState : public BasicQuantumState // Returns the rng from this simulator std::function get_rng() { return _rng; } - // Finishes the threads which wait for the queue - void complete_threads() { - in_use=false; - // If this spuriously wakes up threads, - // they will still end because in_use is false - _current_state = _qubit_data.begin(); - cv.notify_all(); - - for (auto &thread : _thread_pool){ - thread.join(); - } - } - - int get_num_threads() { - #ifndef DOMP_GE_V3 - return static_cast(_thread_pool.size()); - #else - int threads=0; - #pragma omp parallel - { - #pragma omp master - threads = omp_get_num_threads(); - } - return threads; - #endif - } - - private: // Internal type used to store operations with bitsets // instead of vectors of qubit ids @@ -1257,191 +1084,7 @@ class QuantumState : public BasicQuantumState qubit_label _get_mask(std::vector const& indices){ return get_mask(indices); } - - //********* Member variables for multithreading permutations ******// - // Shared iterator through the wavefunction - // Should point to _qubit_data.end() unless in use - decltype(_qubit_data.begin()) _current_state; - // Vector of waiting threads - std::vector _thread_pool; - // Condition variable, locked on _state_mtx - std::condition_variable cv; - // Mutex to prevent concurrent access to _current_state - std::mutex _state_mtx; - // Mutex to prevent concurrent access to _new_qubit_data - std::mutex _wfn_mtx; - // Used as a flag to decide when the thread pool is necessary, - // i.e., it is only set to false when the simulator is being destroyed - std::atomic in_use = true; - // Counts running threads, so controlling thread knows when the permutation is finished - std::atomic _running_threads = 0; - // A vector of operations that all threads point to to read which operations to apply - std::vector _operation_vector; - // New wavefunction which will replace the old one - wavefunction _new_qubit_data; - // The number of elements of the hash map that each thread will handle in one iteration - size_t _jump_size; - // Max number of threads - int max_num_threads = std::thread::hardware_concurrency(); - - // Called on creation, starts the thread pool and waits for all threads to - // finish initialization tasks - void _initialize_threads() { - // Unnecessary if OpenMP Is available - #ifndef DOMP_GE_V3 - // Prevents spurious wake-ups - _current_state = _qubit_data.end(); - _thread_pool = std::vector(); - #ifndef _OPENMP - int num_threads = std::thread::hardware_concurrency(); - #else - int num_threads = 1; - #pragma omp parallel - { - #pragma omp single - num_threads = omp_get_num_threads(); - } - #endif - // Lock the state, so that all the new threads get stopped during their execution - std::unique_lock state_lock(_state_mtx); - for (int i = 0; i < num_threads; i++){ - // Each running thread will decrement this just before it waits on the condition variable - ++_running_threads; - _thread_pool.push_back(std::thread([this](){this->_wait_to_permute();})); - } - // Waits until all the running threads have finished initializing - // This ensures the simulation does not actually start - // until the thread pool has finished initializing - cv.wait(state_lock, [&](){return _running_threads==0;}); - #endif - } - - // Function that all threads in _thread_pool run - // Waits on the condition variable, then applies _operation_vector - // to the qubit data - void _wait_to_permute(){ - // Prep local variables - auto local_state = _qubit_data.begin(); - auto local_end = _qubit_data.end(); - // Initialize a wfn lock to avoid recreating this object in every loop - std::unique_lock wfn_lock(_wfn_mtx); - wfn_lock.unlock(); - // First lock the state so we can wait on it - std::unique_lock state_lock(_state_mtx); - // Set the _current_state to ensure it waits - _current_state = _qubit_data.end(); - // Reduce number of running threads - _running_threads--; - // Notify all (i.e., the constructor thread) - // which will wait until the lock is released to check the number of running threads - cv.notify_all(); - // Wait until we set the current state to the start of the wavefunction - cv.wait(state_lock, [&](){ - return _current_state != _qubit_data.end(); - }); - - // Loop continuously - while (in_use){ - ++_running_threads; - // Loop through the wavefunction - while (_current_state != _qubit_data.end()){ - // Update local variables - local_state = _current_state; - - //_current_state.jump_forward(_jump_size); // Extra hash map functionality missing in STL - for (size_t i=0; (i<_jump_size) && (_current_state != _qubit_data.end()); i++) { - ++_current_state; - } - - local_end = _current_state; - // Unlock state to allow other threads to modify their state - state_lock.unlock(); - // Loop through the chunk that this thread has taken - for (; local_state != local_end; ++local_state){ - qubit_label label = local_state->first; - amplitude val = local_state->second; - - for (auto op : _operation_vector){ - 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; - case OP::Assert: - if (get_parity(label & op.controls) != op.result && std::norm(val) > _precision_squared){ - std::cout << "Problematic state: " << label << "\n"; - std::cout << "Amplitude: " << val << "\n"; - std::cout << "Wavefunction size: " << _qubit_data.size() << "\n"; - throw std::runtime_error("Assert failed"); - } - break; - default: - throw std::runtime_error("Unsupported operation"); - break; - } - } - - wfn_lock.lock(); - _new_qubit_data.emplace(label, val); - wfn_lock.unlock(); - } - // Lock before checking and modifying the current state - state_lock.lock(); - } - // This thread has finished iterating through the wavefunction - --_running_threads; - // Notify all other threads (i.e., the controlling thread) - // if this is the last thread to finish - // The check on _running_threads avoids spurious wake-ups to - // the controlling thread, but takes a bit of time as _running_threads - // is atomic - if (_running_threads ==0) - cv.notify_all(); - // Wait on the state lock before repeating the loop - // The wait unlocks state_lock - cv.wait(state_lock, [&](){return _current_state != _qubit_data.end();}); - } - } - - + // 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> From 2f191c6c595fbf4ee7e4c267e9dda0d92e63afbc Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Wed, 22 Sep 2021 19:34:23 -0700 Subject: [PATCH 22/25] Added C++17 standard missing in Release --- .../SparseSimulatorTests/SparseSimulatorTests.vcxproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj index fb1b867abfc..4cbcb322744 100644 --- a/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj +++ b/src/Simulation/Simulators/SparseSimulator/SparseSimulatorTests/SparseSimulatorTests.vcxproj @@ -149,6 +149,7 @@ NDEBUG;%(PreprocessorDefinitions) true pch.h + stdcpp17 Windows From 2869d65b38fed5bf7483031b830307152116095b Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Wed, 22 Sep 2021 19:54:03 -0700 Subject: [PATCH 23/25] Removed unused files and references --- .../SparseSimulator/Native/flat_hash_map.hpp | 1502 ----------------- .../Native/quantum_hash_map.hpp | 1302 -------------- .../SparseSimulator/Native/quantum_state.hpp | 2 - .../Simulators/SparseSimulator/Native/types.h | 14 - 4 files changed, 2820 deletions(-) delete mode 100644 src/Simulation/Simulators/SparseSimulator/Native/flat_hash_map.hpp delete mode 100644 src/Simulation/Simulators/SparseSimulator/Native/quantum_hash_map.hpp diff --git a/src/Simulation/Simulators/SparseSimulator/Native/flat_hash_map.hpp b/src/Simulation/Simulators/SparseSimulator/Native/flat_hash_map.hpp deleted file mode 100644 index c228b2dc8cd..00000000000 --- a/src/Simulation/Simulators/SparseSimulator/Native/flat_hash_map.hpp +++ /dev/null @@ -1,1502 +0,0 @@ -// Copyright Malte Skarupke 2017. -// Distributed under the Boost Software License, Version 1.0. -// (See http://www.boost.org/LICENSE_1_0.txt) - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef _MSC_VER -#define SKA_NOINLINE(...) __declspec(noinline) __VA_ARGS__ -#else -#define SKA_NOINLINE(...) __VA_ARGS__ __attribute__((noinline)) -#endif - -namespace ska -{ - struct prime_number_hash_policy; - struct power_of_two_hash_policy; - struct fibonacci_hash_policy; - - namespace detailv3 - { - template - struct functor_storage : Functor - { - functor_storage() = default; - functor_storage(const Functor& functor) - : Functor(functor) - { - } - template - Result operator()(Args &&... args) - { - return static_cast(*this)(std::forward(args)...); - } - template - Result operator()(Args &&... args) const - { - return static_cast(*this)(std::forward(args)...); - } - }; - template - struct functor_storage - { - typedef Result(*function_ptr)(Args...); - function_ptr function; - functor_storage(function_ptr function) - : function(function) - { - } - Result operator()(Args... args) const - { - return function(std::forward(args)...); - } - operator function_ptr& () - { - return function; - } - operator const function_ptr& () - { - return function; - } - }; - template - struct KeyOrValueHasher : functor_storage - { - typedef functor_storage hasher_storage; - KeyOrValueHasher() = default; - KeyOrValueHasher(const hasher& hash) - : hasher_storage(hash) - { - } - size_t operator()(const key_type& key) - { - return static_cast(*this)(key); - } - size_t operator()(const key_type& key) const - { - return static_cast(*this)(key); - } - size_t operator()(const value_type& value) - { - return static_cast(*this)(value.first); - } - size_t operator()(const value_type& value) const - { - return static_cast(*this)(value.first); - } - template - size_t operator()(const std::pair& value) - { - return static_cast(*this)(value.first); - } - template - size_t operator()(const std::pair& value) const - { - return static_cast(*this)(value.first); - } - }; - template - struct KeyOrValueEquality : functor_storage - { - typedef functor_storage equality_storage; - KeyOrValueEquality() = default; - KeyOrValueEquality(const key_equal& equality) - : equality_storage(equality) - { - } - bool operator()(const key_type& lhs, const key_type& rhs) - { - return static_cast(*this)(lhs, rhs); - } - bool operator()(const key_type& lhs, const value_type& rhs) - { - return static_cast(*this)(lhs, rhs.first); - } - bool operator()(const value_type& lhs, const key_type& rhs) - { - return static_cast(*this)(lhs.first, rhs); - } - bool operator()(const value_type& lhs, const value_type& rhs) - { - return static_cast(*this)(lhs.first, rhs.first); - } - template - bool operator()(const key_type& lhs, const std::pair& rhs) - { - return static_cast(*this)(lhs, rhs.first); - } - template - bool operator()(const std::pair& lhs, const key_type& rhs) - { - return static_cast(*this)(lhs.first, rhs); - } - template - bool operator()(const value_type& lhs, const std::pair& rhs) - { - return static_cast(*this)(lhs.first, rhs.first); - } - template - bool operator()(const std::pair& lhs, const value_type& rhs) - { - return static_cast(*this)(lhs.first, rhs.first); - } - template - bool operator()(const std::pair& lhs, const std::pair& rhs) - { - return static_cast(*this)(lhs.first, rhs.first); - } - }; - static constexpr int8_t min_lookups = 4; - template - struct sherwood_v3_entry - { - sherwood_v3_entry() - { - } - sherwood_v3_entry(int8_t distance_from_desired) - : distance_from_desired(distance_from_desired) - { - } - ~sherwood_v3_entry() - { - } - static sherwood_v3_entry* empty_default_table() - { - static sherwood_v3_entry result[min_lookups] = { {}, {}, {}, {special_end_value} }; - return result; - } - - bool has_value() const - { - return distance_from_desired >= 0; - } - bool is_empty() const - { - return distance_from_desired < 0; - } - bool is_at_desired_position() const - { - return distance_from_desired <= 0; - } - template - void emplace(int8_t distance, Args &&... args) - { - new (std::addressof(value)) T(std::forward(args)...); - distance_from_desired = distance; - } - - void destroy_value() - { - value.~T(); - distance_from_desired = -1; - } - - int8_t distance_from_desired = -1; - static constexpr int8_t special_end_value = 0; - union { T value; }; - }; - - inline int8_t log2(size_t value) - { - static constexpr int8_t table[64] = - { - 63, 0, 58, 1, 59, 47, 53, 2, - 60, 39, 48, 27, 54, 33, 42, 3, - 61, 51, 37, 40, 49, 18, 28, 20, - 55, 30, 34, 11, 43, 14, 22, 4, - 62, 57, 46, 52, 38, 26, 32, 41, - 50, 36, 17, 19, 29, 10, 13, 21, - 56, 45, 25, 31, 35, 16, 9, 12, - 44, 24, 15, 8, 23, 7, 6, 5 - }; - value |= value >> 1; - value |= value >> 2; - value |= value >> 4; - value |= value >> 8; - value |= value >> 16; - value |= value >> 32; - - // Modification: Cast is necessary because VS decides that when right-shifting signed integers, - // the sign bit should be copied to the new spaces - return table[(unsigned long long)((value - (value >> 1)) * 0x07EDD5E59A4E28C2) >> 58]; - } - - template - struct AssignIfTrue - { - void operator()(T& lhs, const T& rhs) - { - lhs = rhs; - } - void operator()(T& lhs, T&& rhs) - { - lhs = std::move(rhs); - } - }; - template - struct AssignIfTrue - { - void operator()(T&, const T&) - { - } - void operator()(T&, T&&) - { - } - }; - - inline size_t next_power_of_two(size_t i) - { - --i; - i |= i >> 1; - i |= i >> 2; - i |= i >> 4; - i |= i >> 8; - i |= i >> 16; - i |= i >> 32; - ++i; - return i; - } - - template using void_t = void; - - template - struct HashPolicySelector - { - typedef fibonacci_hash_policy type; - }; - template - struct HashPolicySelector> - { - typedef typename T::hash_policy type; - }; - - template - class sherwood_v3_table : private EntryAlloc, private Hasher, private Equal - { - using Entry = detailv3::sherwood_v3_entry; - using AllocatorTraits = std::allocator_traits; - using EntryPointer = typename AllocatorTraits::pointer; - struct convertible_to_iterator; - - public: - - using value_type = T; - using size_type = size_t; - using difference_type = std::ptrdiff_t; - using hasher = ArgumentHash; - using key_equal = ArgumentEqual; - using allocator_type = EntryAlloc; - using reference = value_type&; - using const_reference = const value_type&; - using pointer = value_type*; - using const_pointer = const value_type*; - - sherwood_v3_table() - { - } - explicit sherwood_v3_table(size_type bucket_count, const ArgumentHash& hash = ArgumentHash(), const ArgumentEqual& equal = ArgumentEqual(), const ArgumentAlloc& alloc = ArgumentAlloc()) - : EntryAlloc(alloc), Hasher(hash), Equal(equal) - { - rehash(bucket_count); - } - sherwood_v3_table(size_type bucket_count, const ArgumentAlloc& alloc) - : sherwood_v3_table(bucket_count, ArgumentHash(), ArgumentEqual(), alloc) - { - } - sherwood_v3_table(size_type bucket_count, const ArgumentHash& hash, const ArgumentAlloc& alloc) - : sherwood_v3_table(bucket_count, hash, ArgumentEqual(), alloc) - { - } - explicit sherwood_v3_table(const ArgumentAlloc& alloc) - : EntryAlloc(alloc) - { - } - template - sherwood_v3_table(It first, It last, size_type bucket_count = 0, const ArgumentHash& hash = ArgumentHash(), const ArgumentEqual& equal = ArgumentEqual(), const ArgumentAlloc& alloc = ArgumentAlloc()) - : sherwood_v3_table(bucket_count, hash, equal, alloc) - { - insert(first, last); - } - template - sherwood_v3_table(It first, It last, size_type bucket_count, const ArgumentAlloc& alloc) - : sherwood_v3_table(first, last, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) - { - } - template - sherwood_v3_table(It first, It last, size_type bucket_count, const ArgumentHash& hash, const ArgumentAlloc& alloc) - : sherwood_v3_table(first, last, bucket_count, hash, ArgumentEqual(), alloc) - { - } - sherwood_v3_table(std::initializer_list il, size_type bucket_count = 0, const ArgumentHash& hash = ArgumentHash(), const ArgumentEqual& equal = ArgumentEqual(), const ArgumentAlloc& alloc = ArgumentAlloc()) - : sherwood_v3_table(bucket_count, hash, equal, alloc) - { - if (bucket_count == 0) - rehash(il.size()); - insert(il.begin(), il.end()); - } - sherwood_v3_table(std::initializer_list il, size_type bucket_count, const ArgumentAlloc& alloc) - : sherwood_v3_table(il, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) - { - } - sherwood_v3_table(std::initializer_list il, size_type bucket_count, const ArgumentHash& hash, const ArgumentAlloc& alloc) - : sherwood_v3_table(il, bucket_count, hash, ArgumentEqual(), alloc) - { - } - sherwood_v3_table(const sherwood_v3_table& other) - : sherwood_v3_table(other, AllocatorTraits::select_on_container_copy_construction(other.get_allocator())) - { - } - sherwood_v3_table(const sherwood_v3_table& other, const ArgumentAlloc& alloc) - : EntryAlloc(alloc), Hasher(other), Equal(other), _max_load_factor(other._max_load_factor) - { - rehash_for_other_container(other); - try - { - insert(other.begin(), other.end()); - } - catch (...) - { - clear(); - deallocate_data(entries, num_slots_minus_one, max_lookups); - throw; - } - } - sherwood_v3_table(sherwood_v3_table&& other) noexcept - : EntryAlloc(std::move(other)), Hasher(std::move(other)), Equal(std::move(other)) - { - swap_pointers(other); - } - sherwood_v3_table(sherwood_v3_table&& other, const ArgumentAlloc& alloc) noexcept - : EntryAlloc(alloc), Hasher(std::move(other)), Equal(std::move(other)) - { - swap_pointers(other); - } - sherwood_v3_table& operator=(const sherwood_v3_table& other) - { - if (this == std::addressof(other)) - return *this; - - clear(); - if (AllocatorTraits::propagate_on_container_copy_assignment::value) - { - if (static_cast(*this) != static_cast(other)) - { - reset_to_empty_state(); - } - AssignIfTrue()(*this, other); - } - _max_load_factor = other._max_load_factor; - static_cast(*this) = other; - static_cast(*this) = other; - rehash_for_other_container(other); - insert(other.begin(), other.end()); - return *this; - } - sherwood_v3_table& operator=(sherwood_v3_table&& other) noexcept - { - if (this == std::addressof(other)) - return *this; - else if (AllocatorTraits::propagate_on_container_move_assignment::value) - { - clear(); - reset_to_empty_state(); - AssignIfTrue()(*this, std::move(other)); - swap_pointers(other); - } - else if (static_cast(*this) == static_cast(other)) - { - swap_pointers(other); - } - else - { - clear(); - _max_load_factor = other._max_load_factor; - rehash_for_other_container(other); - for (T& elem : other) - emplace(std::move(elem)); - other.clear(); - } - static_cast(*this) = std::move(other); - static_cast(*this) = std::move(other); - return *this; - } - ~sherwood_v3_table() - { - clear(); - deallocate_data(entries, num_slots_minus_one, max_lookups); - } - - const allocator_type& get_allocator() const - { - return static_cast(*this); - } - const ArgumentEqual& key_eq() const - { - return static_cast(*this); - } - const ArgumentHash& hash_function() const - { - return static_cast(*this); - } - - template - struct templated_iterator - { - templated_iterator() = default; - templated_iterator(EntryPointer current) - : current(current) - { - } - EntryPointer current = EntryPointer(); - - using iterator_category = std::forward_iterator_tag; - using value_type = ValueType; - using difference_type = ptrdiff_t; - using pointer = ValueType*; - using reference = ValueType&; - - friend bool operator==(const templated_iterator& lhs, const templated_iterator& rhs) - { - return lhs.current == rhs.current; - } - friend bool operator!=(const templated_iterator& lhs, const templated_iterator& rhs) - { - return !(lhs == rhs); - } - - templated_iterator& operator++() - { - do - { - ++current; - } while (current->is_empty()); - return *this; - } - templated_iterator operator++(int) - { - templated_iterator copy(*this); - ++* this; - return copy; - } - - ValueType& operator*() const - { - return current->value; - } - ValueType* operator->() const - { - return std::addressof(current->value); - } - - operator templated_iterator() const - { - return { current }; - } - }; - using iterator = templated_iterator; - using const_iterator = templated_iterator; - - iterator begin() - { - for (EntryPointer it = entries;; ++it) - { - if (it->has_value()) - return { it }; - } - } - const_iterator begin() const - { - for (EntryPointer it = entries;; ++it) - { - if (it->has_value()) - return { it }; - } - } - const_iterator cbegin() const - { - return begin(); - } - iterator end() - { - return { entries + static_cast(num_slots_minus_one + max_lookups) }; - } - const_iterator end() const - { - return { entries + static_cast(num_slots_minus_one + max_lookups) }; - } - const_iterator cend() const - { - return end(); - } - - iterator find(const FindKey& key) - { - size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); - EntryPointer it = entries + ptrdiff_t(index); - for (int8_t distance = 0; it->distance_from_desired >= distance; ++distance, ++it) - { - if (compares_equal(key, it->value)) - return { it }; - } - return end(); - } - const_iterator find(const FindKey& key) const - { - return const_cast(this)->find(key); - } - size_t count(const FindKey& key) const - { - return find(key) == end() ? 0 : 1; - } - std::pair equal_range(const FindKey& key) - { - iterator found = find(key); - if (found == end()) - return { found, found }; - else - return { found, std::next(found) }; - } - std::pair equal_range(const FindKey& key) const - { - const_iterator found = find(key); - if (found == end()) - return { found, found }; - else - return { found, std::next(found) }; - } - - template - std::pair emplace(Key&& key, Args &&... args) - { - size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); - EntryPointer current_entry = entries + ptrdiff_t(index); - int8_t distance_from_desired = 0; - for (; current_entry->distance_from_desired >= distance_from_desired; ++current_entry, ++distance_from_desired) - { - if (compares_equal(key, current_entry->value)) - return { { current_entry }, false }; - } - return emplace_new_key(distance_from_desired, current_entry, std::forward(key), std::forward(args)...); - } - - std::pair insert(const value_type& value) - { - return emplace(value); - } - std::pair insert(value_type&& value) - { - return emplace(std::move(value)); - } - template - iterator emplace_hint(const_iterator, Args &&... args) - { - return emplace(std::forward(args)...).first; - } - iterator insert(const_iterator, const value_type& value) - { - return emplace(value).first; - } - iterator insert(const_iterator, value_type&& value) - { - return emplace(std::move(value)).first; - } - - template - void insert(It begin, It end) - { - for (; begin != end; ++begin) - { - emplace(*begin); - } - } - void insert(std::initializer_list il) - { - insert(il.begin(), il.end()); - } - - void rehash(size_t num_buckets) - { - num_buckets = std::max(num_buckets, static_cast(std::ceil(num_elements / static_cast(_max_load_factor)))); - if (num_buckets == 0) - { - reset_to_empty_state(); - return; - } - auto new_prime_index = hash_policy.next_size_over(num_buckets); - if (num_buckets == bucket_count()) - return; - int8_t new_max_lookups = compute_max_lookups(num_buckets); - EntryPointer new_buckets(AllocatorTraits::allocate(*this, num_buckets + new_max_lookups)); - EntryPointer special_end_item = new_buckets + static_cast(num_buckets + new_max_lookups - 1); - for (EntryPointer it = new_buckets; it != special_end_item; ++it) - it->distance_from_desired = -1; - special_end_item->distance_from_desired = Entry::special_end_value; - std::swap(entries, new_buckets); - std::swap(num_slots_minus_one, num_buckets); - --num_slots_minus_one; - hash_policy.commit(new_prime_index); - int8_t old_max_lookups = max_lookups; - max_lookups = new_max_lookups; - num_elements = 0; - for (EntryPointer it = new_buckets, end = it + static_cast(num_buckets + old_max_lookups); it != end; ++it) - { - if (it->has_value()) - { - emplace(std::move(it->value)); - it->destroy_value(); - } - } - deallocate_data(new_buckets, num_buckets, old_max_lookups); - } - - void reserve(size_t num_elements) - { - size_t required_buckets = num_buckets_for_reserve(num_elements); - if (required_buckets > bucket_count()) - rehash(required_buckets); - } - - // the return value is a type that can be converted to an iterator - // the reason for doing this is that it's not free to find the - // iterator pointing at the next element. if you care about the - // next iterator, turn the return value into an iterator - convertible_to_iterator erase(const_iterator to_erase) - { - EntryPointer current = to_erase.current; - current->destroy_value(); - --num_elements; - for (EntryPointer next = current + ptrdiff_t(1); !next->is_at_desired_position(); ++current, ++next) - { - current->emplace(next->distance_from_desired - 1, std::move(next->value)); - next->destroy_value(); - } - return { to_erase.current }; - } - - iterator erase(const_iterator begin_it, const_iterator end_it) - { - if (begin_it == end_it) - return { begin_it.current }; - for (EntryPointer it = begin_it.current, end = end_it.current; it != end; ++it) - { - if (it->has_value()) - { - it->destroy_value(); - --num_elements; - } - } - if (end_it == this->end()) - return this->end(); - ptrdiff_t num_to_move = std::min(static_cast(end_it.current->distance_from_desired), end_it.current - begin_it.current); - EntryPointer to_return = end_it.current - num_to_move; - for (EntryPointer it = end_it.current; !it->is_at_desired_position();) - { - EntryPointer target = it - num_to_move; - target->emplace(it->distance_from_desired - num_to_move, std::move(it->value)); - it->destroy_value(); - ++it; - num_to_move = std::min(static_cast(it->distance_from_desired), num_to_move); - } - return { to_return }; - } - - size_t erase(const FindKey& key) - { - auto found = find(key); - if (found == end()) - return 0; - else - { - erase(found); - return 1; - } - } - - void clear() - { - for (EntryPointer it = entries, end = it + static_cast(num_slots_minus_one + max_lookups); it != end; ++it) - { - if (it->has_value()) - it->destroy_value(); - } - num_elements = 0; - } - - void shrink_to_fit() - { - rehash_for_other_container(*this); - } - - void swap(sherwood_v3_table& other) - { - using std::swap; - swap_pointers(other); - swap(static_cast(*this), static_cast(other)); - swap(static_cast(*this), static_cast(other)); - if (AllocatorTraits::propagate_on_container_swap::value) - swap(static_cast(*this), static_cast(other)); - } - - size_t size() const - { - return num_elements; - } - size_t max_size() const - { - return (AllocatorTraits::max_size(*this)) / sizeof(Entry); - } - size_t bucket_count() const - { - return num_slots_minus_one ? num_slots_minus_one + 1 : 0; - } - size_type max_bucket_count() const - { - return (AllocatorTraits::max_size(*this) - min_lookups) / sizeof(Entry); - } - size_t bucket(const FindKey& key) const - { - return hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); - } - float load_factor() const - { - size_t buckets = bucket_count(); - if (buckets) - return static_cast(num_elements) / bucket_count(); - else - return 0; - } - void max_load_factor(float value) - { - _max_load_factor = value; - } - float max_load_factor() const - { - return _max_load_factor; - } - - bool empty() const - { - return num_elements == 0; - } - - private: - EntryPointer entries = Entry::empty_default_table(); - size_t num_slots_minus_one = 0; - typename HashPolicySelector::type hash_policy; - int8_t max_lookups = detailv3::min_lookups - 1; - float _max_load_factor = 0.5f; - size_t num_elements = 0; - - static int8_t compute_max_lookups(size_t num_buckets) - { - int8_t desired = detailv3::log2(num_buckets); - return std::max(detailv3::min_lookups, desired); - } - - size_t num_buckets_for_reserve(size_t num_elements) const - { - return static_cast(std::ceil(num_elements / std::min(0.5, static_cast(_max_load_factor)))); - } - void rehash_for_other_container(const sherwood_v3_table& other) - { - rehash(std::min(num_buckets_for_reserve(other.size()), other.bucket_count())); - } - - void swap_pointers(sherwood_v3_table& other) - { - using std::swap; - swap(hash_policy, other.hash_policy); - swap(entries, other.entries); - swap(num_slots_minus_one, other.num_slots_minus_one); - swap(num_elements, other.num_elements); - swap(max_lookups, other.max_lookups); - swap(_max_load_factor, other._max_load_factor); - } - - template - SKA_NOINLINE(std::pair) emplace_new_key(int8_t distance_from_desired, EntryPointer current_entry, Key&& key, Args &&... args) - { - using std::swap; - if (num_slots_minus_one == 0 || distance_from_desired == max_lookups || num_elements + 1 > (num_slots_minus_one + 1) * static_cast(_max_load_factor)) - { - grow(); - return emplace(std::forward(key), std::forward(args)...); - } - else if (current_entry->is_empty()) - { - current_entry->emplace(distance_from_desired, std::forward(key), std::forward(args)...); - ++num_elements; - return { { current_entry }, true }; - } - value_type to_insert(std::forward(key), std::forward(args)...); - swap(distance_from_desired, current_entry->distance_from_desired); - swap(to_insert, current_entry->value); - iterator result = { current_entry }; - for (++distance_from_desired, ++current_entry;; ++current_entry) - { - if (current_entry->is_empty()) - { - current_entry->emplace(distance_from_desired, std::move(to_insert)); - ++num_elements; - return { result, true }; - } - else if (current_entry->distance_from_desired < distance_from_desired) - { - swap(distance_from_desired, current_entry->distance_from_desired); - swap(to_insert, current_entry->value); - ++distance_from_desired; - } - else - { - ++distance_from_desired; - if (distance_from_desired == max_lookups) - { - swap(to_insert, result.current->value); - grow(); - return emplace(std::move(to_insert)); - } - } - } - } - - void grow() - { - rehash(std::max(size_t(4), 2 * bucket_count())); - } - - void deallocate_data(EntryPointer begin, size_t num_slots_minus_one, int8_t max_lookups) - { - if (begin != Entry::empty_default_table()) - { - AllocatorTraits::deallocate(*this, begin, num_slots_minus_one + max_lookups + 1); - } - } - - void reset_to_empty_state() - { - deallocate_data(entries, num_slots_minus_one, max_lookups); - entries = Entry::empty_default_table(); - num_slots_minus_one = 0; - hash_policy.reset(); - max_lookups = detailv3::min_lookups - 1; - } - - template - size_t hash_object(const U& key) - { - return static_cast(*this)(key); - } - template - size_t hash_object(const U& key) const - { - return static_cast(*this)(key); - } - template - bool compares_equal(const L& lhs, const R& rhs) - { - return static_cast(*this)(lhs, rhs); - } - - struct convertible_to_iterator - { - EntryPointer it; - - operator iterator() - { - if (it->has_value()) - return { it }; - else - return ++iterator{ it }; - } - operator const_iterator() - { - if (it->has_value()) - return { it }; - else - return ++const_iterator{ it }; - } - }; - - }; - } - - struct prime_number_hash_policy - { - static size_t mod0(size_t) { return 0llu; } - static size_t mod2(size_t hash) { return hash % 2llu; } - static size_t mod3(size_t hash) { return hash % 3llu; } - static size_t mod5(size_t hash) { return hash % 5llu; } - static size_t mod7(size_t hash) { return hash % 7llu; } - static size_t mod11(size_t hash) { return hash % 11llu; } - static size_t mod13(size_t hash) { return hash % 13llu; } - static size_t mod17(size_t hash) { return hash % 17llu; } - static size_t mod23(size_t hash) { return hash % 23llu; } - static size_t mod29(size_t hash) { return hash % 29llu; } - static size_t mod37(size_t hash) { return hash % 37llu; } - static size_t mod47(size_t hash) { return hash % 47llu; } - static size_t mod59(size_t hash) { return hash % 59llu; } - static size_t mod73(size_t hash) { return hash % 73llu; } - static size_t mod97(size_t hash) { return hash % 97llu; } - static size_t mod127(size_t hash) { return hash % 127llu; } - static size_t mod151(size_t hash) { return hash % 151llu; } - static size_t mod197(size_t hash) { return hash % 197llu; } - static size_t mod251(size_t hash) { return hash % 251llu; } - static size_t mod313(size_t hash) { return hash % 313llu; } - static size_t mod397(size_t hash) { return hash % 397llu; } - static size_t mod499(size_t hash) { return hash % 499llu; } - static size_t mod631(size_t hash) { return hash % 631llu; } - static size_t mod797(size_t hash) { return hash % 797llu; } - static size_t mod1009(size_t hash) { return hash % 1009llu; } - static size_t mod1259(size_t hash) { return hash % 1259llu; } - static size_t mod1597(size_t hash) { return hash % 1597llu; } - static size_t mod2011(size_t hash) { return hash % 2011llu; } - static size_t mod2539(size_t hash) { return hash % 2539llu; } - static size_t mod3203(size_t hash) { return hash % 3203llu; } - static size_t mod4027(size_t hash) { return hash % 4027llu; } - static size_t mod5087(size_t hash) { return hash % 5087llu; } - static size_t mod6421(size_t hash) { return hash % 6421llu; } - static size_t mod8089(size_t hash) { return hash % 8089llu; } - static size_t mod10193(size_t hash) { return hash % 10193llu; } - static size_t mod12853(size_t hash) { return hash % 12853llu; } - static size_t mod16193(size_t hash) { return hash % 16193llu; } - static size_t mod20399(size_t hash) { return hash % 20399llu; } - static size_t mod25717(size_t hash) { return hash % 25717llu; } - static size_t mod32401(size_t hash) { return hash % 32401llu; } - static size_t mod40823(size_t hash) { return hash % 40823llu; } - static size_t mod51437(size_t hash) { return hash % 51437llu; } - static size_t mod64811(size_t hash) { return hash % 64811llu; } - static size_t mod81649(size_t hash) { return hash % 81649llu; } - static size_t mod102877(size_t hash) { return hash % 102877llu; } - static size_t mod129607(size_t hash) { return hash % 129607llu; } - static size_t mod163307(size_t hash) { return hash % 163307llu; } - static size_t mod205759(size_t hash) { return hash % 205759llu; } - static size_t mod259229(size_t hash) { return hash % 259229llu; } - static size_t mod326617(size_t hash) { return hash % 326617llu; } - static size_t mod411527(size_t hash) { return hash % 411527llu; } - static size_t mod518509(size_t hash) { return hash % 518509llu; } - static size_t mod653267(size_t hash) { return hash % 653267llu; } - static size_t mod823117(size_t hash) { return hash % 823117llu; } - static size_t mod1037059(size_t hash) { return hash % 1037059llu; } - static size_t mod1306601(size_t hash) { return hash % 1306601llu; } - static size_t mod1646237(size_t hash) { return hash % 1646237llu; } - static size_t mod2074129(size_t hash) { return hash % 2074129llu; } - static size_t mod2613229(size_t hash) { return hash % 2613229llu; } - static size_t mod3292489(size_t hash) { return hash % 3292489llu; } - static size_t mod4148279(size_t hash) { return hash % 4148279llu; } - static size_t mod5226491(size_t hash) { return hash % 5226491llu; } - static size_t mod6584983(size_t hash) { return hash % 6584983llu; } - static size_t mod8296553(size_t hash) { return hash % 8296553llu; } - static size_t mod10453007(size_t hash) { return hash % 10453007llu; } - static size_t mod13169977(size_t hash) { return hash % 13169977llu; } - static size_t mod16593127(size_t hash) { return hash % 16593127llu; } - static size_t mod20906033(size_t hash) { return hash % 20906033llu; } - static size_t mod26339969(size_t hash) { return hash % 26339969llu; } - static size_t mod33186281(size_t hash) { return hash % 33186281llu; } - static size_t mod41812097(size_t hash) { return hash % 41812097llu; } - static size_t mod52679969(size_t hash) { return hash % 52679969llu; } - static size_t mod66372617(size_t hash) { return hash % 66372617llu; } - static size_t mod83624237(size_t hash) { return hash % 83624237llu; } - static size_t mod105359939(size_t hash) { return hash % 105359939llu; } - static size_t mod132745199(size_t hash) { return hash % 132745199llu; } - static size_t mod167248483(size_t hash) { return hash % 167248483llu; } - static size_t mod210719881(size_t hash) { return hash % 210719881llu; } - static size_t mod265490441(size_t hash) { return hash % 265490441llu; } - static size_t mod334496971(size_t hash) { return hash % 334496971llu; } - static size_t mod421439783(size_t hash) { return hash % 421439783llu; } - static size_t mod530980861(size_t hash) { return hash % 530980861llu; } - static size_t mod668993977(size_t hash) { return hash % 668993977llu; } - static size_t mod842879579(size_t hash) { return hash % 842879579llu; } - static size_t mod1061961721(size_t hash) { return hash % 1061961721llu; } - static size_t mod1337987929(size_t hash) { return hash % 1337987929llu; } - static size_t mod1685759167(size_t hash) { return hash % 1685759167llu; } - static size_t mod2123923447(size_t hash) { return hash % 2123923447llu; } - static size_t mod2675975881(size_t hash) { return hash % 2675975881llu; } - static size_t mod3371518343(size_t hash) { return hash % 3371518343llu; } - static size_t mod4247846927(size_t hash) { return hash % 4247846927llu; } - static size_t mod5351951779(size_t hash) { return hash % 5351951779llu; } - static size_t mod6743036717(size_t hash) { return hash % 6743036717llu; } - static size_t mod8495693897(size_t hash) { return hash % 8495693897llu; } - static size_t mod10703903591(size_t hash) { return hash % 10703903591llu; } - static size_t mod13486073473(size_t hash) { return hash % 13486073473llu; } - static size_t mod16991387857(size_t hash) { return hash % 16991387857llu; } - static size_t mod21407807219(size_t hash) { return hash % 21407807219llu; } - static size_t mod26972146961(size_t hash) { return hash % 26972146961llu; } - static size_t mod33982775741(size_t hash) { return hash % 33982775741llu; } - static size_t mod42815614441(size_t hash) { return hash % 42815614441llu; } - static size_t mod53944293929(size_t hash) { return hash % 53944293929llu; } - static size_t mod67965551447(size_t hash) { return hash % 67965551447llu; } - static size_t mod85631228929(size_t hash) { return hash % 85631228929llu; } - static size_t mod107888587883(size_t hash) { return hash % 107888587883llu; } - static size_t mod135931102921(size_t hash) { return hash % 135931102921llu; } - static size_t mod171262457903(size_t hash) { return hash % 171262457903llu; } - static size_t mod215777175787(size_t hash) { return hash % 215777175787llu; } - static size_t mod271862205833(size_t hash) { return hash % 271862205833llu; } - static size_t mod342524915839(size_t hash) { return hash % 342524915839llu; } - static size_t mod431554351609(size_t hash) { return hash % 431554351609llu; } - static size_t mod543724411781(size_t hash) { return hash % 543724411781llu; } - static size_t mod685049831731(size_t hash) { return hash % 685049831731llu; } - static size_t mod863108703229(size_t hash) { return hash % 863108703229llu; } - static size_t mod1087448823553(size_t hash) { return hash % 1087448823553llu; } - static size_t mod1370099663459(size_t hash) { return hash % 1370099663459llu; } - static size_t mod1726217406467(size_t hash) { return hash % 1726217406467llu; } - static size_t mod2174897647073(size_t hash) { return hash % 2174897647073llu; } - static size_t mod2740199326961(size_t hash) { return hash % 2740199326961llu; } - static size_t mod3452434812973(size_t hash) { return hash % 3452434812973llu; } - static size_t mod4349795294267(size_t hash) { return hash % 4349795294267llu; } - static size_t mod5480398654009(size_t hash) { return hash % 5480398654009llu; } - static size_t mod6904869625999(size_t hash) { return hash % 6904869625999llu; } - static size_t mod8699590588571(size_t hash) { return hash % 8699590588571llu; } - static size_t mod10960797308051(size_t hash) { return hash % 10960797308051llu; } - static size_t mod13809739252051(size_t hash) { return hash % 13809739252051llu; } - static size_t mod17399181177241(size_t hash) { return hash % 17399181177241llu; } - static size_t mod21921594616111(size_t hash) { return hash % 21921594616111llu; } - static size_t mod27619478504183(size_t hash) { return hash % 27619478504183llu; } - static size_t mod34798362354533(size_t hash) { return hash % 34798362354533llu; } - static size_t mod43843189232363(size_t hash) { return hash % 43843189232363llu; } - static size_t mod55238957008387(size_t hash) { return hash % 55238957008387llu; } - static size_t mod69596724709081(size_t hash) { return hash % 69596724709081llu; } - static size_t mod87686378464759(size_t hash) { return hash % 87686378464759llu; } - static size_t mod110477914016779(size_t hash) { return hash % 110477914016779llu; } - static size_t mod139193449418173(size_t hash) { return hash % 139193449418173llu; } - static size_t mod175372756929481(size_t hash) { return hash % 175372756929481llu; } - static size_t mod220955828033581(size_t hash) { return hash % 220955828033581llu; } - static size_t mod278386898836457(size_t hash) { return hash % 278386898836457llu; } - static size_t mod350745513859007(size_t hash) { return hash % 350745513859007llu; } - static size_t mod441911656067171(size_t hash) { return hash % 441911656067171llu; } - static size_t mod556773797672909(size_t hash) { return hash % 556773797672909llu; } - static size_t mod701491027718027(size_t hash) { return hash % 701491027718027llu; } - static size_t mod883823312134381(size_t hash) { return hash % 883823312134381llu; } - static size_t mod1113547595345903(size_t hash) { return hash % 1113547595345903llu; } - static size_t mod1402982055436147(size_t hash) { return hash % 1402982055436147llu; } - static size_t mod1767646624268779(size_t hash) { return hash % 1767646624268779llu; } - static size_t mod2227095190691797(size_t hash) { return hash % 2227095190691797llu; } - static size_t mod2805964110872297(size_t hash) { return hash % 2805964110872297llu; } - static size_t mod3535293248537579(size_t hash) { return hash % 3535293248537579llu; } - static size_t mod4454190381383713(size_t hash) { return hash % 4454190381383713llu; } - static size_t mod5611928221744609(size_t hash) { return hash % 5611928221744609llu; } - static size_t mod7070586497075177(size_t hash) { return hash % 7070586497075177llu; } - static size_t mod8908380762767489(size_t hash) { return hash % 8908380762767489llu; } - static size_t mod11223856443489329(size_t hash) { return hash % 11223856443489329llu; } - static size_t mod14141172994150357(size_t hash) { return hash % 14141172994150357llu; } - static size_t mod17816761525534927(size_t hash) { return hash % 17816761525534927llu; } - static size_t mod22447712886978529(size_t hash) { return hash % 22447712886978529llu; } - static size_t mod28282345988300791(size_t hash) { return hash % 28282345988300791llu; } - static size_t mod35633523051069991(size_t hash) { return hash % 35633523051069991llu; } - static size_t mod44895425773957261(size_t hash) { return hash % 44895425773957261llu; } - static size_t mod56564691976601587(size_t hash) { return hash % 56564691976601587llu; } - static size_t mod71267046102139967(size_t hash) { return hash % 71267046102139967llu; } - static size_t mod89790851547914507(size_t hash) { return hash % 89790851547914507llu; } - static size_t mod113129383953203213(size_t hash) { return hash % 113129383953203213llu; } - static size_t mod142534092204280003(size_t hash) { return hash % 142534092204280003llu; } - static size_t mod179581703095829107(size_t hash) { return hash % 179581703095829107llu; } - static size_t mod226258767906406483(size_t hash) { return hash % 226258767906406483llu; } - static size_t mod285068184408560057(size_t hash) { return hash % 285068184408560057llu; } - static size_t mod359163406191658253(size_t hash) { return hash % 359163406191658253llu; } - static size_t mod452517535812813007(size_t hash) { return hash % 452517535812813007llu; } - static size_t mod570136368817120201(size_t hash) { return hash % 570136368817120201llu; } - static size_t mod718326812383316683(size_t hash) { return hash % 718326812383316683llu; } - static size_t mod905035071625626043(size_t hash) { return hash % 905035071625626043llu; } - static size_t mod1140272737634240411(size_t hash) { return hash % 1140272737634240411llu; } - static size_t mod1436653624766633509(size_t hash) { return hash % 1436653624766633509llu; } - static size_t mod1810070143251252131(size_t hash) { return hash % 1810070143251252131llu; } - static size_t mod2280545475268481167(size_t hash) { return hash % 2280545475268481167llu; } - static size_t mod2873307249533267101(size_t hash) { return hash % 2873307249533267101llu; } - static size_t mod3620140286502504283(size_t hash) { return hash % 3620140286502504283llu; } - static size_t mod4561090950536962147(size_t hash) { return hash % 4561090950536962147llu; } - static size_t mod5746614499066534157(size_t hash) { return hash % 5746614499066534157llu; } - static size_t mod7240280573005008577(size_t hash) { return hash % 7240280573005008577llu; } - static size_t mod9122181901073924329(size_t hash) { return hash % 9122181901073924329llu; } - static size_t mod11493228998133068689(size_t hash) { return hash % 11493228998133068689llu; } - static size_t mod14480561146010017169(size_t hash) { return hash % 14480561146010017169llu; } - static size_t mod18446744073709551557(size_t hash) { return hash % 18446744073709551557llu; } - - using mod_function = size_t(*)(size_t); - - mod_function next_size_over(size_t& size) const - { - // prime numbers generated by the following method: - // 1. start with a prime p = 2 - // 2. go to wolfram alpha and get p = NextPrime(2 * p) - // 3. repeat 2. until you overflow 64 bits - // you now have large gaps which you would hit if somebody called reserve() with an unlucky number. - // 4. to fill the gaps for every prime p go to wolfram alpha and get ClosestPrime(p * 2^(1/3)) and ClosestPrime(p * 2^(2/3)) and put those in the gaps - // 5. get PrevPrime(2^64) and put it at the end - static constexpr const size_t prime_list[] = - { - 2llu, 3llu, 5llu, 7llu, 11llu, 13llu, 17llu, 23llu, 29llu, 37llu, 47llu, - 59llu, 73llu, 97llu, 127llu, 151llu, 197llu, 251llu, 313llu, 397llu, - 499llu, 631llu, 797llu, 1009llu, 1259llu, 1597llu, 2011llu, 2539llu, - 3203llu, 4027llu, 5087llu, 6421llu, 8089llu, 10193llu, 12853llu, 16193llu, - 20399llu, 25717llu, 32401llu, 40823llu, 51437llu, 64811llu, 81649llu, - 102877llu, 129607llu, 163307llu, 205759llu, 259229llu, 326617llu, - 411527llu, 518509llu, 653267llu, 823117llu, 1037059llu, 1306601llu, - 1646237llu, 2074129llu, 2613229llu, 3292489llu, 4148279llu, 5226491llu, - 6584983llu, 8296553llu, 10453007llu, 13169977llu, 16593127llu, 20906033llu, - 26339969llu, 33186281llu, 41812097llu, 52679969llu, 66372617llu, - 83624237llu, 105359939llu, 132745199llu, 167248483llu, 210719881llu, - 265490441llu, 334496971llu, 421439783llu, 530980861llu, 668993977llu, - 842879579llu, 1061961721llu, 1337987929llu, 1685759167llu, 2123923447llu, - 2675975881llu, 3371518343llu, 4247846927llu, 5351951779llu, 6743036717llu, - 8495693897llu, 10703903591llu, 13486073473llu, 16991387857llu, - 21407807219llu, 26972146961llu, 33982775741llu, 42815614441llu, - 53944293929llu, 67965551447llu, 85631228929llu, 107888587883llu, - 135931102921llu, 171262457903llu, 215777175787llu, 271862205833llu, - 342524915839llu, 431554351609llu, 543724411781llu, 685049831731llu, - 863108703229llu, 1087448823553llu, 1370099663459llu, 1726217406467llu, - 2174897647073llu, 2740199326961llu, 3452434812973llu, 4349795294267llu, - 5480398654009llu, 6904869625999llu, 8699590588571llu, 10960797308051llu, - 13809739252051llu, 17399181177241llu, 21921594616111llu, 27619478504183llu, - 34798362354533llu, 43843189232363llu, 55238957008387llu, 69596724709081llu, - 87686378464759llu, 110477914016779llu, 139193449418173llu, - 175372756929481llu, 220955828033581llu, 278386898836457llu, - 350745513859007llu, 441911656067171llu, 556773797672909llu, - 701491027718027llu, 883823312134381llu, 1113547595345903llu, - 1402982055436147llu, 1767646624268779llu, 2227095190691797llu, - 2805964110872297llu, 3535293248537579llu, 4454190381383713llu, - 5611928221744609llu, 7070586497075177llu, 8908380762767489llu, - 11223856443489329llu, 14141172994150357llu, 17816761525534927llu, - 22447712886978529llu, 28282345988300791llu, 35633523051069991llu, - 44895425773957261llu, 56564691976601587llu, 71267046102139967llu, - 89790851547914507llu, 113129383953203213llu, 142534092204280003llu, - 179581703095829107llu, 226258767906406483llu, 285068184408560057llu, - 359163406191658253llu, 452517535812813007llu, 570136368817120201llu, - 718326812383316683llu, 905035071625626043llu, 1140272737634240411llu, - 1436653624766633509llu, 1810070143251252131llu, 2280545475268481167llu, - 2873307249533267101llu, 3620140286502504283llu, 4561090950536962147llu, - 5746614499066534157llu, 7240280573005008577llu, 9122181901073924329llu, - 11493228998133068689llu, 14480561146010017169llu, 18446744073709551557llu - }; - static constexpr size_t(* const mod_functions[])(size_t) = - { - &mod0, &mod2, &mod3, &mod5, &mod7, &mod11, &mod13, &mod17, &mod23, &mod29, &mod37, - &mod47, &mod59, &mod73, &mod97, &mod127, &mod151, &mod197, &mod251, &mod313, &mod397, - &mod499, &mod631, &mod797, &mod1009, &mod1259, &mod1597, &mod2011, &mod2539, &mod3203, - &mod4027, &mod5087, &mod6421, &mod8089, &mod10193, &mod12853, &mod16193, &mod20399, - &mod25717, &mod32401, &mod40823, &mod51437, &mod64811, &mod81649, &mod102877, - &mod129607, &mod163307, &mod205759, &mod259229, &mod326617, &mod411527, &mod518509, - &mod653267, &mod823117, &mod1037059, &mod1306601, &mod1646237, &mod2074129, - &mod2613229, &mod3292489, &mod4148279, &mod5226491, &mod6584983, &mod8296553, - &mod10453007, &mod13169977, &mod16593127, &mod20906033, &mod26339969, &mod33186281, - &mod41812097, &mod52679969, &mod66372617, &mod83624237, &mod105359939, &mod132745199, - &mod167248483, &mod210719881, &mod265490441, &mod334496971, &mod421439783, - &mod530980861, &mod668993977, &mod842879579, &mod1061961721, &mod1337987929, - &mod1685759167, &mod2123923447, &mod2675975881, &mod3371518343, &mod4247846927, - &mod5351951779, &mod6743036717, &mod8495693897, &mod10703903591, &mod13486073473, - &mod16991387857, &mod21407807219, &mod26972146961, &mod33982775741, &mod42815614441, - &mod53944293929, &mod67965551447, &mod85631228929, &mod107888587883, &mod135931102921, - &mod171262457903, &mod215777175787, &mod271862205833, &mod342524915839, - &mod431554351609, &mod543724411781, &mod685049831731, &mod863108703229, - &mod1087448823553, &mod1370099663459, &mod1726217406467, &mod2174897647073, - &mod2740199326961, &mod3452434812973, &mod4349795294267, &mod5480398654009, - &mod6904869625999, &mod8699590588571, &mod10960797308051, &mod13809739252051, - &mod17399181177241, &mod21921594616111, &mod27619478504183, &mod34798362354533, - &mod43843189232363, &mod55238957008387, &mod69596724709081, &mod87686378464759, - &mod110477914016779, &mod139193449418173, &mod175372756929481, &mod220955828033581, - &mod278386898836457, &mod350745513859007, &mod441911656067171, &mod556773797672909, - &mod701491027718027, &mod883823312134381, &mod1113547595345903, &mod1402982055436147, - &mod1767646624268779, &mod2227095190691797, &mod2805964110872297, &mod3535293248537579, - &mod4454190381383713, &mod5611928221744609, &mod7070586497075177, &mod8908380762767489, - &mod11223856443489329, &mod14141172994150357, &mod17816761525534927, - &mod22447712886978529, &mod28282345988300791, &mod35633523051069991, - &mod44895425773957261, &mod56564691976601587, &mod71267046102139967, - &mod89790851547914507, &mod113129383953203213, &mod142534092204280003, - &mod179581703095829107, &mod226258767906406483, &mod285068184408560057, - &mod359163406191658253, &mod452517535812813007, &mod570136368817120201, - &mod718326812383316683, &mod905035071625626043, &mod1140272737634240411, - &mod1436653624766633509, &mod1810070143251252131, &mod2280545475268481167, - &mod2873307249533267101, &mod3620140286502504283, &mod4561090950536962147, - &mod5746614499066534157, &mod7240280573005008577, &mod9122181901073924329, - &mod11493228998133068689, &mod14480561146010017169, &mod18446744073709551557 - }; - const size_t* found = std::lower_bound(std::begin(prime_list), std::end(prime_list) - 1, size); - size = *found; - return mod_functions[1 + found - prime_list]; - } - void commit(mod_function new_mod_function) - { - current_mod_function = new_mod_function; - } - void reset() - { - current_mod_function = &mod0; - } - - size_t index_for_hash(size_t hash, size_t /*num_slots_minus_one*/) const - { - return current_mod_function(hash); - } - size_t keep_in_range(size_t index, size_t num_slots_minus_one) const - { - return index > num_slots_minus_one ? current_mod_function(index) : index; - } - - private: - mod_function current_mod_function = &mod0; - }; - - struct power_of_two_hash_policy - { - size_t index_for_hash(size_t hash, size_t num_slots_minus_one) const - { - return hash & num_slots_minus_one; - } - size_t keep_in_range(size_t index, size_t num_slots_minus_one) const - { - return index_for_hash(index, num_slots_minus_one); - } - int8_t next_size_over(size_t& size) const - { - size = detailv3::next_power_of_two(size); - return 0; - } - void commit(int8_t) - { - } - void reset() - { - } - - }; - - // See here for a description: - // https://probablydance.com/2018/06/16/fibonacci-hashing-the-optimization-that-the-world-forgot-or-a-better-alternative-to-integer-modulo/ - struct fibonacci_hash_policy - { - size_t index_for_hash(size_t hash, size_t /*num_slots_minus_one*/) const - { - return (11400714819323198485ull * hash) >> shift; - } - size_t keep_in_range(size_t index, size_t num_slots_minus_one) const - { - return index & num_slots_minus_one; - } - - int8_t next_size_over(size_t& size) const - { - auto mid_result = detailv3::next_power_of_two(size); - size = std::max(size_t(2), detailv3::next_power_of_two(size)); - auto result = detailv3::log2(size); - return 64 - detailv3::log2(size); - } - void commit(int8_t shift) - { - this->shift = shift; - } - void reset() - { - shift = 63; - } - - private: - int8_t shift = 63; - }; - - template, typename E = std::equal_to, typename A = std::allocator > > - class flat_hash_map - : public detailv3::sherwood_v3_table - < - std::pair, - K, - H, - detailv3::KeyOrValueHasher, H>, - E, - detailv3::KeyOrValueEquality, E>, - A, - typename std::allocator_traits::template rebind_alloc>> - > - { - using Table = detailv3::sherwood_v3_table - < - std::pair, - K, - H, - detailv3::KeyOrValueHasher, H>, - E, - detailv3::KeyOrValueEquality, E>, - A, - typename std::allocator_traits::template rebind_alloc>> - >; - public: - - using key_type = K; - using mapped_type = V; - - using Table::Table; - flat_hash_map() - { - } - - inline V& operator[](const K& key) - { - return emplace(key, convertible_to_value()).first->second; - } - inline V& operator[](K&& key) - { - return emplace(std::move(key), convertible_to_value()).first->second; - } - V& at(const K& key) - { - auto found = this->find(key); - if (found == this->end()) - throw std::out_of_range("Argument passed to at() was not in the map."); - return found->second; - } - const V& at(const K& key) const - { - auto found = this->find(key); - if (found == this->end()) - throw std::out_of_range("Argument passed to at() was not in the map."); - return found->second; - } - - using Table::emplace; - std::pair emplace() - { - return emplace(key_type(), convertible_to_value()); - } - template - std::pair insert_or_assign(const key_type& key, M&& m) - { - auto emplace_result = emplace(key, std::forward(m)); - if (!emplace_result.second) - emplace_result.first->second = std::forward(m); - return emplace_result; - } - template - std::pair insert_or_assign(key_type&& key, M&& m) - { - auto emplace_result = emplace(std::move(key), std::forward(m)); - if (!emplace_result.second) - emplace_result.first->second = std::forward(m); - return emplace_result; - } - template - typename Table::iterator insert_or_assign(typename Table::const_iterator, const key_type& key, M&& m) - { - return insert_or_assign(key, std::forward(m)).first; - } - template - typename Table::iterator insert_or_assign(typename Table::const_iterator, key_type&& key, M&& m) - { - return insert_or_assign(std::move(key), std::forward(m)).first; - } - - friend bool operator==(const flat_hash_map& lhs, const flat_hash_map& rhs) - { - if (lhs.size() != rhs.size()) - return false; - for (const typename Table::value_type& value : lhs) - { - auto found = rhs.find(value.first); - if (found == rhs.end()) - return false; - else if (value.second != found->second) - return false; - } - return true; - } - friend bool operator!=(const flat_hash_map& lhs, const flat_hash_map& rhs) - { - return !(lhs == rhs); - } - - private: - struct convertible_to_value - { - operator V() const - { - return V(); - } - }; - }; - - template, typename E = std::equal_to, typename A = std::allocator > - class flat_hash_set - : public detailv3::sherwood_v3_table - < - T, - T, - H, - detailv3::functor_storage, - E, - detailv3::functor_storage, - A, - typename std::allocator_traits::template rebind_alloc> - > - { - using Table = detailv3::sherwood_v3_table - < - T, - T, - H, - detailv3::functor_storage, - E, - detailv3::functor_storage, - A, - typename std::allocator_traits::template rebind_alloc> - >; - public: - - using key_type = T; - - using Table::Table; - flat_hash_set() - { - } - - template - std::pair emplace(Args &&... args) - { - return Table::emplace(T(std::forward(args)...)); - } - std::pair emplace(const key_type& arg) - { - return Table::emplace(arg); - } - std::pair emplace(key_type& arg) - { - return Table::emplace(arg); - } - std::pair emplace(const key_type&& arg) - { - return Table::emplace(std::move(arg)); - } - std::pair emplace(key_type&& arg) - { - return Table::emplace(std::move(arg)); - } - - friend bool operator==(const flat_hash_set& lhs, const flat_hash_set& rhs) - { - if (lhs.size() != rhs.size()) - return false; - for (const T& value : lhs) - { - if (rhs.find(value) == rhs.end()) - return false; - } - return true; - } - friend bool operator!=(const flat_hash_set& lhs, const flat_hash_set& rhs) - { - return !(lhs == rhs); - } - }; - - - template - struct power_of_two_std_hash : std::hash - { - typedef ska::power_of_two_hash_policy hash_policy; - }; - -} // end namespace ska diff --git a/src/Simulation/Simulators/SparseSimulator/Native/quantum_hash_map.hpp b/src/Simulation/Simulators/SparseSimulator/Native/quantum_hash_map.hpp deleted file mode 100644 index d3ca6fe391c..00000000000 --- a/src/Simulation/Simulators/SparseSimulator/Native/quantum_hash_map.hpp +++ /dev/null @@ -1,1302 +0,0 @@ -// Copyright Malte Skarupke 2017. -// Distributed under the Boost Software License, Version 1.0. -// (See http://www.boost.org/LICENSE_1_0.txt) - -// This has small modifications from the original bytell_hash_map, -// noted in comments - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include "flat_hash_map.hpp" -#include -#include - -namespace ska -{ - - namespace detailv8 - { - using ska::detailv3::functor_storage; - using ska::detailv3::KeyOrValueHasher; - using ska::detailv3::KeyOrValueEquality; - using ska::detailv3::AssignIfTrue; - using ska::detailv3::HashPolicySelector; - - template - struct sherwood_v8_constants - { - static constexpr int8_t magic_for_empty = int8_t(0b11111111); - static constexpr int8_t magic_for_reserved = int8_t(0b11111110); - static constexpr int8_t bits_for_direct_hit = int8_t(0b10000000); - static constexpr int8_t magic_for_direct_hit = int8_t(0b00000000); - static constexpr int8_t magic_for_list_entry = int8_t(0b10000000); - - static constexpr int8_t bits_for_distance = int8_t(0b01111111); - inline static int distance_from_metadata(int8_t metadata) - { - return metadata & bits_for_distance; - } - - static constexpr int num_jump_distances = 126; - // jump distances chosen like this: - // 1. pick the first 16 integers to promote staying in the same block - // 2. add the next 66 triangular numbers to get even jumps when - // the hash table is a power of two - // 3. add 44 more triangular numbers at a much steeper growth rate - // to get a sequence that allows large jumps so that a table - // with 10000 sequential numbers doesn't endlessly re-allocate - // - // Visual Studio requires static casts on the larger numbers - static constexpr size_t jump_distances[num_jump_distances] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - - 21, 28, 36, 45, 55, 66, 78, 91, 105, 120, 136, 153, 171, 190, 210, 231, - 253, 276, 300, 325, 351, 378, 406, 435, 465, 496, 528, 561, 595, 630, - 666, 703, 741, 780, 820, 861, 903, 946, 990, 1035, 1081, 1128, 1176, - 1225, 1275, 1326, 1378, 1431, 1485, 1540, 1596, 1653, 1711, 1770, 1830, - 1891, 1953, 2016, 2080, 2145, 2211, 2278, 2346, 2415, 2485, 2556, - - 3741, 8385, 18915, 42486, 95703, 215496, 485605, 1091503, 2456436, - 5529475, 12437578, 27986421, 62972253, 141700195, 318819126, static_cast(717314626), - static_cast(1614000520), static_cast(3631437253), static_cast(8170829695), - static_cast(18384318876), static_cast(41364501751), static_cast(93070021080), - static_cast(93070021080), static_cast(209407709220), - static_cast(471167588430), static_cast(1060127437995), - static_cast(5366895564381), static_cast(12075513791265), static_cast(27169907873235), static_cast(61132301007778), - static_cast(137547673121001), static_cast(309482258302503), static_cast(696335090510256), static_cast(1566753939653640), - static_cast(3525196427195653), static_cast(7931691866727775), static_cast(17846306747368716), - static_cast(40154190394120111), static_cast(90346928493040500), static_cast(203280588949935750), - static_cast(457381324898247375), static_cast(1029107980662394500), static_cast(2315492957028380766), - static_cast(5209859150892887590), - }; - }; - template - constexpr int8_t sherwood_v8_constants::magic_for_empty; - template - constexpr int8_t sherwood_v8_constants::magic_for_reserved; - template - constexpr int8_t sherwood_v8_constants::bits_for_direct_hit; - template - constexpr int8_t sherwood_v8_constants::magic_for_direct_hit; - template - constexpr int8_t sherwood_v8_constants::magic_for_list_entry; - - template - constexpr int8_t sherwood_v8_constants::bits_for_distance; - - template - constexpr int sherwood_v8_constants::num_jump_distances; - template - constexpr size_t sherwood_v8_constants::jump_distances[num_jump_distances]; - - template - - struct sherwood_v8_block - { - sherwood_v8_block() - { - } - ~sherwood_v8_block() - { - } - int8_t control_bytes[BlockSize]; - union - { - T data[BlockSize]; - }; - - static sherwood_v8_block* empty_block() - { - static std::array empty_bytes = [] - { - std::array result; - result.fill(sherwood_v8_constants<>::magic_for_empty); - return result; - }(); - return reinterpret_cast(&empty_bytes); - } - - int first_empty_index() const - { - for (int i = 0; i < BlockSize; ++i) - { - if (control_bytes[i] == sherwood_v8_constants<>::magic_for_empty) - return i; - } - return -1; - } - - void fill_control_bytes(int8_t value) - { - std::fill(std::begin(control_bytes), std::end(control_bytes), value); - } - }; - - template - class sherwood_v8_table : private ByteAlloc, private Hasher, private Equal - { - using AllocatorTraits = std::allocator_traits; - using BlockType = sherwood_v8_block; - using BlockPointer = BlockType*; - using BytePointer = typename AllocatorTraits::pointer; - struct convertible_to_iterator; - using Constants = sherwood_v8_constants<>; - - public: - - using value_type = T; - using size_type = size_t; - using difference_type = std::ptrdiff_t; - using hasher = ArgumentHash; - using key_equal = ArgumentEqual; - using allocator_type = ByteAlloc; - using reference = value_type&; - using const_reference = const value_type&; - using pointer = value_type*; - using const_pointer = const value_type*; - - sherwood_v8_table() - { - } - explicit sherwood_v8_table(size_type bucket_count, const ArgumentHash& hash = ArgumentHash(), const ArgumentEqual& equal = ArgumentEqual(), const ArgumentAlloc& alloc = ArgumentAlloc()) - : ByteAlloc(alloc), Hasher(hash), Equal(equal) - { - if (bucket_count) - rehash(bucket_count); - } - sherwood_v8_table(size_type bucket_count, const ArgumentAlloc& alloc) - : sherwood_v8_table(bucket_count, ArgumentHash(), ArgumentEqual(), alloc) - { - } - sherwood_v8_table(size_type bucket_count, const ArgumentHash& hash, const ArgumentAlloc& alloc) - : sherwood_v8_table(bucket_count, hash, ArgumentEqual(), alloc) - { - } - explicit sherwood_v8_table(const ArgumentAlloc& alloc) - : ByteAlloc(alloc) - { - } - template - sherwood_v8_table(It first, It last, size_type bucket_count = 0, const ArgumentHash& hash = ArgumentHash(), const ArgumentEqual& equal = ArgumentEqual(), const ArgumentAlloc& alloc = ArgumentAlloc()) - : sherwood_v8_table(bucket_count, hash, equal, alloc) - { - insert(first, last); - } - template - sherwood_v8_table(It first, It last, size_type bucket_count, const ArgumentAlloc& alloc) - : sherwood_v8_table(first, last, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) - { - } - template - sherwood_v8_table(It first, It last, size_type bucket_count, const ArgumentHash& hash, const ArgumentAlloc& alloc) - : sherwood_v8_table(first, last, bucket_count, hash, ArgumentEqual(), alloc) - { - } - sherwood_v8_table(std::initializer_list il, size_type bucket_count = 0, const ArgumentHash& hash = ArgumentHash(), const ArgumentEqual& equal = ArgumentEqual(), const ArgumentAlloc& alloc = ArgumentAlloc()) - : sherwood_v8_table(bucket_count, hash, equal, alloc) - { - if (bucket_count == 0) - rehash(il.size()); - insert(il.begin(), il.end()); - } - sherwood_v8_table(std::initializer_list il, size_type bucket_count, const ArgumentAlloc& alloc) - : sherwood_v8_table(il, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) - { - } - sherwood_v8_table(std::initializer_list il, size_type bucket_count, const ArgumentHash& hash, const ArgumentAlloc& alloc) - : sherwood_v8_table(il, bucket_count, hash, ArgumentEqual(), alloc) - { - } - sherwood_v8_table(const sherwood_v8_table& other) - : sherwood_v8_table(other, AllocatorTraits::select_on_container_copy_construction(other.get_allocator())) - { - } - sherwood_v8_table(const sherwood_v8_table& other, const ArgumentAlloc& alloc) - : ByteAlloc(alloc), Hasher(other), Equal(other), _max_load_factor(other._max_load_factor) - { - rehash_for_other_container(other); - try - { - insert(other.begin(), other.end()); - } - catch (...) - { - clear(); - deallocate_data(entries, num_slots_minus_one); - throw; - } - } - sherwood_v8_table(sherwood_v8_table&& other) noexcept - : ByteAlloc(std::move(other)), Hasher(std::move(other)), Equal(std::move(other)) - , _max_load_factor(other._max_load_factor) - { - swap_pointers(other); - } - sherwood_v8_table(sherwood_v8_table&& other, const ArgumentAlloc& alloc) noexcept - : ByteAlloc(alloc), Hasher(std::move(other)), Equal(std::move(other)) - , _max_load_factor(other._max_load_factor) - { - swap_pointers(other); - } - sherwood_v8_table& operator=(const sherwood_v8_table& other) - { - if (this == std::addressof(other)) - return *this; - - clear(); - if (AllocatorTraits::propagate_on_container_copy_assignment::value) - { - if (static_cast(*this) != static_cast(other)) - { - reset_to_empty_state(); - } - AssignIfTrue()(*this, other); - } - _max_load_factor = other._max_load_factor; - static_cast(*this) = other; - static_cast(*this) = other; - rehash_for_other_container(other); - insert(other.begin(), other.end()); - return *this; - } - sherwood_v8_table& operator=(sherwood_v8_table&& other) noexcept - { - if (this == std::addressof(other)) - return *this; - else if (AllocatorTraits::propagate_on_container_move_assignment::value) - { - clear(); - reset_to_empty_state(); - AssignIfTrue()(*this, std::move(other)); - swap_pointers(other); - } - else if (static_cast(*this) == static_cast(other)) - { - swap_pointers(other); - } - else - { - clear(); - _max_load_factor = other._max_load_factor; - rehash_for_other_container(other); - for (T& elem : other) - emplace(std::move(elem)); - other.clear(); - } - static_cast(*this) = std::move(other); - static_cast(*this) = std::move(other); - return *this; - } - ~sherwood_v8_table() - { - clear(); - deallocate_data(entries, num_slots_minus_one); - } - - const allocator_type& get_allocator() const - { - return static_cast(*this); - } - const ArgumentEqual& key_eq() const - { - return static_cast(*this); - } - const ArgumentHash& hash_function() const - { - return static_cast(*this); - } - - template - struct templated_iterator - { - private: - friend class sherwood_v8_table; - BlockPointer current = BlockPointer(); - size_t index = 0; - - public: - templated_iterator() - { - } - templated_iterator(BlockPointer entries, size_t index) - : current(entries) - , index(index) - { - } - - using iterator_category = std::forward_iterator_tag; - using value_type = ValueType; - using difference_type = ptrdiff_t; - using pointer = ValueType*; - using reference = ValueType&; - - friend bool operator==(const templated_iterator& lhs, const templated_iterator& rhs) - { - return lhs.index == rhs.index; - } - friend bool operator!=(const templated_iterator& lhs, const templated_iterator& rhs) - { - return !(lhs == rhs); - } - - templated_iterator& operator++() - { - do - { - if (index % BlockSize == 0) - --current; - if (index-- == 0) - break; - } while (current->control_bytes[index % BlockSize] == Constants::magic_for_empty); - return *this; - } - - // Jumps forward by some number of blocks - // Added for faster quantum state iteration - templated_iterator& jump_forward(size_t n = 1){ - if (index < n*BlockSize){ - current -= (index - 1)/BlockSize; - index = std::numeric_limits::max(); - } else { - index -= n*BlockSize; - current -= n; - while (current->control_bytes[index % BlockSize] == Constants::magic_for_empty) { - if (index % BlockSize == 0) - --current; - if (index-- == 0) - break; - } - } - return *this; - } - - templated_iterator operator++(int) - { - templated_iterator copy(*this); - ++* this; - return copy; - } - - ValueType& operator*() const - { - return current->data[index % BlockSize]; - } - ValueType* operator->() const - { - return current->data + index % BlockSize; - } - - operator templated_iterator() const - { - return { current, index }; - } - }; - using iterator = templated_iterator; - using const_iterator = templated_iterator; - - iterator begin() - { - size_t num_slots = num_slots_minus_one ? num_slots_minus_one + 1 : 0; - return ++iterator{ entries + num_slots / BlockSize, num_slots }; - } - const_iterator begin() const - { - size_t num_slots = num_slots_minus_one ? num_slots_minus_one + 1 : 0; - return ++iterator{ entries + num_slots / BlockSize, num_slots }; - } - const_iterator cbegin() const - { - return begin(); - } - iterator end() - { - return { entries - 1, std::numeric_limits::max() }; - } - const_iterator end() const - { - return { entries - 1, std::numeric_limits::max() }; - } - const_iterator cend() const - { - return end(); - } - - - - inline iterator find(const FindKey& key) - { - size_t index = hash_object(key); - size_t num_slots_minus_one = this->num_slots_minus_one; - BlockPointer entries = this->entries; - index = hash_policy.index_for_hash(index, num_slots_minus_one); - bool first = true; - for (;;) - { - // These are positions within the blocks of headers - // see https://youtu.be/M2fKMP47slQ?t=2135 - size_t block_index = index / BlockSize; - int index_in_block = index % BlockSize; - BlockPointer block = entries + block_index; - int8_t metadata = block->control_bytes[index_in_block]; - if (first) - { - // If the first bit is non-zero: return "end()" - // end() is the address at the end of the table - // It signifies the data is not found - // This occurs here because the metadata indicates that the slot is empty - // see here https://youtu.be/M2fKMP47slQ - // for the description of how the "magic_for_direct_hit" is a bit - // telling us whether or not this is a "real" hash value - if ((metadata & Constants::bits_for_direct_hit) != Constants::magic_for_direct_hit) - return end(); - first = false; - } - // Rreturn the element if the key matches the currently indexed element - if (compares_equal(key, block->data[index_in_block])) - return { block, index }; - // Get the jump distance index - int8_t to_next_index = metadata & Constants::bits_for_distance; - // If there is no more jump distance, we have hit the end of the table - if (to_next_index == 0) - return end(); - // Go to the next slot indicated by the jump distance - // see https://youtu.be/M2fKMP47slQ - index += Constants::jump_distances[to_next_index]; - index = hash_policy.keep_in_range(index, num_slots_minus_one); - } - } - inline const_iterator find(const FindKey& key) const - { - return const_cast(this)->find(key); - } - size_t count(const FindKey& key) const - { - return find(key) == end() ? 0 : 1; - } - std::pair equal_range(const FindKey& key) - { - iterator found = find(key); - if (found == end()) - return { found, found }; - else - return { found, std::next(found) }; - } - std::pair equal_range(const FindKey& key) const - { - const_iterator found = find(key); - if (found == end()) - return { found, found }; - else - return { found, std::next(found) }; - } - - - template - inline std::pair emplace(Key&& key, Args &&... args) - { - size_t index = hash_object(key); - size_t num_slots_minus_one = this->num_slots_minus_one; - BlockPointer entries = this->entries; - index = hash_policy.index_for_hash(index, num_slots_minus_one); - bool first = true; - for (;;) - { - size_t block_index = index / BlockSize; - int index_in_block = index % BlockSize; - BlockPointer block = entries + block_index; - int8_t metadata = block->control_bytes[index_in_block]; - if (first) - { - if ((metadata & Constants::bits_for_direct_hit) != Constants::magic_for_direct_hit) - return emplace_direct_hit({ index, block }, std::forward(key), std::forward(args)...); - first = false; - } - if (compares_equal(key, block->data[index_in_block])) - return { { block, index }, false }; - int8_t to_next_index = metadata & Constants::bits_for_distance; - if (to_next_index == 0) - return emplace_new_key({ index, block }, std::forward(key), std::forward(args)...); - index += Constants::jump_distances[to_next_index]; - index = hash_policy.keep_in_range(index, num_slots_minus_one); - } - } - - std::pair insert(const value_type& value) - { - return emplace(value); - } - std::pair insert(value_type&& value) - { - return emplace(std::move(value)); - } - template - iterator emplace_hint(const_iterator, Args &&... args) - { - return emplace(std::forward(args)...).first; - } - iterator insert(const_iterator, const value_type& value) - { - return emplace(value).first; - } - iterator insert(const_iterator, value_type&& value) - { - return emplace(std::move(value)).first; - } - - template - void insert(It begin, It end) - { - for (; begin != end; ++begin) - { - emplace(*begin); - } - } - void insert(std::initializer_list il) - { - insert(il.begin(), il.end()); - } - - void rehash(size_t num_items) - { - num_items = std::max(num_items, static_cast(std::ceil(num_elements / static_cast(_max_load_factor)))); - if (num_items == 0) - { - reset_to_empty_state(); - return; - } - auto new_prime_index = hash_policy.next_size_over(num_items); - if (num_items == num_slots_minus_one + 1) - return; - size_t num_blocks = num_items / BlockSize; - if (num_items % BlockSize) - ++num_blocks; - size_t memory_requirement = calculate_memory_requirement(num_blocks); - unsigned char* new_memory = &*AllocatorTraits::allocate(*this, memory_requirement); - BlockPointer new_buckets = reinterpret_cast(new_memory); - - BlockPointer special_end_item = new_buckets + num_blocks; - for (BlockPointer it = new_buckets; it <= special_end_item; ++it) - it->fill_control_bytes(Constants::magic_for_empty); - using std::swap; - swap(entries, new_buckets); - swap(num_slots_minus_one, num_items); - --num_slots_minus_one; - hash_policy.commit(new_prime_index); - num_elements = 0; - if (num_items) - ++num_items; - size_t old_num_blocks = num_items / BlockSize; - if (num_items % BlockSize) - ++old_num_blocks; - for (BlockPointer it = new_buckets, end = new_buckets + old_num_blocks; it != end; ++it) - { - for (int i = 0; i < BlockSize; ++i) - { - int8_t metadata = it->control_bytes[i]; - if (metadata != Constants::magic_for_empty && metadata != Constants::magic_for_reserved) - { - emplace(std::move(it->data[i])); - AllocatorTraits::destroy(*this, it->data + i); - } - } - } - deallocate_data(new_buckets, num_items - 1); - } - - void reserve(size_t num_elements) - { - size_t required_buckets = num_buckets_for_reserve(num_elements); - if (required_buckets > bucket_count()) - rehash(required_buckets); - } - - // the return value is a type that can be converted to an iterator - // the reason for doing this is that it's not free to find the - // iterator pointing at the next element. if you care about the - // next iterator, turn the return value into an iterator - convertible_to_iterator erase(const_iterator to_erase) - { - LinkedListIt current = { to_erase.index, to_erase.current }; - if (current.has_next()) - { - LinkedListIt previous = current; - LinkedListIt next = current.next(*this); - while (next.has_next()) - { - previous = next; - next = next.next(*this); - } - AllocatorTraits::destroy(*this, std::addressof(*current)); - AllocatorTraits::construct(*this, std::addressof(*current), std::move(*next)); - AllocatorTraits::destroy(*this, std::addressof(*next)); - next.set_metadata(Constants::magic_for_empty); - previous.clear_next(); - } - else - { - if (!current.is_direct_hit()) - find_parent_block(current).clear_next(); - AllocatorTraits::destroy(*this, std::addressof(*current)); - current.set_metadata(Constants::magic_for_empty); - } - --num_elements; - return { to_erase.current, to_erase.index }; - } - - iterator erase(const_iterator begin_it, const_iterator end_it) - { - if (begin_it == end_it) - return { begin_it.current, begin_it.index }; - if (std::next(begin_it) == end_it) - return erase(begin_it); - if (begin_it == begin() && end_it == end()) - { - clear(); - return { end_it.current, end_it.index }; - } - std::vector> depth_in_chain; - for (const_iterator it = begin_it; it != end_it; ++it) - { - LinkedListIt list_it(it.index, it.current); - if (list_it.is_direct_hit()) - depth_in_chain.emplace_back(0, list_it); - else - { - LinkedListIt root = find_direct_hit(list_it); - int distance = 1; - for (;;) - { - LinkedListIt next = root.next(*this); - if (next == list_it) - break; - ++distance; - root = next; - } - depth_in_chain.emplace_back(distance, list_it); - } - } - std::sort(depth_in_chain.begin(), depth_in_chain.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); - for (auto it = depth_in_chain.rbegin(), end = depth_in_chain.rend(); it != end; ++it) - { - erase(it->second.it()); - } - - if (begin_it.current->control_bytes[begin_it.index % BlockSize] == Constants::magic_for_empty) - return ++iterator{ begin_it.current, begin_it.index }; - else - return { begin_it.current, begin_it.index }; - } - - size_t erase(const FindKey& key) - { - auto found = find(key); - if (found == end()) - return 0; - else - { - erase(found); - return 1; - } - } - - void clear() - { - if (!num_slots_minus_one) - return; - size_t num_slots = num_slots_minus_one + 1; - size_t num_blocks = num_slots / BlockSize; - if (num_slots % BlockSize) - ++num_blocks; - for (BlockPointer it = entries, end = it + num_blocks; it != end; ++it) - { - for (int i = 0; i < BlockSize; ++i) - { - if (it->control_bytes[i] != Constants::magic_for_empty) - { - AllocatorTraits::destroy(*this, std::addressof(it->data[i])); - it->control_bytes[i] = Constants::magic_for_empty; - } - } - } - num_elements = 0; - } - - void shrink_to_fit() - { - rehash_for_other_container(*this); - } - - void swap(sherwood_v8_table& other) - { - using std::swap; - swap_pointers(other); - swap(static_cast(*this), static_cast(other)); - swap(static_cast(*this), static_cast(other)); - if (AllocatorTraits::propagate_on_container_swap::value) - swap(static_cast(*this), static_cast(other)); - } - - size_t size() const - { - return num_elements; - } - size_t max_size() const - { - return (AllocatorTraits::max_size(*this)) / sizeof(T); - } - size_t bucket_count() const - { - return num_slots_minus_one ? num_slots_minus_one + 1 : 0; - } - size_type max_bucket_count() const - { - return (AllocatorTraits::max_size(*this)) / sizeof(T); - } - size_t bucket(const FindKey& key) const - { - return hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); - } - float load_factor() const - { - return static_cast(num_elements) / (num_slots_minus_one + 1); - } - void max_load_factor(float value) - { - _max_load_factor = value; - } - float max_load_factor() const - { - return _max_load_factor; - } - - bool empty() const - { - return num_elements == 0; - } - - private: - BlockPointer entries = BlockType::empty_block(); - size_t num_slots_minus_one = 0; - typename HashPolicySelector::type hash_policy; - float _max_load_factor = 0.9375f; - size_t num_elements = 0; - - size_t num_buckets_for_reserve(size_t num_elements) const - { - return static_cast(std::ceil(num_elements / static_cast(_max_load_factor))); - } - void rehash_for_other_container(const sherwood_v8_table& other) - { - rehash(std::min(num_buckets_for_reserve(other.size()), other.bucket_count())); - } - bool is_full() const - { - if (!num_slots_minus_one) - return true; - else - return num_elements + 1 > (num_slots_minus_one + 1) * static_cast(_max_load_factor); - } - - void swap_pointers(sherwood_v8_table& other) - { - using std::swap; - swap(hash_policy, other.hash_policy); - swap(entries, other.entries); - swap(num_slots_minus_one, other.num_slots_minus_one); - swap(num_elements, other.num_elements); - swap(_max_load_factor, other._max_load_factor); - } - - struct LinkedListIt - { - size_t index = 0; - BlockPointer block = nullptr; - - LinkedListIt() - { - } - LinkedListIt(size_t index, BlockPointer block) - : index(index), block(block) - { - } - - iterator it() const - { - return { block, index }; - } - int index_in_block() const - { - return index % BlockSize; - } - bool is_direct_hit() const - { - return (metadata() & Constants::bits_for_direct_hit) == Constants::magic_for_direct_hit; - } - bool is_empty() const - { - return metadata() == Constants::magic_for_empty; - } - bool has_next() const - { - return jump_index() != 0; - } - int8_t jump_index() const - { - return Constants::distance_from_metadata(metadata()); - } - int8_t metadata() const - { - return block->control_bytes[index_in_block()]; - } - void set_metadata(int8_t metadata) - { - block->control_bytes[index_in_block()] = metadata; - } - - LinkedListIt next(sherwood_v8_table& table) const - { - int8_t distance = jump_index(); - size_t next_index = table.hash_policy.keep_in_range(index + Constants::jump_distances[distance], table.num_slots_minus_one); - return { next_index, table.entries + next_index / BlockSize }; - } - void set_next(int8_t jump_index) - { - int8_t& metadata = block->control_bytes[index_in_block()]; - metadata = (metadata & ~Constants::bits_for_distance) | jump_index; - } - void clear_next() - { - set_next(0); - } - - value_type& operator*() const - { - return block->data[index_in_block()]; - } - bool operator!() const - { - return !block; - } - explicit operator bool() const - { - return block != nullptr; - } - bool operator==(const LinkedListIt& other) const - { - return index == other.index; - } - bool operator!=(const LinkedListIt& other) const - { - return !(*this == other); - } - }; - - template - SKA_NOINLINE(std::pair) emplace_direct_hit(LinkedListIt block, Args &&... args) - { - using std::swap; - if (is_full()) - { - grow(); - return emplace(std::forward(args)...); - } - if (block.metadata() == Constants::magic_for_empty) - { - AllocatorTraits::construct(*this, std::addressof(*block), std::forward(args)...); - block.set_metadata(Constants::magic_for_direct_hit); - ++num_elements; - return { block.it(), true }; - } - else - { - LinkedListIt parent_block = find_parent_block(block); - std::pair free_block = find_free_index(parent_block); - if (!free_block.first) - { - grow(); - return emplace(std::forward(args)...); - } - value_type new_value(std::forward(args)...); - for (LinkedListIt it = block;;) - { - AllocatorTraits::construct(*this, std::addressof(*free_block.second), std::move(*it)); - AllocatorTraits::destroy(*this, std::addressof(*it)); - parent_block.set_next(free_block.first); - free_block.second.set_metadata(Constants::magic_for_list_entry); - if (!it.has_next()) - { - it.set_metadata(Constants::magic_for_empty); - break; - } - LinkedListIt next = it.next(*this); - it.set_metadata(Constants::magic_for_empty); - block.set_metadata(Constants::magic_for_reserved); - it = next; - parent_block = free_block.second; - free_block = find_free_index(free_block.second); - if (!free_block.first) - { - grow(); - return emplace(std::move(new_value)); - } - } - AllocatorTraits::construct(*this, std::addressof(*block), std::move(new_value)); - block.set_metadata(Constants::magic_for_direct_hit); - ++num_elements; - return { block.it(), true }; - } - } - - template - SKA_NOINLINE(std::pair) emplace_new_key(LinkedListIt parent, Args &&... args) - { - if (is_full()) - { - grow(); - return emplace(std::forward(args)...); - } - std::pair free_block = find_free_index(parent); - if (!free_block.first) - { - grow(); - return emplace(std::forward(args)...); - } - AllocatorTraits::construct(*this, std::addressof(*free_block.second), std::forward(args)...); - free_block.second.set_metadata(Constants::magic_for_list_entry); - parent.set_next(free_block.first); - ++num_elements; - return { free_block.second.it(), true }; - } - - LinkedListIt find_direct_hit(LinkedListIt child) const - { - size_t to_move_hash = hash_object(*child); - size_t to_move_index = hash_policy.index_for_hash(to_move_hash, num_slots_minus_one); - return { to_move_index, entries + to_move_index / BlockSize }; - } - LinkedListIt find_parent_block(LinkedListIt child) - { - LinkedListIt parent_block = find_direct_hit(child); - for (;;) - { - LinkedListIt next = parent_block.next(*this); - if (next == child) - return parent_block; - parent_block = next; - } - } - - std::pair find_free_index(LinkedListIt parent) const - { - for (int8_t jump_index = 1; jump_index < Constants::num_jump_distances; ++jump_index) - { - size_t index = hash_policy.keep_in_range(parent.index + Constants::jump_distances[jump_index], num_slots_minus_one); - BlockPointer block = entries + index / BlockSize; - if (block->control_bytes[index % BlockSize] == Constants::magic_for_empty) - return { jump_index, { index, block } }; - } - return { 0, {} }; - } - - void grow() - { - rehash(std::max(size_t(10), 2 * bucket_count())); - } - - size_t calculate_memory_requirement(size_t num_blocks) - { - size_t memory_required = sizeof(BlockType) * num_blocks; - memory_required += BlockSize; // for metadata of past-the-end pointer - return memory_required; - } - - void deallocate_data(BlockPointer begin, size_t num_slots_minus_one) - { - if (begin == BlockType::empty_block()) - return; - - ++num_slots_minus_one; - size_t num_blocks = num_slots_minus_one / BlockSize; - if (num_slots_minus_one % BlockSize) - ++num_blocks; - size_t memory = calculate_memory_requirement(num_blocks); - unsigned char* as_byte_pointer = reinterpret_cast(begin); - AllocatorTraits::deallocate(*this, typename AllocatorTraits::pointer(as_byte_pointer), memory); - } - - void reset_to_empty_state() - { - deallocate_data(entries, num_slots_minus_one); - entries = BlockType::empty_block(); - num_slots_minus_one = 0; - hash_policy.reset(); - } - - template - size_t hash_object(const U& key) - { - return static_cast(*this)(key); - } - template - size_t hash_object(const U& key) const - { - return static_cast(*this)(key); - } - template - bool compares_equal(const L& lhs, const R& rhs) - { - return static_cast(*this)(lhs, rhs); - } - - struct convertible_to_iterator - { - BlockPointer it; - size_t index; - - operator iterator() - { - if (it->control_bytes[index % BlockSize] == Constants::magic_for_empty) - return ++iterator{ it, index }; - else - return { it, index }; - } - operator const_iterator() - { - if (it->control_bytes[index % BlockSize] == Constants::magic_for_empty) - return ++iterator{ it, index }; - else - return { it, index }; - } - }; - }; - template - struct AlignmentOr8Bytes - { - static constexpr size_t value = 8; - }; - template - struct AlignmentOr8Bytes= 1>::type> - { - static constexpr size_t value = alignof(T); - }; - template - struct CalculateBytellBlockSize; - template - struct CalculateBytellBlockSize - { - static constexpr size_t this_value = AlignmentOr8Bytes::value; - static constexpr size_t base_value = CalculateBytellBlockSize::value; - static constexpr size_t value = this_value > base_value ? this_value : base_value; - }; - template<> - struct CalculateBytellBlockSize<> - { - static constexpr size_t value = 8; - }; - } - - template, typename E = std::equal_to, typename A = std::allocator > > - class bytell_hash_map - : public detailv8::sherwood_v8_table - < - std::pair, - K, - H, - detailv8::KeyOrValueHasher, H>, - E, - detailv8::KeyOrValueEquality, E>, - A, - typename std::allocator_traits::template rebind_alloc, - detailv8::CalculateBytellBlockSize::value - > - { - using Table = detailv8::sherwood_v8_table - < - std::pair, - K, - H, - detailv8::KeyOrValueHasher, H>, - E, - detailv8::KeyOrValueEquality, E>, - A, - typename std::allocator_traits::template rebind_alloc, - detailv8::CalculateBytellBlockSize::value - >; - public: - - using key_type = K; - using mapped_type = V; - - using Table::Table; - bytell_hash_map() - { - } - - inline V& operator[](const K& key) - { - return emplace(key, convertible_to_value()).first->second; - } - inline V& operator[](K&& key) - { - return emplace(std::move(key), convertible_to_value()).first->second; - } - V& at(const K& key) - { - auto found = this->find(key); - if (found == this->end()) - throw std::out_of_range("Argument passed to at() was not in the map."); - return found->second; - } - const V& at(const K& key) const - { - auto found = this->find(key); - if (found == this->end()) - throw std::out_of_range("Argument passed to at() was not in the map."); - return found->second; - } - - using Table::emplace; - std::pair emplace() - { - return emplace(key_type(), convertible_to_value()); - } - template - std::pair insert_or_assign(const key_type& key, M&& m) - { - auto emplace_result = emplace(key, std::forward(m)); - if (!emplace_result.second) - emplace_result.first->second = std::forward(m); - return emplace_result; - } - template - std::pair insert_or_assign(key_type&& key, M&& m) - { - auto emplace_result = emplace(std::move(key), std::forward(m)); - if (!emplace_result.second) - emplace_result.first->second = std::forward(m); - return emplace_result; - } - template - typename Table::iterator insert_or_assign(typename Table::const_iterator, const key_type& key, M&& m) - { - return insert_or_assign(key, std::forward(m)).first; - } - template - typename Table::iterator insert_or_assign(typename Table::const_iterator, key_type&& key, M&& m) - { - return insert_or_assign(std::move(key), std::forward(m)).first; - } - - friend bool operator==(const bytell_hash_map& lhs, const bytell_hash_map& rhs) - { - if (lhs.size() != rhs.size()) - return false; - for (const typename Table::value_type& value : lhs) - { - auto found = rhs.find(value.first); - if (found == rhs.end()) - return false; - else if (value.second != found->second) - return false; - } - return true; - } - friend bool operator!=(const bytell_hash_map& lhs, const bytell_hash_map& rhs) - { - return !(lhs == rhs); - } - - private: - struct convertible_to_value - { - operator V() const - { - return V(); - } - }; - }; - - template, typename E = std::equal_to, typename A = std::allocator > - class bytell_hash_set - : public detailv8::sherwood_v8_table - < - T, - T, - H, - detailv8::functor_storage, - E, - detailv8::functor_storage, - A, - typename std::allocator_traits::template rebind_alloc, - detailv8::CalculateBytellBlockSize::value - > - { - using Table = detailv8::sherwood_v8_table - < - T, - T, - H, - detailv8::functor_storage, - E, - detailv8::functor_storage, - A, - typename std::allocator_traits::template rebind_alloc, - detailv8::CalculateBytellBlockSize::value - >; - public: - - using key_type = T; - - using Table::Table; - bytell_hash_set() - { - } - - template - std::pair emplace(Args &&... args) - { - return Table::emplace(T(std::forward(args)...)); - } - std::pair emplace(const key_type& arg) - { - return Table::emplace(arg); - } - std::pair emplace(key_type& arg) - { - return Table::emplace(arg); - } - std::pair emplace(const key_type&& arg) - { - return Table::emplace(std::move(arg)); - } - std::pair emplace(key_type&& arg) - { - return Table::emplace(std::move(arg)); - } - - friend bool operator==(const bytell_hash_set& lhs, const bytell_hash_set& rhs) - { - if (lhs.size() != rhs.size()) - return false; - for (const T& value : lhs) - { - if (rhs.find(value) == rhs.end()) - return false; - } - return true; - } - friend bool operator!=(const bytell_hash_set& lhs, const bytell_hash_set& rhs) - { - return !(lhs == rhs); - } - }; - -} // end namespace ska diff --git a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp index b4159d7ceb4..597486c56a9 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp @@ -14,8 +14,6 @@ #include "basic_quantum_state.hpp" -#include "quantum_hash_map.hpp" - #include "types.h" #include "gates.h" diff --git a/src/Simulation/Simulators/SparseSimulator/Native/types.h b/src/Simulation/Simulators/SparseSimulator/Native/types.h index 051a4bd1fa1..a9f6fb656c4 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/types.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/types.h @@ -7,21 +7,10 @@ #include #include #include -#include -#include - - -#include "quantum_hash_map.hpp" - namespace Microsoft::Quantum::SPARSESIMULATOR { -using mutex_type = std::mutex; -using recursive_mutex_type = std::recursive_mutex; -using lock_type = std::lock_guard; -using recursive_lock_type = std::lock_guard; - #ifndef USE_SINGLE_PRECISION using RealType = double; #else @@ -37,9 +26,6 @@ template using qubit_label_type = std::bitset; // Wavefunctions are hash maps of some key (std::bitset or a string) -//template -//using abstract_wavefunction = ska::bytell_hash_map>; - template using abstract_wavefunction = std::unordered_map; From df838aea7f6d8391468c8a4681955e3aa790a53f Mon Sep 17 00:00:00 2001 From: DmitryVasilevsky <60718360+DmitryVasilevsky@users.noreply.github.com> Date: Mon, 27 Sep 2021 12:21:26 -0700 Subject: [PATCH 24/25] Apply suggestions from code review Co-authored-by: Thomas Haener --- .../SparseSimulator/Native/SparseSimulator.h | 12 ++++++------ .../SparseSimulator/Native/basic_quantum_state.hpp | 4 ++-- .../Simulators/SparseSimulator/Native/capi.cpp | 4 ++++ .../Simulators/SparseSimulator/Native/factory.cpp | 2 +- .../Simulators/SparseSimulator/Native/gates.h | 8 ++++---- .../SparseSimulator/Native/quantum_state.hpp | 12 ++++++------ 6 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h index ecdde7c5fe2..cdcfbb8835b 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h @@ -235,7 +235,7 @@ class SparseSimulator if (_queue_Rx[target]){ _execute_queued_ops(target, OP::Rx); } - // YH = -YH, so we add a phase to track this + // 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 @@ -429,7 +429,7 @@ class SparseSimulator 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 * pow(0.5, power - 1) * M_PI, target); + 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){ @@ -646,7 +646,7 @@ class SparseSimulator } // Returns the amplitude of a given bitstring - amplitude probe(std::string label) { + amplitude probe(std::string const& label) { _execute_queued_ops(); return _quantum_state->probe(label); } @@ -659,7 +659,7 @@ class SparseSimulator // 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 qubits, void (*callback)(char*, double, double)) { + bool dump_qubits(std::vector const& qubits, void (*callback)(char*, double, double)) { _execute_queued_ops(qubits, OP::Ry); return _quantum_state->dump_qubits(qubits, callback); } @@ -803,7 +803,7 @@ class SparseSimulator // 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 indices, OP level = OP::Ry){ + void _execute_queued_ops(std::vector const& indices, OP level = OP::Ry){ _execute_phase_and_permute(); switch (level){ case OP::Ry: @@ -829,7 +829,7 @@ class SparseSimulator // 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){ + void _execute_if(logical_qubit_id target){ if (_queue_Ry[target] || _queue_Rx[target] || _queue_H[target]){ _execute_queued_ops(target, OP::Ry); } diff --git a/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp index 94f57f6e53a..f1e68bf3227 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/basic_quantum_state.hpp @@ -48,9 +48,9 @@ class BasicQuantumState virtual bool Measure(std::vector const&, std::vector const&) = 0; - virtual amplitude probe(std::string label) = 0; + virtual amplitude probe(std::string const& label) = 0; - virtual bool dump_qubits(std::vector qubits, void (*callback)(char*, double, double)) = 0; + virtual bool dump_qubits(std::vector const& qubits, void (*callback)(char*, double, double)) = 0; virtual void dump_all(logical_qubit_id max_qubit_id, void (*callback)(char*, double, double)) = 0; diff --git a/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp b/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp index 0543457cd67..92c153d4443 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/capi.cpp @@ -185,6 +185,7 @@ extern "C" _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); @@ -201,6 +202,7 @@ extern "C" _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); @@ -225,6 +227,7 @@ extern "C" _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); @@ -278,6 +281,7 @@ extern "C" // Asserts that the gates in `b`, measured on the qubits in `q`, return `result` MICROSOFT_QUANTUM_DECL bool Assert_cpp(unsigned 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); diff --git a/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp b/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp index e8aa09a7a22..c6938e1ee23 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp @@ -25,7 +25,7 @@ unsigned createSimulator(logical_qubit_id num_qubits) size_t emptySlot = -1; for (auto const& s : _simulators) { - if (s == NULL) + if (s == nullptr) { emptySlot = &s - &_simulators[0]; break; diff --git a/src/Simulation/Simulators/SparseSimulator/Native/gates.h b/src/Simulation/Simulators/SparseSimulator/Native/gates.h index 6871f2a9e2d..e0a3d8a7b54 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/gates.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/gates.h @@ -213,7 +213,7 @@ struct condensed_operation { std::bitset controls; condensed_operation(OP gate_type_arg, logical_qubit_id target_arg, - std::bitset controls_arg + std::bitset const& controls_arg ) : gate_type(gate_type_arg), target(target_arg), controls(controls_arg){} @@ -229,7 +229,7 @@ struct condensed_operation { //mcswap condensed_operation(OP gate_type_arg, logical_qubit_id target1_arg, - std::bitset controls_arg, + std::bitset const& controls_arg, logical_qubit_id target_2_arg ) : gate_type(gate_type_arg), target(target1_arg), @@ -246,7 +246,7 @@ struct condensed_operation { // MCPhase condensed_operation(OP gate_type_arg, logical_qubit_id target_arg, - std::bitset controls_arg, + std::bitset const& controls_arg, amplitude phase_arg ) : gate_type(gate_type_arg), target(target_arg), @@ -256,7 +256,7 @@ struct condensed_operation { bool result; // Assert condensed_operation(OP gate_type_arg, - std::bitset Zs_arg, + std::bitset const& Zs_arg, bool result_arg ) : target(0), gate_type(gate_type_arg), diff --git a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp index 597486c56a9..51b31e928a9 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp @@ -43,7 +43,7 @@ inline amplitude iExp(int power) } template -bool get_parity(std::bitset bitstring){ +bool get_parity(std::bitset const& bitstring){ return bitstring.count() % 2; } @@ -65,7 +65,7 @@ inline bool operator<(const std::bitset& lhs, const std::bitset& rhs) { // which bits are non-zero template std::bitset get_mask(std::vector const& indices){ - std::bitset mask = std::bitset(); + std::bitset mask; for (logical_qubit_id index : indices) { mask.set(index); } @@ -648,7 +648,7 @@ class QuantumState : public BasicQuantumState // Probe the amplitude of a single basis state - amplitude probe(qubit_label label) { + 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()) { @@ -659,7 +659,7 @@ class QuantumState : public BasicQuantumState } } - amplitude probe(std::string label) { + amplitude probe(std::string const& label) { qubit_label bit_label = qubit_label(label); return probe(bit_label); } @@ -667,7 +667,7 @@ class QuantumState : public BasicQuantumState // 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 qubits, void (*callback)(char*, double, double)) { + bool dump_qubits(std::vector const& qubits, void (*callback)(char*, double, double)) { // Create two wavefunctions // check if they are tensor products wavefunction dump_wfn; @@ -1096,7 +1096,7 @@ class QuantumState : public BasicQuantumState // Thus, the norm of wfn1 will be 1/|c_b|^2; thus the norm of wfn2 is 1/|d_b|^2 = |c_b|^2/|a_bb|^2 // So we iterate through the smaller wavefunction, to get the normalizing constant, // then normalize both - bool _split_wavefunction(qubit_label first_mask, wavefunction &wfn1, wavefunction &wfn2){ + 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())); From eb6538eb97c83819d78ae8b63909f182c50c3fd1 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Tue, 28 Sep 2021 00:32:45 -0700 Subject: [PATCH 25/25] Review feedback and exclusion from build --- .../Microsoft.Quantum.Simulators.csproj | 7 +++++++ .../SparseSimulator/Native/SparseSimulator.h | 2 +- .../Simulators/SparseSimulator/Native/factory.cpp | 6 +++--- .../SparseSimulator/Native/quantum_state.hpp | 3 ++- .../Simulators/SparseSimulator/README.md | 15 --------------- 5 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/Simulation/Simulators/Microsoft.Quantum.Simulators.csproj b/src/Simulation/Simulators/Microsoft.Quantum.Simulators.csproj index 73e638180e8..0869cee10f5 100644 --- a/src/Simulation/Simulators/Microsoft.Quantum.Simulators.csproj +++ b/src/Simulation/Simulators/Microsoft.Quantum.Simulators.csproj @@ -46,4 +46,11 @@ + + + + + + + diff --git a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h index cdcfbb8835b..25e524e9c70 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h +++ b/src/Simulation/Simulators/SparseSimulator/Native/SparseSimulator.h @@ -50,7 +50,7 @@ std::shared_ptr expand_wfn_helper(std::shared_ptr. // Values are non-zero amplitudes represented by std::complex. // Zero amplitudes are simply not stored. diff --git a/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp b/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp index c6938e1ee23..4ba408b6e7b 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/factory.cpp @@ -33,12 +33,12 @@ unsigned createSimulator(logical_qubit_id num_qubits) } if (emptySlot == -1) { - _simulators.push_back(std::shared_ptr(new SparseSimulator(num_qubits))); + _simulators.push_back(std::make_shared(num_qubits)); emptySlot = _simulators.size() - 1; } else { - _simulators[emptySlot] = std::shared_ptr(new SparseSimulator(num_qubits)); + _simulators[emptySlot] = std::make_shared(num_qubits); } return static_cast(emptySlot); @@ -49,7 +49,7 @@ void destroySimulator(unsigned id) { std::lock_guard lock(_mutex); - _simulators[id].reset(); + _simulators[id].reset(); // Set pointer to nullptr } // Returns a simulator at some id (used for the C++/C# API) diff --git a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp index 51b31e928a9..c476f1f9361 100644 --- a/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp +++ b/src/Simulation/Simulators/SparseSimulator/Native/quantum_state.hpp @@ -49,6 +49,7 @@ bool get_parity(std::bitset const& bitstring){ // 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; @@ -60,7 +61,7 @@ inline bool operator<(const std::bitset& lhs, const std::bitset& rhs) { } 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 diff --git a/src/Simulation/Simulators/SparseSimulator/README.md b/src/Simulation/Simulators/SparseSimulator/README.md index 4b5a42eb3f0..a5d774569b6 100644 --- a/src/Simulation/Simulators/SparseSimulator/README.md +++ b/src/Simulation/Simulators/SparseSimulator/README.md @@ -138,22 +138,7 @@ which tells the `SparseSimulator` class to forward the call to its internal `Spa # Internal Logic We desribe the main data structure strategies and optimizations in the paper at https://arxiv.org/abs/2105.01533. -## Threading Logic -The multithreading uses OpenMP where possible, but the Visual Studio compiler's version is too low, and hence it relies on std::thread. The build detects the version of OpenMP: - - If OpenMP is not available, it does not set the flags `DOMP_GE_V3` nor `_OPENMP`. This will select std::thread and use `std::hardware_concurrency` to decide on the number of threads. - - If OpenMP is available but the version is too low (i.e., Visual Studio), it does not set `DOMP_GE_V3` but does set `_OPENMP`. Here it will use std::thread but use `omp_get_num_threads` to decide on the number of threads. - - If OpenMP is availalble and the version is at least 3, it will set both flags to 1 and use OpenMP parallelism - - # Future Optimizations ## Delayed Release Currently the simulator executes any queued operations on any qubits it needs to release. This is not strictly necessary: it could add an assertion that the qubits are zero, and continue on. However, it's important to set the `_occupied_qubits` vector to be `0` for that qubit after it is released, but this will not necessarily be true if the gates are not executed. Hence, to delay the release of qubits, it will need a more involved method to track occupied qubits, and qubits which are actually 0. - - -# Licence -The wavefunction uses the bytell hash map written by Malte Skarupke, which has the following licence: - -Copyright Malte Skarupke 2017. -Distributed under the Boost Software License, Version 1.0. - (See http://www.boost.org/LICENSE_1_0.txt) \ No newline at end of file