Unverified Commit ae2fe2fd authored by Josh A. Mitchell's avatar Josh A. Mitchell Committed by GitHub
Browse files

Move to actively maintained dependencies for API docs (#3201)

* Proof of concept matching current behaviour with Breathe

* Reorganise C++ API docs to work without autosummary

* Revert to Sphinx-native search

* Move remaining pip deps to conda

* Remove unnecessary lunrsearch templates

* Remove lunrsearch from cmake

* Tidy up layout of API docs

* Get code blocks to work with Breathe

Breathe doesn't seem to support the Doxygen <preformatted> tag.
It does support the <code> tag, but better yet it supports using
<verbatim> tags to embed rst into docstrings. This commit changes
all <pre> tags to use RST verbatim, and updates the Python
documentation machinery to support it too.

* Clarified some language

* Have doxygen exclude undocumented classes and have sphinx fail on warnings for C++ API docs

* List custom forces and integrators last

* Add breathe to documentation CI

* Typo

* Fix link to custom forces in extras.rst

* Have Breathe process only public classes

* Strip OpenMM:: prefix from rst files to avoid colons in links

* Remove existing private classes from EXCLUDE_SYMBOLS

* Add comment to C++ cmake describing why we promote warnings to errors

* Revise documentation build instructions for new dependencies
parent e41e8e3e
...@@ -12,8 +12,6 @@ dependencies: ...@@ -12,8 +12,6 @@ dependencies:
- cython - cython
- swig - swig
- doxygen 1.8.14 - doxygen 1.8.14
- pip: - sphinx==4.0.2
- sphinx==4.0.2 - sphinxcontrib-bibtex
- sphinxcontrib-bibtex - breathe>=4.30,<5.0
- sphinxcontrib-lunrsearch
- sphinxcontrib-autodoc_doxygen
\ No newline at end of file
set(STAGING_OUTPUT_FILES "") # Will contain all required package files set(STAGING_OUTPUT_FILES "") # Will contain all required package files
file(GLOB STAGING_INPUT_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" file(GLOB STAGING_INPUT_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
"*.rst" "*.rst"
"*.rst.jinja2"
"*.py" "*.py"
"_static/logo.png" "_static/logo.png"
"_static/custom.css" "_static/custom.css"
"_templates/lunrsearch.html"
"_templates/navigation.html" "_templates/navigation.html"
) )
...@@ -38,24 +36,36 @@ foreach(INIT_FILE ${STAGING_INPUT_FILES}) ...@@ -38,24 +36,36 @@ foreach(INIT_FILE ${STAGING_INPUT_FILES})
endforeach(INIT_FILE ${STAGING_INPUT_FILES}) endforeach(INIT_FILE ${STAGING_INPUT_FILES})
add_custom_command( add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/library.rst" OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/generated/"
COMMAND "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_BINARY_DIR}/render.py" COMMAND "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_BINARY_DIR}/breathe-apidoc.py"
"--generate=class"
"--members"
"--force"
"--brief-titles"
"--rename-output"
"--remove-prefix='OpenMM::'"
"--flat-output"
"--public-only"
"--quiet"
"--output-dir=generated"
"${WRAPPER_DOXYGEN_DIR}/xml/" "${WRAPPER_DOXYGEN_DIR}/xml/"
DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/render.py" DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/breathe-apidoc.py"
"${CMAKE_CURRENT_BINARY_DIR}/library.rst.jinja2"
"${WRAPPER_DOXYGEN_DIR}/xml/index.xml" "${WRAPPER_DOXYGEN_DIR}/xml/index.xml"
) )
add_custom_command( add_custom_command(
OUTPUT "${CMAKE_BINARY_DIR}/api-c++/index.html" OUTPUT "${CMAKE_BINARY_DIR}/api-c++/index.html"
COMMAND "${PYTHON_EXECUTABLE}" -m sphinx . "${CMAKE_BINARY_DIR}/api-c++" COMMAND "${PYTHON_EXECUTABLE}" -m sphinx . "${CMAKE_BINARY_DIR}/api-c++" -W --keep-going # Promote warnings to errors to catch undocumented classes
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/conf.py" DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/conf.py"
"${CMAKE_CURRENT_BINARY_DIR}/library.rst" "${CMAKE_CURRENT_BINARY_DIR}/generated/"
"${CMAKE_CURRENT_BINARY_DIR}/index.rst" "${CMAKE_CURRENT_BINARY_DIR}/index.rst"
"${CMAKE_CURRENT_BINARY_DIR}/forces.rst"
"${CMAKE_CURRENT_BINARY_DIR}/integrators.rst"
"${CMAKE_CURRENT_BINARY_DIR}/extras.rst"
"${CMAKE_CURRENT_BINARY_DIR}/_static/logo.png" "${CMAKE_CURRENT_BINARY_DIR}/_static/logo.png"
"${CMAKE_CURRENT_BINARY_DIR}/_static/custom.css" "${CMAKE_CURRENT_BINARY_DIR}/_static/custom.css"
"${CMAKE_CURRENT_BINARY_DIR}/_templates/lunrsearch.html"
"${CMAKE_CURRENT_BINARY_DIR}/_templates/navigation.html" "${CMAKE_CURRENT_BINARY_DIR}/_templates/navigation.html"
"${WRAPPER_DOXYGEN_DIR}/xml/index.xml" "${WRAPPER_DOXYGEN_DIR}/xml/index.xml"
) )
......
...@@ -313,7 +313,7 @@ EXTRACT_STATIC = NO ...@@ -313,7 +313,7 @@ EXTRACT_STATIC = NO
# defined locally in source files will be included in the documentation. # defined locally in source files will be included in the documentation.
# If set to NO only classes defined in header files are included. # If set to NO only classes defined in header files are included.
EXTRACT_LOCAL_CLASSES = YES EXTRACT_LOCAL_CLASSES = NO
# This flag is only useful for Objective-C code. When set to YES local # This flag is only useful for Objective-C code. When set to YES local
# methods, which are defined in the implementation section but not in # methods, which are defined in the implementation section but not in
...@@ -631,9 +631,8 @@ EXCLUDE_PATTERNS = */tests/* \ ...@@ -631,9 +631,8 @@ EXCLUDE_PATTERNS = */tests/* \
# wildcard * is used, a substring. Examples: ANamespace, AClass, # wildcard * is used, a substring. Examples: ANamespace, AClass,
# AClass::ANamespace, ANamespace::*Test # AClass::ANamespace, ANamespace::*Test
EXCLUDE_SYMBOLS = StandardMMForceField::*Info \ EXCLUDE_SYMBOLS =
GBSAOBCForceField::AtomInfo \
System::ConstraintInfo
# The EXAMPLE_PATH tag can be used to specify one or more files or # The EXAMPLE_PATH tag can be used to specify one or more files or
# directories that contain example code fragments that are included (see # directories that contain example code fragments that are included (see
......
../../api-python/_templates/lunrsearch.html
\ No newline at end of file
# -*- coding: utf-8 -*-
"""
breathe.apidoc
~~~~~~~~~~~~~~
Parses doxygen XML tree looking for C/C++ modules and creates ReST files
appropriately to create code documentation with Sphinx. It also creates a
modules index (See TYPEDICT below.).
This is derived from the "sphinx-autopackage" script, which is:
Copyright 2008 Société des arts technologiques (SAT),
http://www.sat.qc.ca/
:copyright: Originally by Sphinx Team, C++ modifications by Tatsuyuki Ishi
:license: BSD, see LICENSE for details.
"""
from __future__ import print_function
import argparse
import errno
import os
import sys
import xml.etree.ElementTree
from breathe import __version__
# Account for FileNotFoundError in Python 2
# IOError is broader but will hopefully suffice
try:
FileNotFoundError
except NameError:
FileNotFoundError = IOError
# Reference: Doxygen XSD schema file, CompoundKind only
# Only what breathe supports are included
# Translates identifier to English
TYPEDICT = {
"class": "Class",
"interface": "Interface",
"struct": "Struct",
"union": "Union",
"file": "File",
"namespace": "Namespace",
"group": "Group",
}
# Types that accept the :members: option.
MEMBERS_TYPES = ["class", "group", "interface", "namespace", "struct"]
def print_info(msg, args):
if not args.quiet:
print(msg)
def write_file(name, text, args):
"""Write the output file for module/package <name>."""
if args.outflat:
name = os.path.basename(name)
fname = os.path.join(args.destdir, "%s.%s" % (name, args.suffix))
if args.dryrun:
print_info("Would create file %s." % fname, args)
return
if not args.force and os.path.isfile(fname):
print_info("File %s already exists, skipping." % fname, args)
else:
print_info("Creating file %s." % fname, args)
if not os.path.exists(os.path.dirname(fname)):
try:
os.makedirs(os.path.dirname(fname))
except OSError as exc: # Guard against race condition
if exc.errno != errno.EEXIST:
raise
try:
with open(fname, "r") as target:
orig = target.read()
if orig == text:
print_info("File %s up to date, skipping." % fname, args)
return
except FileNotFoundError:
# Don't mind if it isn't there
pass
with open(fname, "w") as target:
target.write(text)
def format_heading(level, text):
"""Create a heading of <level> [1, 2 or 3 supported]."""
underlining = ["=", "-", "~",][
level - 1
] * len(text)
return "%s\n%s\n\n" % (text, underlining)
def format_directive(package_type, package, args):
"""Create the breathe directive and add the options."""
directive = ".. doxygen%s:: %s\n" % (package_type, package)
if args.project:
directive += " :project: %s\n" % args.project
if args.members and package_type in MEMBERS_TYPES:
directive += " :members:\n"
return directive
def create_package_file(package, package_type, package_id, args):
"""Build the text of the file and write the file."""
text = f".. _{package}:\n\n"
if args.brieftitles:
_, _, brief = package.rpartition("::")
text += format_heading(1, f"``{brief}``")
else:
text += format_heading(1, "%s %s" % (TYPEDICT[package_type], package))
text += format_directive(package_type, package, args)
if args.packagenames:
outname = package
else:
outname = package_id
if outname.startswith(args.removeprefix):
outname = outname[len(args.removeprefix) :]
write_file(os.path.join(package_type, outname), text, args)
def create_modules_toc_file(key, value, args):
"""Create the module's index."""
if not os.path.isdir(os.path.join(args.destdir, key)):
return
text = format_heading(1, "%s list" % value)
text += ".. toctree::\n"
text += " :glob:\n\n"
text += " %s/*\n" % key
write_file("%slist" % key, text, args)
def filter_package(refid, kind, args) -> bool:
# Skip over types that weren't requested
if kind not in args.outtypes:
return False
if args.publiconly:
package = xml.etree.ElementTree.parse(
os.path.join(args.rootpath, refid + ".xml")
)
if not package.findall(f'.//compounddef[@prot="public"]'):
return False
return True
def recurse_tree(args):
"""
Look for every file in the directory tree and create the corresponding
ReST files.
"""
index = xml.etree.ElementTree.parse(os.path.join(args.rootpath, "index.xml"))
# Assuming this is a valid Doxygen XML
for compound in index.getroot():
name = compound.findtext("name")
kind = compound.get("kind")
refid = compound.get("refid")
if filter_package(refid, kind, args):
create_package_file(name, kind, refid, args)
class TypeAction(argparse.Action):
def __init__(self, option_strings, dest, **kwargs):
super(TypeAction, self).__init__(option_strings, dest, **kwargs)
self.default = TYPEDICT.keys()
self.metavar = ",".join(TYPEDICT.keys())
def __call__(self, parser, namespace, values, option_string=None):
value_list = values.split(",")
for value in value_list:
if value not in TYPEDICT:
raise ValueError("%s not a valid option" % value)
setattr(namespace, self.dest, value_list)
def main():
"""Parse and check the command line arguments."""
parser = argparse.ArgumentParser(
description="""\
Parse XML created by Doxygen in <rootpath> and create one reST file with
breathe generation directives per definition in the <DESTDIR>.
Note: By default this script will not overwrite already created files.""",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"-o",
"--output-dir",
action="store",
dest="destdir",
help="Directory to place all output",
required=True,
)
parser.add_argument(
"-f",
"--force",
action="store_true",
dest="force",
help="Overwrite existing files",
)
parser.add_argument(
"-m",
"--members",
action="store_true",
dest="members",
help="Include members for types: %s" % MEMBERS_TYPES,
)
parser.add_argument(
"-n",
"--dry-run",
action="store_true",
dest="dryrun",
help="Run the script without creating files",
)
parser.add_argument(
"-T",
"--no-toc",
action="store_true",
dest="notoc",
help="Don't create a table of contents file",
)
parser.add_argument(
"-s",
"--suffix",
action="store",
dest="suffix",
help="file suffix (default: rst)",
default="rst",
)
parser.add_argument(
"-p",
"--project",
action="store",
dest="project",
help="project to add to generated directives",
)
parser.add_argument(
"-g",
"--generate",
action=TypeAction,
dest="outtypes",
help="types of output to generate, comma-separated list",
)
parser.add_argument(
"-b",
"--brief-titles",
action="store_true",
dest="brieftitles",
help="Use only the last part of the compoundname for the title",
)
parser.add_argument(
"-r",
"--rename-output",
action="store_true",
dest="packagenames",
help="Rename output RST files to match input package names",
)
parser.add_argument(
"--remove-prefix",
action="store",
dest="removeprefix",
help="Remove a prefix from output file names",
)
parser.add_argument(
"-F",
"--flat-output",
action="store_true",
dest="outflat",
help="Place all output directly in output directory, no subdirectories",
)
parser.add_argument(
"-P",
"--public-only",
action="store_true",
dest="publiconly",
help="Only process objects that are marked as @public",
)
parser.add_argument(
"-q",
"--quiet",
action="store_true",
dest="quiet",
help="suppress informational messages",
)
parser.add_argument(
"--version",
action="version",
version="Breathe (breathe-apidoc) %s, modified for OpenMM" % __version__,
)
parser.add_argument("rootpath", type=str, help="The directory contains index.xml")
args = parser.parse_args()
if args.suffix.startswith("."):
args.suffix = args.suffix[1:]
if not os.path.isdir(args.rootpath):
print("%s is not a directory." % args.rootpath, file=sys.stderr)
sys.exit(1)
if "index.xml" not in os.listdir(args.rootpath):
print("%s does not contain a index.xml" % args.rootpath, file=sys.stderr)
sys.exit(1)
if not os.path.isdir(args.destdir):
if not args.dryrun:
os.makedirs(args.destdir)
args.rootpath = os.path.abspath(args.rootpath)
recurse_tree(args)
if not args.notoc:
for key in args.outtypes:
create_modules_toc_file(key, TYPEDICT[key], args)
# So program can be started with "python -m breathe.apidoc ..."
if __name__ == "__main__":
main()
import os import os
import sys import sys
extensions = [ extensions = ["sphinx.ext.mathjax", "breathe"]
"sphinx.ext.mathjax",
"sphinx.ext.autosummary",
"sphinx.ext.autodoc",
"sphinxcontrib.lunrsearch",
"sphinxcontrib.autodoc_doxygen",
]
autosummary_generate = True autosummary_generate = False
autodoc_member_order = "bysource" autodoc_member_order = "bysource"
breathe_projects = {
"api-c++": "doxygen/xml",
}
breathe_default_project = "api-c++"
# Tell sphinx what the primary language being documented is.
primary_domain = "cpp"
# Tell sphinx what the pygments highlight language should be.
highlight_language = "cpp"
source_suffix = ".rst" source_suffix = ".rst"
master_doc = "index" master_doc = "index"
...@@ -66,7 +71,7 @@ html_theme_options = { ...@@ -66,7 +71,7 @@ html_theme_options = {
html_sidebars = { html_sidebars = {
"**": [ "**": [
"about.html", "about.html",
"lunrsearch.html", "searchbox.html",
"navigation.html", "navigation.html",
] ]
} }
......
=============
Extra classes
=============
Tabulated functions
===================
These classes use table of values to define a mathematical function and can be
used by various :ref:`custom forces <custom-forces>`.
The :ref:`OpenMM::TabulatedFunction` class is an abstract class that the other classes
extend.
.. toctree::
:maxdepth: 2
generated/TabulatedFunction
generated/Continuous1DFunction
generated/Continuous2DFunction
generated/Continuous3DFunction
generated/Discrete1DFunction
generated/Discrete2DFunction
generated/Discrete3DFunction
Virtual Sites
=============
A virtual site is a particle whose position is computed directly from the
positions of other particles. The :ref:`OpenMM::VirtualSite` class is an abstract
class that the other classes extend.
.. toctree::
:maxdepth: 2
generated/VirtualSite
generated/LocalCoordinatesSite
generated/OutOfPlaneSite
generated/ThreeParticleAverageSite
generated/TwoParticleAverageSite
Serialization
=============
These classes are used to serialize other objects, allowing them to be stored on
disk.
.. toctree::
:maxdepth: 2
generated/SerializationNode
generated/SerializationProxy
generated/XmlSerializer
Other classes
=============
These classes don't fit neatly into the other categories, but that is not to say
that they aren't important!
.. toctree::
:maxdepth: 2
generated/LocalEnergyMinimizer
generated/NoseHooverChain
generated/OpenMMException
generated/Vec3
.. _forces:
======
Forces
======
The ``Force`` abstract class
============================
The ``Force`` objects added to a ``System`` define the behavior of the
particles. ``Force`` is an abstract class; subclasses implement specific behaviors. Classes that extend ``Force`` may implement actual physical forces, or any number of processes that either actually apply forces to particles or directly modify their positions or momenta.
.. toctree::
:maxdepth: 2
generated/Force
Common bonded and non-bonded forces
===================================
These classes implement forces that are widely used in biomolecular simulation.
.. toctree::
:maxdepth: 2
generated/CMAPTorsionForce
generated/DrudeForce
generated/GBSAOBCForce
generated/GayBerneForce
generated/HarmonicAngleForce
generated/HarmonicBondForce
generated/HippoNonbondedForce
generated/NonbondedForce
generated/PeriodicTorsionForce
generated/RBTorsionForce
AMOEBA forces
=============
These forces are used to implement the polarizable AMOEBA force fields.
.. toctree::
:maxdepth: 2
generated/AmoebaGeneralizedKirkwoodForce
generated/AmoebaMultipoleForce
generated/AmoebaTorsionTorsionForce
generated/AmoebaVdwForce
generated/AmoebaWcaDispersionForce
Pseudo-forces
=============
These inherit from ``Force``, but do not describe physical forces. They are used
to implement thermostats or barostats, or otherwise modify the simulation from
step to step. They are conceptually closer to modifications to the integrator,
but providing them as a ``Force`` simplifies implementation and allows them to
be combined in arbitrary ways.
.. toctree::
:maxdepth: 2
generated/AndersenThermostat
generated/CMMotionRemover
generated/MonteCarloAnisotropicBarostat
generated/MonteCarloBarostat
generated/MonteCarloMembraneBarostat
generated/RMSDForce
generated/RPMDMonteCarloBarostat
.. _custom-forces:
Customizing ``Force``
=====================
OpenMM provides a number of classes that make it easier to implement custom
forces for common scenarios. These classes implement constructors that take an
algebraic expression as a string. The class is instantiated (not extended) to
provide a ``Force`` object that efficiently implements the provided
expression.
.. toctree::
:maxdepth: 2
generated/CustomAngleForce
generated/CustomBondForce
generated/CustomCVForce
generated/CustomCentroidBondForce
generated/CustomCompoundBondForce
generated/CustomExternalForce
generated/CustomGBForce
generated/CustomHbondForce
generated/CustomManyParticleForce
generated/CustomNonbondedForce
generated/CustomTorsionForce
==============
OpenMM C++ API OpenMM C++ API
================= ==============
The C++ API provides information about the classes and methods available in OpenMM for C++ developers. The public API is based on a small number of classes. The C++ API provides information about the classes and methods available in OpenMM for C++ developers. OpenMM uses an object-oriented API that makes all its functionality available through a small number of classes.
Core classes
============
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 1
:hidden:
library generated/System
generated/Context
generated/State
generated/Platform
:cpp:class:`OpenMM::System`
---------------------------
:cpp:class:`System <OpenMM::System>`\ : A System specifies generic properties of the system to be A ``System`` specifies generic properties of the molecular system to be
simulated: the number of particles it contains, the mass of each one, the size simulated: the number of particles it contains, the mass of each one, the size
of the periodic box, etc. The interactions between the particles are specified of the periodic box, and so on. The interactions between the particles are
through a set of Force objects (see below) that are added to the System. Force specified through a set of :ref:`Force <forces>` objects that are added to the
field specific parameters, such as particle charges, are not direct properties ``System``. Force field specific parameters, such as particle charges, are
of the System. They are properties of the Force objects contained within the stored in these ``Force`` objects, not as direct properties of the ``System``.
System.
:cpp:class:`OpenMM::Context`
:cpp:class:`Force <OpenMM::Force>`\ : The Force objects added to a System define the behavior of the ----------------------------
particles. Force is an abstract class; subclasses implement specific behaviors.
The Force class is actually slightly more general than its name suggests. A A ``Context`` stores all of the state information for a simulation: particle
Force can, indeed, apply forces to particles, but it can also directly modify positions and velocities, as well as arbitrary parameters defined by the
particle positions and velocities in arbitrary ways. Some thermostats and ``Forces`` in the System. It is possible to create multiple ``Contexts`` for a
barostats, for example, can be implemented as Force classes. Examples of Force single ``System``, and thus have multiple simulations of that ``System`` in
subclasses include :cpp:class:`HarmonincBondForce <OpenMM::HarmonincBondForce>`, :cpp:class:`NonbondedForce <OpenMM::NonbondedForce>`, and :cpp:class:`MonteCarloBarostat <OpenMM::MonteCarloBarostat>`. progress at the same time. ``Context`` does not provide methods for accessing
state variables directly; they must be read via a ``State`` object.
:cpp:class:`Context <OpenMM::Context>`\ : This stores all of the state information for a simulation:
particle positions and velocities, as well as arbitrary parameters defined by
the Forces in the System. It is possible to create multiple Contexts for a :cpp:class:`OpenMM::State`
single System, and thus have multiple simulations of that System in progress at --------------------------
the same time.
A ``State`` object must be constructed before data can be read from a
:cpp:class:`Integrator <OpenMM::Integrator>`\ : This implements an algorithm for advancing the simulation simulation. State variables are not accessible directly via a ``Context`` in
through time. It is an abstract class; subclasses implement specific order to make explicit the precise time that a variable reflects. A ``State``
algorithms. Examples of Integrator subclasses include :cpp:class:`LangevinIntegrator <OpenMM::LangevinIntegrator>`, is created by calling a method on a ``Context`` and stores only the information
requested at invocation.
:cpp:class:`OpenMM::Platform`
-----------------------------
A ``Platform`` is a single implementation of OpenMM at a low level. This allows
the same high level API documented here to be used on all sorts of compute
hardware, from GPUs to supercomputers. A ``Platform`` implements some set of
kernels, which define which operations it supports. Writing a new ``Platform``
allows OpenMM to be ported to new hardware or to be implemented in a new way
without rewriting the entire application.
Forces
======
``Force`` objects define the behavior of the particles in a ``System``. The
``Force`` class is actually slightly more general than its name suggests. A
``Force`` can, indeed, apply forces to particles, but it can also directly
modify particle positions and velocities in arbitrary ways. Some thermostats
and barostats, for example, can be implemented as ``Force`` classes. Examples
of Force subclasses include :cpp:class:`HarmonicBondForce
<OpenMM::HarmonicBondForce>`, :cpp:class:`NonbondedForce
<OpenMM::NonbondedForce>`, and :cpp:class:`MonteCarloBarostat
<OpenMM::MonteCarloBarostat>`.
.. toctree::
:maxdepth: 2
forces
Integrators
===========
An ``Integrator`` implements an algorithm for advancing the simulation through
time. They provide a ``Context`` a means of stepping the simulation forward,
and must be coupled to a ``Context`` to function. Examples of Integrator
subclasses include :cpp:class:`LangevinIntegrator <OpenMM::LangevinIntegrator>`,
:cpp:class:`VerletIntegrator <OpenMM::VerletIntegrator>`, and :cpp:class:`BrownianIntegrator <OpenMM::BrownianIntegrator>`. :cpp:class:`VerletIntegrator <OpenMM::VerletIntegrator>`, and :cpp:class:`BrownianIntegrator <OpenMM::BrownianIntegrator>`.
:cpp:class:`State <OpenMM::State>`\ : A State stores a snapshot of the simulation at a particular point .. toctree::
in time. It is created by calling a method on a Context. This is the only way to query the :maxdepth: 2
values of state variables, such as particle positions and velocities; Context
does not provide methods for accessing them directly. integrators
Extras
======
OpenMM's public API includes a few more classes that support the above.
.. toctree::
:maxdepth: 2
extras
===========
Integrators
===========
The ``Integrator`` abstract class
=================================
An ``Integrator`` implements an algorithm for advancing the simulation through
time. ``Integrator`` is an abstract class; subclasses implement specific
algorithms.
.. toctree::
:maxdepth: 2
generated/Integrator
General purpose integrators
===========================
These are integrators appropriate for traditional MD and BD simulations.
.. toctree::
:maxdepth: 2
generated/BrownianIntegrator
generated/LangevinIntegrator
generated/LangevinMiddleIntegrator
generated/NoseHooverIntegrator
generated/VariableLangevinIntegrator
generated/VariableVerletIntegrator
generated/VerletIntegrator
Drude integrators
=================
These integrators permit modelling polarization with a Drude particle.
.. toctree::
:maxdepth: 2
generated/DrudeIntegrator
generated/DrudeLangevinIntegrator
generated/DrudeNoseHooverIntegrator
generated/DrudeSCFIntegrator
Ring Polymer Molecular Dynamics integrators
===========================================
The RPMD integrator implements Ring Polymer MD.
.. toctree::
:maxdepth: 2
generated/RPMDIntegrator
Customizing ``Integrator``
==========================
These classes facilitate customisation of the integrator. ``CustomIntegrator``
allows a wide variety of integration algorithms to be implemented efficiently
without writing any low-level code. The integrator is built up as a series of
steps, each defined as an algebraic expression. ``CompoundIntegrator`` allows
different integrators to be combined by making it possible to switch the active
integrator in the middle of a simulation.
.. toctree::
:maxdepth: 2
generated/CustomIntegrator
generated/CompoundIntegrator
.. _core-objects:
Core Objects
~~~~~~~~~~~~
.. autodoxysummary::
:toctree: generated/
{% for obj in core %}
~{{obj}}
{% endfor %}
|
|
.. _forces:
Forces
~~~~~~
.. autodoxysummary::
:toctree: generated/
{% for force in forces %}
~{{force}}
{% endfor %}
|
|
.. _integrators:
Integrators
~~~~~~~~~~~
These integrators implement an algorithm for advancing the simulation
through time.
.. autodoxysummary::
:toctree: generated/
{% for integrator in integrators %}
~{{integrator}}
{% endfor %}
|
|
.. _extras:
Extras
~~~~~~
.. autodoxysummary::
:toctree: generated/
{% for extra in extras %}
~{{extra}}
{% endfor %}
from __future__ import print_function
import os
import sys
from functools import reduce
from os.path import basename, dirname, join, splitext
from glob import glob
import jinja2
import lxml.etree as ET
def load_doxygen_xml(doxygen_xml):
files = [os.path.join(doxygen_xml, f)
for f in os.listdir(doxygen_xml)
if f.lower().endswith('.xml') and not f.startswith('._')]
if len(files) == 0:
raise err
document = ET.ElementTree(ET.Element('root')).getroot()
for file in files:
root = ET.parse(file).getroot()
for node in root:
document.append(node)
return document
def subclasses(root, parent):
parent_el = root.xpath('.//compounddef/compoundname[text()="%s"]/..' % parent)
if len(parent_el) == 1:
parent_id = parent_el[0].get('id')
else:
raise ValueError("Can't find %s" % parent)
xp_query = ('.//compounddef/basecompoundref[@refid="%s"]'
'/../compoundname') % parent_id
return [parent] + [n.text.strip() for n in root.xpath(xp_query)]
def allclasses(root):
xp_query = './/compounddef[@kind="class" and @prot="public"]/compoundname'
return [e.text for e in root.xpath(xp_query)]
def template_data(root):
data = {
'core': ('OpenMM::System', 'OpenMM::Context', 'OpenMM::State', 'OpenMM::Platform'),
'forces': sorted(subclasses(root, 'OpenMM::Force')),
'integrators': sorted(subclasses(root, 'OpenMM::Integrator')),
}
data['extras'] = sorted(set(allclasses(root)) -
reduce(set.union, map(set, data.values())))
return data
def main():
if len(sys.argv) == 1:
print('usage: %s <doxygen_xml_path>' % sys.argv[0], file=sys.stderr)
exit(1)
doxygen_xml_path = sys.argv[1]
root = load_doxygen_xml(doxygen_xml_path)
data = template_data(root)
here = dirname(__file__)
templateLoader = jinja2.FileSystemLoader(here)
templateEnv = jinja2.Environment(loader=templateLoader)
for template_fn in map(basename, glob(join(here, '*.jinja2'))):
output_fn = splitext(template_fn)[0]
print('Rendering %s to %s...' % (template_fn, output_fn))
template = templateEnv.get_template(template_fn)
output_text = template.render(data)
with open(output_fn, 'w') as f:
f.write(output_text)
if __name__ == '__main__':
main()
...@@ -9,7 +9,6 @@ file(GLOB STAGING_INPUT_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" ...@@ -9,7 +9,6 @@ file(GLOB STAGING_INPUT_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
"_static/logo.png" "_static/logo.png"
"_static/custom.css" "_static/custom.css"
"_templates/class.rst" "_templates/class.rst"
"_templates/lunrsearch.html"
"_templates/navigation.html" "_templates/navigation.html"
) )
...@@ -46,7 +45,6 @@ add_custom_command( ...@@ -46,7 +45,6 @@ add_custom_command(
"${CMAKE_CURRENT_BINARY_DIR}/_static/logo.png" "${CMAKE_CURRENT_BINARY_DIR}/_static/logo.png"
"${CMAKE_CURRENT_BINARY_DIR}/_static/custom.css" "${CMAKE_CURRENT_BINARY_DIR}/_static/custom.css"
"${CMAKE_CURRENT_BINARY_DIR}/_templates/class.rst" "${CMAKE_CURRENT_BINARY_DIR}/_templates/class.rst"
"${CMAKE_CURRENT_BINARY_DIR}/_templates/lunrsearch.html"
"${CMAKE_CURRENT_BINARY_DIR}/_templates/navigation.html" "${CMAKE_CURRENT_BINARY_DIR}/_templates/navigation.html"
PythonInstall PythonInstall
) )
......
...@@ -212,3 +212,12 @@ div.sphinxsidebarwrapper .extra-nav-links { ...@@ -212,3 +212,12 @@ div.sphinxsidebarwrapper .extra-nav-links {
display: none display: none
} }
} }
/* Space out Breathe a bit more */
.body dl.cpp {
margin-bottom: 20px;
}
.cpp .sig-name {
font-size: 1em;
}
\ No newline at end of file
<!--
sphinxcontrib-lunrsearch injects its own search template at the front of the
line, so to overwrite it, I'm using this template with a different name, and
specifying it in html_sidebars. This is a temporary measure until we replace
or remove sphinxcontrib-lunrsearch
-->
<script type="text/javascript">
var Search = {
store : null,
setIndex : function (data) {
this.store = data.store;
},
};
</script>
{# The script searchindex.js contains the code Search.setIndex(...) where
the content is an object built from IndexBuilder.freeze(). So we need to
setup the Search.setIndex function beforehand just to store the data.
This should all be finished when onload fires, and at that point the code in
searchbox.js will pull the data out of Search.store and build the actual
index and callbacks.
#}
<script src="{{ pathto('searchindex.js', 1) }}" type="text/javascript"></script>
<form class="search" action="" method="get">
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
<input type="hidden" id="ls_lunrsearch-highlight" value="{{ lunrsearch_highlight }}" />
<input type="text" class="search-field" id="ls_search-field" name="q" placeholder="Search API..." autocomplete="off" />
<ul class="results" id="ls_search-results"></ul>
</form>
...@@ -12,7 +12,6 @@ extensions = [ ...@@ -12,7 +12,6 @@ extensions = [
"sphinx.ext.autodoc", "sphinx.ext.autodoc",
"sphinx.ext.napoleon", "sphinx.ext.napoleon",
"process-docstring", "process-docstring",
"sphinxcontrib.lunrsearch",
] ]
autosummary_generate = True autosummary_generate = True
...@@ -76,7 +75,7 @@ html_theme_options = { ...@@ -76,7 +75,7 @@ html_theme_options = {
html_sidebars = { html_sidebars = {
"**": [ "**": [
"about.html", "about.html",
"lunrsearch.html", "searchbox.html",
"navigation.html", "navigation.html",
] ]
} }
......
import re import re
def count_leading_whitespace(s):
count = 0
for c in s:
if c.isspace():
count += 1
else:
break
return count
def process_docstring(app, what, name, obj, options, lines): def process_docstring(app, what, name, obj, options, lines):
"""This hook edits the docstrings to replace "<tt><pre>" html tags """This hook edits the docstrings to replace "<tt><pre>" html tags,
and deprecated markers with sphinx directives. Breathe-style verbatim embed blocks, and deprecated markers with
sphinx directives.
""" """
def repl(m): def repl(m):
s = m.group(1) s = m.group(1)
...@@ -19,6 +29,28 @@ def process_docstring(app, what, name, obj, options, lines): ...@@ -19,6 +29,28 @@ def process_docstring(app, what, name, obj, options, lines):
def repl3(m): def repl3(m):
s = m.group(1) s = m.group(1)
return '*' + s + '*' return '*' + s + '*'
def repl4(m):
s = m.group(1)
if s.startswith("embed:rst"):
lines = s.split(linesep)[1:]
# Match behaviour of Breathe
if s.startswith("embed:rst:leading-asterisk"):
lines = [l.replace("*", " ", 1) for l in lines]
elif s.startswith("embed:rst:leading-slashes"):
lines = [l.replace("///", " ", 1) for l in lines]
# Strip leading whitespace to match first line
to_strip = count_leading_whitespace(lines[0])
lines = [l[to_strip:] for l in lines]
return linesep.join(lines)
else:
s = m.group(1)
if not s.startswith(linesep):
s = linesep + s
newline = '.. verbatim::' + linesep
return newline + ' ' + s.replace(linesep, linesep + ' ')
linesep = '|LINEBREAK|' linesep = '|LINEBREAK|'
joined = linesep.join(lines) joined = linesep.join(lines)
...@@ -27,6 +59,7 @@ def process_docstring(app, what, name, obj, options, lines): ...@@ -27,6 +59,7 @@ def process_docstring(app, what, name, obj, options, lines):
joined = re.sub(r'<tt>(.*?)</tt>', repl, joined) joined = re.sub(r'<tt>(.*?)</tt>', repl, joined)
joined = re.sub(r'@deprecated(.*?\|LINEBREAK\|)', repl2, joined, flags=re.IGNORECASE) joined = re.sub(r'@deprecated(.*?\|LINEBREAK\|)', repl2, joined, flags=re.IGNORECASE)
joined = re.sub(r'<i>(.*?)</i>', repl3, joined) joined = re.sub(r'<i>(.*?)</i>', repl3, joined)
joined = re.sub(r'<verbatim>(.*?)</verbatim>', repl4, joined)
lines[:] = [(l if not l.isspace() else '') for l in joined.split(linesep)] lines[:] = [(l if not l.isspace() else '') for l in joined.split(linesep)]
......
...@@ -345,12 +345,12 @@ Building the Documentation (Optional) ...@@ -345,12 +345,12 @@ Building the Documentation (Optional)
The documentation that you're currently reading, as well as the Developer Guide and API The documentation that you're currently reading, as well as the Developer Guide and API
documentation, can be built through CMake. To do that, you need to install a few documentation, can be built through CMake. To do that, you need to install a few
additional tools. The easiest way is to use :code:`pip` to install them into additional tools. The easiest way is to use :code:`conda` to install them into
your Python environment. The following command installs everything needed to your Python environment. The following command installs everything needed to
build documentation in HTML format. build documentation in HTML format.
:: ::
pip install sphinx sphinxcontrib-bibtex sphinxcontrib-lunrsearch sphinxcontrib-autodoc_doxygen jinja2 conda install -c conda-forge sphinx sphinxcontrib-bibtex breathe jinja2
To build documentation in PDF format, you also must have a functional LaTeX To build documentation in PDF format, you also must have a functional LaTeX
installation. It can be obtained from https://www.latex-project.org/get. installation. It can be obtained from https://www.latex-project.org/get.
......
...@@ -41,18 +41,24 @@ namespace OpenMM { ...@@ -41,18 +41,24 @@ namespace OpenMM {
* A Kernel encapsulates a particular implementation of a calculation that can be performed on the data * A Kernel encapsulates a particular implementation of a calculation that can be performed on the data
* in a Context. Kernel objects are created by Platforms: * in a Context. Kernel objects are created by Platforms:
* *
* <pre> * \verbatim embed:rst:leading-asterisk
* .. code-block:: cpp
*
* Kernel kernel = platform.createKernel(kernelName); * Kernel kernel = platform.createKernel(kernelName);
* </pre> *
* \endverbatim
* *
* The Kernel class itself does not specify any details of what calculation is to be performed or the API * The Kernel class itself does not specify any details of what calculation is to be performed or the API
* for calling it. Instead, subclasses of KernelImpl will define APIs which are appropriate to particular * for calling it. Instead, subclasses of KernelImpl will define APIs which are appropriate to particular
* calculations. To execute a Kernel, you therefore request its implementation object and cast it to the * calculations. To execute a Kernel, you therefore request its implementation object and cast it to the
* correct type: * correct type:
* *
* <pre> * \verbatim embed:rst:leading-asterisk
* .. code-block:: cpp
*
* dynamic_cast<AddStreamsImpl&>(kernel.getImpl()).execute(stream1, stream2); * dynamic_cast<AddStreamsImpl&>(kernel.getImpl()).execute(stream1, stream2);
* </pre> *
* \endverbatim
*/ */
class OPENMM_EXPORT Kernel { class OPENMM_EXPORT Kernel {
......
...@@ -52,9 +52,12 @@ class KernelFactory; ...@@ -52,9 +52,12 @@ class KernelFactory;
* *
* To get a Platform object, call * To get a Platform object, call
* *
* <pre> * \verbatim embed:rst:leading-asterisk
* .. code-block:: cpp
*
* Platform& platform = Platform::findPlatform(kernelNames); * Platform& platform = Platform::findPlatform(kernelNames);
* </pre> *
* \endverbatim
* *
* passing in the names of all kernels that will be required for the calculation you plan to perform. It * passing in the names of all kernels that will be required for the calculation you plan to perform. It
* will return the fastest available Platform which provides implementations of all the specified kernels. * will return the fastest available Platform which provides implementations of all the specified kernels.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment