diff --git a/.github/workflows/build-fast.yml b/.github/workflows/build-fast.yml index d8c1fa7..e824947 100644 --- a/.github/workflows/build-fast.yml +++ b/.github/workflows/build-fast.yml @@ -1,4 +1,3 @@ -# Build directly on the GitHub runner with caching name: build-fast on: workflow_dispatch: @@ -10,10 +9,10 @@ concurrency: jobs: linux: - name: ${{matrix.runner}}-${{matrix.compiler}}-${{matrix.version}}-llvm${{matrix.llvm}} + name: ${{matrix.build-config.runner}}-${{matrix.build-config.compiler}}-${{matrix.build-config.version}}-llvm${{matrix.build-config.llvm}}${{ matrix.use-lightning == true && '-lightning' || '' }} strategy: matrix: - include: + build-config: - runner: jammy compiler: gcc version: 12 @@ -22,43 +21,65 @@ jobs: compiler: clang version: 15 llvm: 15 + use-lightning: [false, true] runs-on: >- - ${{ matrix.runner == 'focal' && 'ubuntu-20.04' - || matrix.runner == 'jammy' && 'ubuntu-22.04' + ${{ matrix.build-config.runner == 'focal' && 'ubuntu-20.04' + || matrix.build-config.runner == 'jammy' && 'ubuntu-22.04' || null }} env: CCACHE_DIR: "${{github.workspace}}/.ccache" CCACHE_MAXSIZE: "10G" - CC: ${{matrix.compiler}}-${{matrix.version}} - CXX: ${{matrix.compiler == 'gcc' && 'g++' || 'clang++'}}-${{matrix.version}} + CC: ${{matrix.build-config.compiler}}-${{matrix.build-config.version}} + CXX: ${{matrix.build-config.compiler == 'gcc' && 'g++' || 'clang++'}}-${{matrix.build-config.version}} steps: + - name: Install Python (if building for Lightning) + if: matrix.use-lightning == true + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install dependencies run: | sudo apt-get -q -y update sudo apt-get -q -y install \ ccache cmake ninja-build libgtest-dev \ - llvm-${{matrix.llvm}}-dev \ - ${{matrix.compiler}}-${{matrix.version}} \ - ${{matrix.compiler == 'gcc' && format('g++-{0}', matrix.version) || ''}} + llvm-${{matrix.build-config.llvm}}-dev \ + ${{matrix.build-config.compiler}}-${{matrix.build-config.version}} \ + ${{matrix.build-config.compiler == 'gcc' && format('g++-{0}', matrix.build-config.version) || ''}} + + if [[ "${{ matrix.use-lightning }}" == "true" ]]; then + echo "Installing Lightning Python dependencies..." + python -m pip install pennylane-lightning==0.43.0 + fi + echo "Installed toolchain:" ld --version | head -1 $CC --version | head -1 $CXX --version | head -1 - llvm-config-${{matrix.llvm}} --version | head -1 + llvm-config-${{matrix.build-config.llvm}} --version | head -1 + - name: Check out uses: actions/checkout@v4 + - name: Set up ccache uses: actions/cache@v4 with: path: ${{env.CCACHE_DIR}} - key: ccache-${{matrix.runner}}-${{matrix.compiler}}-${{matrix.version}}-${{github.run_id}} - restore-keys: ccache-${{matrix.runner}}-${{matrix.compiler}}-${{matrix.version}} + key: ccache-${{matrix.build-config.runner}}-${{matrix.build-config.compiler}}-${{matrix.build-config.version}}-${{github.run_id}} + restore-keys: ccache-${{matrix.build-config.runner}}-${{matrix.build-config.compiler}}-${{matrix.build-config.version}} + - name: Zero ccache stats run: | ccache -z + - name: Configure run: | + if [[ "${{ matrix.use-lightning }}" == "true" ]]; then + export LIGHTNING_PATH=$(bash ./scripts/lightning-path.sh qubit) + else + export LIGHTNING_PATH="" + fi mkdir build && cd build cmake -GNinja \ -DQIREE_GIT_DESCRIBE="${{github.event.pull_request @@ -67,23 +88,29 @@ jobs: -DQIREE_BUILD_TESTS:BOOL=ON \ -DQIREE_DEBUG:BOOL=ON \ -DQIREE_USE_XACC:BOOL=OFF \ + -DQIREE_USE_LIGHTNING:BOOL=${{ matrix.use-lightning == true && 'ON' || 'OFF' }} \ + -DQIREE_LIGHTNING_SIM_PATH="$LIGHTNING_PATH" \ -DCMAKE_BUILD_TYPE="Release" \ -DCMAKE_INSTALL_PREFIX="${{github.workspace}}/install" \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_FLAGS="-Wall -Wextra -pedantic" \ .. + - name: Build all working-directory: build run: | ninja + - name: Run tests working-directory: build run: | ctest --parallel 2 --timeout 15 --output-on-failure + - name: Install working-directory: build run: | ninja install + - name: Show ccache stats run: | ccache -s diff --git a/CMakeLists.txt b/CMakeLists.txt index 36fe82e..a4704ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,7 @@ option(QIREE_BUILD_TESTS "Build QIR-EE unit tests" ON) option(QIREE_BUILD_EXAMPLES "Build QIR-EE examples" OFF) option(QIREE_USE_QSIM "Download and build Google qsim backend" ON) option(QIREE_USE_XACC "Build XACC interface" OFF) +option(QIREE_USE_LIGHTNING "Build Pennylane Lightning backend" OFF) qiree_set_default(BUILD_TESTING ${QIREE_BUILD_TESTS}) @@ -137,6 +138,27 @@ if(QIREE_USE_QSIM) ) endif() +if(QIREE_USE_LIGHTNING) + qiree_add_library(qiree_lightning INTERFACE) + add_library(QIREE::lightning ALIAS qiree_lightning) + # Fetch Catalyst runtime include files + include("${CMAKE_CURRENT_LIST_DIR}/cmake/support_catalyst.cmake") + FindCatalyst(qiree_lightning) + target_include_directories(qiree_lightning SYSTEM INTERFACE + "$" + "$" + ) + set(QIREE_LIGHTNING_SIM_PATH "" CACHE FILEPATH "Path to the Lightning simulator shared library") + if(NOT QIREE_LIGHTNING_SIM_PATH) + message(FATAL_ERROR "QIREE_LIGHTNING_SIM_PATH is not set. Please specify the path using: -DQIREE_LIGHTNING_SIM_PATH=/path/to/lib") + endif() + message(STATUS "Using Lightning simulator shared library: ${QIREE_LIGHTNING_SIM_PATH}") + target_compile_definitions(qiree_lightning INTERFACE + QIREE_LIGHTNING_RTDLIB="${QIREE_LIGHTNING_SIM_PATH}" + ) +endif() + + if(QIREE_USE_XACC) find_package(XACC REQUIRED) endif() diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 10ad345..aea11be 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -28,6 +28,20 @@ if(QIREE_USE_QSIM) ) endif() +#-----------------------------------------------------------------------------# +# LIGHTNING FRONT END +#-----------------------------------------------------------------------------# + +if(QIREE_USE_LIGHTNING) + qiree_add_executable(qir-lightning + qir-lightning.cc + ) + target_link_libraries(qir-lightning + PUBLIC QIREE::qiree QIREE::qirlightning + PRIVATE CLI11::CLI11 + ) +endif() + #-----------------------------------------------------------------------------# # XACC FRONT END #-----------------------------------------------------------------------------# diff --git a/app/qir-lightning.cc b/app/qir-lightning.cc new file mode 100644 index 0000000..cff96f2 --- /dev/null +++ b/app/qir-lightning.cc @@ -0,0 +1,74 @@ +//----------------------------------*-C++-*----------------------------------// +// Copyright 2024 UT-Battelle, LLC, and other QIR-EE developers. +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +//---------------------------------------------------------------------------// +//! \file app/qir-lightning.cc +//---------------------------------------------------------------------------// +#include +#include +#include +#include + +#include "qiree/Executor.hh" +#include "qiree/Module.hh" +#include "qiree/ResultDistribution.hh" +#include "qirlightning/LightningQuantum.hh" +#include "qirlightning/LightningRuntime.hh" + +using namespace std::string_view_literals; + +namespace qiree +{ +namespace app +{ +//---------------------------------------------------------------------------// +void run(std::string const& filename, int num_shots) +{ + // Load the input + Executor execute{Module{filename}}; + + // Set up qsim + LightningQuantum sim(std::cout, 0); + LightningRuntime rt(std::cout, sim); + ResultDistribution distribution; + + // Run several time = shots (default 1) + for (int i = 0; i < num_shots; i++) + { + execute(sim, rt); + distribution.accumulate(rt.result()); + } + + std::cout << distribution.to_json() << std::endl; +} + +//---------------------------------------------------------------------------// +} // namespace app +} // namespace qiree + +//---------------------------------------------------------------------------// +/*! + * Execute and run. + */ +int main(int argc, char* argv[]) +{ + int num_shots{1}; + std::string filename; + + CLI::App app; + + auto* filename_opt + = app.add_option("--input,-i,input", filename, "QIR input file"); + filename_opt->required(); + + auto* nshot_opt + = app.add_option("-s,--shots", num_shots, "Number of shots"); + nshot_opt->capture_default_str(); + + CLI11_PARSE(app, argc, argv); + + qiree::app::run(filename, num_shots); + + return EXIT_SUCCESS; +} diff --git a/cmake/support_catalyst.cmake b/cmake/support_catalyst.cmake new file mode 100644 index 0000000..07ab613 --- /dev/null +++ b/cmake/support_catalyst.cmake @@ -0,0 +1,80 @@ +############################################################################################### +# This file provides macros to process Catalyst. +############################################################################################### + +# Include this only once +include_guard() + +macro(FindCatalyst target_name) + if(LIGHTNING_CATALYST_SRC_PATH) + if(NOT IS_ABSOLUTE ${LIGHTNING_CATALYST_SRC_PATH}) + message(FATAL_ERROR " LIGHTNING_CATALYST_SRC_PATH=${LIGHTNING_CATALYST_SRC_PATH} must be set to an absolute path") + endif() + if(CATALYST_GIT_TAG) + message(WARN " Setting `LIGHTNING_CATALYST_SRC_PATH=${LIGHTNING_CATALYST_SRC_PATH}` overrides `CATALYST_GIT_TAG=${CATALYST_GIT_TAG}`") + endif() + + # Acquire local git hash and use for CATALYST_GIT_TAG + execute_process(COMMAND git rev-parse --short HEAD + WORKING_DIRECTORY ${LIGHTNING_CATALYST_SRC_PATH} + OUTPUT_VARIABLE CATALYST_GIT_TAG + ) + message(INFO " Building against local Catalyst - path: ${LIGHTNING_CATALYST_SRC_PATH} - GIT TAG: ${CATALYST_GIT_TAG}") + + target_include_directories(${target_name} INTERFACE + $ + $ + ) + + else() + if(NOT CATALYST_GIT_TAG) + set(CATALYST_GIT_TAG "v0.13.0" CACHE STRING "GIT_TAG value to build Catalyst") + endif() + message(INFO " Building against Catalyst GIT TAG ${CATALYST_GIT_TAG}") + + set(CATALYST_DOWNLOAD_INCLUDE_DIR "${PROJECT_BINARY_DIR}/catalyst-headers") + + # Fetching /lib/backend/common hpp headers + set(LIB_BACKEND_COMMON_HEADERS CacheManager.hpp + QubitManager.hpp + Utils.hpp + ) + + foreach(HEADER ${LIB_BACKEND_COMMON_HEADERS}) + string(REGEX REPLACE "\\.[^.]*$" "" HEADER_NAME ${HEADER}) + FetchContent_Declare( + ${HEADER_NAME} + URL https://raw.githubusercontent.com/PennyLaneAI/catalyst/${CATALYST_GIT_TAG}/runtime/lib/backend/common/${HEADER} + DOWNLOAD_NO_EXTRACT True + SOURCE_DIR ${CATALYST_DOWNLOAD_INCLUDE_DIR} + ) + + FetchContent_MakeAvailable(${HEADER_NAME}) + endforeach() + + # Fetching include hpp headers + set(INCLUDE_HEADERS DataView.hpp + Exception.hpp + QuantumDevice.hpp + RuntimeCAPI.h + Types.h + ) + + foreach(HEADER ${INCLUDE_HEADERS}) + string(REGEX REPLACE "\\.[^.]*$" "" HEADER_NAME ${HEADER}) + FetchContent_Declare( + ${HEADER_NAME} + URL https://raw.githubusercontent.com/PennyLaneAI/catalyst/${CATALYST_GIT_TAG}/runtime/include/${HEADER} + DOWNLOAD_NO_EXTRACT True + SOURCE_DIR ${CATALYST_DOWNLOAD_INCLUDE_DIR} + ) + + FetchContent_MakeAvailable(${HEADER_NAME}) + endforeach() + + target_include_directories(${target_name} INTERFACE + $ + ) + + endif() +endmacro() diff --git a/scripts/lightning-path.sh b/scripts/lightning-path.sh new file mode 100755 index 0000000..b459b5f --- /dev/null +++ b/scripts/lightning-path.sh @@ -0,0 +1,61 @@ +#!/bin/bash -e +# +# This script determines the absolute path to a PennyLane-Lightning simulator +# library to be used with QIREE's Lightning backend. +# +# Usage: +# ./scripts/lightning-path.sh +# +# Example: +# ./scripts/lightning-path.sh qubit + +if [ -z "$1" ]; then + echo "Error: Missing argument. Usage: $0 " >&2 + echo "Example: $0 qubit" >&2 + exit 1 +fi + +# Validate simulator type +case "$1" in + qubit|gpu|kokkos) + ;; + *) + echo "Error: Invalid simulator type '$1'. Must be one of 'qubit', 'gpu', or 'kokkos'." >&2 + exit 1 + ;; +esac + +SIM_TYPE="$1" + +# Determine OS-specific library suffix +UNAME_S=$(uname -s) +case "$UNAME_S" in + Linux*) LIB_SUFFIX=".so";; + Darwin*) LIB_SUFFIX=".dylib";; + *) + echo "Error: Unsupported platform '$UNAME_S'. QIREE with PennyLane-Lightning only supports Linux and macOS." >&2 + exit 1 + ;; +esac + +# Find the base PennyLane-Lightning installation directory using Python +BASE_PATH=$(python -c "import site; print(f'{site.getsitepackages()[0]}/pennylane_lightning')") + +if [ -z "$BASE_PATH" ]; then + echo "Error: Could not determine pennylane_lightning path via Python." >&2 + echo "Is pennylane-lightning installed in your current Python environment?" >&2 + exit 1 +fi + +# Construct the full library path +LIB_NAME="liblightning_${SIM_TYPE}_catalyst${LIB_SUFFIX}" +FULL_PATH="${BASE_PATH}/${LIB_NAME}" + +# Check if the file actually exists +if [ ! -f "$FULL_PATH" ]; then + echo "Error: Simulator library not found at: $FULL_PATH" >&2 + echo "Ensure you have installed the correct simulator type ('$SIM_TYPE')" >&2 + exit 1 +fi + +echo "$FULL_PATH" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fb5f8f2..04bd923 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,4 +22,8 @@ if(QIREE_USE_QSIM) add_subdirectory(qirqsim) endif() +if(QIREE_USE_LIGHTNING) + add_subdirectory(qirlightning) +endif() + #---------------------------------------------------------------------------## diff --git a/src/qirlightning/CMakeLists.txt b/src/qirlightning/CMakeLists.txt new file mode 100644 index 0000000..086e2e8 --- /dev/null +++ b/src/qirlightning/CMakeLists.txt @@ -0,0 +1,28 @@ +#---------------------------------*-CMake-*----------------------------------# +# Copyright 2024 UT-Battelle, LLC, and other QIR-EE developers. +# See the top-level COPYRIGHT file for details. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#----------------------------------------------------------------------------# + +# Adding lightning as a library to qiree +qiree_add_library(qirlightning + LightningQuantum.cc + LightningRuntime.cc +) + +#Link the lightning library to qiree and any other relevant libraries +target_link_libraries(qirlightning + PUBLIC QIREE::qiree # Link to qiree + PUBLIC QIREE::lightning +) + +#----------------------------------------------------------------------------# +# HEADERS +#----------------------------------------------------------------------------# + +# Install headers, matching the relevant .hh files for qsim integration +install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/qirlightning" + COMPONENT development + FILES_MATCHING REGEX ".*\\.hh?$" +) diff --git a/src/qirlightning/LightningQuantum.cc b/src/qirlightning/LightningQuantum.cc new file mode 100644 index 0000000..47c4545 --- /dev/null +++ b/src/qirlightning/LightningQuantum.cc @@ -0,0 +1,207 @@ +//----------------------------------*-C++-*----------------------------------// +// Copyright 2024 UT-Battelle, LLC, and other QIR-EE developers. +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +//---------------------------------------------------------------------------// +//! \file qirlightning/LightningQuantum.cc +//---------------------------------------------------------------------------// + +#include "LightningQuantum.hh" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qiree/Assert.hh" + +extern "C" Catalyst::Runtime::QuantumDevice* +GenericDeviceFactory(char const* kwargs); +namespace qiree +{ +using namespace Catalyst::Runtime; + +//---------------------------------------------------------------------------// +/*! + * Initialize the Lightning simulator + */ +LightningQuantum::LightningQuantum(std::ostream& os, unsigned long int seed) + : output_(os), seed_(seed) +{ + auto rtld_flags = RTLD_LAZY | RTLD_NODELETE; + rtd_dylib_handler_ = dlopen(QIREE_LIGHTNING_RTDLIB, rtld_flags); + + QIREE_VALIDATE(rtd_dylib_handler_, + << "failed to load Lightning runtime library '" + << QIREE_LIGHTNING_RTDLIB << "'"); + + // Find device factory + std::vector const factory_names + = {"LightningSimulatorFactory", + "LightningKokkosSimulatorFactory", + "LightningGPUSimulatorFactory"}; + + for (auto const& factory_name : factory_names) + { + dlerror(); + factory_f_ptr_ = dlsym(rtd_dylib_handler_, factory_name.c_str()); + if (factory_f_ptr_) + { + break; + } + } + + QIREE_VALIDATE(factory_f_ptr_, + << "failed to find valid device factory function"); +} + +//---------------------------------------------------------------------------// +//! Default destructor +LightningQuantum::~LightningQuantum() +{ + if (rtd_dylib_handler_) + { + dlclose(rtd_dylib_handler_); + } +}; + +//---------------------------------------------------------------------------// +/*! + * Prepare to build a quantum circuit for an entry point + */ +void LightningQuantum::set_up(EntryPointAttrs const& attrs) +{ + QIREE_VALIDATE(attrs.required_num_qubits > 0, + << "input is not a quantum program"); + num_qubits_ = attrs.required_num_qubits; // Set the number of qubits + results_.resize(attrs.required_num_results); + + std::string rtd_kwargs = {}; + rtd_qdevice_ = std::unique_ptr( + reinterpret_cast(factory_f_ptr_)( + rtd_kwargs.c_str())); + + rtd_qdevice_->AllocateQubits(num_qubits_); +} + +//---------------------------------------------------------------------------// +/*! + * Complete an execution + */ +void LightningQuantum::tear_down() {} + +//---------------------------------------------------------------------------// +/*! + * Reset the qubit + */ +void LightningQuantum::reset(Qubit q) +{ + q.value = 0; +} + +//----------------------------------------------------------------------------// +/*! + * Read the value of a result. + */ +QState LightningQuantum::read_result(Result r) const +{ + QIREE_EXPECT(r.value < results_.size()); + auto result_bool = static_cast(results_[r.value]); + return static_cast(result_bool); +} + +//---------------------------------------------------------------------------// +/*! + * Map a qubit to a result index. + */ +void LightningQuantum::mz(Qubit q, Result r) +{ + QIREE_EXPECT(q.value < this->num_qubits()); + QIREE_EXPECT(r.value < this->num_results()); + std::mt19937 gen(seed_); + seed_++; + rtd_qdevice_->SetDevicePRNG(&gen); + auto result + = rtd_qdevice_->Measure(static_cast(q.value), std::nullopt); + results_[r.value] = *result; +} + +//---------------------------------------------------------------------------// +/* + * Quantum Instruction Mapping + */ + +// 1. Entangling gates +void LightningQuantum::cx(Qubit q1, Qubit q2) +{ + rtd_qdevice_->NamedOperation( + "CNOT", + {}, + {static_cast(q1.value), static_cast(q2.value)}); +} +void LightningQuantum::cnot(Qubit q1, Qubit q2) +{ + rtd_qdevice_->NamedOperation( + "CNOT", + {}, + {static_cast(q1.value), static_cast(q2.value)}); +} +void LightningQuantum::cz(Qubit q1, Qubit q2) +{ + rtd_qdevice_->NamedOperation( + "CZ", + {}, + {static_cast(q1.value), static_cast(q2.value)}); +} +// 2. Local gates +void LightningQuantum::h(Qubit q) +{ + rtd_qdevice_->NamedOperation( + "Hadamard", {}, {static_cast(q.value)}); +} +void LightningQuantum::s(Qubit q) +{ + rtd_qdevice_->NamedOperation("S", {}, {static_cast(q.value)}); +} +void LightningQuantum::t(Qubit q) +{ + rtd_qdevice_->NamedOperation("T", {}, {static_cast(q.value)}); +} +// 2.1 Pauli gates +void LightningQuantum::x(Qubit q) +{ + rtd_qdevice_->NamedOperation( + "PauliX", {}, {static_cast(q.value)}); +} +void LightningQuantum::y(Qubit q) +{ + rtd_qdevice_->NamedOperation( + "PauliY", {}, {static_cast(q.value)}); +} +void LightningQuantum::z(Qubit q) +{ + rtd_qdevice_->NamedOperation( + "PauliZ", {}, {static_cast(q.value)}); +} +// 2.2 rotation gates +void LightningQuantum::rx(double theta, Qubit q) +{ + rtd_qdevice_->NamedOperation( + "RX", {theta}, {static_cast(q.value)}); +} +void LightningQuantum::ry(double theta, Qubit q) +{ + rtd_qdevice_->NamedOperation( + "RY", {theta}, {static_cast(q.value)}); +} +void LightningQuantum::rz(double theta, Qubit q) +{ + rtd_qdevice_->NamedOperation( + "RZ", {theta}, {static_cast(q.value)}); +} + +} // namespace qiree diff --git a/src/qirlightning/LightningQuantum.hh b/src/qirlightning/LightningQuantum.hh new file mode 100644 index 0000000..ebadd71 --- /dev/null +++ b/src/qirlightning/LightningQuantum.hh @@ -0,0 +1,108 @@ +//----------------------------------*-C++-*----------------------------------// +// Copyright 2024 UT-Battelle, LLC, and other QIR-EE developers. +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +//---------------------------------------------------------------------------// +//! \file qirlightning/LightningQuantum.hh +//---------------------------------------------------------------------------// +#pragma once + +#include +#include +#include + +#include "qiree/Assert.hh" +#include "qiree/Macros.hh" +#include "qiree/QuantumNotImpl.hh" +#include "qiree/RuntimeInterface.hh" +#include "qiree/Types.hh" + +// Lightning +#include "QuantumDevice.hpp" + +namespace qiree +{ +//---------------------------------------------------------------------------// +/*! + * Create and execute quantum circuits using Pennylane Lightning. + */ +class LightningQuantum final : virtual public QuantumNotImpl +{ + public: + // Construct with number of shots + LightningQuantum(std::ostream& os, unsigned long int seed); + ~LightningQuantum(); + + QIREE_DELETE_COPY_MOVE(LightningQuantum); // Delete copy and move + // constructors + + //!@{ + //! \name Accessors + + //! Number of qubits in the circuit + size_type num_qubits() const { return num_qubits_; } + + //! Number of classical result registers + size_type num_results() const { return results_.size(); } + + //!@} + + //!@{ + //! \name Quantum interface + // Prepare to build a quantum circuit for an entry point + void set_up(EntryPointAttrs const&) override; + + // Complete an execution + void tear_down() override; + + // Map a qubit to a result index + void mz(Qubit, Result) final; + + // Read the value of a result. + QState read_result(Result) const final; + //!@} + + //!@{ + //! \name Circuit construction + // void ccx(Qubit, Qubit) final; + void ccnot(Qubit, Qubit, Qubit); + void cnot(Qubit, Qubit) final; + void cx(Qubit, Qubit) final; + // void cy(Qubit, Qubit) final; + void cz(Qubit, Qubit) final; + void h(Qubit) final; + void reset(Qubit) final; + void rx(double, Qubit) final; + void ry(double, Qubit) final; + void rz(double, Qubit) final; + // void rzz(double, Qubit, Qubit) final; + void s(Qubit) final; + // void s_adj(Qubit) final; + // void swap(Qubit, Qubit) final; + void t(Qubit) final; + // void t_adj(Qubit) final; + void x(Qubit) final; + void y(Qubit) final; + void z(Qubit) final; + //!@} + + private: + //// TYPES //// + + struct Factory; + struct State; + + //// DATA //// + + std::ostream& output_; + unsigned long int seed_{}; + void* rtd_dylib_handler_; + void* factory_f_ptr_; + std::unique_ptr rtd_qdevice_; + std::vector results_; + + size_type num_qubits_{}; + std::vector result_to_qubit_; +}; + +} // namespace qiree diff --git a/src/qirlightning/LightningRuntime.cc b/src/qirlightning/LightningRuntime.cc new file mode 100644 index 0000000..fb91b2a --- /dev/null +++ b/src/qirlightning/LightningRuntime.cc @@ -0,0 +1,40 @@ +//----------------------------------*-C++-*----------------------------------// +// Copyright 2024 UT-Battelle, LLC, and other QIR-EE developers. +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +//---------------------------------------------------------------------------// +//! \file qirlightning/LightningRuntime.cc +//---------------------------------------------------------------------------// +#include "LightningRuntime.hh" + +#include + +#include "LightningQuantum.hh" +#include "qiree/Assert.hh" + +namespace qiree +{ +//---------------------------------------------------------------------------// +/*! + * Construct with quantum reference to access classical registers. + */ +LightningRuntime::LightningRuntime(std::ostream& output, + LightningQuantum const& sim) + : SingleResultRuntime{sim}, output_(output) +{ +} + +//---------------------------------------------------------------------------// +/*! + * Initialize the execution environment, resetting qubits. + */ + +void LightningRuntime::initialize(OptionalCString env) +{ + if (env) + { + output_ << "Argument to initialize: " << env << std::endl; + } +} + +} // namespace qiree diff --git a/src/qirlightning/LightningRuntime.hh b/src/qirlightning/LightningRuntime.hh new file mode 100644 index 0000000..0623d16 --- /dev/null +++ b/src/qirlightning/LightningRuntime.hh @@ -0,0 +1,37 @@ +//----------------------------------*-C++-*----------------------------------// +// Copyright 2024 UT-Battelle, LLC, and other QIR-EE developers. +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +//---------------------------------------------------------------------------// +//! \file qirlightning/LightningRuntime.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "qiree/SingleResultRuntime.hh" + +namespace qiree +{ +//---------------------------------------------------------------------------// +class LightningQuantum; + +//---------------------------------------------------------------------------// + +class LightningRuntime final : virtual public SingleResultRuntime +{ + public: + // Construct with quantum reference to access classical registers + LightningRuntime(std::ostream& output, LightningQuantum const& sim); + + //!@{ + //! \name Runtime interface + + // Initialize the execution environment, resetting qubits + void initialize(OptionalCString env) override; + + //!@} + + private: + std::ostream& output_; +}; + +} // namespace qiree diff --git a/src/qirlightning/README.md b/src/qirlightning/README.md new file mode 100644 index 0000000..cc03c38 --- /dev/null +++ b/src/qirlightning/README.md @@ -0,0 +1,88 @@ +# QIR-EE with Lightning simulator backend + +The [PennyLane-Lightning](https://github.com/PennyLaneAI/pennylane-lightning) plugins are high-performance quantum simulators, which are part of the [PennyLane](https://github.com/PennyLaneAI/pennylane) ecosystem. The simulators include the following backends (which can be used with QIREE): +- `lightning.qubit`: a fast state-vector simulator with optional OpenMP additions and parallelized gate-level SIMD kernels. +- `lightning.gpu`: a state-vector simulator based on the NVIDIA cuQuantum SDK. +- `lightning.kokkos`: a state-vector simulator written with Kokkos. It can exploit the inherent parallelism of modern processing units supporting the OpenMP, CUDA or HIP programming models. + +## Installing a Lightning simulator + +For more information on installing Pennylane Lightning simulators from source, please visit the [Lightning installation page](https://docs.pennylane.ai/projects/lightning/en/latest/dev/installation.html). + +**Note:** QIREE is tested to work with PennyLane Lightning simulators v0.43. + +### Quick start + +The easiest way to get started is to install a Lightning simulator (`pennylane-lightning`/`pennylane-lightning-gpu`/`pennylane-lightning-kokkos`) from PyPI via pip: + +``` +$ pip install pennylane-lightning-kokkos==0.43.0 + +$ pip show pennylane-lightning-kokkos +Name: PennyLane_Lightning_Kokkos +Version: 0.43.0 +Summary: PennyLane-Lightning plugin +Home-page: https://github.com/PennyLaneAI/pennylane-lightning +Author: +Author-email: +License: Apache License 2.0 +Location: +Requires: pennylane, pennylane-lightning +``` + +**Note:** PennyLane and PennyLane lightning supports Python 3.11-3.13. + +Running `pip install pennylane` or `pip install pennylane-lightning` will automatically install the `lightning.qubit` (CPU) simulator, and other simulators can be installed by running `pip install pennylane-lightning-kokkos / pennylane-lightning-gpu`. + +**Note:** By default, the pre-built `lightning.kokkos` wheels from pip are built with Kokkos OpenMP enabled for CPU. To build Kokkos for other devices (e.g. CUDA or HIP GPUs), please install from source. Instruction can be found [here](https://docs.pennylane.ai/projects/lightning/en/latest/lightning_kokkos/installation.html). + +When installing Pennylane-Lightning from pip or from source, you will have the shared libraries for each of the simulator installed. These are named `liblightning_qubit_catalyst.so`/`liblightning_kokkos_catalyst.so`/`liblightning_GPU_catalyst.so` respectively. + +To obtain the path to the library: +``` +$ export PL_PATH=$(python -c "import site; print( f'{site.getsitepackages()[0]}/pennylane_lightning')") + +$ ls $PL_PATH +... liblightning_qubit_catalyst.so liblightning_kokkos_catalyst.so ... +``` + +The helper script `qiree/scripts/lightning-path.sh ` can be used to obtain the absolute path of the shared library. + +## Compile QIR-EE with Lightning backend + +To compile QIR-EE with lightning backend: + +``` +cd qiree/ + +# Set the path for the lightning simulator shared library using the +# helper script. Update to qubit / gpu / kokkos as required. + +export LIGHTNING_SIM_PATH=$(bash ./scripts/lightning-path.sh ) + +# Proceed with usual build instructions +# but with the extra `-DQIREE_USE_LIGHTNING=ON` and +# `-DQIREE_LIGHTNING_SIM_PATH` cmake flags + +mkdir build; cd build +cmake -DQIREE_USE_LIGHTNING=ON -DQIREE_LIGHTNING_SIM_PATH=$LIGHTNING_SIM_PATH .. +make + +``` + +**Note:** +- when running with `lightning.gpu` simulator for Nvidia GPUs, include `cuquantum` libraries in the library path (which will be installed as a dependency from Python), i.e. + +``` +export LD_LIBRARY_PATH=$(python -c "import site; print( f'{site.getsitepackages()[0]}/cuquantum')")/lib:$LD_LIBRARY_PATH +``` + + +## Running the example + +To run (in the `build` directory): + +``` +$ ./bin/qir-lightning ../examples/bell.ll -s 100 +{"00":43,"11":57} +``` diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6c90dcb..de39931 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -88,3 +88,13 @@ if(QIREE_USE_QSIM) endif() #---------------------------------------------------------------------------## + +#---------------------------------------------------------------------------## +# QIRLIGHTNING TESTS +#---------------------------------------------------------------------------## + +if(QIREE_USE_LIGHTNING) + qiree_add_test(qirlightning LightningQuantum) +endif() + +#---------------------------------------------------------------------------## diff --git a/test/qirlightning/LightningQuantum.test.cc b/test/qirlightning/LightningQuantum.test.cc new file mode 100644 index 0000000..237792b --- /dev/null +++ b/test/qirlightning/LightningQuantum.test.cc @@ -0,0 +1,116 @@ +//----------------------------------*-C++-*----------------------------------// +// Copyright 2024 UT-Battelle, LLC, and other QIR-EE developers. +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +//---------------------------------------------------------------------------// +//! \file qirlightning/LightningQuantum.test.cc +//---------------------------------------------------------------------------// +#include "qirlightning/LightningQuantum.hh" + +#include + +#include "qiree/Types.hh" +#include "qiree_test.hh" +#include "qirlightning/LightningRuntime.hh" + +namespace qiree +{ +namespace test +{ +//---------------------------------------------------------------------------// + +class LightningQuantumTest : public ::qiree::test::Test +{ + protected: + void SetUp() override {} + + static std::string clean_output(std::string&& s) + { + std::string result = std::move(s); + static std::regex const subs_ptr("0x[0-9a-f]+"); + result = std::regex_replace(result, subs_ptr, "0x0"); + return result; + } +}; + +TEST_F(LightningQuantumTest, sim_dynamicbv) +{ + using Q = Qubit; + using R = Result; + + std::ostringstream os; + os << '\n'; + + // Create a simulator that will write to the string stream + LightningQuantum lightning_sim{os, 0}; + LightningRuntime lightning_rt{os, lightning_sim}; + // Call functions in the same sequence that dynamicbv.ll would + lightning_sim.set_up([] { + EntryPointAttrs attrs; + attrs.required_num_qubits = 2; + attrs.required_num_results = 2; + return attrs; + }()); + ASSERT_EQ(2, lightning_sim.num_qubits()); + ASSERT_EQ(2, lightning_sim.num_results()); + + lightning_sim.h(Q{0}); + lightning_sim.x(Q{1}); + lightning_sim.h(Q{1}); + lightning_sim.cnot(Q{0}, Q{1}); + lightning_sim.h(Q{0}); + lightning_sim.mz(Q{0}, R{0}); + lightning_sim.read_result(R{0}); + lightning_sim.mz(Q{1}, R{1}); + lightning_sim.read_result(R{1}); + lightning_rt.array_record_output(2, ""); + lightning_rt.result_record_output(R{0}, ""); + lightning_rt.result_record_output(R{1}, ""); + EXPECT_EQ(QState::one, lightning_sim.read_result(R{0})); + + lightning_sim.tear_down(); +} + +TEST_F(LightningQuantumTest, result_order) +{ + using Q = Qubit; + using R = Result; + + std::ostringstream os; + os << '\n'; + + // Create a simulator that will write to the string stream + LightningQuantum qis{os, 0}; + LightningRuntime rt{os, qis}; + + // Call functions in the same sequence that dynamicbv.ll would + qis.set_up([] { + EntryPointAttrs attrs; + attrs.required_num_qubits = 4; + attrs.required_num_results = 3; + return attrs; + }()); + qis.mz(Q{0}, R{2}); + qis.mz(Q{1}, R{1}); + qis.mz(Q{2}, R{0}); + std::vector expected; + expected.push_back(static_cast(qis.read_result(R{2}))); + expected.push_back(static_cast(qis.read_result(R{0}))); + expected.push_back(static_cast(qis.read_result(R{1}))); + // So the internal result "buffer" is now {true, false, true} + rt.array_record_output(3, "array"); + rt.result_record_output(R{2}, "foo"); // pushes true + rt.result_record_output(R{0}, "bar"); // pushes true + rt.result_record_output(R{1}, "baz"); // pushes false + + auto const& result = rt.result(); + EXPECT_EQ("array", result.container_label()); + EXPECT_EQ(expected, result.bits()); + EXPECT_EQ((std::vector{"foo", "bar", "baz"}), + result.entry_labels()); + + qis.tear_down(); +} +//---------------------------------------------------------------------------// +} // namespace test +} // namespace qiree