From 3f429eed66bfbbfac3a5f74f97584b83d3992d9a Mon Sep 17 00:00:00 2001 From: giangiac <30329377+giangiac@users.noreply.github.com> Date: Wed, 6 May 2020 09:07:05 -0700 Subject: [PATCH 1/8] Add badge for Python build in README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c4f6823..7ec2a193 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -![C++ build with CMake](https://github.com/iqusoft/intel-qs/workflows/C++%20build%20with%20CMake/badge.svg?branch=test%2Fgithub-action) +![C++ build with CMake](https://github.com/iqusoft/intel-qs/workflows/C++%20build%20with%20CMake/badge.svg) +![Python build (no MPI)](https://github.com/iqusoft/intel-qs/workflows/Python%20build%20(no%20MPI)/badge.svg) [![arXiv](https://img.shields.io/static/v1?label=arXiv&message=2001.10554&color=success)](https://arxiv.org/abs/2001.10554) [![arXiv](https://img.shields.io/static/v1?label=arXiv&message=1601.07195&color=inactive)](https://arxiv.org/abs/1601.07195) From 87994cfae85746bfdb40cfa1159efeadc61f2e32 Mon Sep 17 00:00:00 2001 From: Fabio Baruffa Date: Thu, 7 May 2020 17:27:01 +0200 Subject: [PATCH 2/8] Create the python build with MPI: Action --- .github/workflows/python_build_mpi.yml | 42 ++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/python_build_mpi.yml diff --git a/.github/workflows/python_build_mpi.yml b/.github/workflows/python_build_mpi.yml new file mode 100644 index 00000000..21f28c8b --- /dev/null +++ b/.github/workflows/python_build_mpi.yml @@ -0,0 +1,42 @@ +name: Python build with MPI + +# Triggers the workflow on push or pull request events but only for the master branch +on: + push: + branches: [ master ] + pull_request: + branches: [ master , development ] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build-ubuntu-conda-gcc" + build-ubuntu-conda-gcc: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - uses: goanpeca/setup-miniconda@v1 + with: + auto-update-conda: true + python-version: 3.7 + - name: Conda info, list, pybind install and activate + shell: bash -l {0} + run: | + conda info + conda list + conda install -y pybind11 + conda activate /usr/share/miniconda/envs/test + + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: configure + shell: bash -l {0} + run: mkdir build && cd build && CXX=mpicc cmake PYTHON_EXECUTABLE=/usr/share/miniconda/envs/test/bin/python -DIqsMPI=ON -DIqsPython=ON -DIqsUtest=OFF .. + - name: build + run: cmake --build build --target intelqs_py + + - name: unit test + run: cd unit_test && /usr/share/miniconda/envs/test/bin/python import_iqs.py + From c203c51ee2ad3419551be1d52f3b01a44b33b565 Mon Sep 17 00:00:00 2001 From: Fabio Baruffa Date: Thu, 7 May 2020 17:31:18 +0200 Subject: [PATCH 3/8] Update python MPI action --- .github/workflows/python_build_mpi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python_build_mpi.yml b/.github/workflows/python_build_mpi.yml index 21f28c8b..d4dbd204 100644 --- a/.github/workflows/python_build_mpi.yml +++ b/.github/workflows/python_build_mpi.yml @@ -33,7 +33,7 @@ jobs: - name: configure shell: bash -l {0} - run: mkdir build && cd build && CXX=mpicc cmake PYTHON_EXECUTABLE=/usr/share/miniconda/envs/test/bin/python -DIqsMPI=ON -DIqsPython=ON -DIqsUtest=OFF .. + run: mkdir build && cd build && CXX=mpicc++ cmake PYTHON_EXECUTABLE=/usr/share/miniconda/envs/test/bin/python -DIqsMPI=ON -DIqsPython=ON -DIqsUtest=OFF .. - name: build run: cmake --build build --target intelqs_py From ec34ded33a7f2a6413c7061373fe5cc6fc07ad6c Mon Sep 17 00:00:00 2001 From: Fabio Baruffa Date: Thu, 7 May 2020 17:41:54 +0200 Subject: [PATCH 4/8] Added the installation of MPICH in the Action --- .github/workflows/python_build_mpi.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/python_build_mpi.yml b/.github/workflows/python_build_mpi.yml index d4dbd204..b482dbc4 100644 --- a/.github/workflows/python_build_mpi.yml +++ b/.github/workflows/python_build_mpi.yml @@ -27,6 +27,8 @@ jobs: conda list conda install -y pybind11 conda activate /usr/share/miniconda/envs/test + - name: Install MPICH + run: sudo apt-get install mpich # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 From a141ad28cc22cde7b8f9af96407fa900b9268550 Mon Sep 17 00:00:00 2001 From: Fabio Baruffa Date: Thu, 7 May 2020 18:48:05 +0200 Subject: [PATCH 5/8] deleted the file for the action --- .github/workflows/python_build_mpi.yml | 44 -------------------------- 1 file changed, 44 deletions(-) delete mode 100644 .github/workflows/python_build_mpi.yml diff --git a/.github/workflows/python_build_mpi.yml b/.github/workflows/python_build_mpi.yml deleted file mode 100644 index b482dbc4..00000000 --- a/.github/workflows/python_build_mpi.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Python build with MPI - -# Triggers the workflow on push or pull request events but only for the master branch -on: - push: - branches: [ master ] - pull_request: - branches: [ master , development ] - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build-ubuntu-conda-gcc" - build-ubuntu-conda-gcc: - # The type of runner that the job will run on - runs-on: ubuntu-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - uses: goanpeca/setup-miniconda@v1 - with: - auto-update-conda: true - python-version: 3.7 - - name: Conda info, list, pybind install and activate - shell: bash -l {0} - run: | - conda info - conda list - conda install -y pybind11 - conda activate /usr/share/miniconda/envs/test - - name: Install MPICH - run: sudo apt-get install mpich - - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - - name: configure - shell: bash -l {0} - run: mkdir build && cd build && CXX=mpicc++ cmake PYTHON_EXECUTABLE=/usr/share/miniconda/envs/test/bin/python -DIqsMPI=ON -DIqsPython=ON -DIqsUtest=OFF .. - - name: build - run: cmake --build build --target intelqs_py - - - name: unit test - run: cd unit_test && /usr/share/miniconda/envs/test/bin/python import_iqs.py - From 2d0bf33fa9c3801742e5b477d9ae7070e94172e6 Mon Sep 17 00:00:00 2001 From: mlxd Date: Tue, 2 Jun 2020 15:28:04 +0100 Subject: [PATCH 6/8] Add support for n-controlled gate decomposition Changes added: - Support for n-controlled Pauli operators - Intermediate gate caching - Circuit depth and circuit width optimisation NCU --- include/GateCache.hpp | 147 ++++++++++++++++++++++++++++++++++ include/mat_ops.hpp | 65 +++++++++++++++ include/ncu.hpp | 182 ++++++++++++++++++++++++++++++++++++++++++ include/qureg.hpp | 5 +- src/CMakeLists.txt | 3 +- src/qureg_ncu.cpp | 39 +++++++++ 6 files changed, 439 insertions(+), 2 deletions(-) create mode 100644 include/GateCache.hpp create mode 100644 include/mat_ops.hpp create mode 100644 include/ncu.hpp create mode 100644 src/qureg_ncu.cpp diff --git a/include/GateCache.hpp b/include/GateCache.hpp new file mode 100644 index 00000000..b1a3a713 --- /dev/null +++ b/include/GateCache.hpp @@ -0,0 +1,147 @@ +/** + * @file Gates.hpp + * @author Lee J. O'Riordan (lee.oriordan@ichec.ie) + * @author Myles Doyle (myles.doyle@ichec.ie) + * @brief Gates wrapper/storage class. Adapted from QNLP. + * @version 0.2 + * @date 2020-06-01 + */ + +#ifndef GATECACHE +#define GATECACHE + +#include + +#include +#include +#include + +#include + +#include "qureg.hpp" + +/** + * @brief Class to cache intermediate matrix values used within other parts of the computation. + * Heavily depended upon by NCU to store sqrt matrix values following Barenco et al. (1995) decomposition. + * + * @tparam SimulatorType The simulator type with SimulatorGeneral as base class + */ +template +class GateCache { + private: + //using GateType = TM2x2; + std::size_t cache_depth; + + public: + GateCache() : cache_depth(0) { }; + + GateCache(std::size_t default_depth) : cache_depth(default_depth) { + initCache(cache_depth); + } + + ~GateCache(){ clearCache(); } + + //Take the 2x2 matrix type from the template SimulatorType + using GateType = TM2x2; + + //Maintain a map for each gate label (X,Y,Z, etc.), and use vectors to store sqrt (indexed by 1/2^(i), and pairing matrix and adjoint) + std::unordered_map > > gateCacheMap; + + void clearCache(){ + gateCacheMap.clear(); + cache_depth = 0; + } + + /** + * @brief Initialise the gate cache with PauliX,Y,Z and H up to a given sqrt depth + * + * @param qReg The QubitRegister object + * @param sqrt_depth The depth to which calculate sqrt matrices and their respective adjoints + */ + void initCache(QubitRegister& qReg, const std::size_t sqrt_depth){ + // If we do not have a sufficient circuit depth, clear and rebuild up to given depth. + + if(cache_depth < sqrt_depth ){ + gateCacheMap.clear(); + cache_depth = 0; + } + + if (gateCacheMap.empty()){ + gateCacheMap["X"] = std::vector< std::pair > { std::make_pair( construct_pauli_x(), adjointMatrix( construct_pauli_x() ) ) }; + gateCacheMap["Y"] = std::vector< std::pair > { std::make_pair( construct_pauli_y(), adjointMatrix( construct_pauli_y() ) ) }; + gateCacheMap["Z"] = std::vector< std::pair > { std::make_pair( construct_pauli_z(), adjointMatrix( construct_pauli_z() ) ) }; + gateCacheMap["H"] = std::vector< std::pair > { std::make_pair( construct_hadamard(), adjointMatrix( construct_hadamard() ) ) }; + + for( std::size_t depth = 1; depth <= sqrt_depth; depth++ ){ + for( auto& kv : gateCacheMap ){ + kv.second.reserve(sqrt_depth + 1); + auto m = matrixSqrt(kv.second[depth-1].first); + kv.second.emplace(kv.second.begin() + depth, std::make_pair( m, adjointMatrix( m ) ) ); + } + } + cache_depth = sqrt_depth; + } + } + + /** + * @brief Adds new gate to the cache up to a given sqrt depth + * + * @param gateLabel Label of gate to index into map + * @param gate Gate matrix + * @param max_depth Depth of calculations for sqrt and associate adjoints + */ + void addToCache(QubitRegister& qReg, const std::string gateLabel, const GateType& gate, std::size_t max_depth){ + if(max_depth <= cache_depth && gateCacheMap.find(gateLabel) != gateCacheMap.end() ){ + return; + } + else if(max_depth > cache_depth){ + initCache(qReg, max_depth); + } + + std::vector< std::pair > v; + + v.reserve(max_depth + 1); + v.push_back(std::make_pair( gate, adjointMatrix( gate ) ) ); + for( std::size_t depth = 1; depth <= max_depth; depth++ ){ + auto m = matrixSqrt( v[depth-1].first ); + v.emplace(v.begin() + depth, std::make_pair( m, adjointMatrix( m ) ) ); + } + gateCacheMap.emplace(std::make_pair(gateLabel, v) ); + } + + constexpr GateType construct_pauli_x(){ + GateType px; + px(0, 0) = Type(0., 0.); + px(0, 1) = Type(1., 0.); + px(1, 0) = Type(1., 0.); + px(1, 1) = Type(0., 0.); + return px; + } + constexpr GateType construct_pauli_y(){ + GateType py; + py(0, 0) = Type(0., 0.); + py(0, 1) = Type(0., -1.); + py(1, 0) = Type(0., 1.); + py(1, 1) = Type(0., 0.); + return py; + } + constexpr GateType construct_pauli_z(){ + GateType pz; + pz(0, 0) = Type(1., 0.); + pz(0, 1) = Type(0., 0.); + pz(1, 0) = Type(0., 0.); + pz(1, 1) = Type(-1., 0.); + return pz; + } + constexpr GateType construct_hadamard(){ + GateType h; + decltype(std::declval(real(h(0,0)))) f = 1. / std::sqrt(2.); + h(0, 0) = h(0, 1) = h(1, 0) = Type(f, 0.); + h(1, 1) = Type(-f, 0.); + return h; + } +}; +#endif + + + diff --git a/include/mat_ops.hpp b/include/mat_ops.hpp new file mode 100644 index 00000000..4937e49e --- /dev/null +++ b/include/mat_ops.hpp @@ -0,0 +1,65 @@ + +/** + * @file mat_ops.hpp + * @author Lee J. O'Riordan (lee.oriordan@ichec.ie) + * @brief Templated methods to manipulate small matrices. Adapted from QNLP. + * @version 0.2 + * @date 2020-06-01 + */ + +#ifndef MAT_OPS +#define MAT_OPS + +#include +#include +#include + +/** + * @brief Calculates the unitary matrix square root (U == VV, where V is returned) + * + * @tparam Type ComplexDP or ComplexSP + * @param U Unitary matrix to be rooted + * @return Matrix V such that VV == U + */ +template +const Mat2x2Type matrixSqrt(const Mat2x2Type& U){ + Mat2x2Type V(U); + std::complex delta = U(0,0)*U(1,1) - U(0,1)*U(1,0); + std::complex tau = U(0,0) + U(1,1); + std::complex s = sqrt(delta); + std::complex t = sqrt(tau + 2.0*s); + + //must be a way to vectorise these; TinyMatrix have a scale/shift option? + V(0,0) += s; + V(1,1) += s; + std::complex scale_factor(1.,0.); + scale_factor /= t; + V(0,0) *= scale_factor; //(std::complex(1.,0.)/t); + V(0,1) *= scale_factor; //(1/t); + V(1,0) *= scale_factor; //(1/t); + V(1,1) *= scale_factor; //(1/t); + + return V; +} + +/** + * @brief Function to calculate the adjoint of an input matrix + * + * @tparam Type ComplexDP or ComplexSP + * @param U Unitary matrix to be adjointed + * @return qhipster::TinyMatrix U^{\dagger} + */ +template +Mat2x2Type adjointMatrix(const Mat2x2Type& U){ + Mat2x2Type Uadjoint(U); + std::complex tmp; + tmp = Uadjoint(0,1); + Uadjoint(0,1) = Uadjoint(1,0); + Uadjoint(1,0) = tmp; + Uadjoint(0,0) = std::conj(Uadjoint(0,0)); + Uadjoint(0,1) = std::conj(Uadjoint(0,1)); + Uadjoint(1,0) = std::conj(Uadjoint(1,0)); + Uadjoint(1,1) = std::conj(Uadjoint(1,1)); + return Uadjoint; +} +#endif \ No newline at end of file diff --git a/include/ncu.hpp b/include/ncu.hpp new file mode 100644 index 00000000..a49e5960 --- /dev/null +++ b/include/ncu.hpp @@ -0,0 +1,182 @@ +/** + * @file ncu.hpp + * @author Lee J. O'Riordan (lee.oriordan@ichec.ie) + * @author Myles Doyle (myles.doyle@ichec.ie) + * @brief Functions for applying n-qubit controlled U (unitary) gates. Adapted from QNLP. + * @version 0.2 + * @date 2020-06-01 + */ + +#ifndef NCU_H +#define NCU_H + +#include +#include +#include + +#include +#include + +#include + +namespace ncu { + +/** + * @brief Class definition for applying n-qubit controlled unitary operations. + * + * @tparam SimulatorType Class Simulator Type + */ +template +class NCU { + private: + using Matrix2x2Type = TM2x2; + GateCache gate_cache; + + protected: + static std::size_t num_gate_ops; + + public: + /** + * @brief Construct a new NCU object + * + */ + NCU() { + gate_cache = GateCache(); + }; + + /** + * @brief Destroy the NCU object + * + */ + ~NCU(){ + clearMaps(); + gate_cache.clearCache(); + }; + + /** + * @brief Add the PauliX and the given unitary U to the maps + * + * @param U + */ + void initialiseMaps( std::size_t num_ctrl_lines){ + gate_cache.initCache( num_ctrl_lines ); + } + + /** + * @brief Add the given unitary matrix to the maps up to the required depth + * + * @param U + */ + void addToMaps( std::string U_label, const Matrix2x2Type& U, std::size_t num_ctrl_lines){ + gate_cache.addToCache( U_label, U, num_ctrl_lines); + } + + /** + * @brief Get the Map of cached gates. Keys are strings, and values are vectors of paired (gate, gate adjoint) types where the index give the value of (gate)^(1/2^i) + * + * @return GateCache type + */ + GateCache& getGateCache(){ + return gate_cache; + } + + /** + * @brief Clears the maps of stored sqrt matrices + * + */ + void clearMaps(){ + gate_cache.clearCache(); + } + + /** + * @brief Decompose n-qubit controlled op into 1 and 2 qubit gates. Control indices can be in any specified location. Ensure the gate cache has been populated with the appropriate gate type before running. This avoids O(n) checking of the container at each call for the associated gates. + * + * @tparam Type ComplexDP or ComplexSP + * @param qReg Qubit register + * @param ctrlIndices Vector of indices for control lines + * @param qTarget Target qubit for the unitary matrix U + * @param U Unitary matrix, U + * @param depth Depth of recursion. + */ + void applyNQubitControl(QubitRegister& qReg, + const std::vector ctrlIndices, + const std::vector auxIndices, + const unsigned int qTarget, + const std::string gateLabel, + const Matrix2x2Type& U, + const std::size_t depth + ){ + int local_depth = depth + 1; + + //Determine the range over which the qubits exist; consider as a count of the control ops, hence +1 since extremeties included + std::size_t cOps = ctrlIndices.size(); + + // Assuming given a set of auxiliary qubits, utilise the qubits for better depth optimisation. + if( (cOps >= 5) && ( auxIndices.size() >= cOps-2 ) && (gateLabel == "X") && (depth == 0) ){ //161 -> 60 2-qubit gate calls + qReg.ApplyToffoli( ctrlIndices.back(), *(auxIndices.begin() + ctrlIndices.size() - 3), qTarget); + + for (std::size_t i = ctrlIndices.size()-2; i >= 2; i--){ + qReg.ApplyToffoli( *(ctrlIndices.begin()+i), *(auxIndices.begin() + (i-2)), *(auxIndices.begin() + (i-1))); + } + qReg.ApplyToffoli( *(ctrlIndices.begin()), *(ctrlIndices.begin()+1), *(auxIndices.begin()) ); + + for (std::size_t i = 2; i <= ctrlIndices.size()-2; i++){ + qReg.ApplyToffoli( *(ctrlIndices.begin()+i), *(auxIndices.begin()+(i-2)), *(auxIndices.begin()+(i-1))); + } + qReg.ApplyToffoli( ctrlIndices.back(), *(auxIndices.begin() + ctrlIndices.size() - 3), qTarget); + + for (std::size_t i = ctrlIndices.size()-2; i >= 2; i--){ + qReg.ApplyToffoli( *(ctrlIndices.begin()+i), *(auxIndices.begin() + (i-2)), *(auxIndices.begin() + (i-1))); + } + qReg.ApplyToffoli( *(ctrlIndices.begin()), *(ctrlIndices.begin()+1), *(auxIndices.begin()) ); + for (std::size_t i = 2; i <= ctrlIndices.size()-2; i++){ + qReg.ApplyToffoli( *(ctrlIndices.begin()+i), *(auxIndices.begin()+(i-2)), *(auxIndices.begin()+(i-1))); + } + } + // Optimisation for replacing 17 2-qubit with 13 2-qubit gate calls + else if(cOps == 3){ + //Apply the 13 2-qubit gate calls + qReg.ApplyControlled1QubitGate( ctrlIndices[0], qTarget, gate_cache.gateCacheMap[gateLabel][local_depth+1].first ); + qReg.ApplyCPauliX( ctrlIndices[0], ctrlIndices[1]); + + qReg.ApplyControlled1QubitGate( ctrlIndices[1], qTarget, gate_cache.gateCacheMap[gateLabel][local_depth+1].second ); + qReg.ApplyCPauliX( ctrlIndices[0], ctrlIndices[1]); + + qReg.ApplyControlled1QubitGate( ctrlIndices[1], qTarget, gate_cache.gateCacheMap[gateLabel][local_depth+1].first ); + qReg.ApplyCPauliX( ctrlIndices[1], ctrlIndices[2]); + + qReg.ApplyControlled1QubitGate( ctrlIndices[2], qTarget, gate_cache.gateCacheMap[gateLabel][local_depth+1].second ); + qReg.ApplyCPauliX( ctrlIndices[0], ctrlIndices[2]); + + qReg.ApplyControlled1QubitGate( ctrlIndices[2], qTarget, gate_cache.gateCacheMap[gateLabel][local_depth+1].first ); + qReg.ApplyCPauliX( ctrlIndices[1], ctrlIndices[2]); + + qReg.ApplyControlled1QubitGate( ctrlIndices[2], qTarget, gate_cache.gateCacheMap[gateLabel][local_depth+1].second ); + qReg.ApplyCPauliX( ctrlIndices[0], ctrlIndices[2]); + + qReg.ApplyControlled1QubitGate( ctrlIndices[2], qTarget, gate_cache.gateCacheMap[gateLabel][local_depth+1].first ); + } + // Default quadratic decomposition from Barenco et al (1995) + else if (cOps >= 2 && cOps !=3){ + std::vector subCtrlIndices(ctrlIndices.begin(), ctrlIndices.end()-1); + + qReg.ApplyControlled1QubitGate( ctrlIndices.back(), qTarget, gate_cache.gateCacheMap[gateLabel][local_depth].first ); + + applyNQubitControl(qReg, subCtrlIndices, auxIndices, ctrlIndices.back(), "X", gate_cache.gateCacheMap["X"][0].first, 0 ); + + qReg.ApplyControlled1QubitGate( ctrlIndices.back(), qTarget, gate_cache.gateCacheMap[gateLabel][local_depth].second ); + + applyNQubitControl(qReg, subCtrlIndices, auxIndices, ctrlIndices.back(), "X", gate_cache.gateCacheMap["X"][0].first, 0 ); + + applyNQubitControl(qReg, subCtrlIndices, auxIndices, qTarget, gateLabel, gate_cache.gateCacheMap[gateLabel][local_depth+1].first, local_depth ); + } + + // If the number of control qubits is less than 2, assume we have decomposed sufficiently + else{ + qReg.ApplyControlled1QubitGate( ctrlIndices[0], qTarget, gate_cache.gateCacheMap[gateLabel][depth].first ); + } + } +}; + +}; +#endif \ No newline at end of file diff --git a/include/qureg.hpp b/include/qureg.hpp index 7f4b473e..a5493b82 100644 --- a/include/qureg.hpp +++ b/include/qureg.hpp @@ -184,7 +184,10 @@ class QubitRegister void ApplyCHadamard(unsigned const control_qubit, unsigned const target_qubit); void ApplyCPhaseRotation(unsigned const qubit, unsigned const qubit2, BaseType theta); - + + // Algorithms + void ApplyNCU(TM2x2 gate, const std::vector& ctrl_indices, const std::vector& aux_indices, unsigned const target); + // fusion void TurnOnFusion(unsigned log2llc = 20); void TurnOffFusion(); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 99cfd521..9e88a02e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,6 +13,7 @@ set(IQS_FILES qureg_expectval.cpp qureg_fusion.cpp qureg_init.cpp + qureg_ncu.cpp qureg_measure.cpp qureg_noisysimul.cpp qureg_permute.cpp @@ -26,7 +27,7 @@ set(IQS_FILES add_library(iqs SHARED ${IQS_FILES}) -target_include_directories(iqs PUBLIC ../include) +target_include_directories(iqs PUBLIC ${CMAKE_SOURCE_DIR}/include) target_include_directories(iqs INTERFACE $) diff --git a/src/qureg_ncu.cpp b/src/qureg_ncu.cpp new file mode 100644 index 00000000..eaeaf52f --- /dev/null +++ b/src/qureg_ncu.cpp @@ -0,0 +1,39 @@ +#include "qureg.hpp" +#include "ncu.hpp" +#include "GateCache.hpp" +#include "mat_ops.hpp" +#include + +//template class QubitRegister; +//template class QubitRegister; + +template +void QubitRegister::ApplyNCU( + TM2x2 gate, + const std::vector& ctrl_indices, + const std::vector& aux_indices, + unsigned const target) +{ + ncu::NCU ncu; + ncu.initialiseMaps(ctrl_indices.size()); + + auto gcache = ncu.getGateCache(); + + // initial support for Pauli gates only + std::string gate_label = ""; + if( gate == gcache.construct_pauli_x() ){ + gate_label = "X"; + } + else if( gate == gcache.construct_pauli_y() ){ + gate_label = "Y"; + } + else if( gate == gcache.construct_pauli_z() ){ + gate_label = "Z"; + } + else { + std::cerr << "Gate not currently support for NCU: " << gate.tostr() << std::endl; + std::abort(); + } + ncu.applyNQubitControl(*this, ctrl_indices, aux_indices, target, gate_label, gate, 0); +} + From f3974f926ff34256e47684615f9bdc37fe000bed Mon Sep 17 00:00:00 2001 From: Myles Doyle Date: Fri, 12 Jun 2020 16:10:19 +0100 Subject: [PATCH 7/8] Add unit tests and examples for NCU Changes include: - Sample application demonstrating NCU usage - Unit tests using default and optimised decompositions - Fixes for NCU templating --- examples/CMakeLists.txt | 5 + examples/test_of_ncu_gates.cpp | 105 +++++ include/GateCache.hpp | 18 +- include/ncu.hpp | 6 +- src/qureg_ncu.cpp | 13 +- unit_test/include/apply_ncu_test.hpp | 630 +++++++++++++++++++++++++++ unit_test/suite_of_tests.cpp | 1 + 7 files changed, 763 insertions(+), 15 deletions(-) create mode 100644 examples/test_of_ncu_gates.cpp create mode 100644 unit_test/include/apply_ncu_test.hpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e1b11480..8b4bee70 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -18,6 +18,10 @@ target_link_libraries(noisy_circuit_test.exe iqs) add_executable(test_of_custom_gates.exe test_of_custom_gates.cpp) target_link_libraries(test_of_custom_gates.exe iqs) +add_executable(test_of_ncu_gates.exe test_of_ncu_gates.cpp) +target_link_libraries(test_of_ncu_gates.exe iqs) + + ################################################################################ set_target_properties( benchgates.exe @@ -26,6 +30,7 @@ set_target_properties( benchgates.exe heisenberg_dynamics.exe noisy_circuit_test.exe test_of_custom_gates.exe + test_of_ncu_gates.exe PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin" ) diff --git a/examples/test_of_ncu_gates.cpp b/examples/test_of_ncu_gates.cpp new file mode 100644 index 00000000..796e107f --- /dev/null +++ b/examples/test_of_ncu_gates.cpp @@ -0,0 +1,105 @@ +/** + * @file test_of_ncu_gates.cpp + * @author Lee J. O'Riordan (lee.oriordan@ichec.ie) + * @author Myles Doyle (myles.doyle@ichec.ie) + * @brief Application to show functionality of ApplyNCU gate call. + * @version 0.2 + * @date 2020-06-12 + */ + +#include "../include/qureg.hpp" + +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// + +int main(int argc, char **argv) +{ + unsigned myrank=0, nprocs=1; + qhipster::mpi::Environment env(argc, argv); + myrank = env.GetStateRank(); + nprocs = qhipster::mpi::Environment::GetStateSize(); + if (env.IsUsefulRank() == false) return 0; + int num_threads = 1; +#ifdef _OPENMP +#pragma omp parallel + { + num_threads = omp_get_num_threads(); + } +#endif + + // number of qubits in compute register. + std::size_t num_qubits_compute = 4; + if(argc != 2){ + fprintf(stderr, "usage: %s \n", argv[0]); + exit(1); + } + else{ + num_qubits_compute = atoi(argv[1]); + } + + // pauliX gate that will be applied in NCU + ComplexDP zero = {0.,0.}; + ComplexDP one = {1.,0.}; + TM2x2 pauliX{{zero,one,one,zero}}; + + // Setup vector to store compute and auxiliary quantum register indices. + // |compute reg>|auxiliary reg> + // Set number of auxiliary qubits to equal number of controlqubits + // if there are more than 4 control qubits, else auxiliary register + // will be empty as it will not be used in the NCU routine for + // optimisations. + std::size_t num_qubits_control = num_qubits_compute - 1; + std::size_t num_qubits_auxiliary = (num_qubits_compute - 2) * (num_qubits_control > 4); + std::vector reg_compute(num_qubits_compute); + std::vector reg_auxiliary(num_qubits_auxiliary); + + // Set qubit indices of registers + { + std::size_t qubit_index = 0; + for(std::size_t i = 0; i < num_qubits_compute; i++){ + reg_compute[i] = qubit_index; + qubit_index++; + } + for(std::size_t i = 0; i < num_qubits_auxiliary; i++){ + reg_auxiliary[i] = qubit_index; + qubit_index++; + } + } + + // Set qubit indices for qubits acting as control + std::vector control_ids(num_qubits_control); + + // Set vector containing indices of the qubits acting as + // control for the NCU gate. + for(std::size_t i = 0; i < num_qubits_control; i++){ + control_ids[i] = reg_compute[i]; + } + + // Set index of target qubit + std::size_t target_id = num_qubits_compute - 1; + + QubitRegister psi(num_qubits_compute + num_qubits_auxiliary); + psi.Initialize("base", 0); + + { + psi.EnableStatistics(); + + // Apply a Hadamard gate to first num_qubits_compute-1 + // qubits in the compute register. + for(std::size_t qubit_id = 0; qubit_id < num_qubits_compute-1; qubit_id++){ + psi.ApplyHadamard(reg_compute[qubit_id]); + } + + psi.Print("Before NCU"); + psi.GetStatistics(); + + // Apply NCU + psi.ApplyNCU(pauliX, control_ids, reg_auxiliary, target_id); + + // Observe only state with the first num_qubits_compute-1 + // qubits in the compute register set to 1 executes PauliX + // on the target qubit. + psi.Print("After NCU"); + } +} diff --git a/include/GateCache.hpp b/include/GateCache.hpp index b1a3a713..92040261 100644 --- a/include/GateCache.hpp +++ b/include/GateCache.hpp @@ -55,10 +55,9 @@ class GateCache { /** * @brief Initialise the gate cache with PauliX,Y,Z and H up to a given sqrt depth * - * @param qReg The QubitRegister object * @param sqrt_depth The depth to which calculate sqrt matrices and their respective adjoints */ - void initCache(QubitRegister& qReg, const std::size_t sqrt_depth){ + void initCache(const std::size_t sqrt_depth){ // If we do not have a sufficient circuit depth, clear and rebuild up to given depth. if(cache_depth < sqrt_depth ){ @@ -90,12 +89,12 @@ class GateCache { * @param gate Gate matrix * @param max_depth Depth of calculations for sqrt and associate adjoints */ - void addToCache(QubitRegister& qReg, const std::string gateLabel, const GateType& gate, std::size_t max_depth){ + void addToCache(const std::string gateLabel, const GateType& gate, std::size_t max_depth){ if(max_depth <= cache_depth && gateCacheMap.find(gateLabel) != gateCacheMap.end() ){ return; } else if(max_depth > cache_depth){ - initCache(qReg, max_depth); + initCache(max_depth); } std::vector< std::pair > v; @@ -135,13 +134,14 @@ class GateCache { } constexpr GateType construct_hadamard(){ GateType h; - decltype(std::declval(real(h(0,0)))) f = 1. / std::sqrt(2.); - h(0, 0) = h(0, 1) = h(1, 0) = Type(f, 0.); - h(1, 1) = Type(-f, 0.); + auto f = Type(1. / std::sqrt(2.), 0.0); + h(0, 0) = h(0, 1) = h(1, 0) = f; + h(1, 1) = -f; return h; } }; -#endif - +template class GateCache; +template class GateCache; +#endif \ No newline at end of file diff --git a/include/ncu.hpp b/include/ncu.hpp index a49e5960..efacdd5d 100644 --- a/include/ncu.hpp +++ b/include/ncu.hpp @@ -30,7 +30,7 @@ template class NCU { private: using Matrix2x2Type = TM2x2; - GateCache gate_cache; + GateCache gate_cache; protected: static std::size_t num_gate_ops; @@ -41,7 +41,7 @@ class NCU { * */ NCU() { - gate_cache = GateCache(); + gate_cache = GateCache(); }; /** @@ -76,7 +76,7 @@ class NCU { * * @return GateCache type */ - GateCache& getGateCache(){ + GateCache& getGateCache(){ return gate_cache; } diff --git a/src/qureg_ncu.cpp b/src/qureg_ncu.cpp index eaeaf52f..e2363530 100644 --- a/src/qureg_ncu.cpp +++ b/src/qureg_ncu.cpp @@ -4,9 +4,6 @@ #include "mat_ops.hpp" #include -//template class QubitRegister; -//template class QubitRegister; - template void QubitRegister::ApplyNCU( TM2x2 gate, @@ -37,3 +34,13 @@ void QubitRegister::ApplyNCU( ncu.applyNQubitControl(*this, ctrl_indices, aux_indices, target, gate_label, gate, 0); } +template void QubitRegister::ApplyNCU( + TM2x2 gate, + const std::vector& ctrl_indices, + const std::vector& aux_indices, + unsigned const target); +template void QubitRegister::ApplyNCU( + TM2x2 gate, + const std::vector& ctrl_indices, + const std::vector& aux_indices, + unsigned const target); diff --git a/unit_test/include/apply_ncu_test.hpp b/unit_test/include/apply_ncu_test.hpp new file mode 100644 index 00000000..6960431d --- /dev/null +++ b/unit_test/include/apply_ncu_test.hpp @@ -0,0 +1,630 @@ +/** + * @file apply_ncu_test.hpp + * @author Lee J. O'Riordan (lee.oriordan@ichec.ie) + * @author Myles Doyle (myles.doyle@ichec.ie) + * @brief Test functionality of ApplyNCU gate call. + * @version 0.2 + * @date 2020-06-12 + */ + +#ifndef APPLY_NCU_TEST_HPP +#define APPLY_NCU_TEST_HPP + +#include "../../include/qureg.hpp" + +////////////////////////////////////////////////////////////////////////////// +// Test fixture class. + +class ApplyNCUGateTest : public ::testing::Test +{ + protected: + + ApplyNCUGateTest(){ + } + + // just after the 'constructor' + void SetUp() override{ + // All tests are skipped if the rank is dummy. + if (qhipster::mpi::Environment::IsUsefulRank() == false){ + GTEST_SKIP(); + } + + // All tests are skipped if the 4-qubit state is distributed in more than 2^3 ranks. + // In fact the MPI version needs to allocate half-the-local-storage for communication. + if (qhipster::mpi::Environment::GetStateSize() > 8){ + GTEST_SKIP(); + } + } + + // Pauli gates that will be applied in NCU + ComplexDP zero = {0.,0.}; + ComplexDP one_re = {1.,0.}; + ComplexDP one_im = {0.,1.}; + ComplexDP neg_one_re = {-1.,0.}; + ComplexDP neg_one_im = {0.,-1.}; + TM2x2 pauliX_{{zero,one_re,one_re,zero}}; + TM2x2 pauliY_{{zero,neg_one_im,one_im,zero}}; + TM2x2 pauliZ_{{one_re,zero,zero,neg_one_re}}; + + double accepted_error_ = 1e-14; + double sqrt2_ = std::sqrt(2.); + + // Standard NCU + const std::size_t num_qubits_ = 6; + const std::size_t num_qubits_control_ = 3; + size_t init0000_ = 0, init0001 = 8, init111000_ = 7, init111001_ = 39; + + std::size_t target_index_ = num_qubits_-1; + std::vector control_indices_ = {0,1,2}; + std::vector auxiliary_indices_empty_; + + std::size_t num_states_ = pow(2, num_qubits_control_); + double percent_equal_dist_ = 1.0/(double) num_states_; + double amplitude_ = 1.0/std::sqrt(num_states_); + + // NCU with optimisations + const std::size_t num_qubits_opt_ = 11; + const std::size_t num_qubits_control_opt_ = 6; + const std::size_t num_qubits_auxiliary_opt_ = 4; + size_t init11111100001_ = 63+1024, init11111100000_ = 63; + + std::size_t target_index_opt_ = num_qubits_opt_ -1; + std::vector control_indices_opt_ = {0,1,2,3,4,5}; + std::vector auxiliary_indices_opt_ = {6,7,8,9}; + + std::size_t num_states_opt_ = pow(2, num_qubits_control_opt_); + double percent_equal_dist_opt_ = 1.0/(double) num_states_opt_; + double amplitude_opt_ = 1.0/std::sqrt(num_states_opt_); +}; + +////////////////////////////////////////////////////////////////////////////// +// For all 1-qubit gates we test the expected behavior: +// - on the last qubit +// - for initial states in orthogonal basis (X and Z eigensattes) +// +// +////////////////////////////////////////////////////////////////////////////// +// PauliX on |0> +TEST_F(ApplyNCUGateTest, PauliXSingleState0){ + + // Apply to |111>|00>|0> + // + QubitRegister psi (num_qubits_,"base",init111000_); + psi.ApplyNCU(pauliX_, control_indices_, auxiliary_indices_empty_, target_index_); + + ASSERT_NEAR(psi.ComputeNorm(), 1., accepted_error_); + + // Ensure control qubits remain unchanged. + for(std::size_t i = 0; i < num_qubits_control_; i++){ + ASSERT_NEAR(psi.GetProbability(control_indices_[i]), 1., accepted_error_); + } + + // Ensure target qubit was set correctly. + ASSERT_NEAR(psi.GetProbability(target_index_), 1.,accepted_error_); + + ASSERT_NEAR( psi.GetGlobalAmplitude(init111001_).real(), 1., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init111001_).imag(), 0., accepted_error_); +} + +////////////////////////////////////////////////////////////////////////////// +// PauliX on |1> +TEST_F(ApplyNCUGateTest, PauliXSingleState1){ + + // Apply to |111>|00>|1> + // + QubitRegister psi (num_qubits_,"base",init111001_); + psi.ApplyNCU(pauliX_, control_indices_, auxiliary_indices_empty_, target_index_); + + ASSERT_NEAR(psi.ComputeNorm(), 1., accepted_error_); + + // Ensure control qubits remain unchanged. + for(std::size_t i = 0; i < num_qubits_control_; i++){ + ASSERT_NEAR(psi.GetProbability(control_indices_[i]), 1., accepted_error_); + } + + // Ensure target qubit was set correctly. + ASSERT_NEAR(psi.GetProbability(target_index_), 0.,accepted_error_); + + ASSERT_NEAR( psi.GetGlobalAmplitude(init111000_).real(), 1., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init111000_).imag(), 0., accepted_error_); + + +} + +////////////////////////////////////////////////////////////////////////////// +// PauliY on |0> +TEST_F(ApplyNCUGateTest, PauliYSingleState0){ + + // Apply to |111>|00>|0> + // + QubitRegister psi (num_qubits_,"base",init111000_); + psi.ApplyNCU(pauliY_, control_indices_, auxiliary_indices_empty_, target_index_); + + ASSERT_NEAR(psi.ComputeNorm(), 1., accepted_error_); + + // Ensure control qubits remain unchanged. + for(std::size_t i = 0; i < num_qubits_control_; i++){ + ASSERT_NEAR(psi.GetProbability(control_indices_[i]), 1., accepted_error_); + } + + // Ensure target qubit was set correctly. + ASSERT_NEAR(psi.GetProbability(target_index_), 1., accepted_error_); + + + ASSERT_NEAR( psi.GetGlobalAmplitude(init111001_).real(), 0., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init111001_).imag(), 1., accepted_error_); +} + +////////////////////////////////////////////////////////////////////////////// +// PauliY on |1> +TEST_F(ApplyNCUGateTest, PauliYSingleState1){ + + // Apply to |111>|00>|1> + // + QubitRegister psi (num_qubits_,"base",init111001_); + psi.ApplyNCU(pauliY_, control_indices_, auxiliary_indices_empty_, target_index_); + + ASSERT_NEAR(psi.ComputeNorm(), 1., accepted_error_); + + // Ensure control qubits remain unchanged. + for(std::size_t i = 0; i < num_qubits_control_; i++){ + ASSERT_NEAR(psi.GetProbability(control_indices_[i]), 1., accepted_error_); + } + // Ensure target qubit was set correctly. + ASSERT_NEAR(psi.GetProbability(target_index_), 0., accepted_error_); + + + ASSERT_NEAR( psi.GetGlobalAmplitude(init111000_).real(), 0., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init111000_).imag(), -1., accepted_error_); +} + +////////////////////////////////////////////////////////////////////////////// +// PauliZ on |0> +TEST_F(ApplyNCUGateTest, PauliZSingleState0){ + + // Apply to |111>|00>|0> + // + QubitRegister psi (num_qubits_,"base",init111000_); + psi.ApplyNCU(pauliZ_, control_indices_, auxiliary_indices_empty_, target_index_); + + ASSERT_NEAR(psi.ComputeNorm(), 1., accepted_error_); + + // Ensure control qubits remain unchanged. + for(std::size_t i = 0; i < num_qubits_control_; i++){ + ASSERT_NEAR(psi.GetProbability(control_indices_[i]), 1., accepted_error_); + } + // Ensure target qubit was set correctly. + ASSERT_NEAR(psi.GetProbability(target_index_), 0., accepted_error_); + + // Check amplitude_ + ASSERT_NEAR( psi.GetGlobalAmplitude(init111000_).real(), 1., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init111000_).imag(), 0., accepted_error_); +} + +////////////////////////////////////////////////////////////////////////////// +// PauliZ on |1> +TEST_F(ApplyNCUGateTest, PauliZSingleState1){ + + // Apply to |111>|00>|1> + // + QubitRegister psi (num_qubits_,"base",init111001_); + psi.ApplyNCU(pauliZ_, control_indices_, auxiliary_indices_empty_, target_index_); + + ASSERT_NEAR(psi.ComputeNorm(), 1., accepted_error_); + + // Ensure control qubits remain unchanged. + for(std::size_t i = 0; i < num_qubits_control_; i++){ + ASSERT_NEAR(psi.GetProbability(control_indices_[i]), 1., accepted_error_); + } + // Ensure target qubit was set correctly. + ASSERT_NEAR(psi.GetProbability(target_index_), 1., accepted_error_); + + ASSERT_NEAR( psi.GetGlobalAmplitude(init111001_).real(), -1., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init111001_).imag(), 0., accepted_error_); + +} + +////////////////////////////////////////////////////////////////////////////// +// PauliX on superposition of states on |0> +TEST_F(ApplyNCUGateTest, PauliXMultiState0){ + + QubitRegister psi (num_qubits_,"base",0); + for(std::size_t i = 0; i < num_qubits_control_; i++){ + psi.ApplyHadamard(control_indices_[i]); + } + + psi.ApplyNCU(pauliX_, control_indices_, auxiliary_indices_empty_, target_index_); + + ASSERT_NEAR(psi.ComputeNorm(), 1., accepted_error_); + + // Ensure target qubit was set correctly. + ASSERT_NEAR(psi.GetProbability(target_index_), percent_equal_dist_,accepted_error_); + + // Check states without all control qubits set have + // correct value. + for(std::size_t i = 0; i < num_states_-1; i++){ + ASSERT_NEAR( psi.GetGlobalAmplitude(i).real(), amplitude_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(i).imag(), 0., accepted_error_); + } + + ASSERT_NEAR( psi.GetGlobalAmplitude(init111000_).real(), 0., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init111000_).imag(), 0., accepted_error_); + + ASSERT_NEAR( psi.GetGlobalAmplitude(init111001_).real(), amplitude_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init111001_).imag(), 0., accepted_error_); +} + +////////////////////////////////////////////////////////////////////////////// +// PauliY on superposition of states on |0> +TEST_F(ApplyNCUGateTest, PauliYMultiState0){ + + QubitRegister psi (num_qubits_,"base",0); + for(std::size_t i = 0; i < num_qubits_control_; i++){ + psi.ApplyHadamard(control_indices_[i]); + } + + psi.ApplyNCU(pauliY_, control_indices_, auxiliary_indices_empty_, target_index_); + + ASSERT_NEAR(psi.ComputeNorm(), 1., accepted_error_); + + // Ensure target qubit was set correctly. + ASSERT_NEAR(psi.GetProbability(target_index_), percent_equal_dist_,accepted_error_); + + // Check states without all control qubits set have + // correct value. + for(std::size_t i = 0; i < num_states_-1; i++){ + ASSERT_NEAR( psi.GetGlobalAmplitude(i).real(), amplitude_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(i).imag(), 0., accepted_error_); + } + + ASSERT_NEAR( psi.GetGlobalAmplitude(init111000_).real(), 0., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init111000_).imag(), 0., accepted_error_); + + ASSERT_NEAR( psi.GetGlobalAmplitude(init111001_).real(), 0., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init111001_).imag(), amplitude_, accepted_error_); +} + +////////////////////////////////////////////////////////////////////////////// +// PauliZ on superposition of states on |0> +TEST_F(ApplyNCUGateTest, PauliZMultiState0){ + + QubitRegister psi (num_qubits_,"base",0); + for(std::size_t i = 0; i < num_qubits_control_; i++){ + psi.ApplyHadamard(control_indices_[i]); + } + + psi.ApplyNCU(pauliZ_, control_indices_, auxiliary_indices_empty_, target_index_); + + ASSERT_NEAR(psi.ComputeNorm(), 1., accepted_error_); + + // Ensure target qubit was set correctly. + ASSERT_NEAR(psi.GetProbability(target_index_), 0.,accepted_error_); + + // Check states without all control qubits set have + // correct value. + for(std::size_t i = 0; i < num_states_-1; i++){ + ASSERT_NEAR( psi.GetGlobalAmplitude(i).real(), amplitude_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(i).imag(), 0., accepted_error_); + } + + ASSERT_NEAR( psi.GetGlobalAmplitude(init111000_).real(), amplitude_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init111000_).imag(), 0., accepted_error_); + + ASSERT_NEAR( psi.GetGlobalAmplitude(init111001_).real(), 0., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init111001_).imag(), 0., accepted_error_); +} + +////////////////////////////////////////////////////////////////////////////// +// PauliX on superposition of states on |1> +TEST_F(ApplyNCUGateTest, PauliXMultiState1){ + + QubitRegister psi (num_qubits_,"base",0); + for(std::size_t i = 0; i < num_qubits_control_; i++){ + psi.ApplyHadamard(control_indices_[i]); + } + // Flip target qubit to |1> + psi.ApplyPauliX(target_index_); + + psi.ApplyNCU(pauliX_, control_indices_, auxiliary_indices_empty_, target_index_); + + ASSERT_NEAR(psi.ComputeNorm(), 1., accepted_error_); + + // Ensure target qubit was set correctly. + ASSERT_NEAR(1. - psi.GetProbability(target_index_), percent_equal_dist_,accepted_error_); + + // Check states without all control qubits set have + // correct value. + for(std::size_t i = 0; i < num_states_-1; i++){ + ASSERT_NEAR( psi.GetGlobalAmplitude(i + 32).real(), amplitude_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(i + 32).imag(), 0., accepted_error_); + } + + ASSERT_NEAR( psi.GetGlobalAmplitude(init111000_).real(), amplitude_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init111000_).imag(), 0., accepted_error_); + + ASSERT_NEAR( psi.GetGlobalAmplitude(init111001_).real(), 0., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init111001_).imag(), 0., accepted_error_); +} + +////////////////////////////////////////////////////////////////////////////// +// PauliY on superposition of states on |1> +TEST_F(ApplyNCUGateTest, PauliYMultiState1){ + + QubitRegister psi (num_qubits_,"base",0); + for(std::size_t i = 0; i < num_qubits_control_; i++){ + psi.ApplyHadamard(control_indices_[i]); + } + // Flip target qubit to |1> + psi.ApplyPauliX(target_index_); + + psi.ApplyNCU(pauliY_, control_indices_, auxiliary_indices_empty_, target_index_); + + ASSERT_NEAR(psi.ComputeNorm(), 1., accepted_error_); + + // Ensure target qubit was set correctly. + ASSERT_NEAR(1. - psi.GetProbability(target_index_), percent_equal_dist_,accepted_error_); + + // Check states without all control qubits set have + // correct value. + for(std::size_t i = 0; i < num_states_-1; i++){ + ASSERT_NEAR( psi.GetGlobalAmplitude(i + 32).real(), amplitude_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(i + 32).imag(), 0., accepted_error_); + } + + ASSERT_NEAR( psi.GetGlobalAmplitude(init111000_).real(), 0., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init111000_).imag(), -amplitude_, accepted_error_); + + ASSERT_NEAR( psi.GetGlobalAmplitude(init111001_).real(), 0., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init111001_).imag(), 0., accepted_error_); +} + +////////////////////////////////////////////////////////////////////////////// +// PauliZ on superposition of states on |1> +TEST_F(ApplyNCUGateTest, PauliZMultiState1){ + + QubitRegister psi (num_qubits_,"base",0); + for(std::size_t i = 0; i < num_qubits_control_; i++){ + psi.ApplyHadamard(control_indices_[i]); + } + // Flip target qubit to |1> + psi.ApplyPauliX(target_index_); + + psi.ApplyNCU(pauliZ_, control_indices_, auxiliary_indices_empty_, target_index_); + + ASSERT_NEAR(psi.ComputeNorm(), 1., accepted_error_); + + // Ensure target qubit was set correctly. + ASSERT_NEAR(psi.GetProbability(target_index_), 1.,accepted_error_); + + // Check states without all control qubits set have + // correct value. + for(std::size_t i = 0; i < num_states_-1; i++){ + ASSERT_NEAR( psi.GetGlobalAmplitude(i + 32).real(), amplitude_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(i + 32).imag(), 0., accepted_error_); + } + + ASSERT_NEAR( psi.GetGlobalAmplitude(init111000_).real(), 0., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init111000_).imag(), 0., accepted_error_); + + ASSERT_NEAR( psi.GetGlobalAmplitude(init111001_).real(), -amplitude_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init111001_).imag(), 0., accepted_error_); +} + +////////////////////////////////////////////////////////////////////////////// +// Test expcted Failure of NCU if arbitrary +// U that is not a Pauli matrix is passed. +TEST_F(ApplyNCUGateTest, FailArbitraryU) +{ + + TM2x2 U; + U(0, 0) = {0.592056606032915, 0.459533060553574}; + U(0, 1) = {-0.314948020757856, -0.582328159830658}; + U(1, 0) = {0.658235557641767, 0.070882241549507}; + U(1, 1) = {0.649564427121402, 0.373855203932477}; + // |psi> = |1110> + QubitRegister psi (num_qubits_,"base",init111000_); + ASSERT_DEATH(psi.ApplyNCU(U, control_indices_, auxiliary_indices_empty_, target_index_),"Gate not currently support for NCU:*" ); +} + +////////////////////////////////////////////////////////////////////////////// +// NCU with Optimisations +// +// PauliX on superposition of states with NCU Optimised using +// auxilairy register acting on |0> +TEST_F(ApplyNCUGateTest, OptimisedNCUPauliXMultiState0){ + + QubitRegister psi (num_qubits_opt_,"base",0); + for(std::size_t i = 0; i < num_qubits_control_opt_; i++){ + psi.ApplyHadamard(control_indices_opt_[i]); + } + + psi.ApplyNCU(pauliX_, control_indices_opt_, auxiliary_indices_opt_, target_index_opt_); + + ASSERT_NEAR(psi.ComputeNorm(), 1., accepted_error_); + + // Ensure target qubit was set correctly. + ASSERT_NEAR(psi.GetProbability(target_index_opt_), percent_equal_dist_opt_, accepted_error_); + + // Check states without all control qubits set have + // correct value. + for(std::size_t i = 0; i < num_states_opt_-1; i++){ + ASSERT_NEAR( psi.GetGlobalAmplitude(i).real(), amplitude_opt_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(i).imag(), 0., accepted_error_); + } + + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100000_).real(), 0., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100000_).imag(), 0., accepted_error_); + + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100001_).real(), amplitude_opt_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100001_).imag(), 0., accepted_error_); +} + +////////////////////////////////////////////////////////////////////////////// +// +// PauliY on superposition of states with NCU Optimised using +// auxilairy register acting on |0> +TEST_F(ApplyNCUGateTest, OptimisedNCUPauliYMultiState0){ + + QubitRegister psi (num_qubits_opt_,"base",0); + for(std::size_t i = 0; i < num_qubits_control_opt_; i++){ + psi.ApplyHadamard(control_indices_opt_[i]); + } + + psi.ApplyNCU(pauliY_, control_indices_opt_, auxiliary_indices_opt_, target_index_opt_); + + //psi.Print("Y"); + ASSERT_NEAR(psi.ComputeNorm(), 1., accepted_error_); + + // Ensure target qubit was set correctly. + ASSERT_NEAR(psi.GetProbability(target_index_opt_), percent_equal_dist_opt_, accepted_error_); + + // Check states without all control qubits set have + // correct value. + for(std::size_t i = 0; i < num_states_opt_-1; i++){ + ASSERT_NEAR( psi.GetGlobalAmplitude(i).real(), amplitude_opt_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(i).imag(), 0., accepted_error_); + } + + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100000_).real(), 0., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100000_).imag(), 0., accepted_error_); + + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100001_).real(), 0., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100001_).imag(), amplitude_opt_, accepted_error_); +} + +////////////////////////////////////////////////////////////////////////////// +// +// PauliZ on superposition of states with NCU Optimised using +// auxilairy register acting on |0> +TEST_F(ApplyNCUGateTest, OptimisedNCUPauliZMultiState0){ + + QubitRegister psi (num_qubits_opt_,"base",0); + for(std::size_t i = 0; i < num_qubits_control_opt_; i++){ + psi.ApplyHadamard(control_indices_opt_[i]); + } + + psi.ApplyNCU(pauliZ_, control_indices_opt_, auxiliary_indices_opt_, target_index_opt_); + + ASSERT_NEAR(psi.ComputeNorm(), 1., accepted_error_); + + // Ensure target qubit was set correctly. + ASSERT_NEAR(psi.GetProbability(target_index_opt_), 0., accepted_error_); + + // Check states without all control qubits set have + // correct value. + for(std::size_t i = 0; i < num_states_opt_-1; i++){ + ASSERT_NEAR( psi.GetGlobalAmplitude(i).real(), amplitude_opt_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(i).imag(), 0., accepted_error_); + } + + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100000_).real(), amplitude_opt_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100000_).imag(), 0., accepted_error_); + + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100001_).real(), 0., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100001_).imag(), 0., accepted_error_); +} + +////////////////////////////////////////////////////////////////////////////// +// +/// PauliX on superposition of states with NCU Optimised using +// auxilairy register acting on |1> +TEST_F(ApplyNCUGateTest, OptimisedNCUPauliXMultiState1){ + + QubitRegister psi (num_qubits_opt_,"base",0); + for(std::size_t i = 0; i < num_qubits_control_opt_; i++){ + psi.ApplyHadamard(control_indices_opt_[i]); + } + // Flip target qubit to |1> + psi.ApplyPauliX(target_index_opt_); + + psi.ApplyNCU(pauliX_, control_indices_opt_, auxiliary_indices_opt_, target_index_opt_); + + ASSERT_NEAR(psi.ComputeNorm(), 1., accepted_error_); + + // Ensure target qubit was set correctly. + ASSERT_NEAR(1. - psi.GetProbability(target_index_opt_), percent_equal_dist_opt_, accepted_error_); + + // Check states without all control qubits set have + // correct value. + for(std::size_t i = 0; i < num_states_opt_-1; i++){ + ASSERT_NEAR( psi.GetGlobalAmplitude(i + 1024).real(), amplitude_opt_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(i + 1024).imag(), 0., accepted_error_); + } + + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100000_).real(), amplitude_opt_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100000_).imag(), 0., accepted_error_); + + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100001_).real(), 0., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100001_).imag(), 0., accepted_error_); +} + +////////////////////////////////////////////////////////////////////////////// +// +// PauliY on superposition of states with NCU Optimised using +// auxilairy register acting on |1> +TEST_F(ApplyNCUGateTest, OptimisedNCUPauliYMultiState1){ + + QubitRegister psi (num_qubits_opt_,"base",0); + for(std::size_t i = 0; i < num_qubits_control_opt_; i++){ + psi.ApplyHadamard(control_indices_opt_[i]); + } + // Flip target qubit to |1> + psi.ApplyPauliX(target_index_opt_); + + psi.ApplyNCU(pauliY_, control_indices_opt_, auxiliary_indices_opt_, target_index_opt_); + + //psi.Print("Y"); + ASSERT_NEAR(psi.ComputeNorm(), 1., accepted_error_); + + // Ensure target qubit was set correctly. + ASSERT_NEAR(1. - psi.GetProbability(target_index_opt_), percent_equal_dist_opt_, accepted_error_); + + // Check states without all control qubits set have + // correct value. + for(std::size_t i = 0; i < num_states_opt_-1; i++){ + ASSERT_NEAR( psi.GetGlobalAmplitude(i + 1024).real(), amplitude_opt_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(i + 1024).imag(), 0., accepted_error_); + } + + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100000_).real(), 0., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100000_).imag(), -amplitude_opt_, accepted_error_); + + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100001_).real(), 0., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100001_).imag(), 0., accepted_error_); +} + +////////////////////////////////////////////////////////////////////////////// +// +// PauliZ on superposition of states with NCU Optimised using +// auxilairy register acting on |1> +TEST_F(ApplyNCUGateTest, OptimisedNCUPauliZMultiState1){ + + QubitRegister psi (num_qubits_opt_,"base",0); + for(std::size_t i = 0; i < num_qubits_control_opt_; i++){ + psi.ApplyHadamard(control_indices_opt_[i]); + } + // Flip target qubit to |1> + psi.ApplyPauliX(target_index_opt_); + + psi.ApplyNCU(pauliZ_, control_indices_opt_, auxiliary_indices_opt_, target_index_opt_); + + ASSERT_NEAR(psi.ComputeNorm(), 1., accepted_error_); + + // Ensure target qubit was set correctly. + ASSERT_NEAR(psi.GetProbability(target_index_opt_), 1., accepted_error_); + + // Check states without all control qubits set have + // correct value. + for(std::size_t i = 0; i < num_states_opt_-1; i++){ + ASSERT_NEAR( psi.GetGlobalAmplitude(i + 1024).real(), amplitude_opt_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(i + 1024).imag(), 0., accepted_error_); + } + + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100000_).real(), 0., accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100000_).imag(), 0., accepted_error_); + + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100001_).real(), -amplitude_opt_, accepted_error_); + ASSERT_NEAR( psi.GetGlobalAmplitude(init11111100001_).imag(), 0., accepted_error_); +}///////////////////////////////////////////////////////////////////////////// + +#endif // header guard APPLY_NCU_TEST_HPP diff --git a/unit_test/suite_of_tests.cpp b/unit_test/suite_of_tests.cpp index c4c5c4d6..7edbb592 100644 --- a/unit_test/suite_of_tests.cpp +++ b/unit_test/suite_of_tests.cpp @@ -45,6 +45,7 @@ ASSERT_NEAR(val1.imag(),val2.imag(),error); #include "include/gate_counter_test.hpp" #include "include/noisy_simulation_test.hpp" #include "include/extra_features_test.hpp" +#include "include/apply_ncu_test.hpp" // These tests make sense only when MPI is enabled. #include "include/multiple_states_test.hpp" From 6d70397175e3b415e5573245fd45d0f28b14d469 Mon Sep 17 00:00:00 2001 From: mlxd Date: Fri, 2 Oct 2020 16:10:53 +0100 Subject: [PATCH 8/8] Ensure thread safety with tests --- .github/workflows/cpp_build_with_cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cpp_build_with_cmake.yml b/.github/workflows/cpp_build_with_cmake.yml index d9670914..1b06cbfb 100644 --- a/.github/workflows/cpp_build_with_cmake.yml +++ b/.github/workflows/cpp_build_with_cmake.yml @@ -26,4 +26,4 @@ jobs: run: cmake --build build - name: unit test - run: ./build/bin/utest + run: ./build/bin/utest --gtest_death_test_style=threadsafe