From a6d2eaaabf9246fa9e9237cf98ac6a991ba329b5 Mon Sep 17 00:00:00 2001 From: "James D. Mitchell" Date: Fri, 12 Apr 2024 11:39:56 +0100 Subject: [PATCH] Add v3 support for action.*pp --- docs/source/action.rst | 115 +++++++ docs/source/conf.py | 3 + docs/source/index.rst | 3 +- etc/replace-strings-in-doc.py | 2 + libsemigroups_pybind11/__init__.py | 7 +- libsemigroups_pybind11/action.py | 95 ++++++ libsemigroups_pybind11/adapters.py | 65 ++++ src/action.cpp | 424 +++++++++++++++++++++++++ src/adapters.cpp | 68 ++++ src/bmat8.cpp | 477 +++++++++++++++++++++++++++++ src/knuth-bendix.cpp | 2 +- src/main.cpp | 4 + src/main.hpp | 2 + tests/test_action.py | 95 ++++++ tests/test_adapters.py | 39 +++ 15 files changed, 1397 insertions(+), 4 deletions(-) create mode 100644 docs/source/action.rst create mode 100644 libsemigroups_pybind11/action.py create mode 100644 libsemigroups_pybind11/adapters.py create mode 100644 src/action.cpp create mode 100644 src/adapters.cpp create mode 100644 src/bmat8.cpp create mode 100644 tests/test_action.py create mode 100644 tests/test_adapters.py diff --git a/docs/source/action.rst b/docs/source/action.rst new file mode 100644 index 00000000..c6aa5152 --- /dev/null +++ b/docs/source/action.rst @@ -0,0 +1,115 @@ +.. Copyright (c) 2024, James D. Mitchell + + 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 + +Actions +======= + +This page contains details of the ``RowActionBMat8`` class in +``libsemigroups_pybind11`` for finding actions of semigroups, or groups, on sets. The +notion of an "action" in the context of ``libsemigroups_pybind11`` is analogous to the +notion of an orbit of a group. + +The function :any:`run ` finds points that can be obtained by +acting on the seeds of the action by the generators of the action until no further +points can be found, or :any:`stopped ` returns ``True``. This +is achieved by performing a breadth first search. + +In this documentation we refer to: + +* ``Element`` -- the type of the elements of the underlying semigroup +* ``Point`` -- the type of the objects on which the semigroup elements act +* ``Func`` -- the function that computes the action of ``Element`` on ``Point`` +* ``Side`` -- the side of the action (if it is a left or a right action). + +The following helper functions are also available: + +* :any:`LeftAction` +* :any:`RightAction` + +.. doctest:: + + >>> from libsemigroups_pybind11 import RightAction, BMat8 + >>> o = RightAction(Element=BMat8, Point=BMat8) + >>> o.add_seed( + ... BMat8( + ... [[1, 1, 1, 0], + ... [1, 1, 0, 0], + ... [0, 1, 0, 1], + ... [0, 1, 0, 0]]).row_space_basis() + ... ).add_generator( + ... BMat8([[1, 0, 0, 0], + ... [0, 1, 0, 0], + ... [0, 0, 1, 0], + ... [0, 0, 0, 1]]) + ... ).add_generator( + ... BMat8([[0, 1, 0, 0], + ... [1, 0, 0, 0], + ... [0, 0, 1, 0], + ... [0, 0, 0, 1]]) + ... ).add_generator( + ... BMat8([[0, 1, 0, 0], + ... [0, 0, 1, 0], + ... [0, 0, 0, 1], + ... [1, 0, 0, 0]]) + ... ).add_generator( + ... BMat8([[1, 0, 0, 0], + ... [0, 1, 0, 0], + ... [0, 0, 1, 0], + ... [1, 0, 0, 1]]) + ... ).add_generator( + ... BMat8([[1, 0, 0, 0], + ... [0, 1, 0, 0], + ... [0, 0, 1, 0], + ... [0, 0, 0, 0]]) + ... ) + + >>> o.size() + 553 + +Contents +-------- + +.. autosummary:: + :nosignatures: + + ~RowActionBMat8 + RowActionBMat8.__getitem__ + RowActionBMat8.add_generator + RowActionBMat8.add_seed + RowActionBMat8.cache_scc_multipliers + RowActionBMat8.current_size + RowActionBMat8.empty + RowActionBMat8.init + RowActionBMat8.iterator + RowActionBMat8.multiplier_from_scc_root + RowActionBMat8.multiplier_to_scc_root + RowActionBMat8.position + RowActionBMat8.reserve + RowActionBMat8.root_of_scc + RowActionBMat8.scc + RowActionBMat8.size + RowActionBMat8.word_graph + +Full API +-------- + +.. currentmodule:: libsemigroups_pybind11 + +.. autofunction:: RightAction + +.. autofunction:: LeftAction + +.. currentmodule:: _libsemigroups_pybind11 + +.. autoclass:: RowActionBMat8 + :members: + :show-inheritance: + :class-doc-from: class + + .. autofunction:: libsemigroups_pybind11.action.Action + diff --git a/docs/source/conf.py b/docs/source/conf.py index 72b8dd04..44aaddfb 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -68,6 +68,9 @@ # signature if "good type" is a valid (potentially user defined) python type type_replacements = { r"libsemigroups::Presentation, std::allocator > >": r"Presentation", + r"libsemigroups::BMat8": r"BMat8", + r"libsemigroups::WordGraph": r"WordGraph", + r"libsemigroups::Gabow": r"Gabow", } # This dictionary should be of the form class_name -> (pattern, repl), where diff --git a/docs/source/index.rst b/docs/source/index.rst index 91c19d04..dfe4702f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -49,7 +49,8 @@ See the installation instructions: .. toctree:: :caption: API REFERENCE :hidden: - + + action congruences digraph elements diff --git a/etc/replace-strings-in-doc.py b/etc/replace-strings-in-doc.py index e7fc47cb..fd488a3d 100755 --- a/etc/replace-strings-in-doc.py +++ b/etc/replace-strings-in-doc.py @@ -41,6 +41,8 @@ def dive(path): "KnuthBendixRewriteTrie": "KnuthBendix", r"_libsemigroups_pybind11.": "", "libsemigroups::Presentation, std::allocator > >": "Presentation", + "RowActionBMat8": "Action", + "libsemigroups::BMat8": "BMat8", } files = all_html_files(html_path) diff --git a/libsemigroups_pybind11/__init__.py b/libsemigroups_pybind11/__init__.py index 47b15aca..2469afc4 100644 --- a/libsemigroups_pybind11/__init__.py +++ b/libsemigroups_pybind11/__init__.py @@ -51,21 +51,24 @@ to_word, LibsemigroupsError, is_obviously_infinite, + BMat8, + side, ) except ModuleNotFoundError as e: raise ModuleNotFoundError( ( f'{e.msg}, did you forget to run "pip install ." in the libsemigroups_pybind11 ' - f"director? {DISCLAIMER}" + f"directory? {DISCLAIMER}" ) ) from e +from .action import Action, RightAction, LeftAction +from .adapters import ImageRightAction, ImageLeftAction from .knuth_bendix import KnuthBendix from .presentation import Presentation from .transf import PPerm, Transf - # from .froidure_pin import FroidurePin # from .konieczny import Konieczny # from .matrix import Matrix, MatrixKind, make_identity diff --git a/libsemigroups_pybind11/action.py b/libsemigroups_pybind11/action.py new file mode 100644 index 00000000..b92c37d6 --- /dev/null +++ b/libsemigroups_pybind11/action.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2024, J. D. Mitchell +# +# Distributed under the terms of the GPL license version 3. +# +# The full license is in the file LICENSE, distributed with this software. + +# pylint:disable=no-name-in-module, unused-import + +""" +This package provides the user-facing python part of libsemigroups_pybind11 for +the Action class from libsemigroups. +""" + +from _libsemigroups_pybind11 import RowActionBMat8 as _RowActionBMat8 +from _libsemigroups_pybind11 import ColActionBMat8 as _ColActionBMat8 +from _libsemigroups_pybind11 import BMat8, side +from .adapters import ImageRightAction, ImageLeftAction + + +def Action(**kwargs): # pylint: disable=invalid-name + """ + Construct an :any:`Action` instance. + + :Keyword Arguments: + * *Element* -- the type of the elements in the action + * *Point* -- the type of the points acted on + * *Func* -- the function defining the action + * *Side* -- the side (or handedness) of the action + """ + if len(kwargs) != 4: + raise TypeError(f"expected 4 keyword arguments, found {len(kwargs)}") + for kwarg in ("Element", "Point", "Func", "Side"): + if kwarg not in kwargs: + raise ValueError( + f'unexpected keyword argument "{kwarg}", expected' + + '"Element", "Point", "Func", and "Side"' + ) + + if ( + kwargs["Element"] == BMat8 + and kwargs["Point"] == BMat8 + and kwargs["Func"] == ImageRightAction + and kwargs["Side"] == side.right + ): + return _RowActionBMat8() + if ( + kwargs["Element"] == BMat8 + and kwargs["Point"] == BMat8 + and kwargs["Func"] == ImageLeftAction + and kwargs["Side"] == side.left + ): + return _ColActionBMat8() + raise ValueError("unexpected keyword argument combination") + + +def RightAction( + Func=ImageRightAction, **kwargs +): # pylint: disable=invalid-name + """ + Construct a right :any:`Action` instance. + + :Keyword Arguments: + * *Element* -- the type of the elements in the action + * *Point* -- the type of the points acted on + * *Func* -- the function defining the action (defaults to :any:`ImageRightAction`) + + """ + # TODO probably this will generate unhelpful error messages + return Action( + Func=Func, + Element=kwargs["Element"], + Point=kwargs["Point"], + Side=side.right, + ) + + +def LeftAction(Func=ImageLeftAction, **kwargs): # pylint: disable=invalid-name + """ + Construct a left :any:`Action` instance. + + :Keyword Arguments: + * *Element* -- the type of the elements in the action + * *Point* -- the type of the points acted on + * *Func* -- the function defining the action (defaults to :any:`ImageLeftAction`) + + """ + # TODO probably this will generate unhelpful error messages + return Action( + Func=Func, + Element=kwargs["Element"], + Point=kwargs["Point"], + Side=side.left, + ) diff --git a/libsemigroups_pybind11/adapters.py b/libsemigroups_pybind11/adapters.py new file mode 100644 index 00000000..963ae238 --- /dev/null +++ b/libsemigroups_pybind11/adapters.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2024, J. D. Mitchell +# +# Distributed under the terms of the GPL license version 3. +# +# The full license is in the file LICENSE, distributed with this software. + +# pylint:disable=no-name-in-module, unused-import + +""" +This package provides the user-facing python part of libsemigroups_pybind11 for +various adapters from libsemigroups. +""" +from _libsemigroups_pybind11 import ( + ImageRightActionBMat8BMat8 as _ImageRightActionBMat8BMat8, + ImageLeftActionBMat8BMat8 as _ImageLeftActionBMat8BMat8, +) +from _libsemigroups_pybind11 import BMat8 + + +def ImageRightAction(**kwargs): # pylint: disable=invalid-name + """ + Construct a ImageRightAction instance. + + :Keyword Arguments: + * *Element* -- the type of the elements in the action + * *Point* -- the type of the points acted on + """ + if len(kwargs) != 2: + raise TypeError(f"expected 2 keyword arguments, found {len(kwargs)}") + for kwarg in ("Element", "Point"): + if kwarg not in kwargs: + raise ValueError( + f'unexpected keyword argument "{kwarg}", expected' + + '"Element" and "Point"' + ) + + if kwargs["Element"] == BMat8 and kwargs["Point"] == BMat8: + return _ImageRightActionBMat8BMat8() + + raise ValueError("unexpected keyword argument combination") + + +def ImageLeftAction(**kwargs): # pylint: disable=invalid-name + """ + Construct a ImageLeftAction instance. + + :Keyword Arguments: + * *Element* -- the type of the elements in the action + * *Point* -- the type of the points acted on + """ + if len(kwargs) != 2: + raise TypeError(f"expected 2 keyword arguments, found {len(kwargs)}") + for kwarg in ("Element", "Point"): + if kwarg not in kwargs: + raise ValueError( + f'unexpected keyword argument "{kwarg}", expected' + + '"Element" and "Point"' + ) + + if kwargs["Element"] == BMat8 and kwargs["Point"] == BMat8: + return _ImageLeftActionBMat8BMat8() + + raise ValueError("unexpected keyword argument combination") diff --git a/src/action.cpp b/src/action.cpp new file mode 100644 index 00000000..9bca8063 --- /dev/null +++ b/src/action.cpp @@ -0,0 +1,424 @@ + +// +// libsemigroups_pybind11 +// Copyright (C) 2024 James D. Mitchell +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +// C++ stl headers.... +#include // for basic_string, string + +// libsemigroups headers +#include +#include +#include + +#include // for format, print + +// pybind11.... +#include +#include + +// libsemigroups_pybind11.... +#include "main.hpp" // for init_TODO + +namespace py = pybind11; + +namespace libsemigroups { + + namespace { + template + void bind_action(py::module& m, std::string const& name) { + using Action_ = Action; + using const_reference_point_type = + typename Action_::const_reference_point_type; + using index_type = typename Action_::index_type; + py::class_ thing(m, name.c_str()); + // The Action_ class doc is in action.rst + + thing.def("__repr__", &detail::to_string); + thing.def(py::init<>(), R"pbdoc( +Default constructor. A constructor that creates an uninitialized +action representing a left or right action. + +:complexity: + Constant. +)pbdoc"); + thing.def(py::init(), R"pbdoc( +Default copy constructor. +)pbdoc"); + thing.def("init", + &Action_::init, + R"pbdoc( +Initialize an existing object. This function puts an action +object back into the same state as if it had been newly default +constructed. + +:returns: ``self``. +:rtype: Action +)pbdoc"); + thing.def("reserve", + &Action_::reserve, + py::arg("val"), + R"pbdoc( +Increase the capacity to a value that is greater or equal to ``val``. + +:param val: new capacity of an action instance. +:type val: int + +:raises ValueError: if ``val`` is too large. + +:complexity: At most linear in the :any:`size()` of the ``Action``. + +:returns: ``self``. +:rtype: Action +)pbdoc"); + thing.def("add_seed", + &Action_::add_seed, + py::arg("seed"), + R"pbdoc( +Add a seed to the action. + +A *seed* is just a starting point for the action, it will belong to the action, as will every point that can be obtained from the seed by acting with the generators of the action. + +:param seed: the seed to add. +:type seed: Point + +:complexity: Constant. + +:returns: ``self`` +:rtype: Action +)pbdoc"); + thing.def("add_generator", + &Action_::add_generator, + py::arg("gen"), + R"pbdoc( +Add a generator to the action. + +An ``Action`` instance represents the action of the semigroup generated by the elements added via this member function. + +:param gen: the generator to add. +:type gen: Element + +:complexity: Constant. + +:returns: ``self``. +:rtype: Action +)pbdoc"); + thing.def("number_of_generators", + &Action_::number_of_generators, + R"pbdoc( +Returns the number of generators. + +:complexity: + Constant. + +:returns: + The number of generators. + +:rtype: + int +)pbdoc"); + thing.def("generators", + &Action_::generators, + R"pbdoc( +Returns the list of generators. + +:complexity: + Constant. + +:returns: + The generators. + +:rtype: + list[Element] +)pbdoc"); + thing.def("position", + &Action_::position, + py::arg("pt"), + R"pbdoc( +Returns the position of a point in the so far discovered points. + +:param pt: the point whose position is sought. +:type pt: Point + +:complexity: Constant. + +:returns: The index of ``pt`` in ``self`` or :any:`UNDEFINED`. +:rtype: int +)pbdoc"); + thing.def("empty", + &Action_::empty, + R"pbdoc( +Checks if the Action contains any points. + +:complexity: + Constant. + +:returns: + ``True`` if the action contains no points (including seeds), and + ``False`` if not. + +:rtype: + bool +)pbdoc"); + thing.def("__getitem__", + &Action_::at, + py::is_operator(), + py::arg("pos"), + R"pbdoc( +Returns the point in a given position. + +:param pos: + the index of an point. + +:type pos: + int + +:raises IndexError: + if ``pos >= current_size())``. + +:complexity: + Constant. + +:returns: + The *Point* in position ``pos`` of the currently enumerated points. + +:rtype: + *Point* +)pbdoc"); + thing.def("size", + &Action_::size, + R"pbdoc( +Returns the size of the fully enumerated action. + +:complexity: + The time complexity is :math:`O(mn)` where :math:`m` is the total + number of points in the orbit and :math:`n` is the number of + generators. + +:returns: + The size of the action, a value of type ``int``. + +:rtype: + int +)pbdoc"); + thing.def("current_size", + &Action_::current_size, + R"pbdoc( +Returns the number of points found so far. + +:complexity: + Constant. + +:returns: + The current size. + +:rtype: + int +)pbdoc"); + thing.def( + "iterator", + [](Action_ const& self) { + return py::make_iterator(self.cbegin(), self.cend()); + }, + R"pbdoc( +Returns an iterator containing the so far enumerated points in the orbit (if any). +No enumeration is triggered by calling this function. + +:complexity: + Constant. + +:returns: + An ``Iterator``. + +:rtype: + Iterator +)pbdoc"); + thing.def( + "cache_scc_multipliers", + [](Action_ const& self) { return self.cache_scc_multipliers(); }, + R"pbdoc( +Returns whether or not we are caching scc multipliers. If the returned +value of this function is ``True`` , then the values returned by +:any:`multiplier_from_scc_root()` and :any:`multiplier_to_scc_root()` +are cached, and not recomputed every time one of these functions is +called. + +:complexity: + Constant. + +:returns: + ``True`` if caching is enabled, and ``False`` if not. + +:rtype: + bool +)pbdoc"); + thing.def( + "cache_scc_multipliers", + [](Action_& self, bool val) { + return self.cache_scc_multipliers(val); + }, + py::arg("val"), + R"pbdoc( +Set whether or not to cache scc multipliers. + +If the parameter **val** is ``True`` , then the values returned by :any:`multiplier_from_scc_root()` and :any:`multiplier_to_scc_root()` are cached, and not recomputed every time one of these functions is called. + +:param val: the value. +:type val: bool + +:complexity: Constant. + +:returns: ``self``. + +:rtype: Action +)pbdoc"); + thing.def("multiplier_from_scc_root", + &Action_::multiplier_from_scc_root, + py::arg("pos"), + R"pbdoc( +Returns a multiplier from a scc root to a given index. + +Returns an element ``x`` of the semigroup generated by the generators in the action such that if ``r`` is the root of the strongly connected component containing ``at(pos)``, then calling ``Func(res, r, x)`` the point ``res`` equals ``at(pos)``. + +:param pos: a position in the action. +:type pos: int + +:complexity: At most :math:`O(mn)` where :math:`m` is the complexity of multiplying elements of type *Element* and :math:`n` is the size of the fully enumerated orbit. + +:raises LibsemigroupsError: if there are no generators yet added or the index ``pos`` is out of range. + +:returns: The multiplier. +:rtype: *Element* +)pbdoc"); + thing.def("multiplier_to_scc_root", + &Action_::multiplier_to_scc_root, + py::arg("pos"), + R"pbdoc( +Returns a multiplier from a given index to a scc root. + +Returns an element ``x`` of the semigroup generated by the generators in the action such that after ``Func(res, at(pos), x)`` the point ``res`` is the root of the strongly connected component containing ``at(pos)``. + +:param pos: a position in the action. +:type pos: int + +:complexity: At most :math:`O(mn)` where :math:`m` is the complexity of multiplying elements of type *Element* and :math:`n` is the size of the fully enumerated orbit. + +:raises LibsemigroupsError: if there are no generators yet added or the index ``pos`` is out of range. + +:returns: The multiplier. +:rtype: *Element* +)pbdoc"); + thing.def( + "root_of_scc", + [](Action_& self, const_reference_point_type x) { + return self.root_of_scc(x); + }, + py::arg("x"), + R"pbdoc( +Returns the root point of a strongly connected component containing an *Point*. + +:param x: the point whose root we want to find. +:type x: Point + +:complexity: At most :math:`O(mn)` where :math:`m` is the complexity of multiplying elements of type *Element* and :math:`n` is the size of the fully enumerated orbit. + +:raises LibsemigroupsError: if the point ``x`` does not belong to the action. + +:returns: The root point. +:rtype: *Point* +)pbdoc"); + thing.def( + "root_of_scc", + [](Action_& self, index_type pos) { return self.root_of_scc(pos); }, + py::arg("pos"), + R"pbdoc( +Returns the root point of a strongly connected component. + +:param pos: the index of the point in the action whose root we want to find. +:type pos: int + +:complexity: At most :math:`O(mn)` where :math:`m` is the complexity of multiplying elements of type *Element* and :math:`n` is the size of the fully enumerated orbit. + +:raises LibsemigroupsError: if the index ``pos`` is out of range. + +:returns: The root point. +:rtype: *Point* +)pbdoc"); + thing.def("word_graph", + &Action_::word_graph, + R"pbdoc( +Returns the word graph of the completely enumerated action. + +:complexity: + At most :math:`O(mn)` where :math:`m` is the complexity of + multiplying elements of type *Element* and :math:`n` is the + size of the fully enumerated orbit. + +:returns: + The word graph of the action. +:rtype: + WordGraph +)pbdoc"); + thing.def("scc", + &Action_::scc, + R"pbdoc( +Returns a Gabow object for strongly connected components. + +:complexity: + At most :math:`O(mn)` where :math:`m` is the complexity of + multiplying elements of type *Element* and :math:`n` is the + size of the fully enumerated orbit. + +:returns: + A :any:`Gabow` object. +:rtype: + Gabow +)pbdoc"); + } // bind_action + } // namespace + void init_action(py::module& m) { + py::enum_(m, "side", R"pbdoc( +This value indicates that the action in an :any:`Action` instance should +be a left action. +)pbdoc") + .value("left", side::left, R"pbdoc( +This value indicates that the action in an :any:`Action` instance should +be a left action. +)pbdoc") + .value("right", side::right, R"pbdoc( +This value indicates that the action in an :any:`Action` instance should +be a right action. +)pbdoc"); + + // One call to bind is required per list of types + bind_action, + ActionTraits, + side::right>(m, "RowActionBMat8"); + bind_action, + ActionTraits, + side::left>(m, "ColActionBMat8"); + } + +} // namespace libsemigroups diff --git a/src/adapters.cpp b/src/adapters.cpp new file mode 100644 index 00000000..303e5716 --- /dev/null +++ b/src/adapters.cpp @@ -0,0 +1,68 @@ +// +// libsemigroups_pybind11 +// Copyright (C) 2024 James D. Mitchell +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +// libsemigroups headers +#include +#include + +// pybind11.... +#include + +// libsemigroups_pybind11.... +#include "main.hpp" // for init_TODO + +namespace py = pybind11; + +namespace libsemigroups { + + namespace { + template + void bind_imagerightaction(py::module& m, std::string const& name) { + using ImageRightAction_ = ImageRightAction; + + py::class_(m, name.c_str()) + .def(py::init<>()) + .def("__call__", + [](ImageRightAction_ const& self, + Point& res, + Point const& pt, + Element const& x) { self(res, pt, x); }); + } // bind_imagerightaction + + template + void bind_imageleftaction(py::module& m, std::string const& name) { + using ImageLeftAction_ = ImageLeftAction; + + py::class_(m, name.c_str()) + .def(py::init<>()) + .def("__call__", + [](ImageLeftAction_ const& self, + Point& res, + Point const& pt, + Element const& x) { self(res, pt, x); }); + } // bind_imageleftaction + + } // namespace + // + void init_imagerightaction(py::module& m) { + // One call to bind is required per list of types + bind_imagerightaction(m, "ImageRightActionBMat8BMat8"); + bind_imageleftaction(m, "ImageLeftActionBMat8BMat8"); + } + +} // namespace libsemigroups diff --git a/src/bmat8.cpp b/src/bmat8.cpp new file mode 100644 index 00000000..1a3cd579 --- /dev/null +++ b/src/bmat8.cpp @@ -0,0 +1,477 @@ +// +// libsemigroups_pybind11 +// Copyright (C) 2024 James D. Mitchell +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +// C std headers.... +// TODO complete or delete + +// C++ stl headers.... +// TODO complete or delete + +// libsemigroups headers +#include + +// pybind11.... +#include +#include +#include + +// libsemigroups_pybind11.... +#include "main.hpp" // for init_bmat8 + +namespace py = pybind11; + +namespace libsemigroups { + + void init_bmat8(py::module& m) { + py::class_ thing(m, + "BMat8", + R"pbdoc( +Defined in ``bmat8.hpp``.Class for fast boolean matrices of dimension up to 8 x 8The member functions for these small matrices over the boolean semiring are more optimised than the generic member functions for boolean matrices. Note that all :any:`BMat8` are represented internally as an 8 x 8 matrix; any entries not defined by the user are taken to be 0. This does not affect the results of any calculations. :any:`BMat8` is a trivial class.)pbdoc"); + thing.def("__repr__", &detail::to_string); + thing.def(py::init<>(), R"pbdoc( +Default constructor.There is no guarantee about the contents of the +matrix constructed. + +:parameters: + (None) + +:exceptions: + This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: + Constant. +)pbdoc"); + thing.def(py::init(), R"pbdoc( + +:param mat: the integer representation of the matrix being constructed. +:type mat: int +Construct from uint64_t.This constructor initializes a :any:`BMat8` to have rows equal to the 8 chunks, of 8 bits each, of the binary representation of ``mat``. + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: Constant.)pbdoc"); + thing.def(py::init> const&>(), R"pbdoc( + +:param mat: the vector of vectors representation of the matrix being constructed. +:type mat: std::vector > +A constructor.This constructor initializes a matrix where the rows of the matrix are the vectors in ``mat``. + +:raises LibsemigroupsError: if ``mat`` has 0 rows. + +:raises LibsemigroupsError: if ``mat`` has more than 8 rows. + +:raises LibsemigroupsError: if the rows of ``mat`` are not all of the same length. + +:complexity: Constant.)pbdoc"); + thing.def(py::init(), R"pbdoc( +Default copy constructor. + +:exceptions: + This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: + Constant. +)pbdoc"); + thing.def(py::self == py::self, + py::arg("that"), + R"pbdoc( + +:param that: the BMat8 for comparison. +:type that: BMat8 +Returns ``True`` if ``self`` equals ``that``.This member function checks the mathematical equality of two :any:`BMat8` objects. + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: Constant.)pbdoc"); + thing.def(py::self != py::self, + py::arg("that"), + R"pbdoc( + +:param that: the BMat8 for comparison. +:type that: BMat8 +Returns ``True`` if ``self`` does not equal ``that``This member function checks the mathematical inequality of two :any:`BMat8` objects. + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: Constant.)pbdoc"); + thing.def(py::self < py::self, + py::arg("that"), + R"pbdoc( + +:param that: the BMat8 for comparison. +:type that: BMat8 +Returns ``True`` if ``self`` is less than ``that``.This member function checks whether a :any:`BMat8` objects is less than another. We order by the results of :any:`to_int()` for each matrix. + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: Constant.)pbdoc"); + thing.def(py::self > py::self, + py::arg("that"), + R"pbdoc( + +:param that: the BMat8 for comparison. +:type that: BMat8 +Returns ``True`` if ``self`` is greater than ``that``.This member function checks whether a :any:`BMat8` objects is greater than another. We order by the results of :any:`to_int()` for each matrix. + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: Constant.)pbdoc"); + thing.def("get", + &BMat8::get, + py::arg("i"), + py::arg("j"), + R"pbdoc( + +:param i: the row of the entry sought. +:type i: int + +:param j: the column of the entry sought. +:type j: int +Returns the entry in the ( ``i`` , ``j`` )th position.This member function returns the entry in the ( ``i`` , ``j`` )th position. + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: Constant. + + +:returns: A ``bool``. + +:rtype: bool +)pbdoc"); + thing.def("set", + &BMat8::set, + py::arg("i"), + py::arg("j"), + py::arg("val"), + R"pbdoc( + +:param i: the row +:type i: int + +:param j: the column +:type j: int + +:param val: the value to set in position (i, j)th +:type val: bool +Sets the ( ``i`` , ``j`` )th position to ``val``.This member function sets the ( ``i`` , ``j`` )th entry of ``self`` to ``val`` . Uses the bit twiddle for setting bits found`here `_. + +:raises LibsemigroupsError: if ``i`` or ``j`` is out of bounds. + +:complexity: Constant. + + +:returns: (None) + +:rtype: None +)pbdoc"); + thing.def("to_int", + &BMat8::to_int, + R"pbdoc( +Returns the integer representation of ``self``.Returns an unsigned +integer obtained by interpreting an 8 x 8 :any:`BMat8` as a sequence of +64 bits (reading rows left to right, from top to bottom) and then +realising this sequence as an unsigned int. + +:exceptions: + This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: + Constant. + +:parameters: + (None) + +:returns: + A ``int``. + +:rtype: + int +)pbdoc"); + thing.def("transpose", + &BMat8::transpose, + R"pbdoc( +Returns the transpose of ``self``.Uses the technique found in`Knu09 +<../biblio.html#knuth2009aa>`_. + +:exceptions: + This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: + Constant. + +:parameters: + (None) + +:returns: + A :any:`BMat8`. + +:rtype: + BMat8 +)pbdoc"); + thing.def(py::self * py::self, + py::arg("that"), + R"pbdoc( + +:param that: the matrix we want to multiply by this. +:type that: BMat8 +Returns the matrix product of ``self`` and ``that``This member function returns the standard matrix product (over the boolean semiring) of two :any:`BMat8` objects. Uses the technique given`here `_. + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: Constant. + + +:returns: A :any:`BMat8`. + +:rtype: BMat8 +)pbdoc"); + thing.def("swap", + &BMat8::swap, + py::arg("that"), + R"pbdoc( + +:param that: the BMat8 to swap this with. +:type that: BMat8 +Swaps ``self`` with ``that``.This member function swaps the values of ``self`` and ``that``. + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: Constant. + + +:returns: (None) + +:rtype: None +)pbdoc"); + thing.def("row_space_basis", + &BMat8::row_space_basis, + R"pbdoc( +Find a basis for the row space of ``self``.This member function returns +a :any:`BMat8` whose non-zero rows form a basis for the row space of +``self``. + +:exceptions: + This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: + Constant. + +:parameters: + (None) + +:returns: + A :any:`BMat8`. + +:rtype: + BMat8 +)pbdoc"); + thing.def("col_space_basis", + &BMat8::col_space_basis, + R"pbdoc( +Find a basis for the column space of ``self``.This member function +returns a :any:`BMat8` whose non-zero columns form a basis for the +column space of ``self``. + +:exceptions: + This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: + Constant. + +:parameters: + (None) + +:returns: + A :any:`BMat8`. + +:rtype: + BMat8 +)pbdoc"); + thing.def("rows", + &BMat8::rows, + R"pbdoc( +Returns a vector containing the rows of ``self``This member function +returns a :any:`list` of uint8_ts representing the rows of ``self`` . +The vector will always be of length 8, even if ``self`` was constructed +with fewer rows. + +:exceptions: + This function guarantees not to throw a ``LibsemigroupsError``. + +:complexity: + Constant. + +:parameters: + (None) + +:returns: + A std::vector. + +:rtype: + list +)pbdoc"); + thing.def("row_space_size", + &BMat8::row_space_size, + R"pbdoc( +Find the size of the row space of ``self``. + +:exceptions: This function guarantees not to throw a ``LibsemigroupsError``. + +:complexity: :math:`O(n)` where :math:`n` is the return value of this function. + +:parameters: (None) + +.. seealso:: :any:`bmat8::col_space_size`. + +:returns: A ``int``. + +:rtype: int +)pbdoc"); + thing.def("number_of_rows", + &BMat8::number_of_rows, + R"pbdoc( +Returns the number of non-zero rows in ``self``.BMat8s do not know their "dimension" - in effect they are all of dimension 8. However, this member function can be used to obtain the number of non-zero rows of ``self``. + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: Constant. + +:parameters: (None) + +.. seealso:: :any:`bmat8::number_of_cols` and :any:`bmat8::minimum_dim`. + +:returns: A ``int``. + +:rtype: int +)pbdoc"); + thing.def("is_regular_element", + &BMat8::is_regular_element, + R"pbdoc( +Check whether ``self`` is a regular element of the full boolean matrix +monoid of appropriate dimension. + +:exceptions: + This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: + Constant. + +:parameters: + (None) + +:returns: + A ``True`` if there exists a boolean matrix ``y`` such that ``x * y * + x = x`` where ``x`` is ``self``. + +:rtype: + bool +)pbdoc"); + thing.def_static( + "random", + [](size_t dim) { return BMat8::random(dim); }, + py::arg("dim") = 8, + R"pbdoc( +Construct a random :any:`BMat8` of dimension at most ``dim``.This static +member function returns a :any:`BMat8` chosen at random, where only the +top-left ``dim`` x ``dim`` entries can be non-zero. + +:exceptions: + This function guarantees not to throw a ``LibsemigroupsError``. + +:parameters: + (None) + +:returns: + A :any:`BMat8`. + +:rtype: + BMat8 +)pbdoc"); + thing.def_static("one", + &BMat8::one, + py::arg("dim"), + R"pbdoc( + +:param dim: the dimension of the identity (default: 8) +:type dim: int +Returns the identity :any:`BMat8`.This member function returns the :any:`BMat8` with the first ``dim`` entries in the main diagonal equal to ``1`` and every other value equal to ``0``. + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: Constant. + + +:returns: A :any:`BMat8`. + +:rtype: BMat8 +)pbdoc"); + + m.def("number_of_cols", + &bmat8::number_of_cols, + py::arg("x"), + R"pbdoc( + +:param x: the BMat8 whose number of columns we want. +:type x: BMat8 +Returns the number of non-zero columns in ``x``.BMat8s do not know their "dimension" - in effect they are all of dimension 8. However, this member function can be used to obtain the number of non-zero rows of ``self``. + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: Constant. + +.. seealso:: :any:`BMat8::number_of_rows` and :any:`bmat8::minimum_dim`. + + +:returns: A ``int``. + +:rtype: int +)pbdoc"); + m.def("col_space_size", + &bmat8::col_space_size, + py::arg("x"), + R"pbdoc( + +:param x: a BMat8. +:type x: BMat8 +Find the size of the column space of ``x``. + +:exceptions: This function guarantees not to throw a ``LibsemigroupsError``. + +:complexity: :math:`O(n)` where :math:`n` is the return value of this function. + +.. seealso:: :any:`BMat8::row_space_size`. + + +:returns: A ``int``. + +:rtype: int +)pbdoc"); + m.def("minimum_dim", + &bmat8::minimum_dim, + py::arg("x"), + R"pbdoc( + +:param x: a BMat8. +:type x: BMat8 +Find the minimum dimension of ``x``.This member function returns the maximal ``i`` such that row ``i`` or column ``i`` contains a ``1``. + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. + +:complexity: Constant.)pbdoc"); + } +} // namespace libsemigroups diff --git a/src/knuth-bendix.cpp b/src/knuth-bendix.cpp index 542ed7f7..3cff912d 100644 --- a/src/knuth-bendix.cpp +++ b/src/knuth-bendix.cpp @@ -45,7 +45,7 @@ #include // for PyUnicode_DecodeLatin1 // libsemigroups_pybind11.... -#include "doc-strings.hpp" // for init_knuth_bendix +#include "doc-strings.hpp" // for dead, finished, kill, report #include "main.hpp" // for init_knuth_bendix namespace py = pybind11; diff --git a/src/main.cpp b/src/main.cpp index f9806530..8b2f9f09 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -129,6 +129,10 @@ namespace libsemigroups { py::class_( m, "CongruenceInterface"); + init_imagerightaction(m); + init_action(m); + init_bmat8(m); + init_forest(m); init_gabow(m); init_knuth_bendix(m); diff --git a/src/main.hpp b/src/main.hpp index 832a41a5..0fcedb12 100644 --- a/src/main.hpp +++ b/src/main.hpp @@ -27,6 +27,7 @@ namespace libsemigroups { void init_reporter(py::module&); void init_runner(py::module&); + void init_bmat8(py::module&); void init_imagerightaction(py::module&); void init_forest(py::module&); @@ -38,6 +39,7 @@ namespace libsemigroups { void init_transf(py::module&); void init_words(py::module&); void init_word_graph(py::module&); + void init_action(py::module&); } // namespace libsemigroups diff --git a/tests/test_action.py b/tests/test_action.py new file mode 100644 index 00000000..b794f04c --- /dev/null +++ b/tests/test_action.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2024, J. D. Mitchell +# +# Distributed under the terms of the GPL license version 3. +# +# The full license is in the file LICENSE, distributed with this software. + +""" +This module contains some tests for the libsemigroups_pybind11 functionality +arising from action.*pp in libsemigroups. +""" + +# pylint: disable=no-name-in-module, missing-function-docstring, invalid-name + + +from libsemigroups_pybind11 import RightAction, LeftAction, BMat8 + + +def test_action_001(): + rows = RightAction(Point=BMat8, Element=BMat8) + rows.add_seed( + BMat8( + [[1, 1, 1, 0], [1, 1, 0, 0], [0, 1, 0, 1], [0, 1, 0, 0]] + ).row_space_basis() + ) + + rows.add_generator( + BMat8([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) + ) + rows.add_generator( + BMat8([[0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) + ) + rows.add_generator( + BMat8([[0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], [1, 0, 0, 0]]) + ) + rows.add_generator( + BMat8([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [1, 0, 0, 1]]) + ) + rows.add_generator( + BMat8([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 0]]) + ) + + assert rows.size() == 553 + rows.init() + + rows.add_seed( + BMat8( + [[1, 1, 1, 0], [1, 1, 0, 0], [0, 1, 0, 1], [0, 1, 0, 0]] + ).row_space_basis() + ) + + rows.add_generator( + BMat8([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) + ) + rows.add_generator( + BMat8([[0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) + ) + rows.add_generator( + BMat8([[0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], [1, 0, 0, 0]]) + ) + rows.add_generator( + BMat8([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [1, 0, 0, 1]]) + ) + rows.add_generator( + BMat8([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 0]]) + ) + + assert rows.size() == 553 + + cols = LeftAction(Point=BMat8, Element=BMat8) + cols.add_seed( + BMat8( + [[1, 1, 1, 0], [1, 1, 0, 0], [0, 1, 0, 1], [0, 1, 0, 0]] + ).col_space_basis() + ) + + cols.add_generator( + BMat8([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) + ) + cols.add_generator( + BMat8([[0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) + ) + cols.add_generator( + BMat8([[0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], [1, 0, 0, 0]]) + ) + cols.add_generator( + BMat8([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [1, 0, 0, 1]]) + ) + cols.add_generator( + BMat8([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 0]]) + ) + + assert cols.size() == 553 + assert cols != rows diff --git a/tests/test_adapters.py b/tests/test_adapters.py new file mode 100644 index 00000000..dce3e5d9 --- /dev/null +++ b/tests/test_adapters.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2024, J. D. Mitchell +# +# Distributed under the terms of the GPL license version 3. +# +# The full license is in the file LICENSE, distributed with this software. + +""" +This module contains some tests for the libsemigroups_pybind11 functionality +arising from adapters.*pp in libsemigroups. +""" + +# pylint: disable=no-name-in-module, missing-function-docstring, invalid-name + + +from libsemigroups_pybind11 import ImageRightAction, ImageLeftAction, BMat8 + + +def test_adapters_017(): + right = ImageRightAction(Point=BMat8, Element=BMat8) + # Point1, Point2, Element -> Point1 = Point2 ^ Element + A, B, C = BMat8.random(8), BMat8(0), BMat8.one(8) + right(B, A, C) + assert B == A.row_space_basis() + + right(B, C, A) + assert B == A.row_space_basis() + + left = ImageLeftAction(Point=BMat8, Element=BMat8) + left(B, A, C) + assert B == A.col_space_basis() + + left(B, C, A) + assert B == A.col_space_basis() + + right(B, A, A) + left(C, A.transpose(), A.transpose()) + assert B == C.transpose()