Write a Reaction Plugin

developer
In this tutorial, you will learn how to create your own reaction plugin in a GitHub repository.
Author

Eric Hartmann

Creating a GitHub repository

By creating a GitHub repository, you have version control for your plugin and can share it with others. To set it up, follow these instructions.

Adding the functionality of the plugin

Main code

A reaction plugin has to be a derived class from the ReactionPlugin base class. Such a class has the attributes name, runmng and config and the method get_recipe_collection, which takes a TaskFiles object as argument and returns a RecipeCollection.

The name is a simple string and may be useful for logging. runmng is the central RunManager of a kimmdy run and has plenty of useful attributes for your plugin, especially the system Topology, which can be accessed via self.runmg.top. config contains the reaction plugin configuration as specified in the kimmdy configuration file (typically named kimmdy.yml). A RecipeCollection contains Recipes with predefined RecipeSteps that can be used to define the modification to the system for the reaction. A Recipe also contains the rates of the specified reaction and the timespans during which the corresponding rates are valid. An example plugin can be seen below.

Plugin main code (reaction.py)
from kimmdy.recipe import (
    Bind,
    Recipe,
    RecipeCollection,
)
from kimmdy.plugins import ReactionPlugin
from kimmdy.tasks import TaskFiles
from kimmdy.utils import (
    get_atomnrs_from_plumedid,
)
import logging
from kimmdy.parsing import (
    read_plumed,
    read_distances_dat,
)

# start the name of your logger with kimmdy.<plugin name>
# such that it ends up in the kimmdy log file
logger = logging.getLogger("kimmdy.hydrolysis")

class BindReaction(ReactionPlugin):
    """Reaction to bind two particles if they are in proximity
    """

    def get_recipe_collection(self, files: TaskFiles):
        logger = files.logger
        logger.debug("Getting recipe for reaction: Bind")

        # get cutoff distance from config, unit is [nm]
        cutoff = self.config.distance

        # Initialization of objects from files
        distances = read_distances_dat(files.input["plumed_out"])
        plumed = read_plumed(files.input["plumed"])

        recipes = []
        # check distances file for values lower than the cutoff
        for plumedid, dists in distances.items():
            if plumedid == "time":
                continue
            # checks only last frame
            if dists[-1] < cutoff:
                # get atomnrs from plumedid 
                atomnrs = get_atomnrs_from_plumedid(plumedid, plumed)
                recipes.append(
                    Recipe(
                        recipe_steps=[
                            Bind(atom_id_1=atomnrs[0], atom_id_2=atomnrs[1]),
                        ],
                        rates=[1],
                        timespans=[(distances["time"][0], distances["time"][-1])],
                    )
                )

        return RecipeCollection(recipes)

Configuration file schema

A plugin defines which variables it needs in a schema. The schema can contain default values and types of these variables. For a Kimmdy run, reaction plugin variables are defined in the configuration file (kimmdy.yml). An example schema can be seen below.

Plugin schema (kimmdy-yaml-schema.json)
kimmdy-yaml-schema.json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "$id": "bind-config",
  "description": "Settings for bind reactions",
  "additionalProperties": false,
  "properties": {
    "distance": {
      "description": "Cutoff distance for two particles to bind [nm]",
      "type": "float",
      "pytype": "float"
    },
    "kmc": {
      "description": "KMC algorithm for this reaction.",
      "type": "string",
      "pytype": "str",
      "enum": ["rfkmc", "frm", "extrande"],
      "default": "rfkmc"
    }
  },
  "required": ["distance"]
}

Making a python package

It is necessary to make the plugin a python package to interface with Kimmdy. For this, package setup configuration files are necessary, for example setup.py and setup.cfg. In setup.cfg dependencies can be specified, which will be installed alongside the plugin. The interface with kimmdy is specified in the [options.entry_points] section. This section needs to refer to the class we created in the plugin main code and assign it to kimmdy.reaction_plugins, i.e. kimmdy.reaction_plugins = bind = <path>.<to>.<main file>:<ClassName>. Also, the directory containing the source code (typically src) is defined in [options.packages.find]. An example for setup.py and setup.cfg can be found below.

setup.py
from setuptools import setup

setup()
setup.cfg
setup.cfg
[metadata]
name = kimmdy-reactions
version = 0.1
license = GPL-3.0 
description = Reaction template for KIMMDY
long_description = file: README.md
author = Eric Hartmann
author_email = eric.Hartmann@h-its.org
classifiers=
        Programming Language :: Python :: 3
        License :: OSI Approved :: MIT License
        Operating System :: OS Independent

[options]
packages = find:
package_dir =
    =src
include_package_data = True
install_requires =
    MDAnalysis

python_requires = >= 3.9

[options.packages.find]
where=src

[options.entry_points]
kimmdy.reaction_plugins =
    bind = bind.reaction:BindReaction

The main code and configuration schema file should then be moved to the specified source directory in a directory that has the name of the reaction plugin, i.e. src/<plugin name>/.

Tip

By adding the kimmdy tag on GitHub you can make your plugin discoverable by other users. It will show up in this list

Improving code quality and reproducibility

Adding tests will help in ensuring the plugin is working as expected. An example would help users to understand what your plugin does.

Back to top