Distributing a Hoomd Plugin
A piece of software I have been using in my research is Hoomd, a 'relatively' new package for running Molecular Dynamics (MD) simulations. These MD simulations have the basic premise of throwing hundreds of balls into a box and shaking it to find out what happens. The relative newness of Hoomd is in comparison to other software packages like LAMMPS and GROMACS which have been around for decades, while the initial release of Hoomd was in 2012. There are some major benefits of a newer approach to MD simulations and Hoomd is most notably designed to leverage the computational power of GPUs. Despite the benefits of a modern approach, Hoomd doesn't have the range of built-in simulation types of the more mature software packages. To allow researchers with specific problems to still use Hoomd, it has a plugin architecture to simplify the implementation of custom functionality.
This article documents how I took a plugin I wrote for my research and made it installable in a conda environment alongside Hoomd.
The Plugin
The example plugin is one which I have written for my own research. It implements a harmonic pinning potential on the positions and rotations of rigid bodies in a MD simulation. The complete plugin is available for reference on GitHub.
To get started writing your own plugin
there is a short guide in the documentation
which directs you to the example_plugin
in the Hoomd source code.
This code is located on bitbucket and there is also a copy on GitHub.
I also found that finding a class that shared the same base class was useful for writing the implementation.
The Makefile
The example code for the example_plugin
doesn't include a Makefile
,
requiring multiple steps to compile the plugin.
A Makefile is useful in this case for
both being able to install with a single command
and ensuring the install options are consistent with the conda installed Hoomd.
While my plugin doesn't use CUDA for calculations, for compatibility with the conda installed Hoomd enabling CUDA on Linux is required. When I was compiling with CUDA disabled I came across unusual behaviour, for example numbers being far too large, which appeared to be a buffer overflow. I believe this is because of conditional definitions in the header files resulting in a slightly different memory layout for a class defined with or without CUDA.
If you are able to build your plugin using cmake
like the example plugin,
the following Makefile will make compilation simpler
and set the appropriate flags assuming Hoomd was installed using conda.
build_dir = build
# Check OS to determine if CUDA is enabled, defaults to True
CUDA_ENABLED=True
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
CUDA_ENABLED=True
endif
ifeq ($(UNAME_S),Darwin)
CUDA_ENABLED=False
endif
all: $(build_dir)
cd $(build_dir); cmake .. -DENABLE_CUDA=$(CUDA_ENABLED)
$(MAKE) -C $(build_dir)
install: all
$(MAKE) -C $(build_dir) install
clean:
rm -rf $(build_dir)
test:
pytest
$(build_dir):
mkdir -p $@
.PHONY: test clean
Apart from making it simpler to install the package manually, it also makes it simplifies the build using conda.
Packaging
The conda package manager is the recommended method of installing Hoomd, primarily because of the simplicity of installation. Conda allows you to upload your own packages for anyone to download, which is how we are going to make this plugin simple to install.
To define the package for upload to the Anaconda Cloud repository
we have to write a meta.yaml
file.
This defines everything required to build the package including;
the package details,
where to find the source code,
dependencies required to build the package,
dependencies to install the package, and
how to build the package.
There is [extensive documentation][meta.yaml documentation] for the meta.yaml
file
which covers a variety of use cases.
The file uses the yaml syntax,
which is a method of encoding python data structures
in a more human readable format.
The meta.yaml
file contains a series of sections
each containing different information about the package.
Some of these keys are;
package
,about
,source
,dependencies
, andtest
.
The documentation link above contains more extensive information on the options available for each of these sections. I am going to explain how I have used all the different sections to create this plugin.
The package
key contains the name and version of the package. This section is
compulsory, while all others are optional, although that doesn't mean they
aren't required.
package:
name: hoomd-harmonic-force
version: 0.1.7
The about
key is where you can find more information on the package, including the
homepage and the license the package uses. In this case the homepage is a link to the Github
repository.
about:
home: https://github.com/malramsay64/hoomd-harmonic-force
license: MIT
license_file: LICENSE
The source
section defines where to find the source code for the build phase. I have specified
a specific tag from the git repository, enabling me to checkout an old commit to build an older
version.
source:
git_url: https://github.com/malramsay64/hoomd-harmonic-force.git
git_rev: v0.1.7
Another useful option for the source
section is use the path
key. This key
allows for the specification of the relative (or absolute) path to the source
code. This is most useful in the development process, allowing for quickly
testing whether a change has worked.
source:
path: ./
The next section is the requirements
, defining both the packages required for
the build phase, and those required when the package is installed. These package definitions
include the specification of compatible versions of the dependencies. The version specification
is flexible enough for complex build processes, while also having the option for simplicity.
In this guide I describe the simplest method of version specification. When you need something more complex,
the documentation on build variants covers a wide range of use cases.
The build requirements are all the programs required for the build; in this
case compilation with cmake
. Since we are building a python module we require
both python and setuptools. I am using python 3.6, so have set the python
version to 3.6.*
which means any point release of python 3.6, at the time of
writing being 3.6.5. The numpy and Hoomd dependencies are also requirements of
the build process, for which I have specified the latest versions. For python,
numpy and Hoomd you can specify the versions you use for your work. Where you
use multiple versions, like python 2.7, 3.5, and 3.6, the start of the build
variants documentation has examples to adapt. The next
dependency, cudatoolkit, is not strictly necessary in this case, however before
Hoomd v2.2.5 there was a typo in the Hoomd version specification allowing
a newer incompatible version to be installed. The final requirement, cmake
,
builds the package and while you might have it installed on your system
already, someone else may not. It should be noted that the nvcc
binary is
also required on linux which I can't find a conda package for.
requirements:
build:
- python 3.6.*
- setuptools
- numpy 1.14.*
- hoomd 2.3.*
- cudatoolkit 8.*
- cmake >=2.8.0
With the build dependencies specified, we need to specify the run dependencies.
Version numbers under the [semantic versioning][] scheme have a format of
<major>.<minor>.<patch>
. Version numbers with the same major and minor
numbers are typically compatible which is why I have specified the run
requirements with the same minor version as the build requirements. I have come
across previous versions of Hoomd which are only compatible with a singe patch
version of python. Unfortunately, you can't pin the python version to a single patch
as conda build overrides the pinning.
requirements:
run:
- python 3.6.*
- numpy 1.14.*
- hoomd 2.3.*
- cudatoolkit 8.*
Having waded through the complications of dependency specification we now need
to tell conda how to build our plugin. This is where creating the Makefile is
useful, we can now use the same commands for build process as for a manual
installation; make clean && make install
. The make clean
command removes
the build files when using the path
option for the source
. The other
element of the build section is the number, incremented when uploading another
package with the same version and reset to 0 on a new version. The number acts
as a sub-version number allowing for small fixes, like correcting the version
pinning, without incrementing the package version number.
build:
script: make clean && make install
number: 0
The final section I will discuss is the test section. At its simplest this can
import your newly created package, or it can run a complex unit and integration
test suite. The test section failing will be a fail the build, providing
a final check for bugs before release. In the example below I am checking the
package will import as a first simple test, before running more extensive
testing with pytest. To ensure pytest is installed, there is
a requirements
key in the test
section allowing for the specification
of test specific requirements. I have also specified the files for running the
tests using the source_files
key, which is the entire directory of test
files. The final key is the commands
to run your test suite. Since I have the
test rule configured in my Makefile I make use of it here.
test:
imports:
- hoomd.harmonic_force
requires:
- pytest
source_files:
- test/*
commands:
- make test
I have put all the snippets into a single block of code at the end of this article or downloadable here. For more complex examples, you can have a look at the Hoomd repository or the example in my repository.
Building and Distribution
With all the metadata defined, conda makes it straightforward to create the
package for upload. There are two conda packages required for building and
uploading to Anaconda Cloud, which are both required in the root
environment. You can install both packages with the command below, where the
-n
flag specifies installing to the root
environment.
$ conda install -n root conda-build anaconda-client
With the conda-build
package installed, you can run conda build .
where the
.
is the path to the directory containing meta.yaml
file. As part of build
process conda will create new environments for both the build and test phases,
preventing the packages in your current environment from interfering with the
build process and ensuring you have specified all requirements. The build phase
prepares the package for upload to Anaconda Cloud, which is handled by
anaconda client. If you have already provided credentials the upload should
be automatic, though on failure the error messages include steps to complete
the upload.
With your package uploaded to the Anaconda Cloud, anybody can install it by specifying your repository. You can install my plugin with the command
$ conda install -c malramsay hoomd-harmonic-force
although I wouldn't recommend using it at this stage. That said, contributions are most welcome.
While the process of packaging is difficult, I have hopefully made it somewhat
more approachable. The good thing is that once you have a meta.yaml
file for
a project, maintenance will mostly be updates of version numbers. As an added
benefit, installing or updating your software is much simpler for you, and
importantly, anyone else that wants to try it out. For why open source software
if you don't intend for someone else to actually use it.
Entire meta.yaml
I have included the entire meta.yaml
file below for ease of copying.
# meta.yaml
package:
name: hoomd-harmonic-force
version: 0.1.7
about:
home: https://github.com/malramsay64/hoomd-harmonic-force
license: MIT
license_file: LICENSE
source:
git_url: https://github.com/malramsay64/hoomd-harmonic-force.git
git_rev: v0.1.7
requirements:
build:
- python 3.6.*
- setuptools
- numpy 1.14.*
- hoomd 2.3.*
- cudatoolkit 8.*
- cmake >=2.8.0
run:
- python 3.6.*
- numpy 1.14.*
- hoomd 2.3.*
- cudatoolkit 8.*
build:
script: make clean && make install
number: 0
test:
imports:
- hoomd.harmonic_force
requires:
- pytest
source_files:
- test/*
commands:
- make test