diff --git a/docs/source/data-structures/elements/matrix/matrix.rst b/docs/source/data-structures/elements/matrix/matrix.rst index 40f76940d..812cf5bb7 100644 --- a/docs/source/data-structures/elements/matrix/matrix.rst +++ b/docs/source/data-structures/elements/matrix/matrix.rst @@ -7,450 +7,46 @@ .. currentmodule:: libsemigroups_pybind11 -.. - TODO check layout is the same as other parts of the doc. - -Matrix -====== - -This page contains the documentation for functionality in -``libsemigroups_pybind11`` for matrices. - -Matrices over various semirings can be constructed using the function -:py:class:`Matrix`. :py:class:`Matrix` is a function that returns an instance of -one of a number of internal classes. These internal types are optimised in -various ways so that the underlying semiring operations are as fast as possible. - -While :py:class:`Matrix` is not a class the objects returned by -:py:class:`Matrix` have identical methods, and so we document :py:class:`Matrix` -as if it was a class. - -Some helper functions for :py:class:`Matrix` objects are documented in the -submodule :any:`libsemigroups_pybind11.matrix`. - -.. doctest:: - - >>> from libsemigroups_pybind11 import Matrix, MatrixKind - >>> x = Matrix(MatrixKind.Integer, [[2]]) - >>> x ** 64 - Matrix(MatrixKind.Integer, [[0]]) - >>> x = Matrix(MatrixKind.Integer, [[0, 1, 1], [1, 2, 3], [-1, 0, -1]]) - >>> x[0, 0] - 0 - >>> x[0, 1] - 1 - >>> x[1] - [1, 2, 3] - >>> x[0, 0] = 666 - >>> x - Matrix(MatrixKind.Integer, [[666, 1, 1], - [ 1, 2, 3], - [ -1, 0, -1]]) - - >>> x[0] = [0, 1, 1] - >>> x - Matrix(MatrixKind.Integer, [[ 0, 1, 1], - [ 1, 2, 3], - [-1, 0, -1]]) - >>> x += 1 - >>> x - Matrix(MatrixKind.Integer, [[1, 2, 2], - [2, 3, 4], - [0, 1, 0]]) - >>> x *= 2 - >>> x - Matrix(MatrixKind.Integer, [[2, 4, 4], - [4, 6, 8], - [0, 2, 0]]) - >>> x += x - >>> x - Matrix(MatrixKind.Integer, [[ 4, 8, 8], - [ 8, 12, 16], - [ 0, 4, 0]]) - >>> x + x - Matrix(MatrixKind.Integer, [[ 8, 16, 16], - [16, 24, 32], - [ 0, 8, 0]]) - >>> x * x - Matrix(MatrixKind.Integer, [[ 80, 160, 160], - [128, 272, 256], - [ 32, 48, 64]]) - >>> y = x.one() - >>> y - Matrix(MatrixKind.Integer, [[1, 0, 0], - [0, 1, 0], - [0, 0, 1]]) - >>> x.one(2) - Matrix(MatrixKind.Integer, [[1, 0], - [0, 1]]) - >>> x.swap(y) - >>> x - Matrix(MatrixKind.Integer, [[1, 0, 0], - [0, 1, 0], - [0, 0, 1]]) - >>> y - Matrix(MatrixKind.Integer, [[ 4, 8, 8], - [ 8, 12, 16], - [ 0, 4, 0]]) - >>> x.number_of_rows() - 3 - >>> x.number_of_cols() - 3 - >>> y = x.copy() - >>> x is not y - True - >>> x == y - True - >>> x != y - False - >>> x < y - False - >>> x != y - False - >>> x > y - False - >>> x >= y - True - >>> x <= y - True - >>> x ** 10 == y - True - >>> len(x) - 3 - >>> list(x) - [[1, 0, 0], [0, 1, 0], [0, 0, 1]] - >>> x + 2 == 2 + x - True - >>> x * 2 == 2 * x - True - >>> import copy - >>> z = copy.copy(y) - >>> z *= 0 - >>> z - Matrix(MatrixKind.Integer, [[0, 0, 0], - [0, 0, 0], - [0, 0, 0]]) - >>> z = Matrix(MatrixKind.Integer, 4, 4) - >>> z - Matrix(MatrixKind.Integer, [[0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0]]) - >>> d = {z: True} - >>> z in d - True - -.. warning:: +The Matrix class +================ - The entries in a ``libsemigroups_pybind11`` matrix are stored internally as - 64-bit signed integers, and there are no checks that the multiplication does - not overflow. +.. autoclass:: Matrix + :doc-only: + :class-doc-from: class MatrixKind ---------- +.. TODO move MatrixKind to the "enums" page when it exists + .. autoclass:: MatrixKind :show-inheritance: -.. - TODO later summary - -The Matrix class ----------------- - -.. py:class:: Matrix - - Instances of this class implement matrices over the semirings listed - above in :any:`MatrixKind`. - - .. py:method:: __init__(self: Matrix, kind: MatrixKind, rows: list[list[int | PositiveInfinity | NegativeInfinity]]) -> None - :noindex: - - Construct a matrix from rows. - - :param kind: specifies the underlying semiring. - :type kind: MatrixKind - - :param rows: the rows of the matrix. - :type rows: list[list[int | PositiveInfinity | NegativeInfinity]] - - :raise RunTimeError: if *kind* is - :py:attr:`MatrixKind.MaxPlusTrunc`, - :py:attr:`MatrixKind.MinPlusTrunc`, or - :py:attr:`MatrixKind.NTP`. - - :raise LibsemigroupsError: - if the entries in *rows* are not of equal length. - - :raise LibsemigroupsError: - if any of the entries of the lists in *rows* do not belong to - the underlying semiring. - - - .. py:method:: __init__(self: Matrix, kind: MatrixKind, threshold: int, rows: list[list[int | PositiveInfinity | NegativeInfinity]]) -> None - :noindex: - - Construct a matrix from threshold and rows. - - :param kind: specifies the underlying semiring. - :type kind: MatrixKind - - :param threshold: the threshold of the underlying semiring. - :type threshold: int - - :param rows: the rows of the matrix. - :type rows: list[list[int | PositiveInfinity | NegativeInfinity]] - - :raise RunTimeError: if *kind* is not - :py:attr:`MatrixKind.MaxPlusTrunc`, or - :py:attr:`MatrixKind.MinPlusTrunc`. - - :raise LibsemigroupsError: - if the entries in *rows* are not of equal length. - - :raise LibsemigroupsError: - if any of the entries of the lists in *rows* do not belong to - the underlying semiring. - - - .. py:method:: __init__(self: Matrix, kind: MatrixKind, threshold: int, period: int, rows: list[list[int | PositiveInfinity | NegativeInfinity]]) -> None - :noindex: - - Construct a matrix from rows. - - :param kind: specifies the underlying semiring. - :type kind: MatrixKind - - :param threshold: the threshold of the underlying semiring. - :type threshold: int - - :param period: the period of the underlying semiring. - :type period: int - - :param rows: the rows of the matrix. - :type rows: list[list[int | PositiveInfinity | NegativeInfinity]] - - :raise RunTimeError: if *kind* is not :py:attr:`MatrixKind.NTP`. - - :raise LibsemigroupsError: - if the entries in *rows* are not of equal length. - - :raise LibsemigroupsError: - if any of the entries of the lists in *rows* do not belong to - the underlying semiring. - - - .. py:method:: __init__(self: Matrix, kind: MatrixKind, r: int, c: int) -> None - :noindex: - - Construct an uninitialized *r* by *c* matrix. - - :param kind: specifies the underlying semiring. - :type kind: MatrixKind - - :param r: the number of rows in the matrix - :type r: int - - :param c: the number of columns in the matrix - :type c: int - - :raise RunTimeError: if *kind* is - :py:attr:`MatrixKind.MaxPlusTrunc`, - :py:attr:`MatrixKind.MinPlusTrunc`, - or :py:attr:`MatrixKind.NTP`. - - .. doctest:: - - >>> from libsemigroups_pybind11 import Matrix, MatrixKind - >>> # construct a 2 x 3 boolean matrix - >>> Matrix(MatrixKind.Boolean, 2, 3) - Matrix(MatrixKind.Boolean, [[0, 0, 0], - [0, 0, 0]]) - - - .. py:method:: __init__(self: Matrix, kind: MatrixKind, threshold: int, r: int, c: int) -> None - :noindex: - - Construct an uninitialized `r` by `c` matrix. - - :param kind: specifies the underlying semiring. - :type kind: MatrixKind - - :param threshold: the threshold of the underlying semiring. - :type threshold: int - - :param r: the number of rows in the matrix - :type r: int - - :param c: the number of columns in the matrix - :type c: int - - :raise RunTimeError: - if *kind* is not :py:attr:`MatrixKind.MaxPlusTrunc` or - :py:attr:`MatrixKind.MinPlusTrunc`. - - .. doctest:: - - >>> from libsemigroups_pybind11 import Matrix, MatrixKind - >>> # construct a 2 x 3 max-plus truncated matrix - >>> Matrix(MatrixKind.MaxPlusTrunc, 11, 2, 3) - Matrix(MatrixKind.MaxPlusTrunc, 11, [[0, 0, 0], - [0, 0, 0]]) - - - .. py:method:: __init__(self: Matrix, kind: MatrixKind, threshold: int, period: int, r: int, c: int) -> None - :noindex: - - Construct an uninitialized `r` by `c` matrix. - - :param kind: specifies the underlying semiring. - :type kind: MatrixKind - - :param threshold: the threshold of the underlying semiring. - :type threshold: int - - :param period: the period of the underlying semiring. - :type period: int - - :param r: the number of rows in the matrix. - :type r: int - - :param c: the number of columns in the matrix. - :type c: int - - :raise RunTimeError: if *kind* is not :py:attr:`MatrixKind.NTP`. - - .. doctest:: - - >>> from libsemigroups_pybind11 import Matrix, MatrixKind - >>> # construct a 2 x 3 ntp matrix - >>> Matrix(MatrixKind.NTP, 5, 7, 2, 3) - Matrix(MatrixKind.NTP, 5, 7, [[0, 0, 0], - [0, 0, 0]]) - - - .. py:method:: number_of_cols(self: Matrix) -> int - - Returns the number of columns. - - :returns: The number of columns in the matrix. - :rtype: int - - .. doctest:: - - >>> from libsemigroups_pybind11 import Matrix, MatrixKind - >>> x = Matrix(MatrixKind.Integer, [[0, 1], [1, 0]]) - >>> x.number_of_cols() - 2 - - - .. py:method:: number_of_rows(self: Matrix) -> int - - Returns the number of rows. - - :returns: The number of rows in the matrix. - :rtype: int - - .. doctest:: - - >>> from libsemigroups_pybind11 import Matrix, MatrixKind - >>> x = Matrix(MatrixKind.Integer, [[0, 1], [1, 0]]) - >>> x.number_of_rows() - 2 - - - .. py:method:: one(self: Matrix, n: int) -> Matrix - - Construct the :math:`n \times n` identity matrix. The second argument - is optional and if not specified then ``x.number_of_rows()`` is used). - - :param n: the dimension of the matrix (optional). - :type n: int - - :returns: An identity matrix. - :rtype: Matrix - - - .. py:method:: product_inplace(self: Matrix, x: Matrix, y: Matrix) -> None - - Multiply two matrices and stores the product in *self*. - - :param x: first matrix to multiply. - :type x: Matrix - :param y: second matrix to multiply. - :type y: Matrix - - :raises LibsemigroupsError: - if *x* and *y* are not square, or do not have the same number of rows. - - :raises RunTimeError: - if *x* and *y* are not defined over the same semiring. - - - .. py:method:: row(self: Matrix, i: int) -> Matrix - - Returns the specified row. - - :param i: the index of the row. - :type i: int - - :returns: A :py:class:`Matrix`. - - :raises LibsemigroupsError: - if *i* is greater than or equal to :any:`number_of_rows`. - - - .. py:method:: rows(self: Matrix) -> list[Matrix] - - Returns a list of all rows of a matrix. - - :returns: A list of the rows. - :rtype: list[Matrix] - - - .. py:method:: scalar_one(self: Matrix) -> int - - Returns the multiplicative identity of the underlying semiring of a - matrix. - - :returns: The multiplicative identity of the underlying semiring. - :rtype: int - - .. doctest:: - - >>> from libsemigroups_pybind11 import Matrix, MatrixKind - >>> x = Matrix(MatrixKind.MinPlusTrunc, 11 ,[[0, 1, 1], [0] * 3, [1] * 3]) - >>> x.scalar_one() - 0 - - - .. py:method:: scalar_zero(self: Matrix) -> int | PositiveInfinity | NegativeInfinity - - Returns the additive identity of the underlying semiring of a - matrix. - - :returns: The additive identity of the underlying semiring. - :rtype: int | PositiveInfinity | NegativeInfinity - - .. doctest:: - - >>> from libsemigroups_pybind11 import Matrix, MatrixKind, POSITIVE_INFINITY - >>> x = Matrix(MatrixKind.MinPlusTrunc, 11 ,[[0, 1, 1], [0] * 3, [1] * 3]) - >>> x.scalar_zero() == POSITIVE_INFINITY - True - - - .. py:method:: swap(self: Matrix, that: Matrix) -> None - - Swaps the contents of *self* with the contents of *that*. - - :param that: the matrix to swap contents with - :type that: Matrix - - - .. py:method:: transpose(self: Matrix) -> None - - Transposes the matrix in-place. - - :raises LibsemigroupsError: - if *self* is not a square matrix. +Contents +-------- + +.. autosummary:: + :signatures: short + + Matrix.__init__ + Matrix.copy + Matrix.degree + Matrix.number_of_cols + Matrix.number_of_rows + Matrix.one + Matrix.product_inplace + Matrix.row + Matrix.rows + Matrix.scalar_one + Matrix.scalar_zero + Matrix.swap + Matrix.transpose + +Full API +-------- + +.. autoclass:: Matrix + :class-doc-from: init + :members: + :inherited-members: + :exclude-members: py_template_params_from_cxx_obj, init_cxx_obj diff --git a/src/libsemigroups_pybind11/__init__.py b/src/libsemigroups_pybind11/__init__.py index b437c2d70..d948d2270 100644 --- a/src/libsemigroups_pybind11/__init__.py +++ b/src/libsemigroups_pybind11/__init__.py @@ -22,7 +22,7 @@ from .kambites import Kambites from .knuth_bendix import KnuthBendix from .konieczny import Konieczny -from .matrix import _Matrix as Matrix, _MatrixKind as MatrixKind +from .matrix import _Matrix as Matrix, MatrixKind from .presentation import Presentation, InversePresentation from .schreier_sims import SchreierSims from .sims import ( @@ -95,7 +95,9 @@ ) ) from e - -# The following fools sphinx into thinking that MatrixKind is not an alias. +# The following fools sphinx into thinking that MatrixKind + Matrix are not +# aliases. +Matrix.__module__ = __name__ +Matrix.__name__ = "Matrix" MatrixKind.__module__ = __name__ MatrixKind.__name__ = "MatrixKind" diff --git a/src/libsemigroups_pybind11/detail/decorators.py b/src/libsemigroups_pybind11/detail/decorators.py index 797ebc9e1..8da75866d 100644 --- a/src/libsemigroups_pybind11/detail/decorators.py +++ b/src/libsemigroups_pybind11/detail/decorators.py @@ -11,8 +11,61 @@ libsemigroups_pybind11. """ +import re as _re -def copydoc(original): + +def _get_overloaded_doc(func): + """Return the docstring of a function as if that function was overloaded""" + doc = func.__doc__ + if "Overloaded function." in doc: + return doc + return "1. " + doc + + +def _correct_overloads(target, base_func, extra_funcs): + """ + Fix the docstring of copied overloaded functions + + When functions are bound, pybind11 adds some decorations to the docstring + (unless this is explicitly turned off using + py::options::disable_function_signatures()). When a function is overloaded, + this decoration includes the string 'Overloaded function.', a new signature + with parameters *args and **kwargs, and some enumeration of the different + overloads. + + To combine multiple docstrings of functions, it is necessary to: + * remove all but the first occurrence of the pybind11 inserted strings; + * ensure every function is documented as if it was overloaded; and + * number the overloads 1, 2, ..., N. + + This function does these things. + """ + target_name = target.__name__ + old_names = set(fn.__name__ for fn in extra_funcs) | {base_func.__name__} + base_doc = _get_overloaded_doc(base_func) + extra_doc = "".join([_get_overloaded_doc(func) for func in extra_funcs]) + + # Remove pybind11 inserted strings + replacements = { + f"{old_name}(*args, **kwargs)\n": "" for old_name in old_names + } + replacements["Overloaded function.\n"] = "" + for old, new in replacements.items(): + extra_doc = extra_doc.replace(old, new) + + # Fix overload numbering + overload_counter = 1 + doc_blocks = _re.split( + rf"\d+\. (?:{'|'.join(old_names)})", base_doc + extra_doc + ) + new_doc = doc_blocks[0] + for doc_block in doc_blocks[1:]: + new_doc += f"{overload_counter}. {target_name}" + doc_block + overload_counter += 1 + return new_doc + + +def copydoc(func, *extra_funcs): """ Decorator that can be used to copy the doc from one function to another, for example: @@ -21,11 +74,13 @@ def copydoc(original): def __init___(self) -> None: pass """ - - original_doc = original.__doc__ + new_doc = func.__doc__ def wrapper(target): - target.__doc__ = original_doc + if extra_funcs: + target.__doc__ = _correct_overloads(target, func, extra_funcs) + else: + target.__doc__ = new_doc return target return wrapper diff --git a/src/libsemigroups_pybind11/matrix.py b/src/libsemigroups_pybind11/matrix.py index c9019eedf..3fd2d76d9 100644 --- a/src/libsemigroups_pybind11/matrix.py +++ b/src/libsemigroups_pybind11/matrix.py @@ -13,6 +13,8 @@ from enum import Enum as _Enum +from typing_extensions import Self as _Self, Union as _Union + from _libsemigroups_pybind11 import ( # pylint: disable=no-name-in-module,unused-import BMat as _BMat, IntMat as _IntMat, @@ -26,16 +28,25 @@ POSITIVE_INFINITY as _POSITIVE_INFINITY, PositiveInfinity as _PositiveInfinity, ProjMaxPlusMat as _ProjMaxPlusMat, - matrix_row_space_size as row_space_size, - matrix_period as period, - matrix_row_basis as row_basis, - matrix_threshold as threshold, + matrix_row_space_size as _row_space_size, + matrix_period as _period, + matrix_row_basis as _row_basis, + matrix_threshold as _threshold, +) + +from .detail.cxx_wrapper import ( + CxxWrapper as _CxxWrapper, + to_cxx as _to_cxx, + copy_cxx_mem_fns as _copy_cxx_mem_fns, + wrap_cxx_free_fn as _wrap_cxx_free_fn, ) +from .detail.decorators import copydoc as _copydoc + # the underscore prefix stops this from appearing in the doc of the # "matrix" submodule -class _MatrixKind(_Enum): +class MatrixKind(_Enum): # pylint: disable=invalid-name """ @@ -94,25 +105,116 @@ class _MatrixKind(_Enum): NTP = 7 -_MatrixKindToCxxType = { - _MatrixKind.Boolean: _BMat, - _MatrixKind.Integer: _IntMat, - _MatrixKind.MaxPlus: _MaxPlusMat, - _MatrixKind.MinPlus: _MinPlusMat, - _MatrixKind.ProjMaxPlus: _ProjMaxPlusMat, - _MatrixKind.MaxPlusTrunc: _MaxPlusTruncMat, - _MatrixKind.MinPlusTrunc: _MinPlusTruncMat, - _MatrixKind.NTP: _NTPMat, -} +######################################################################## +# Matrix python class +######################################################################## + - -# the underscore prefix stops this from appearing in the doc of the -# "matrix" submodule. -# TODO could update to use kwargs for threshold and period -def _Matrix(kind: _MatrixKind, *args): # pylint: disable=invalid-name - """ - Documented in docs/source/elements/matrix/matrix.rst - """ - if not isinstance(kind, _MatrixKind): - raise TypeError("the 1st argument must be a _MatrixKind") - return _MatrixKindToCxxType[kind](*args) +class _Matrix(_CxxWrapper): + __doc__ = _BMat.__doc__ + + _py_template_params_to_cxx_type = { + (MatrixKind.Boolean,): _BMat, + (MatrixKind.Integer,): _IntMat, + (MatrixKind.MaxPlus,): _MaxPlusMat, + (MatrixKind.MinPlus,): _MinPlusMat, + (MatrixKind.ProjMaxPlus,): _ProjMaxPlusMat, + (MatrixKind.MaxPlusTrunc,): _MaxPlusTruncMat, + (MatrixKind.MinPlusTrunc,): _MinPlusTruncMat, + (MatrixKind.NTP,): _NTPMat, + } + + _cxx_type_to_py_template_params = dict( + zip( + _py_template_params_to_cxx_type.values(), + _py_template_params_to_cxx_type.keys(), + ) + ) + + _all_wrapped_cxx_types = {*_py_template_params_to_cxx_type.values()} + + # TODO could update to use kwargs for threshold and period + @_copydoc(_BMat.__init__, _MaxPlusTruncMat.__init__, _NTPMat.__init__) + def __init__(self: _Self, kind: MatrixKind, *args) -> None: + super().__init__( + *args, + required_kwargs=(), + ) + if _to_cxx(self) is not None: + return + # TODO arg checks? + if not isinstance(kind, MatrixKind): + raise TypeError("the 1st argument must be a MatrixKind") + self.py_template_params = (kind,) + self.init_cxx_obj(*args) + + def __getitem__( + self: _Self, *args + ) -> _Union[int, _Self, _PositiveInfinity, _NegativeInfinity]: + return _to_cxx(self).__getitem__(*args) + + def __setitem__(self: _Self, *args): + return _to_cxx(self).__setitem__(*args) + + def __imul__( + self: _Self, + that: _Union[_Self, int, _PositiveInfinity, _NegativeInfinity], + ) -> _Self: + return _to_cxx(self).__imul__(that) + + def __mul__( + self: _Self, + that: _Union[_Self, int, _PositiveInfinity, _NegativeInfinity], + ) -> _Self: + return _to_cxx(self).__mul__(that) + + def __iadd__( + self: _Self, + that: _Union[_Self, int, _PositiveInfinity, _NegativeInfinity], + ) -> _Self: + return _to_cxx(self).__iadd__(that) + + def __add__( + self: _Self, + that: _Union[_Self, int, _PositiveInfinity, _NegativeInfinity], + ) -> _Self: + return _to_cxx(self).__add__(that) + + def __gt__(self: _Self, that: _Union[_Self, int]) -> bool: + return _to_cxx(self).__gt__(that) + + def __ge__(self: _Self, that: _Union[_Self, int]) -> bool: + return _to_cxx(self).__ge__(that) + + def __ne__(self: _Self, that: _Union[_Self, int]) -> bool: + return _to_cxx(self).__ne__(that) + + def __eq__(self: _Self, that: _Union[_Self, int]) -> bool: + return _to_cxx(self).__eq__(that) + + def __lt__(self: _Self, that: _Union[_Self, int]) -> bool: + return _to_cxx(self).__lt__(that) + + def __le__(self: _Self, that: _Union[_Self, int]) -> bool: + return _to_cxx(self).__le__(that) + + def __len__(self: _Self) -> int: + return _to_cxx(self).__len__() + + def __pow__(self: _Self, e: int) -> _Self: + return _to_cxx(self).__pow__(e) + + def __hash__(self: _Self) -> int: + return _to_cxx(self).__hash__() + + +_copy_cxx_mem_fns(_NTPMat, _Matrix) + +######################################################################## +# Helper functions +######################################################################## + +row_space_size = _wrap_cxx_free_fn(_row_space_size) +period = _wrap_cxx_free_fn(_period) +row_basis = _wrap_cxx_free_fn(_row_basis) +threshold = _wrap_cxx_free_fn(_threshold) diff --git a/src/matrix.cpp b/src/matrix.cpp index fe33e7f90..813550614 100644 --- a/src/matrix.cpp +++ b/src/matrix.cpp @@ -167,8 +167,134 @@ namespace libsemigroups { py::class_ thing(m, py_type.c_str(), R"pbdoc( -Instances of this class implement matrices over the semirings listed -above in :any:`MatrixKind`. +This page contains the documentation for functionality in +``libsemigroups_pybind11`` for matrices. + +Matrices over various semirings can be constructed using the function +:py:class:`Matrix`. :py:class:`Matrix` is a function that returns an instance of +one of a number of internal classes. These internal types are optimised in +various ways so that the underlying semiring operations are as fast as possible. + +Some helper functions for :py:class:`Matrix` objects are documented in the +submodule :any:`libsemigroups_pybind11.matrix`. + +.. doctest:: + + >>> from libsemigroups_pybind11 import Matrix, MatrixKind + >>> x = Matrix(MatrixKind.Integer, [[2]]) + >>> x ** 64 + Matrix(MatrixKind.Integer, [[0]]) + >>> x = Matrix(MatrixKind.Integer, [[0, 1, 1], [1, 2, 3], [-1, 0, -1]]) + >>> x[0, 0] + 0 + >>> x[0, 1] + 1 + >>> x[1] + [1, 2, 3] + >>> x[0, 0] = 666 + >>> x + Matrix(MatrixKind.Integer, [[666, 1, 1], + [ 1, 2, 3], + [ -1, 0, -1]]) + + >>> x[0] = [0, 1, 1] + >>> x + Matrix(MatrixKind.Integer, [[ 0, 1, 1], + [ 1, 2, 3], + [-1, 0, -1]]) + >>> x += 1 + >>> x + Matrix(MatrixKind.Integer, [[1, 2, 2], + [2, 3, 4], + [0, 1, 0]]) + >>> x *= 2 + >>> x + Matrix(MatrixKind.Integer, [[2, 4, 4], + [4, 6, 8], + [0, 2, 0]]) + >>> x += x + >>> x + Matrix(MatrixKind.Integer, [[ 4, 8, 8], + [ 8, 12, 16], + [ 0, 4, 0]]) + >>> x + x + Matrix(MatrixKind.Integer, [[ 8, 16, 16], + [16, 24, 32], + [ 0, 8, 0]]) + >>> x * x + Matrix(MatrixKind.Integer, [[ 80, 160, 160], + [128, 272, 256], + [ 32, 48, 64]]) + >>> y = x.one() + >>> y + Matrix(MatrixKind.Integer, [[1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + >>> x.one(2) + Matrix(MatrixKind.Integer, [[1, 0], + [0, 1]]) + >>> x.swap(y) + >>> x + Matrix(MatrixKind.Integer, [[1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + >>> y + Matrix(MatrixKind.Integer, [[ 4, 8, 8], + [ 8, 12, 16], + [ 0, 4, 0]]) + >>> x.number_of_rows() + 3 + >>> x.number_of_cols() + 3 + >>> y = x.copy() + >>> x is not y + True + >>> x == y + True + >>> x != y + False + >>> x < y + False + >>> x != y + False + >>> x > y + False + >>> x >= y + True + >>> x <= y + True + >>> x ** 10 == y + True + >>> len(x) + 3 + >>> list(x) + [[1, 0, 0], [0, 1, 0], [0, 0, 1]] + >>> x + 2 == 2 + x + True + >>> x * 2 == 2 * x + True + >>> import copy + >>> z = copy.copy(y) + >>> z *= 0 + >>> z + Matrix(MatrixKind.Integer, [[0, 0, 0], + [0, 0, 0], + [0, 0, 0]]) + >>> z = Matrix(MatrixKind.Integer, 4, 4) + >>> z + Matrix(MatrixKind.Integer, [[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]]) + >>> d = {z: True} + >>> z in d + True + +.. warning:: + + The entries in a ``libsemigroups_pybind11`` matrix are stored internally as + 64-bit signed integers, and there are no checks that the multiplication does + not overflow. )pbdoc"); thing.def("__repr__", repr); @@ -363,33 +489,171 @@ above in :any:`MatrixKind`. [](Mat const& thing) { return thing.number_of_rows(); }); thing.def("__pow__", &matrix::pow); - thing.def("copy", [](Mat const& x) { return Mat(x); }); - thing.def("product_inplace", [](Mat& xy, Mat const& thing, Mat const& y) { - matrix::throw_if_bad_dim(thing, y); - matrix::throw_if_bad_dim(xy, thing); - xy.product_inplace_no_checks(thing, y); - }); - thing.def("transpose", [](Mat& thing) { thing.transpose(); }); - thing.def("swap", &Mat::swap); - thing.def("scalar_zero", - [](Mat const& thing) { return from_int(thing.scalar_zero()); }); - thing.def("scalar_one", - [](Mat const& thing) { return from_int(thing.scalar_one()); }); - thing.def("number_of_rows", - [](Mat const& thing) { return thing.number_of_rows(); }); - thing.def("degree", - [](Mat const& thing) { return thing.number_of_rows(); }); - thing.def("number_of_cols", - [](Mat const& thing) { return thing.number_of_cols(); }); - thing.def("row", - [](Mat const& thing, size_t i) { return Row(thing.row(i)); }); - thing.def("rows", [](Mat const& thing) { - std::vector rows; - for (size_t i = 0; i < thing.number_of_rows(); ++i) { - rows.push_back(Row(thing.row(i))); - } - return rows; - }); + thing.def("copy", [](Mat const& x) { return Mat(x); }, R"pbdoc( +:sig=(self: Matrix) -> Matrix: +Copy a :any:`Matrix` object. + +:returns: A copy. +:rtype: Matrix + )pbdoc"); + thing.def( + "product_inplace", + [](Mat& xy, Mat const& thing, Mat const& y) { + matrix::throw_if_bad_dim(thing, y); + matrix::throw_if_bad_dim(xy, thing); + xy.product_inplace_no_checks(thing, y); + }, + R"pbdoc( +:sig=(self: Matrix, x: Matrix, y: Matrix) -> None: + +Multiply two matrices and stores the product in *self*. + +:param x: first matrix to multiply. +:type x: Matrix +:param y: second matrix to multiply. +:type y: Matrix + +:raises LibsemigroupsError: + if *x* and *y* are not square, or do not have the same number of rows. + +:raises RunTimeError: + if *x* and *y* are not defined over the same semiring.)pbdoc"); + thing.def( + "transpose", + [](Mat& thing) { thing.transpose(); }, + R"pbdoc( +:sig=(self: Matrix) -> None: + +Transposes the matrix in-place. + +:raises LibsemigroupsError: + if *self* is not a square matrix.)pbdoc"); + thing.def("swap", &Mat::swap, R"pbdoc( +:sig=(self: Matrix, that: Matrix) -> None: + +Swaps the contents of *self* with the contents of *that*. + +:param that: the matrix to swap contents with +:type that: Matrix)pbdoc"); + thing.def( + "scalar_zero", + [](Mat const& thing) { return from_int(thing.scalar_zero()); }, + R"pbdoc( +:sig=(self: Matrix) -> int | PositiveInfinity | NegativeInfinity: + +Returns the additive identity of the underlying semiring of a +matrix. + +:returns: The additive identity of the underlying semiring. +:rtype: int | PositiveInfinity | NegativeInfinity + +.. doctest:: + + >>> from libsemigroups_pybind11 import Matrix, MatrixKind, POSITIVE_INFINITY + >>> x = Matrix(MatrixKind.MinPlusTrunc, 11 ,[[0, 1, 1], [0] * 3, [1] * 3]) + >>> x.scalar_zero() == POSITIVE_INFINITY + True)pbdoc"); + thing.def( + "scalar_one", + [](Mat const& thing) { return thing.scalar_one(); }, + R"pbdoc( +:sig=(self: Matrix) -> int: + +Returns the multiplicative identity of the underlying semiring of a +matrix. + +:returns: The multiplicative identity of the underlying semiring. +:rtype: int + +.. doctest:: + + >>> from libsemigroups_pybind11 import Matrix, MatrixKind + >>> x = Matrix(MatrixKind.MinPlusTrunc, 11 ,[[0, 1, 1], [0] * 3, [1] * 3]) + >>> x.scalar_one() + 0)pbdoc"); + + thing.def( + "number_of_rows", + [](Mat const& thing) { return thing.number_of_rows(); }, + R"pbdoc( +:sig=(self: Matrix) -> int: + +Returns the number of rows. + +:returns: The number of rows in the matrix. +:rtype: int + +.. doctest:: + + >>> from libsemigroups_pybind11 import Matrix, MatrixKind + >>> x = Matrix(MatrixKind.Integer, [[0, 1], [1, 0]]) + >>> x.number_of_rows() + 2)pbdoc"); + + thing.def( + "degree", + [](Mat const& thing) { return thing.number_of_rows(); }, + R"pbdoc( +:sig=(self: Matrix) -> int: + +Returns the degree of a :any:`Matrix`. + +Returns the degree of a :any:`Matrix`, where the *degree* of a :any:`Matrix` is +just the number of rows. + +:returns: The degree. +:rtype: int + +:complexity: Constant. +)pbdoc"); + thing.def( + "number_of_cols", + [](Mat const& thing) { return thing.number_of_cols(); }, + R"pbdoc( +:sig=(self: Matrix) -> int: + +Returns the number of columns. + +:returns: The number of columns in the matrix. +:rtype: int + +.. doctest:: + + >>> from libsemigroups_pybind11 import Matrix, MatrixKind + >>> x = Matrix(MatrixKind.Integer, [[0, 1], [1, 0]]) + >>> x.number_of_cols() + 2)pbdoc"); + thing.def( + "row", + [](Mat const& thing, size_t i) { return Row(thing.row(i)); }, + R"pbdoc( +:sig=(self: Matrix, i: int) -> Matrix: + +Returns the specified row. + +:param i: the index of the row. +:type i: int + +:returns: A :py:class:`Matrix`. + +:raises LibsemigroupsError: + if *i* is greater than or equal to :any:`number_of_rows`.)pbdoc"); + thing.def( + "rows", + [](Mat const& thing) { + std::vector rows; + for (size_t i = 0; i < thing.number_of_rows(); ++i) { + rows.push_back(Row(thing.row(i))); + } + return rows; + }, + R"pbdoc( +:sig=(self: Matrix) -> list[Matrix]: + +Returns a list of all rows of a matrix. + +:returns: A list of the rows. +:rtype: list[Matrix])pbdoc"); return thing; } @@ -405,6 +669,8 @@ above in :any:`MatrixKind`. rows) { return make(to_ints(rows)); }), py::arg("rows"), R"pbdoc( +:sig=(self: Matrix, kind: MatrixKind, rows: list[list[int | PositiveInfinity | NegativeInfinity]]) -> None: + Construct a matrix from rows. :param kind: specifies the underlying semiring. @@ -425,7 +691,33 @@ Construct a matrix from rows. if any of the entries of the lists in *rows* do not belong to the underlying semiring. )pbdoc"); - thing.def(py::init()); + thing.def(py::init(), + R"pbdoc( +:sig=(self: Matrix, kind: MatrixKind, r: int, c: int) -> None: + +Construct an uninitialized *r* by *c* matrix. + +:param kind: specifies the underlying semiring. +:type kind: MatrixKind + +:param r: the number of rows in the matrix. +:type r: int + +:param c: the number of columns in the matrix. +:type c: int + +:raise RunTimeError: if *kind* is + :py:attr:`MatrixKind.MaxPlusTrunc`, + :py:attr:`MatrixKind.MinPlusTrunc`, + or :py:attr:`MatrixKind.NTP`. + +.. doctest:: + + >>> from libsemigroups_pybind11 import Matrix, MatrixKind + >>> # construct a 2 x 3 boolean matrix + >>> Matrix(MatrixKind.Boolean, 2, 3) + Matrix(MatrixKind.Boolean, [[0, 0, 0], + [0, 0, 0]]))pbdoc"); thing.def("one", [](Mat const& self, size_t n) { return Mat::one(n); }); thing.def("one", py::overload_cast<>(&Mat::one, py::const_)); } @@ -438,15 +730,70 @@ Construct a matrix from rows. auto thing = bind_matrix_common(m); thing.def(py::init([](size_t threshold, size_t r, size_t c) { - return Mat(semiring(threshold), r, c); - })); - thing.def(py::init( - [](size_t threshold, - std::vector>> const& - entries) { - return make(semiring(threshold), - to_ints(entries)); - })); + return Mat(semiring(threshold), r, c); + }), + R"pbdoc( +:sig=(self: Matrix, kind: MatrixKind, threshold: int, r: int, c: int) -> None: + +Construct an uninitialized `r` by `c` matrix. + +:param kind: specifies the underlying semiring. +:type kind: MatrixKind + +:param threshold: the threshold of the underlying semiring. +:type threshold: int + +:param r: the number of rows in the matrix +:type r: int + +:param c: the number of columns in the matrix +:type c: int + +:raise RunTimeError: + if *kind* is not :py:attr:`MatrixKind.MaxPlusTrunc` or + :py:attr:`MatrixKind.MinPlusTrunc`. + +.. doctest:: + + >>> from libsemigroups_pybind11 import Matrix, MatrixKind + >>> # construct a 2 x 3 max-plus truncated matrix + >>> Matrix(MatrixKind.MaxPlusTrunc, 11, 2, 3) + Matrix(MatrixKind.MaxPlusTrunc, 11, [[0, 0, 0], + [0, 0, 0]]))pbdoc"); + thing.def( + py::init( + [](size_t threshold, + std::vector>> const& + rows) { + return make(semiring(threshold), + to_ints(rows)); + }), + R"pbdoc( +:sig=(self: Matrix, kind: MatrixKind, threshold: int, rows: list[list[int | PositiveInfinity | NegativeInfinity]]) -> None: + +Construct a matrix from threshold and rows. + +:param kind: specifies the underlying semiring. +:type kind: MatrixKind + +:param threshold: the threshold of the underlying semiring. +:type threshold: int + +:param rows: the rows of the matrix. +:type rows: list[list[int | PositiveInfinity | NegativeInfinity]] + +:raise RunTimeError: + if *kind* is not :py:attr:`MatrixKind.MaxPlusTrunc` or + :py:attr:`MatrixKind.MinPlusTrunc`. + +:raise LibsemigroupsError: + if the entries in *rows* are not of equal length. + +:raise LibsemigroupsError: + if any of the entries of the lists in *rows* do not belong to + the underlying semiring. +)pbdoc"); + thing.def("one", [](Mat const& self, size_t n) { return Mat::one(semiring(matrix::threshold(self)), n); }); @@ -457,7 +804,7 @@ Construct a matrix from rows. [](Mat const& x) { return matrix::threshold(x); }, py::arg("x"), R"pbdoc( -:sig=(x:Matrix)->int: +:sig=(x: Matrix) -> int: :only-document-once: Returns the threshold of a matrix over a truncated semiring. @@ -483,24 +830,101 @@ that is a matrix whose kind is any of: auto thing = bind_matrix_common(m); - thing.def(py::init( - [](size_t threshold, - size_t period, - std::vector>> const& - entries) { - return make(semiring(threshold, period), - to_ints(entries)); - })); + thing.def( + py::init( + [](size_t threshold, + size_t period, + std::vector>> const& + entries) { + return make(semiring(threshold, period), + to_ints(entries)); + }), + R"pbdoc( +:sig=(self: Matrix, kind: MatrixKind, threshold: int, period: int, rows: list[list[int]]) -> None: + +Construct a matrix from threshold, period, and rows. + +:param kind: specifies the underlying semiring. +:type kind: MatrixKind + +:param threshold: the threshold of the underlying semiring. +:type threshold: int + +:param period: the period of the underlying semiring. +:type period: int + +:param rows: the rows of the matrix. +:type rows: list[list[int]] + +:raise RunTimeError: if *kind* is not :py:attr:`MatrixKind.NTP`. + +:raise LibsemigroupsError: + if the entries in *rows* are not of equal length. + +:raise LibsemigroupsError: + if any of the entries of the lists in *rows* do not belong to + the underlying semiring.)pbdoc"); thing.def( py::init([](size_t threshold, size_t period, size_t r, size_t c) { return Mat(semiring(threshold, period), r, c); - })); - thing.def("one", [](Mat const& self, size_t n) { - return Mat::one(semiring(matrix::threshold(self), - matrix::period(self)), - n); - }); - thing.def("one", [](Mat const& self) { return self.one(); }); + }), + R"pbdoc( +:sig=(self: Matrix, kind: MatrixKind, threshold: int, period: int, r: int, c: int) -> None: + +Construct an uninitialized `r` by `c` matrix with threshold and period. + +:param kind: specifies the underlying semiring. +:type kind: MatrixKind + +:param threshold: the threshold of the underlying semiring. +:type threshold: int + +:param period: the period of the underlying semiring. +:type period: int + +:param r: the number of rows in the matrix. +:type r: int + +:param c: the number of columns in the matrix. +:type c: int + +:raise RunTimeError: if *kind* is not :py:attr:`MatrixKind.NTP`. + +.. doctest:: + + >>> from libsemigroups_pybind11 import Matrix, MatrixKind + >>> # construct a 2 x 3 ntp matrix + >>> Matrix(MatrixKind.NTP, 5, 7, 2, 3) + Matrix(MatrixKind.NTP, 5, 7, [[0, 0, 0], + [0, 0, 0]]) +)pbdoc"); + thing.def( + "one", + [](Mat const& self, size_t n) { + return Mat::one(semiring(matrix::threshold(self), + matrix::period(self)), + n); + }, + R"pbdoc( +:sig=(self: Matrix, n: int) -> Matrix: + +Construct the :math:`n \times n` identity matrix. + +:param n: the dimension of the matrix. +:type n: int + +:returns: An identity matrix. +:rtype: Matrix)pbdoc"); + thing.def( + "one", + [](Mat const& self) { return self.one(); }, + R"pbdoc( +:sig=(self: Matrix) -> Matrix: + +This function returns the identity matrix of the same dimensions as *self*. + +:returns: An identity matrix. +:rtype: Matrix)pbdoc"); m.def( "matrix_period", diff --git a/src/pbr.cpp b/src/pbr.cpp index 07824ce4c..c9481e672 100644 --- a/src/pbr.cpp +++ b/src/pbr.cpp @@ -228,7 +228,7 @@ then bad things will happen. "copy", [](PBR const& self) { return PBR(self); }, R"pbdoc( -Copy a PBR object. +Copy a :any:`PBR` object. :returns: A copy. :rtype: PBR