diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 3623e116..c69337b9 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -1,4 +1,24 @@ -## Release 0.1.0 +## Release 0.2.0 (development release) + +### New features since last release + +* Running CMake with `-DBUILD_PYTHON=ON` now generates Python bindings within a `jet` package. [(#1)](https://github.com/XanaduAI/jet/pull/1) + +### Improvements + +### Breaking Changes + +### Bug Fixes + +### Documentation + +### Contributors + +This release contains contributions from (in alphabetical order): + +[Mikhail Andrenkov](https://github.com/Mandrenkov) and [Jack Brown](https://github.com/brownj85). + +## Release 0.1.0 (current release) ### New features since last release diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 8f7686d4..5054a7b4 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -6,11 +6,12 @@ on: - main paths: - "include/**" + - "python/**" - "test/**" jobs: - format: - name: Format + format-cpp: + name: Format (C++) runs-on: ubuntu-20.04 steps: @@ -26,4 +27,30 @@ jobs: uses: actions/checkout@v2 - name: Run formatter - run: ./bin/format --check include test \ No newline at end of file + run: ./bin/format --check include python/src test + + format-python: + name: Format (Python) + runs-on: ubuntu-20.04 + + steps: + - name: Cancel previous runs + uses: styfle/cancel-workflow-action@0.4.1 + with: + access_token: ${{ github.token }} + + - name: Install dependencies + run: sudo apt update && sudo apt -y install python3 + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Create virtual environment + run: | + cd python + make setup + + - name: Run formatter + run: | + cd python + make format check=1 \ No newline at end of file diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 00000000..19e6662b --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,85 @@ +name: Python +on: + pull_request: + push: + branches: + - main + paths-ignore: + - ".github/**" + - "docs/**" + - "README.rst" + +jobs: + test-ubuntu: + name: Build (Ubuntu) + runs-on: ubuntu-20.04 + + steps: + - name: Cancel previous runs + uses: styfle/cancel-workflow-action@0.4.1 + with: + access_token: ${{ github.token }} + + - name: Install dependencies + run: sudo apt install -y libopenblas-dev python3.8-dev + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Initialize build directory + run: | + mkdir build + cd build + cmake -DBUILD_PYTHON=ON ../ + + - name: Generate Python bindings + run: | + cd build + make -j`nproc` + + - name: Create virtual environment + run: | + cd python + make setup + + - name: Run tests + run: | + cd python + make test + + test-macos: + name: Build (MacOS) + runs-on: macos-10.15 + + steps: + - name: Cancel previous runs + uses: styfle/cancel-workflow-action@0.4.1 + with: + access_token: ${{ github.token }} + + - name: Install dependencies + run: brew install libomp + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Initialize build directory + run: | + mkdir build + cd build + cmake -DBUILD_PYTHON=ON ../ + + - name: Generate Python bindings + run: | + cd build + make -j`sysctl -n hw.physicalcpu` + + - name: Create virtual environment + run: | + cd python + make setup + + - name: Run tests + run: | + cd python + make test \ No newline at end of file diff --git a/.gitignore b/.gitignore index c709e58a..f7fc3e3f 100644 --- a/.gitignore +++ b/.gitignore @@ -41,8 +41,9 @@ build # CMake build artifacts include/jet/CmakeMacros.hpp -# Python virtualenv +# Python .venv +__pycache__ # Sphinx documentation docs/__pycache__/* diff --git a/CMakeLists.txt b/CMakeLists.txt index a8a5e66b..ea4bc49e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ ########################## ## Set Project version ########################## -cmake_minimum_required(VERSION 3.13) +cmake_minimum_required(VERSION 3.14) set(JET_LOGO " ▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ██ ██▀▀▀▀▀▀ ▀▀▀██▀▀▀ @@ -30,11 +30,12 @@ option(ENABLE_NATIVE "Enable native build tuning" OFF) option(ENABLE_IPO "Enable interprocedural/link-time optimisation" OFF) # Build options +option(BUILD_PYTHON "Generate Python bindings" OFF) +option(BUILD_TESTS "Build tests" OFF) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Default build type: Release" FORCE) endif() -option(BUILD_TESTS "Build tests" OFF) ########################## ## Enfore Compiler Support @@ -77,18 +78,18 @@ list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") find_package(MKL QUIET) if(MKL_FOUND) - add_definitions("-DENABLE_MKL") - set(BLAS_INCLUDE_DIRS "${MKL_INCLUDE_DIR}") - set(BLAS_LIBRARIES ${MKL_LIBRARY}) + add_definitions("-DENABLE_MKL") + set(BLAS_INCLUDE_DIRS "${MKL_INCLUDE_DIR}") + set(BLAS_LIBRARIES ${MKL_LIBRARY}) else() - find_package(CBLAS REQUIRED) - set(BLAS_LIBRARIES ${CBLAS_LIBRARIES}) - set(BLAS_INCLUDE_DIRS ${CBLAS_INCLUDE_DIRS}) + find_package(CBLAS REQUIRED) + set(BLAS_LIBRARIES ${CBLAS_LIBRARIES}) + set(BLAS_INCLUDE_DIRS ${CBLAS_INCLUDE_DIRS}) endif() ########################## -## Fetch Taskflow +## Fetch dependencies ########################## Include(FetchContent) @@ -98,30 +99,20 @@ FetchContent_Declare( GIT_TAG v3.1.0 ) -# FetchContent_MakeAvailable() requires CMake 3.14 or newer. -FetchContent_GetProperties(Taskflow) -if(NOT Taskflow_POPULATED) - FetchContent_Populate(Taskflow) - # Don't build the Taskflow tests or examples. - set(TF_BUILD_EXAMPLES OFF CACHE INTERNAL "Build Taskflow examples") - set(TF_BUILD_TESTS OFF CACHE INTERNAL "Build Taskflow tests") - add_subdirectory(${taskflow_SOURCE_DIR} ${taskflow_BINARY_DIR}) -endif() +# Don't build the Taskflow examples or tests. +set(TF_BUILD_EXAMPLES OFF CACHE INTERNAL "Build Taskflow examples") +set(TF_BUILD_TESTS OFF CACHE INTERNAL "Build Taskflow tests") -find_package(OpenMP QUIET) +FetchContent_MakeAvailable(Taskflow) -message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") -message(STATUS "BLAS_LIBRARIES: ${BLAS_LIBRARIES}") -message(STATUS "BLAS_INCLUDE_DIRS: ${BLAS_INCLUDE_DIRS}") -message(STATUS "ENABLE_NATIVE: ${ENABLE_NATIVE}") -message(STATUS "ENABLE_IPO: ${ENABLE_IPO}") +find_package(OpenMP QUIET) ########################## ## Create Jet target ########################## add_library(Jet INTERFACE) -target_include_directories(Jet INTERFACE +target_include_directories(Jet INTERFACE $ ${BLAS_INCLUDE_DIRS} ) @@ -132,30 +123,52 @@ target_link_libraries(Jet INTERFACE ${BLAS_LIBRARIES} Taskflow) ## Compile options ########################## -if (ENABLE_OPENMP AND OPENMP_FOUND) - target_link_libraries(Jet INTERFACE OpenMP::OpenMP_CXX) +if(ENABLE_OPENMP AND OPENMP_FOUND) + target_link_libraries(Jet INTERFACE OpenMP::OpenMP_CXX) elseif (ENABLE_OPENMP AND NOT OPENMP_FOUND) - message(FATAL_ERROR "\nOpenMP is enabled but could not be found") + message(FATAL_ERROR "\nOpenMP is enabled but could not be found") endif() + if(ENABLE_SANITIZERS) - target_compile_options(Jet INTERFACE -g -fsanitize=address,undefined) - target_link_options(Jet INTERFACE -fsanitize=address,undefined) + target_compile_options(Jet INTERFACE -g -fsanitize=address,undefined) + target_link_options(Jet INTERFACE -fsanitize=address,undefined) endif() + if(ENABLE_WARNINGS) - target_compile_options(Jet INTERFACE -Wall -Wextra -Werror) + target_compile_options(Jet INTERFACE -Wall -Wextra -Werror) endif() + if(ENABLE_NATIVE) - target_compile_options(Jet INTERFACE -march=native) + target_compile_options(Jet INTERFACE -march=native) endif() + if(ENABLE_IPO) - target_compile_options(Jet INTERFACE -flto) + target_compile_options(Jet INTERFACE -flto) endif() ########################## -## Build tests +## Report +########################## + +message(STATUS "BLAS_INCLUDE_DIRS: ${BLAS_INCLUDE_DIRS}") +message(STATUS "BLAS_LIBRARIES: ${BLAS_LIBRARIES}") +message(STATUS "BUILD_PYTHON: ${BUILD_PYTHON}") +message(STATUS "BUILD_TESTS: ${BUILD_TESTS}") +message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") +message(STATUS "ENABLE_IPO: ${ENABLE_IPO}") +message(STATUS "ENABLE_NATIVE: ${ENABLE_NATIVE}") +message(STATUS "ENABLE_SANITIZERS: ${ENABLE_SANITIZERS}") +message(STATUS "ENABLE_WARNINGS: ${ENABLE_WARNINGS}") + +########################## +## Build targets ########################## +if(BUILD_PYTHON) + add_subdirectory(python) +endif() + if(BUILD_TESTS) - enable_testing() - add_subdirectory(test) + enable_testing() + add_subdirectory(test) endif(BUILD_TESTS) diff --git a/Makefile b/Makefile index ea74e853..797c206d 100644 --- a/Makefile +++ b/Makefile @@ -30,9 +30,9 @@ help: .PHONY: format format: ifdef check - ./bin/format --check include test + ./bin/format --check include python/src test else - ./bin/format include test + ./bin/format include python/src test endif diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt new file mode 100644 index 00000000..034725ed --- /dev/null +++ b/python/CMakeLists.txt @@ -0,0 +1,14 @@ +Include(FetchContent) + +FetchContent_Declare( + Pybind11 + GIT_REPOSITORY https://github.com/pybind/pybind11.git + GIT_TAG v2.6.2 +) + +FetchContent_MakeAvailable(Pybind11) + +# The following command also creates a CMake target called "jet". +pybind11_add_module(jet src/Python.cpp) + +target_link_libraries(jet PRIVATE Jet) diff --git a/python/Makefile b/python/Makefile new file mode 100644 index 00000000..7714d4af --- /dev/null +++ b/python/Makefile @@ -0,0 +1,50 @@ +.VENV_DIR=.venv +.VENV_BIN=$(.VENV_DIR)/bin + +python=python3 + +define HELP_BODY +Please use 'make [target]'. + +TARGETS + + setup [python=] Set up virtualenv using the Python interpreter at , defaults to $(python) + + test [args=] Run tests; use with 'args=' to pass test arguments + + format [check=1] Apply formatters; use with 'check=1' to check instead of modify + + clean Remove all build artifacts + +endef + +.PHONY: help +help: + @: $(info $(HELP_BODY)) + +.PHONY: setup +setup: $(.VENV_DIR)/requirements_test.txt.touch + +.PHONY: format +format: +ifdef check + $(.VENV_BIN)/black --check tests && $(.VENV_BIN)/isort --profile black --check-only tests +else + $(.VENV_BIN)/black tests && $(.VENV_BIN)/isort --profile black tests +endif + +.PHONY: test +test: $(.VENV_DIR)/requirements_test.txt.touch + PYTHONPATH="../build/python" $(.VENV_BIN)/python -m pytest ./tests $(args) + +.PHONY: clean +clean: + rm -rf $(.VENV_DIR) + +$(.VENV_DIR)/requirements_test.txt.touch: $(.VENV_DIR)/touch requirements_test.txt + $(.VENV_DIR)/bin/pip install -r requirements_test.txt + @touch $@ + +$(.VENV_DIR)/touch: + $(python) -m venv ${.VENV_DIR} + @touch $@ \ No newline at end of file diff --git a/python/requirements_test.txt b/python/requirements_test.txt new file mode 100644 index 00000000..804d570d --- /dev/null +++ b/python/requirements_test.txt @@ -0,0 +1,3 @@ +black +isort>5 +pytest>=5,<6 diff --git a/python/src/Python.cpp b/python/src/Python.cpp new file mode 100644 index 00000000..225c2a47 --- /dev/null +++ b/python/src/Python.cpp @@ -0,0 +1,11 @@ +#include + +#include "Version.hpp" + +PYBIND11_MODULE(jet, m) +{ + m.doc() = "Jet is a library for simulating quantum circuits using tensor " + "network contractions."; + + AddBindingsForVersion(m); +} \ No newline at end of file diff --git a/python/src/Version.hpp b/python/src/Version.hpp new file mode 100644 index 00000000..b238b031 --- /dev/null +++ b/python/src/Version.hpp @@ -0,0 +1,22 @@ +#include + +#include + +namespace py = pybind11; + +/** + * @brief Adds Python bindings for the include/jet/Version.hpp file. + * + * @param m Jet pybind11 module. + */ +void AddBindingsForVersion(py::module_ &m) +{ + m.attr("__version__") = Jet::Version(); + + m.def("version", Jet::Version, R"( + Returns the current Jet version. + + Returns: + String representation of the current Jet version. + )"); +} \ No newline at end of file diff --git a/python/tests/test_version.py b/python/tests/test_version.py new file mode 100644 index 00000000..9e1170f2 --- /dev/null +++ b/python/tests/test_version.py @@ -0,0 +1,14 @@ +import re + +import jet + + +class TestVersion: + def test_attribute(self): + """Tests that the version attribute has the correct form.""" + semver_pattern = re.compile(r"^\d+\.\d+\.\d+$") + assert semver_pattern.match(jet.__version__) + + def test_function(self): + """Tests that the version attribute matches the version function.""" + assert jet.__version__ == jet.version() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 52a9994a..221397af 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -9,12 +9,7 @@ FetchContent_Declare( GIT_TAG v2.13.1 ) -# FetchContent_MakeAvailable() requires CMake 3.14 or newer. -FetchContent_GetProperties(Catch2) -if(NOT Catch2_POPULATED) - FetchContent_Populate(Catch2) - add_subdirectory(${catch2_SOURCE_DIR} ${catch2_BINARY_DIR}) -endif() +FetchContent_MakeAvailable(Catch2) target_link_libraries(runner Catch2::Catch2)