Documenting the interface (Programmer’s reference)

The DOLFIN Programmer’s Reference is generated for the DOLFIN C++ library and Python module from the source code using the documentation tool Sphinx. This page describes how to generate the DOLFIN documentation locally and how to extend the contents of the Programmer’s Reference.

How to locally build the DOLFIN documentation

The DOLFIN documentation can be generated and built from the DOLFIN source directly as follows:

  • Make sure that Sphinx is installed.

  • Build DOLFIN (for instructions, see installation_from_source).

  • Build the documentation by running:

    make doc

    in the DOLFIN build directory.

For make doc to successfully run, the DOLFIN Python module must be installed.

How to improve and extend the DOLFIN Programmer’s reference

The documentation contents are extracted from specially formatted comments (docstring comments) in the source code, converted to reStructuredText, and formatted using Sphinx. The syntax used for these specially formatted comments is described below.

To document a feature,

  1. Add appropriate docstring comments to source files (see Syntax for docstring comments).
  2. If you made changes to C++ header files or docstrings in dolfin_dir/dolfin/swig/*.i you should update the dolfin_dir/dolfin/swig/codeexamples.py file with an example snippet if applicable and run the script dolfin_dir/dolfin/swig/generate.py to update the dolfin_dir/dolfin/swig/docstrings.i file.
  3. Build the documentation as described in How to locally build the DOLFIN documentation to check the result.

Syntax for docstring comments

As Sphinx does not allow sections in the markup for class/function documentation, we use italics (*italics*) and definition lists to group information. This is to keep the markup as simple as possible since the reST source for the Python documentation of classes and functions will be used ‘as is’ in the docstrings of the DOLFIN module.

Most information can be put in the three sections:

  • Arguments, which are formatted using definition lists following this structure:

    *Arguments*
        <name> (<type>)
            <description>
        <name2> (<type>)
            <description>
    

    For example:

    *Arguments*
        dim (int)
            some dimension.
        d (double)
            some value.
    
  • Returns, which is formatted in a similar fashion:

    *Returns*
        <return type>
            <description>
    

    For example:

    *Returns*
        int
            Some random integer.
    
  • Example, a very small code snippet that shows how the class/function works. It does not necessarily have to be a stand-alone program.

An example of how to document a feature

To make matters more concrete let’s consider the case of writing documentation for the member function closest_cell of the DOLFIN Mesh class. The Python interface to this class is generated by Swig and it is not extended in the Python layer. Writing documentation for other classes and functions in DOLFIN which are not extended or added in the Python layer follow a similar procedure.

The Mesh::closest_cell function is defined in the file dolfin_dir/dolfin/mesh/Mesh.h, and the comment lines and function definition look as follows:

/// Computes the index of the cell in the mesh which is closest to the
/// point query.
///
/// *Arguments*
///     point (_Point_)
///         A _Point_ object.
///
/// *Returns*
///     uint
///         The index of the cell in the mesh which is closest to point.
///
/// *Example*
///     .. code-block:: c++
///
///         UnitSquare mesh(1, 1);
///         Point point(0.0, 2.0);
///         info("%d", mesh.closest_cell(point));
///
///     output::
///
///         1
dolfin::uint closest_cell(const Point& point) const;

Note that the documentation of a function or class is placed above the definition in the source code. The structure and content follow the guidelines in the previous section.

The Point object is a class like Mesh and it is defined in the FEniCS interface. To insert a link to the documentation of this class use leading and trailing underscore i.e., _Point_. When parsing the comment lines this string will be substituted with either :cpp:class:`Point` or :py:class:`Point` depending on whether documentation for the C++ or Python interface is being generated. The return type, in this case dolfin::uint, will automatically be mapped to the correct Python type when generating the documentation for the Python interface. Note that if you are writing documentation for one of the functions/classes which are added to the Python layer manually you have to add manually the correct links and types.

The example code uses C++ syntax because it is located in the C++ header file. Translating this code to a correct Python equivalent is rather difficult. It is therefore necessary to add example code using the Python syntax manually. This code should be put in the dolfin_dir/dolfin/swig/codeexamples.py which contains a simple dictionary of example code. The dictionary containing only the example code for the example above should look as follows:

codesnippets = {
"Mesh":{
"dolfin::uint closest_cell(const Point& point) const":
"""
.. code-block:: python

    >>> mesh = dolfin.UnitSquare(1, 1)
    >>> point = dolfin.Point(0.0, 2.0)
    >>> mesh.closest_cell(point)
    1
"""}
}

The first dictionary contains dictionaries for all classes with code examples for each function. Note that the full C++ function signature has been used to identify the function to which the code example belongs.

After adding the documentation to the Mesh.h file and Python code example to the codeexamples.py file, you have to run the script dolfin/dolfin/swig/generate.py to generate the dolfin/dolfin/swig/docstrings.i file and then build DOLFIN to update the docstrings in the dolfin Python module.

Why is the documentation procedure so elaborate?

The procedure for writing documentation might seem cumbersome so let’s have a look at the design considerations which have led to this ostensible case of overengineering.

The Python interface is (partially) generated automatically using Swig from the C++ implementation of DOLFIN. Some classes are extended when building (see the dolfin/dolfin/swig/*post.i files) while others are added or extended manually in the Python layer defined in dolfin/site-packages/dolfin. While this approach saves a lot of work when implementing the Python interface it puts some constraints on the way the documentation can be handled. In addition we have the following design goals for writing and maintaining the documentation:

Avoid duplicate text
In many cases the documentation of a feature will be virtually identical for the C++ and Python interfaces, and since the Python interface is generated from the C++ code, the documentation should be in the C++ source code. To avoid that the documentation on these pages and the comments in the source code (and the implementation itself) diverge, the documentation should be automatically generated from the C++ source code. Therefore the comments should be written using Sphinx markup.
Help in the Python interpreter
The documentation of a class/function when running help(dolfin.foo) in the Python interpreter should be identical to what can be found online. In practice this means that we have to generate the dolfin/dolfin/swig/docstrings.i file using the comments extracted from the C++ source before building the Python interface with Swig.
Simple markup
Since the documentation is written directly in the C++ source code, we want markup to be simple such that we have ‘code with comments’ rather than ‘comments with code’. Another reason for preferring simple markup is that it is the raw docstring which will be available from the Python interpreter.