diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644 index 0000000..3cdc3d8 --- /dev/null +++ b/.bumpversion.cfg @@ -0,0 +1,8 @@ +[bumpversion] +current_version = 0.0.0 +commit = False +tag = False + +[bumpversion:file:src/version.py] + +[bumpversion:file:src/pytest/test_version.py] diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..684ae7a --- /dev/null +++ b/.clang-format @@ -0,0 +1,36 @@ +--- +BasedOnStyle: WebKit +AccessModifierOffset: 0 +AlignAfterOpenBracket: Align +AlignEscapedNewlines: 'Left' +AlignOperands: 'true' +AlignTrailingComments: 'true' +AllowAllArgumentsOnNextLine: 'false' +AllowAllParametersOfDeclarationOnNextLine: 'false' +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: 'false' +AllowShortLoopsOnASingleLine: 'false' +BinPackArguments: 'false' +BinPackParameters: 'false' +BreakBeforeBraces: Whitesmiths +BreakConstructorInitializers: BeforeColon +ColumnLimit: '100' +CompactNamespaces: 'true' +Cpp11BracedListStyle: 'true' +FixNamespaceComments: 'true' +IndentWidth: '4' +KeepEmptyLinesAtTheStartOfBlocks: false +Language: Cpp +NamespaceIndentation: None +PointerAlignment: Left +SortIncludes: 'true' +SpaceAfterCStyleCast: 'false' +SpaceAfterTemplateKeyword: 'false' +SpaceBeforeParens: ControlStatements +SpacesInAngles: 'false' +SpaceInEmptyParentheses: false +Standard: Cpp11 +TabWidth: '4' +UseTab: Never + +... diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..bba0703 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + target-branch: trunk + schedule: + interval: "weekly" + time: "07:00" + timezone: "EST5EDT" + pull-request-branch-name: + separator: "-" + open-pull-requests-limit: 2 diff --git a/.github/release.yaml b/.github/release.yaml new file mode 100644 index 0000000..1dc7dd0 --- /dev/null +++ b/.github/release.yaml @@ -0,0 +1,14 @@ +changelog: + exclude: + authors: + - dependabot + categories: + - title: Added + labels: + - enhancement + - title: Fixed + labels: + - bug + - title: Changed + labels: + - "*" diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml new file mode 100644 index 0000000..6c8d1c5 --- /dev/null +++ b/.github/workflows/pre-commit.yaml @@ -0,0 +1,23 @@ +name: pre-commit + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + pull_request: + push: + branches: [trunk] + + workflow_dispatch: + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: '3.12' + - run: pip install pre-commit + - run: pre-commit run --all-files diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..f1ffbbc --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,60 @@ +name: GitHub release + +on: + pull_request: + + push: + branches: + - "trunk" + tags: + - "v*" + + workflow_dispatch: + +permissions: + contents: write + +defaults: + run: + shell: bash + +jobs: + release: + name: Publish [GitHub] + runs-on: ubuntu-latest + + steps: + - name: Determine version number + id: version + run: | + TAG=${{ github.ref_name }} + # Remove v from start of tag name + VERSION=${TAG#v} + # Replace / with - in merge names + VERSION=${VERSION//\//-} + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "name=${{ github.event.repository.name }}-${VERSION}" >> "$GITHUB_OUTPUT" + + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true + path: ${{ steps.version.outputs.name }} + + - name: Remove .git + run: | + rm -rf ${{ steps.version.outputs.name }}/.git + ls -laR ${{ steps.version.outputs.name }} + + - name: Tar source + run: tar --zstd -cvf ${{ steps.version.outputs.name }}.tar.zst ${{ steps.version.outputs.name }} + + - name: Create release + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/v') + with: + files: "*.tar.zst" + name: ${{ steps.version.outputs.version }} + generate_release_notes: True + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/unit-test.yaml b/.github/workflows/unit-test.yaml new file mode 100644 index 0000000..f5fb0ee --- /dev/null +++ b/.github/workflows/unit-test.yaml @@ -0,0 +1,119 @@ +name: Unit test + +env: + # TODO: Set COMPONENT_NAME to the name of your Python package. + COMPONENT_NAME: template + + # Most components should not modify the rest of this file. When needed, merge in updates from + # https://github.com/glotzerlab/hoomd-component-template/ + + ############################################################################################# + # HOOMD-blue version to build. + HOOMD_BLUE_VERSION: 4.4.0 + # prevent deadlocked MPI tests from causing the job to cancel + MPIEXEC_TIMEOUT: 3000 + # allow mpirun to execute as root in the tests + OMPI_ALLOW_RUN_AS_ROOT: 1 + OMPI_ALLOW_RUN_AS_ROOT_CONFIRM: 1 + # allow openmpi to oversubscribe cores + OMPI_MCA_rmaps_base_oversubscribe: 1 + # prevent errors from mis-configured openib systems + OMPI_MCA_btl: "vader,self" + # import HOOMD out of the build directory + PYTHONPATH: ${{ github.workspace }}/install + + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + pull_request: + push: + branches: [trunk] + + workflow_dispatch: + +defaults: + run: + shell: bash + +jobs: + build_test: + name: Build and test [${{ matrix.name }}] + runs-on: ubuntu-latest + container: + image: glotzerlab/ci:2023.11.27-cuda120_gcc11_py310 + strategy: + fail-fast: false + matrix: + include: + - name: 'CPU' + enable_gpu: 'OFF' + enable_mpi: 'OFF' + - name: 'CPU, MPI' + enable_gpu: 'OFF' + enable_mpi: 'ON' + - name: 'GPU' + enable_gpu: 'ON' + enable_mpi: 'OFF' + - name: 'GPU, MPI' + enable_gpu: 'ON' + enable_mpi: 'ON' + + steps: + - name: Restore cached HOOMD-blue build + id: cache + uses: actions/cache/restore@v3 + with: + path: install + key: hoomd-blue-${{ env.HOOMD_BLUE_VERSION }}-2023.11.27-cuda120_gcc11_py310-mpi-${{ matrix.enable_mpi }}-gpu-${{ matrix.enable_gpu }} + - name: Checkout HOOMD-blue + if: steps.cache.outputs.cache-hit != 'true' + uses: actions/checkout@v4 + with: + repository: glotzerlab/hoomd-blue + path: hoomd-blue + submodules: true + ref: v${{ env.HOOMD_BLUE_VERSION }} + - name: Configure HOOMD-blue + if: steps.cache.outputs.cache-hit != 'true' + run: | + cmake -B build-hoomd-blue -S hoomd-blue \ + -GNinja \ + -DCMAKE_BUILD_TYPE=Release \ + -DENABLE_GPU=${ENABLE_GPU} \ + -DENABLE_MPI=${ENABLE_MPI} \ + -DCUDA_ARCH_LIST="70" \ + -DBUILD_TESTING=OFF \ + -DPLUGINS="" \ + -DCMAKE_INSTALL_PREFIX=${GITHUB_WORKSPACE}/install + env: + ENABLE_GPU: ${{ matrix.enable_gpu }} + ENABLE_MPI: ${{ matrix.enable_mpi }} + - name: Build HOOMD-blue + if: steps.cache.outputs.cache-hit != 'true' + run: ninja install -j $(($(getconf _NPROCESSORS_ONLN) + 2)) + working-directory: build-hoomd-blue + - name: Cache HOOMD-blue build + if: steps.cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v3 + with: + path: install + key: hoomd-blue-${{ env.HOOMD_BLUE_VERSION }}-2023.11.27-cuda120_gcc11_py310-mpi-${{ matrix.enable_mpi }}-gpu-${{ matrix.enable_gpu }} + + - name: Checkout component + uses: actions/checkout@v4 + with: + path: component + - name: Configure component + run: CMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/install cmake -S component -B build-component -GNinja -DCMAKE_BUILD_TYPE=Release + - name: Build component + run: ninja install -j $(($(getconf _NPROCESSORS_ONLN) + 2)) + working-directory: build-component + + - name: Run pytest (serial) + run: python3 -m pytest --pyargs hoomd.${COMPONENT_NAME} -x -v -ra --durations=0 --durations-min=0.1 + - name: Run pytest (MPI) + if: ${{ matrix.enable_mpi == 'ON' }} + run: mpirun -n 2 ${GITHUB_WORKSPACE}/install/hoomd/pytest/pytest-openmpi.sh --pyargs hoomd.${COMPONENT_NAME} -x -v -ra --durations=0 --durations-min=0.1 || (( cat pytest.out.1 && exit 1 )) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..147dc1c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,55 @@ +ci: + autoupdate_schedule: quarterly + autoupdate_branch: 'trunk' + autofix_prs: false + +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: 'v4.4.0' + hooks: + - id: end-of-file-fixer + exclude_types: [svg] + - id: trailing-whitespace + exclude_types: [svg] + - id: check-json + - id: check-yaml + - id: check-case-conflict + - id: fix-encoding-pragma + args: + - --remove + - id: mixed-line-ending +- repo: https://github.com/glotzerlab/fix-license-header + rev: v0.2.0 + hooks: + - id: fix-license-header + name: Fix license headers (Python) + types_or: [python] + args: + - --license-file=LICENSE + - --add=Part of HOOMD-blue, released under the BSD 3-Clause License. + - --keep-before=#! + - id: fix-license-header + name: Fix license headers (C) + types_or: [c, c++, cuda, inc] + args: + - --license-file=LICENSE + - --add=Part of HOOMD-blue, released under the BSD 3-Clause License. + - --comment-prefix=// + - id: fix-license-header + name: Fix license headers (reStructuredText) + types_or: [rst] + args: + - --license-file=LICENSE + - --add=Part of HOOMD-blue, released under the BSD 3-Clause License. + - --keep-after=.. include + - --comment-prefix=.. +- repo: https://github.com/pre-commit/mirrors-clang-format + rev: v16.0.6 + hooks: + - id: clang-format + types_or: [c, c++, cuda, inc] +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.4 + hooks: + - id: ruff-format + - id: ruff diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 0000000..72cf54a --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,50 @@ +target-version = "py38" + +extend-select = [ + "A", + "B", + "D", + "E501", + "EM", + "I", + "ICN", + "ISC", + "N", + "NPY", + "PL", + "PT", + "RET", + "RUF", + "UP", + "W", +] + +ignore = [ + "N806", "N803", # Allow occasional use of uppercase variable and argument names (e.g. N). + "D107", # Do not document __init__ separately from the class. + "PLR09", # Allow "too many" statements/arguments/etc... + "N816", # Allow mixed case names like kT. +] + +[lint.per-file-ignores] + +"__init__.py" = ["F401", # __init__.py import submodules for use by the package importer. +] + +[pydocstyle] +convention = "google" + +[format] +quote-style = "single" + +[lint.flake8-import-conventions] +# Prefer no import aliases +aliases = {} +# Always import hoomd without 'from' +banned-from = ["hoomd"] + +# Ban standard import conventions and force common packages to be imported by their actual name. +[lint.flake8-import-conventions.banned-aliases] +"numpy" = ["np"] +"pandas" = ["pd"] +"matplotlib" = ["mpl"] diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ad254d8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 3.9 FATAL_ERROR) + +# Name the plugin project. +# TODO: Set the project title to the name of your Python package. +project(hoomd_component_template LANGUAGES C CXX) + +# Find the installed HOOMD. +find_package(HOOMD 4.0.0 REQUIRED) + +message(STATUS "Found HOOMD ${HOOMD_VERSION}: ${HOOMD_INSTALL_PREFIX}/${PYTHON_SITE_INSTALL_DIR}") + +# Force installation to the HOOMD installation location. +set(CMAKE_INSTALL_PREFIX ${HOOMD_INSTALL_PREFIX} CACHE PATH "Installation prefix" FORCE) + +# Enable compiler warnings on gcc and clang. +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wconversion -Wno-sign-conversion -Wno-unknown-pragmas -Wno-deprecated-declarations -Wno-unused-result") +endif() + +# Add the component's source directory. +add_subdirectory(src) diff --git a/README.md b/README.md new file mode 100644 index 0000000..161655d --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# HOOMD-blue component template + +`hoomd-component-template` provides a framework to develop components that extend +[**HOOMD-blue**](https://glotzerlab.engin.umich.edu/hoomd-blue/). It includes template C++ and +Python modules, an example unit test, CMake scripts to build the component, and GitHub Actions +workflows. + +## Building the component + +To build this component: + +1. Build and install **HOOMD-blue** from source. +2. Obtain the component's source. + ``` + $ git clone https://github.com/glotzerlab/hoomd-component-template + ``` +3. Configure. + ``` + $ cmake -B build/hoomd-component-template -S hoomd-component-template + ``` +4. Build the component. + ``` + $ cmake --build build/hoomd-component-template + ``` +5. Install the component. + ``` + $ cmake --install build/hoomd-component-template + ``` + +Once installed, the template is available for import via: +``` +import hoomd.template +``` + +## Creating a new component + +To create a new component: + +1. Fork [hoomd-component-template](https://github.com/glotzerlab/hoomd-component-template/). +2. Address all **TODO** comments (including those in `.github/`) +3. Add C++ and Python files to `src/`. +4. Add unit tests in `src/pytest`. +5. Format and check code style with [pre-commit](https://pre-commit.com/). + +## Using the provided GitHub Actions configuration + +When you push your changes to GitHub, the [unit test workflow](.github/workflows/unit-test.yaml) +compile your code on the CPU (with and without MPI) and on the GPU (with and without MPI). The +workflow also executes the unit tests on the CPU. You should run GPU unit tests locally, as GitHub +does not provide free GPU runners for GitHub Actions. + +When you push a new tag, the [release workflow](.github/workflows/release.yaml) will create a +new GitHub release with automatically generated release notes. + +## Maintaining your component + +The HOOMD-blue developers will periodically update +[hoomd-component-template](https://github.com/glotzerlab/hoomd-component-template/), including +updates to the GitHub Actions workflow, pre-commit configuration, and CMake scripts. Merge these +changes into your fork to support the latest version of HOOMD-blue. + +## Documenting and releasing your component + +TODO: Document your component in `README.md` (this file) and remove documentation relevant to the +template. + +When appropriate: + +* Add [Sphinx](https://www.sphinx-doc.org) documentation and publish it on +[readthedocs](https://www.readthedocs.org). +* Add a [conda-forge](https://conda-forge.org/) package. +* Announce your component on the [HOOMD-blue discussion board](https://github.com/glotzerlab/hoomd-blue/discussions). diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..d1f2bc3 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,54 @@ +# TODO: Set COMPONENT_NAME to the name of your Python package. +set(COMPONENT_NAME template) + +# TODO: List all host C++ source code files in _${COMPONENT_NAME}_sources. +set(_${COMPONENT_NAME}_sources + module.cc + ) + +# TODO: List all GPU C++ source code files in _${COMPONENT_NAME}_cu_sources. +set(_${COMPONENT_NAME}_cu_sources + ) + +# TODO: List all Python modules in python_files. +set(python_files + __init__.py + version.py + ) + +if (ENABLE_HIP) +set(_cuda_sources ${_${COMPONENT_NAME}_cu_sources}) +endif (ENABLE_HIP) + +pybind11_add_module(_${COMPONENT_NAME} SHARED ${_${COMPONENT_NAME}_sources} ${_cuda_sources} NO_EXTRAS) +# Alias into the HOOMD namespace so that external and symlinked components both work. +add_library(HOOMD::_${COMPONENT_NAME} ALIAS _${COMPONENT_NAME}) + +if (APPLE) +set_target_properties(_${COMPONENT_NAME} PROPERTIES INSTALL_RPATH "@loader_path/..;@loader_path") +else() +set_target_properties(_${COMPONENT_NAME} PROPERTIES INSTALL_RPATH "\$ORIGIN/..;\$ORIGIN") +endif() + +# Link the library to its dependencies. Add or remove HOOMD extension modules (and/or external C++ +# libraries) as needed. +target_link_libraries(_${COMPONENT_NAME} + PUBLIC HOOMD::_hoomd + PUBLIC HOOMD::_md + ) + +# Install the library. +install(TARGETS _${COMPONENT_NAME} + LIBRARY DESTINATION ${PYTHON_SITE_INSTALL_DIR}/${COMPONENT_NAME} + ) + +# Install the Python package. +install(FILES ${python_files} + DESTINATION ${PYTHON_SITE_INSTALL_DIR}/${COMPONENT_NAME} + ) + +# Copy the Python package to the build directory. +copy_files_to_build("${python_files}" "hoomd-component-${COMPONENT_NAME}" "*.py") + +# Python tests. +add_subdirectory(pytest) diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..30fd9dc --- /dev/null +++ b/src/__init__.py @@ -0,0 +1,8 @@ +# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +"""Template HOOMD-blue component.""" +# TODO: Document your component. + +# TODO: Import all Python modules in your component. +from . import version diff --git a/src/module.cc b/src/module.cc new file mode 100644 index 0000000..2763883 --- /dev/null +++ b/src/module.cc @@ -0,0 +1,26 @@ +// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +// TODO: Include the header files of classes that will be exported to Python. + +#include + +namespace hoomd + { +namespace md + { + +// TODO: Set the name of the python module to match ${COMPONENT_NAME} (set in +// CMakeLists.txt), prefixed with an underscore. +PYBIND11_MODULE(_template, m) + { + // TODO: Call export_Class(m) for each C++ class to be exported to Python. + +#ifdef ENABLE_HIP + // TODO: Call export_ClassGPU(m) for each GPU enabled C++ class to be exported + // to Python. +#endif + } + + } // end namespace md + } // end namespace hoomd diff --git a/src/pytest/CMakeLists.txt b/src/pytest/CMakeLists.txt new file mode 100644 index 0000000..8f2d47e --- /dev/null +++ b/src/pytest/CMakeLists.txt @@ -0,0 +1,12 @@ +# TODO: List all pytest files in test_files. +set(test_files __init__.py + test_version.py + ) + +# Copy tests to the install directory. +install(FILES ${test_files} + DESTINATION ${PYTHON_SITE_INSTALL_DIR}/${COMPONENT_NAME}/pytest + ) + +# Copy tests to the build directory for testing prior to installation. +copy_files_to_build("${test_files}" "hoomd-component-${COMPONENT_NAME}-pytest" "*.py") diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py new file mode 100644 index 0000000..c6121fc --- /dev/null +++ b/src/pytest/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +"""Unit tests.""" diff --git a/src/pytest/test_version.py b/src/pytest/test_version.py new file mode 100644 index 0000000..fea608c --- /dev/null +++ b/src/pytest/test_version.py @@ -0,0 +1,11 @@ +# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +"""Test the version module.""" + +import hoomd.template + + +def test_version(): + """Test the version attribute.""" + assert hoomd.template.version.version == '0.0.0' diff --git a/src/version.py b/src/version.py new file mode 100644 index 0000000..5969534 --- /dev/null +++ b/src/version.py @@ -0,0 +1,6 @@ +# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +"""Version information.""" + +version = '0.0.0'