Skip to content

Commit

Permalink
Improve Toolkit Wrapper and Registry API docs (#1787)
Browse files Browse the repository at this point in the history
* Add NAGLToolkitWrapper to API docs

* Write docstrings for NAGLToolkitWrapper

* Update changelog

* Move toolkit wrapper API docs to own page

* Update changelog with #1787

* fix indentation

* Add newlines to python doc examples in toolkits.md

* Revise and augment toolkit registry API docs

* Write API docs for GLOBAL_TOOLKIT_REGISTRY

* Revise toolkit API table of contents and overview docs

* Typos

* Fix rdkit/oetk to_smiles output

* Clarify toolkits.md and move complex info to API docs

* Improve docs for toolkit wrapper base class

* Fix API docs formatting

* Move ToolkitRegistry.call to top of methods list

* Rephrase toolkit_registry_manager guidance

* Update phrasing of release history

* Lint Markdown/rST files

---------

Co-authored-by: Jeff Wagner <[email protected]>
Co-authored-by: Matthew W. Thompson <[email protected]>
  • Loading branch information
3 people authored May 7, 2024
1 parent ef2f514 commit 60abeb4
Show file tree
Hide file tree
Showing 9 changed files with 340 additions and 202 deletions.
5 changes: 5 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,8 @@ repos:
- id: nbqa-flake8
args:
- '--select=F'
- repo: https://github.com/adamchainz/blacken-docs
rev: 1.16.0
hooks:
- id: blacken-docs
files: ^docs/.*\.(rst|md)$
98 changes: 98 additions & 0 deletions docs/api/toolkits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Toolkit Wrappers and Registries

The toolkit wrappers provide a simple uniform API for accessing some functionality of cheminformatics toolkits. They are used internally by the OpenFF Toolkit to avoid re-implementing existing scientific algorithms, and can be used by users to specify exactly what software is used for any given section of code.

These toolkit wrappers are generally used through a [`ToolkitRegistry`], which facilitates combining software with different capabilities and can be constructed with a list of toolkit wrappers ordered by precedence.

```pycon
>>> from openff.toolkit import (
... ToolkitRegistry,
... OpenEyeToolkitWrapper,
... RDKitToolkitWrapper,
... AmberToolsToolkitWrapper,
... )
>>> toolkit_registry = ToolkitRegistry(
... [
... OpenEyeToolkitWrapper,
... RDKitToolkitWrapper,
... AmberToolsToolkitWrapper,
... ]
... )

```

The toolkit wrappers' functionality can then be accessed through the registry. The first toolkit in the list that provides a method with the given name will be used:

```pycon
>>> from openff.toolkit import Molecule
>>> molecule = Molecule.from_smiles('Cc1ccccc1')
>>> smiles = toolkit_registry.call('to_smiles', molecule)

```

For further details on how this search is performed and how it handles exceptions, see the [`ToolkitRegistry.call()` API docs].

Many functions in the OpenFF Toolkit API include a `toolkit_registry` argument that can be used to specify the toolkit wrappers used by a call to that function. The value of this argument can be either a single toolkit wrapper instance, or an entire toolkit registry:

```pycon
>>> smiles = molecule.to_smiles(toolkit_registry=RDKitToolkitWrapper())
>>> smiles = molecule.to_smiles(toolkit_registry=toolkit_registry)

```

For example, differences in `to_smiles` functionality between the OpenEye and RDKit can be explored by specifying the desired toolkit wrapper:

```pycon
>>> molecule.to_smiles(toolkit_registry=RDKitToolkitWrapper())
'[H][c]1[c]([H])[c]([H])[c]([C]([H])([H])[H])[c]([H])[c]1[H]'
>>> molecule.to_smiles(toolkit_registry=OpenEyeToolkitWrapper())
'[H]c1c(c(c(c(c1[H])[H])C([H])([H])[H])[H])[H]'

```

The default value of this argument is the [`GLOBAL_TOOLKIT_REGISTRY`], which by default includes all the toolkits that OpenFF recommends for everyday use:

```pycon
>>> from openff.toolkit import GLOBAL_TOOLKIT_REGISTRY
>>> len(GLOBAL_TOOLKIT_REGISTRY.registered_toolkits)
4

```

The [`toolkit_registry_manager`] context manager allows `GLOBAL_TOOLKIT_REGISTRY` to be changed temporarily:

```pycon
>>> from openff.toolkit.utils import toolkit_registry_manager
>>> print(len(GLOBAL_TOOLKIT_REGISTRY.registered_toolkits))
4
>>> with toolkit_registry_manager(ToolkitRegistry([RDKitToolkitWrapper(), AmberToolsToolkitWrapper()])):
... print(len(GLOBAL_TOOLKIT_REGISTRY.registered_toolkits))
2

```

For more information about modifying `GLOBAL_TOOLKIT_REGISTRY`, see the [`GLOBAL_TOOLKIT_REGISTRY` API docs].

[`ToolkitRegistry`]: openff.toolkit.utils.toolkits.ToolkitRegistry
[`ToolkitRegistry.call()` API docs]: openff.toolkit.utils.toolkits.ToolkitRegistry.call
[`ToolkitRegistry.call`]: openff.toolkit.utils.toolkits.ToolkitRegistry.call
[`GLOBAL_TOOLKIT_REGISTRY`]: openff.toolkit.utils.toolkits.GLOBAL_TOOLKIT_REGISTRY
[`toolkit_registry_manager`]: openff.toolkit.utils.toolkits.toolkit_registry_manager
[`GLOBAL_TOOLKIT_REGISTRY` API docs]: openff.toolkit.utils.toolkits.GLOBAL_TOOLKIT_REGISTRY

```{eval-rst}
.. currentmodule:: openff.toolkit.utils.toolkits
.. autosummary::
:nosignatures:
:toctree: generated/
ToolkitRegistry
ToolkitWrapper
OpenEyeToolkitWrapper
RDKitToolkitWrapper
AmberToolsToolkitWrapper
NAGLToolkitWrapper
BuiltInToolkitWrapper
GLOBAL_TOOLKIT_REGISTRY
toolkit_registry_manager
```
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ maxdepth: 1

topology
typing
api/toolkits
utils
Exceptions <api/generated/openff.toolkit.utils.exceptions.rst>
:::
1 change: 1 addition & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ All available toolkits are automatically registered in the `GLOBAL_TOOLKIT_REGIS

```python
from openff.toolkit import GLOBAL_TOOLKIT_REGISTRY

print(GLOBAL_TOOLKIT_REGISTRY.registered_toolkit_versions)
# {'The RDKit': '2022.03.5', 'AmberTools': '22.0', 'Built-in Toolkit': None}
```
1 change: 1 addition & 0 deletions docs/releasehistory.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Releases follow the `major.minor.micro` scheme recommended by [PEP440](https://w

- [PR #1870](https://github.com/openforcefield/openff-toolkit/pull/1870): Updates examples to use openff-2.2.0,
and modernizes use of some API points.
- [PR #1787](https://github.com/openforcefield/openff-toolkit/pull/1787): Improve documentation for toolkit wrappers and registries

## 0.16.0

Expand Down
106 changes: 0 additions & 106 deletions docs/utils.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,112 +3,6 @@
Utilities
=========

Toolkit wrappers
----------------

The toolkit wrappers provide a simple uniform API for accessing minimal functionality of cheminformatics toolkits.

These toolkit wrappers are generally used through a :class:`ToolkitRegistry`, which can be constructed with a desired precedence of toolkits:

.. code-block:: python
>>> from openff.toolkit.utils.toolkits import ToolkitRegistry, OpenEyeToolkitWrapper, RDKitToolkitWrapper, AmberToolsToolkitWrapper
>>> toolkit_registry = ToolkitRegistry()
>>> toolkit_precedence = [OpenEyeToolkitWrapper, RDKitToolkitWrapper, AmberToolsToolkitWrapper]
>>> [ toolkit_registry.register_toolkit(toolkit) for toolkit in toolkit_precedence if toolkit.is_available() ]
[None, None, None]
The toolkit wrappers can then be accessed through the registry:

.. code-block:: python
>>> from openff.toolkit.utils.toolkits import GLOBAL_TOOLKIT_REGISTRY as toolkit_registry
>>> from openff.toolkit import Molecule
>>> molecule = Molecule.from_smiles('Cc1ccccc1')
>>> smiles = toolkit_registry.call('to_smiles', molecule)
The order of toolkits, as specified in ``toolkit_precedence`` above, determines the order in which
the called method is resolved, i.e. if the toolkit with highest precedence has a ``to_smiles``
method, that is the toolkit that will be called. If the toolkit with highest precedence does not
have such a method, it is attempted with other toolkits until one is found. By default, if a toolkit with an appropriately-named method raises an exception of any type, then iteration over the registered toolkits stops and that exception is raised. To continue iteration if specific exceptions are encountered, customize this behavior using the optional ``raise_exception_types`` keyword argument to ``ToolkitRegistry.call``. If no registered
toolkits have the method, a ValueError is raised, containing a message listing the registered toolkits and exceptions (if any) that were ignored.

Alternatively, the global toolkit registry (which will attempt to register any available toolkits) can be used:

.. code-block:: python
>>> from openff.toolkit.utils.toolkits import GLOBAL_TOOLKIT_REGISTRY as toolkit_registry
>>> len(toolkit_registry.registered_toolkits)
4
Individual toolkits can be registered or deregistered to control the backend that ToolkitRegistry calls resolve to. This can
be useful for debugging and exploring subtley different behavior between toolkit wrappers.

To temporarily change the state of ``GLOBAL_TOOLKIT_REGISTRY``, we provide the ``toolkit_registry_manager``
context manager.

.. code-block:: python
>>> from openff.toolkit.utils.toolkits import RDKitToolkitWrapper, AmberToolsToolkitWrapper, GLOBAL_TOOLKIT_REGISTRY
>>> from openff.toolkit.utils import toolkit_registry_manager
>>> print(len(GLOBAL_TOOLKIT_REGISTRY.registered_toolkits))
4
>>> with toolkit_registry_manager(ToolkitRegistry([RDKitToolkitWrapper(), AmberToolsToolkitWrapper()])):
... print(len(GLOBAL_TOOLKIT_REGISTRY.registered_toolkits))
2
To remove ``ToolkitWrappers`` permanently from a ``ToolkitRegistry``, the ``deregister_toolkit`` method can be used:

.. code-block:: python
>>> from openff.toolkit.utils.toolkits import OpenEyeToolkitWrapper, BuiltInToolkitWrapper
>>> from openff.toolkit.utils.toolkits import GLOBAL_TOOLKIT_REGISTRY as toolkit_registry
>>> print(len(toolkit_registry.registered_toolkits))
4
>>> toolkit_registry.deregister_toolkit(RDKitToolkitWrapper)
>>> print(len(toolkit_registry.registered_toolkits))
3
>>> toolkit_registry.register_toolkit(RDKitToolkitWrapper)
>>> print(len(toolkit_registry.registered_toolkits))
4
For example, differences in ``to_smiles`` functionality between OpenEye toolkits and The RDKit can
be explored by selecting which toolkit(s) are and are not registered.

.. code-block:: python
>>> from openff.toolkit.utils.toolkits import OpenEyeToolkitWrapper, GLOBAL_TOOLKIT_REGISTRY as toolkit_registry
>>> from openff.toolkit import Molecule
>>> molecule = Molecule.from_smiles('Cc1ccccc1')
>>> smiles_via_openeye = toolkit_registry.call('to_smiles', molecule)
>>> print(smiles_via_openeye)
[H]c1c(c(c(c(c1[H])[H])C([H])([H])[H])[H])[H]
>>> toolkit_registry.deregister_toolkit(OpenEyeToolkitWrapper)
>>> smiles_via_rdkit = toolkit_registry.call('to_smiles', molecule)
>>> print(smiles_via_rdkit)
[H][c]1[c]([H])[c]([H])[c]([C]([H])([H])[H])[c]([H])[c]1[H]
.. currentmodule:: openff.toolkit.utils.toolkits
.. autosummary::
:nosignatures:
:toctree: api/generated/

ToolkitRegistry
ToolkitWrapper
OpenEyeToolkitWrapper
RDKitToolkitWrapper
AmberToolsToolkitWrapper
NAGLToolkitWrapper
BuiltInToolkitWrapper

.. currentmodule:: openff.toolkit.utils.toolkit_registry
.. autosummary::
:nosignatures:
:toctree: api/generated/

toolkit_registry_manager

Serialization support
---------------------

Expand Down
8 changes: 7 additions & 1 deletion openff/toolkit/utils/base_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,15 @@ def _mol_to_ctab_and_aro_key(

class ToolkitWrapper:
"""
Toolkit wrapper base class.
Base class for wrappers around external software toolkits.
.. warning :: This API is experimental and subject to change.
See also
========
ToolkitRegistry, OpenEyeToolkitWrapper, RDKitToolkitWrapper,
AmberToolsToolkitWrapper, NAGLToolkitWrapper, BuiltInToolkitWrapper
"""

_is_available: Optional[bool] = None # True if toolkit is available
Expand Down
Loading

0 comments on commit 60abeb4

Please sign in to comment.