To build libsemigroups_pybind11
, it is first required to have a system
install of libsemigroups
. This is explained in full detail in the
libsemigroups
documentation.
It is recommended to install libsemigroups without hpcombi
and with an
external version of fmt
that can be found using the environment variable
$LD_LIBRARY_PATH
. Then, with libsemigroups
installed, the Python
bindings can be pip
installed. This may require the environment variable
$PKG_CONFIG_PATH
to be edited.
To create an environment with fmt
, pip
, and correct environment
variables:
source etc/make-dev-environment.sh [package_manager]
where [package_manager] is your favourite conda-like package manager, such as conda, mamba. The default value is mamba. Note that this DOES NOT yet work with micromamba.
To build libsemigroups (with the above environment active):
git clone https://github.com/libsemigroups/libsemigroups
cd libsemigroups
./autogen.sh && ./configure --disable-hpcombi --with-external-fmt && make -j8
sudo make install
where -j8
instructs the compiler to use 8 threads.
To build the Python bindings (with CCache) inside the libsemigroups_pybind11
directory:
CC="ccache gcc" CXX="ccache g++" pip install .
If you are adding the bindings for a libsemigroups
class that does not yet
exist in libsemigroups_pybind11
, please consider running the script
generate_pybind11.py
found in the etc/
directory of
libsemigroups
.
For example:
etc/generate_pybind11.py libsemigroups::KnuthBendix
to create a skeleton of the code required to bind the KnuthBendix
class.
This script will not generate everything for you; you will be required to edit
the output. If you followed the guidance output by the script, the class will
now be accessible in _libsemigroups_pybind11
. You must now check the
contents of that file adheres to the styles set out in this guide.
The purpose of this section is to aid in the writing of the bindings, with
a focus on the required style. This is not a detailed guide on how to use
pybind11
. For that, please consult the
pybind11 documentation.
A basic guide on how to create bindings for a simple function can be found here.
In the libsemigroups
context, to bind the function bar
from the class
foo
to the module m
:
// This is the left-hand side of the file
m.def("bar",
&libsemigroups::foo::bar,
py::arg("a"),
py::arg("b"),
R"pbdoc(
A brief description of bar that is one line long
A more detailed description of bar, that may explain how each of the
parameters *a* and *b* should be used. This can be more than one line long,
and can contain cross-references to other python objects such as
:py:meth:`baz`, :py:class:`int` or :py:obj:`Konieczny`. It can also contain
literals like ``True`` or ``False``.
:param a: an explanation of what *a* is.
:type a: str
:param b: an explanation of what *b* is.
:type b: int
:raises LibsemigroupsError: Why this raises the error
:return: The value that should be returned
:rtype: int
.. seealso:: Something that might be interesting.
)pbdoc");
Notice that there should be NO BLOCK INDENTATION in the docstring. This is
so that sphinx
builds the docs correctly.
Please adhere to the Sphinx docstring format when writing your documentation. A summary of some useful conventions are shown below.
Element | Markup | See also |
---|---|---|
Parameter | *args* |
reStructuredText markup (Python Developer's Guide) |
Literals | ``True`` ,
``len(s) - 1`` |
|
Cross-references | :role:`target` |
Cross referencing (Sphinx) |
Python cross-references | :py:class:`int` :py:obj:`collections.abc.Iterator[(str, str)]` :py:meth:`knuth_bendix.by_overlap_length <libsemigroups_pybind11.knuth_bendix.by_overlap_length>` |
Cross-referencing Python Objects (Sphinx) |
Clever cross-references | :any:`int` |
Cross-referencing anything (Sphinx) |
Maths | :math:`O(mn)` |
Interpreted text roles (Docutils) |
Code (with doctest) | .. doctest::
>>> 2+2
4 |
Doctest blocks (Sphinx) |
If the class you are binding inherits from another class, this should also be
reflected in Python. This is done when creating the pybind11::class
object
by passing a template parameter for the class that is being inherited from. As
an example, since the KnuthBendix
class inherits from the
CongruenceInterface
class, the code for the bindings of the KnuthBendix
class will start with:
pybind11::class_<KnuthBendix<Rewriter>, CongruenceInterface> kb(m, name.c_str());
^^^^^^^^^^^^^^^^^^^
If you followed the instructions in the generate_pybind11.py
script from the
libsemigroups
project, the class you have added bindings for should now be
available in _libsemigroups_pybind11
(note the leading underscore). How to
make this available in libsemigroups_pybind11
depends on several factors.
If the class you are binding has no templates or helper functions, then you need to add it to the list imports in libsemigroups_pybind11/__init__.py.
If a class has a helper namespace, this should be respected in Python by
creating a module with the same name in the libsemigroups_pybind11
directory. In that module, all of the relevant helper functions should be
imported from _libsemigroups_pybind11
.
If a class has templates parameters then, in _libsemigroups_pybind11
, there
will be one class for each combination of templates. Instead of calling these
directly, a Python function should be created that acts as a constructor, that
then calls the the corresponding _libsemigroups_pybind11
constructor
depending on the keyword arguments specified. This function should then be
imported in libsemigroups_pybind11/__init__.py.
Each class that does not have a helper namespace should have a .rst
file in
docs/source/api
that looks like this:
.. Copyright (c) 20XX, Name
Distributed under the terms of the GPL license version 3.
The full license is in the file LICENSE, distributed with this software.
.. currentmodule:: _libsemigroups_pybind11
Class-Name
==========
A description of what the methods in this class do.
.. doctest::
>>> # This should be a quick example of how to create an instance of
>>> # YourClass, and run a few functions.
>>> from libsemigroups_pybind11 import YourClass
>>> y = YourClass()
>>> y.run()
True
>>> y.count()
42
Contents
--------
.. autosummary::
:nosignatures:
YourClass.foo
YourClass.bar
YourClass.baz
YourClass.qux
YourClass.quux
YourClass.corge
Full API
--------
.. autoclass:: YourClass
:members:
For an example, see docs/source/knuth-bendix/knuth-bendix.rst
Each class that has a helper namespace needs more than a single .rst
file.
It also needs a file that documents the helper functions, and an index.rst
file that gives an overview of what the class and its helpers should be used
for. These files will go in their own folder in docs/source
:
docs/ └── source/ └── class-name/ ├── class-helpers.rst ├── class.rst └── index.rst
A sample class-helpers.rst
may look like this:
.. Copyright (c) 20XX, YOUR NAME
Distributed under the terms of the GPL license version 3.
The full license is in the file LICENSE, distributed with this software.
.. currentmodule:: _libsemigroups_pybind11
Class-name helpers
====================
This page contains the documentation for various helper functions for
manipulating ``class`` objects. All such functions are contained in the
submodule ``libsemigroups_pybind11.class``.
Contents
--------
.. autosummary::
:nosignatures:
foo
bar
baz
Full API
--------
.. automodule:: libsemigroups_pybind11.class
:members:
:imported-members:
A sample index.rst
file may look like this:
.. Copyright (c) 20XX, YOUR NAME
Distributed under the terms of the GPL license version 3.
The full license is in the file LICENSE, distributed with this software.
Class
=====
This page describes the functionality for the class in
``libsemigroups_pybind11``.
.. toctree::
:maxdepth: 1
class
class-helpers
When make doc
is run, the content of these .rst
files is converted to
html. Before this is done, some processing can be done on the docs. In
docs/source/conf.py, there are three dictionaries that can be used to make
replacements.
The first dictionary is called type_replacements
that serves as a map from
bad type names -> good type names that should be replaced in the signature
of every function. This can be used to translate from confusing C++ type names
to nice Python type names.
The second dictionary is called class_specific_replacements
that serves as a
map from "class name" -> ("good type", "bad type"). This will be used to
replace bad type names with good type names in all signatures of a particular
class.
The third dictionary is called docstring_replacements
and serves as a map
from "bad" strings to "good" strings in the doc-strings of each function. This
will be used to change things in the doc-string that can't easily be done by
editing the source code of that doc-string. One example of this is when pybind11
includes the signature of a function in its docstring so that some Sphinx parser
knows how to correctly render that function. If you want to import that function
with a different name (e.g. ... import pbr_one as one
), the
pybind11-inserted signature is wrong, and should be removed. You can use this
dictionary to do that.
After the doc has been converted to html, it may still be desirable to make
text replacements. This can be done by adding to the replacements
dictionary
in etc/replace-strings-in-doc.py.
Inside docs/source/index.rst, you will find the table of contents tree
(toctree) for this project. Within that, you will find the names of files
(without the .rst
extension) of different classifications of structures that
libsemigroups_pybind11
implements, such as congruences, digraphs, semigroups
and words. Within each of these files, there is another toctree containing
the paths to the docs of various classes.
To the relevant toctree, add the path to either the index.rst
file for the
class (if it has helper functions), or the class-name.rst
(if it does not
have helper functions). For example, if ClassA
is a class relating to
digraphs that doesn't have helper functions, api/class-a
should be added to
the toctree in``docs/source/digraph.rst``. If ClassB
is a class relating to
congruences that does have helper functions, class-b/index
should be added
to the toctree in docs/source/congruences.rst
.
When you think you have finished writing the bindings, please add a test file to
the tests/
directory that tests each of the functions that have just had
bindings added, including inherited functions.
Then run
make check
and ensure everything passes.
As a quick reference, the files that you may need to create, edit or refer to whilst contributing are:
libsemigroups_pybind11/ ├── docs/ │ └── source/ │ ├── class-name/ │ │ ├── index.rst │ │ ├── class-helper.rst │ │ └── class.rst │ ├── conf.py │ └── index.rst ├── etc/ │ └── replace-string-in-doc.py ├── libsemigroups_pybind11/ │ ├── __init__.py │ └── class_name.py ├── src/ │ └── class-name.cpp ├── tests/ │ └── test_class_name.py └── CONTRIBUTING.rst (this file!)