diff --git a/docs/DoxygenLayout.xml b/docs/DoxygenLayout.xml index e43bbdde4..750d873a7 100644 --- a/docs/DoxygenLayout.xml +++ b/docs/DoxygenLayout.xml @@ -41,6 +41,7 @@ + @@ -74,6 +75,8 @@ title="StaticRowView" /> + diff --git a/docs/libsemigroups.bib b/docs/libsemigroups.bib index 17a76cff1..da266f1ee 100644 --- a/docs/libsemigroups.bib +++ b/docs/libsemigroups.bib @@ -277,6 +277,16 @@ @article{Maltcev2007aa year = 2007, bdsk-url-1 = {https://doi.org/10.21136/mb.2007.134125}} +@misc{Martin2011aa, + title={Partitioned binary relations}, + author={Paul Martin and Volodymyr Mazorchuk}, + year={2011}, + eprint={1102.0862}, + archivePrefix={arXiv}, + primaryClass={math.RT}, + url={https://arxiv.org/abs/1102.0862}, +} + @article{Mitchell2021aa, author = {J. D. Mitchell and M. Tsalakou}, date-added = {2021-03-09 15:23:03 +0000}, diff --git a/include/libsemigroups/bipart.hpp b/include/libsemigroups/bipart.hpp index 5d850b0dd..2d5533ce8 100644 --- a/include/libsemigroups/bipart.hpp +++ b/include/libsemigroups/bipart.hpp @@ -778,7 +778,7 @@ namespace libsemigroups { //! used in the GAP package [Semigroups package for //! GAP](https://semigroups.github.io/Semigroups/). //! - //! \sa libsemigroups::validate(Bipartition const&). + //! \sa bipartition::validate(Bipartition const&). // TODO(2) add more explanation to the doc here class Bipartition { private: diff --git a/include/libsemigroups/pbr.hpp b/include/libsemigroups/pbr.hpp index 640bf6494..1aa8d1c97 100644 --- a/include/libsemigroups/pbr.hpp +++ b/include/libsemigroups/pbr.hpp @@ -1,6 +1,6 @@ // // libsemigroups - C++ library for semigroups and monoids -// Copyright (C) 2019 James D. Mitchell +// Copyright (C) 2019-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 @@ -25,51 +25,87 @@ #include // for uint32_t, int32_t #include // for initializer_list #include // for ostream, ostringstream +#include // for string #include // for forward +#include // for forward #include // for vector, operator<, operator==, allocator -#include "adapters.hpp" // for Hash +#include "adapters.hpp" // for Hash +#include "libsemigroups/exception.hpp" // for LIBSEMIGROUPS_E... namespace libsemigroups { - //! Class for partitioned binary relations (PBR). + //! \defgroup pbr_group Partitioned binary relations (PBRs) //! - //! Partitioned binary relations (PBRs) are a generalisation of bipartitions, - //! which were introduced by - //! [Martin and Mazorchuk](https://arxiv.org/abs/1102.0862). - class PBR { - friend void validate(PBR const& x); + //! Defined ``pbr.hpp``. + //! + //! This page contains an overview of the functionality in `libsemigroups` + //! for partitioned binary relations (PBRs). + //! + //! PBRs are a generalisation of bipartitions, which were introduced by + //! Martin and Mazorchuk in \cite Martin2011aa. + //! + //! Helper functions for PBRs are documented at + //! \ref libsemigroups::pbr "Helper functions for PBRs" + //! + //! @{ + //! \brief Class for representing PBRs. + //! + //! Defined in ``pbr.hpp``. + //! + //! *Partitioned binary relations* (PBRs) are a generalisation of a + //! bipartitions, and were introduced by Martin and Mazorchuk in + //! \cite Martin2011aa. + //! + //! \sa pbr::throw_if_invalid(PBR const&). + class PBR { public: + //! \brief Type of constructor argument. + //! //! Type of constructor argument. template using vector_type = std::vector> const&; + //! \brief Type of constructor argument. + //! //! Type of constructor argument. template using initializer_list_type = std::initializer_list> const&; - //! Deleted. + //! \brief Deleted. + //! + //! Deleted. To avoid the situation where the underlying container is not + //! defined, it is not possible to default construct a PBR object. PBR() = delete; + //! \brief Default copy constructor. + //! //! Default copy constructor. PBR(PBR const&) = default; + //! \brief Default move constructor. + //! //! Default move constructor. PBR(PBR&&) = default; + //! \brief Default copy assignment operator. + //! //! Default copy assignment operator. PBR& operator=(PBR const&) = default; - //! Default move assignment operator. + //! \brief Default move assignment operator. + //! PBR& operator=(PBR&&) = default; + //! \brief Construct from adjacencies \c 0 to `2n - 1`. + //! //! Construct from adjacencies \c 0 to `2n - 1`. //! - //! The parameter \p x must be a container of vectors of - //! \c uint32_t with size \f$2n\f$ for some integer - //! \f$n\f$, the vector in position \f$i\f$ is the list of points adjacent - //! to \f$i\f$ in the PBR constructed. + //! The parameter \p x must be a container of vectors of \c uint32_t with + //! size \f$2n\f$ for some integer \f$n\f$, and the vector in position + //! \f$i\f$ is the list of points adjacent to \f$i\f$ in the PBR + //! constructed. //! //! \param x the container of vectors of adjacencies. //! @@ -79,12 +115,14 @@ namespace libsemigroups { //! \warning //! No checks whatsoever on the validity of \p x are performed. //! - //! \sa \ref libsemigroups::validate(PBR const&) + //! \sa \ref pbr::throw_if_invalid(PBR const&) explicit PBR(vector_type x); //! \copydoc PBR(vector_type) explicit PBR(initializer_list_type x); + //! \brief Construct empty PBR of given \ref degree. + //! //! Construct empty PBR of given \ref degree. //! //! \param n the degree @@ -93,8 +131,9 @@ namespace libsemigroups { //! \no_libsemigroups_except explicit PBR(size_t n); - //! Construct from adjacencies \c 1 to \c n and \c -1 to \c - //! -n. + //! \brief Construct from adjacencies \c 1 to \c n and \c -1 to \c -n. + //! + //! Construct from adjacencies \c 1 to \c n and \c -1 to \c -n. //! //! The parameters \p left and \p right should be containers of //! \f$n\f$ vectors of integer values, so that @@ -113,8 +152,9 @@ namespace libsemigroups { //! No checks whatsoever on the validity of \p left or \p right are //! performed. //! - //! \sa libsemigroups::validate(PBR const&) and - //! make(initializer_list_type, initializer_list_type) + //! \sa \ref pbr::throw_if_invalid(PBR const&) and + //! \ref libsemigroups::to_pbr(initializer_list_type, + //! initializer_list_type) PBR(initializer_list_type left, initializer_list_type right); @@ -123,101 +163,36 @@ namespace libsemigroups { // clang-format on PBR(vector_type left, vector_type right); - //! Construct and validate. + //! \brief Returns the degree of a PBR. //! - //! \tparam T the types of the arguments - //! - //! \param args the arguments to forward to the constructor. - //! - //! \returns - //! A PBR constructed from \p args and validated. - //! - //! \throws LibsemigroupsException if libsemigroups::validate(PBR const&) - //! throws when called with the constructed PBR. - template - static PBR make(T... args) { - // TODO(later) validate_args - PBR result(std::forward(args)...); - validate(result); - return result; - } - - //! Construct and validate. - //! - //! \param args the arguments to forward to the constructor. - //! - //! \returns - //! A PBR constructed from \p args and validated. - //! - //! \throws LibsemigroupsException if libsemigroups::validate(PBR const&) - //! throws when called with the constructed PBR. - static PBR make(initializer_list_type args) { - return make(args); - } - - //! Construct and validate. - //! - //! \param left the 1st argument to forward to the constructor. - //! \param right the 2nd argument to forward to the constructor. - //! - //! \returns - //! A PBR constructed from \p args and validated. - //! - //! \throws LibsemigroupsException if libsemigroups::validate(PBR const&) - //! throws when called with the constructed PBR. - static PBR make(initializer_list_type left, - initializer_list_type right) { - return make(left, right); - } - - //! Returns the degree of a PBR. - //! - //! The *degree* of a PBR is half the number of points in the PBR. - //! - //! \parameters - //! (None) + //! Returns the degree of a PBR, where the *degree* of a PBR is half the + //! number of points in the PBR. //! //! \returns //! A value of type \c size_t. //! //! \exceptions //! \noexcept + //! + //! \complexity + //! Constant. size_t degree() const noexcept; - //! Returns the identity PBR with degree degree(). - //! - //! This member function returns a new PBR with degree equal to the degree - //! of \c this where every value is adjacent to its negative. Equivalently, - //! \f$i\f$ is adjacent \f$i + n\f$ and vice versa for every \f$i\f$ less - //! than the degree \f$n\f$. + //! \brief Returns the number of points of a PBR. //! - //! \parameters - //! (None) + //! Returns the number of points of a PBR. //! //! \returns - //! A PBR. + //! A value of type \c size_t. //! //! \exceptions - //! \no_libsemigroups_except - PBR identity() const; - - //! Returns the identity PBR with specified degree. - //! - //! This function returns a new PBR with degree equal to \p n where every - //! value is adjacent to its negative. Equivalently, \f$i\f$ is adjacent - //! \f$i + n\f$ and vice versa for every \f$i\f$ less than the degree - //! \f$n\f$. - //! - //! \param n the degree. - //! - //! \returns - //! A PBR. + //! \noexcept //! - //! \exceptions - //! \no_libsemigroups_except - static PBR identity(size_t n); + //! \complexity + //! Constant. + size_t number_of_points() const noexcept; - //! Multiply two PBR objects and store the product in \c this. + //! \brief Multiply two PBR objects and store the product in \c this. //! //! Replaces the contents of \c this by the product of \p x and \p y. //! @@ -230,20 +205,23 @@ namespace libsemigroups { //! \param y a PBR. //! \param thread_id the index of the calling thread (defaults to \c 0). //! - //! \returns - //! (None) - //! //! \exceptions //! \no_libsemigroups_except //! //! \warning //! No checks are made on whether or not the parameters are compatible. If //! \p x and \p y have different degrees, then bad things will happen. - void product_inplace(PBR const& x, PBR const& y, size_t thread_id = 0); + void product_inplace_no_checks(PBR const& x, + PBR const& y, + size_t thread_id = 0); - //! Check equality. + // TODO(later) product_inplace with checks + + //! \brief Compare two PBRs for equality. + //! + //! Compare two PBRs for equality. //! - //! \param that a PBR + //! \param that a PBR to compare with. //! //! \returns \c true if \c this equals \p that, and \c false otherwise. //! @@ -252,13 +230,16 @@ namespace libsemigroups { //! //! \complexity //! At worst linear in degree(). + // TODO(later): a better explanation of what equality means for PBRs bool operator==(PBR const& that) const { return _vector == that._vector; } - //! Compare. + //! \brief Compare for less //! - //! \param that a PBR object + //! Compare for less. + //! + //! \param that a PBR to compare with. //! //! \returns \c true if \c this is less than \p that, and \c false //! otherwise. @@ -272,6 +253,8 @@ namespace libsemigroups { return _vector < that._vector; } + //! \brief Returns a reference to the points adjacent to a given point. + //! //! Returns a reference to the points adjacent to a given point. //! //! \param i the point. @@ -287,6 +270,9 @@ namespace libsemigroups { return _vector[i]; } + //! \brief Returns a const reference to the points adjacent to a given + //! point. + //! //! Returns a const reference to the points adjacent to a given point. //! //! \param i the point. @@ -302,9 +288,10 @@ namespace libsemigroups { return _vector[i]; } + //! \brief Returns a hash value for a PBR. + //! //! Returns a hash value for a PBR. //! - //! This value is recomputed every time this function is called. //! //! \returns A hash value for a \c this. //! @@ -314,56 +301,244 @@ namespace libsemigroups { //! \complexity //! Linear in `degree()`. //! - //! \parameters - //! (None) + //! \note + //! This value is recomputed every time this function is called. // not noexcept because Hash::operator() isn't size_t hash_value() const { return Hash>>()(_vector); } - //! Insertion operator + //! \brief Insertion operator //! - //! This member function allows PBR objects to be inserted into an - //! \ostringstream + //! This member function allows PBR objects to be inserted into a + //! std::ostringstream. friend std::ostringstream& operator<<(std::ostringstream&, PBR const&); - //! Insertion operator + //! \brief Insertion operator //! - //! This member function allows PBR objects to be inserted into an \ostream. + //! This member function allows PBR objects to be inserted into a + //! std::ostream. friend std::ostream& operator<<(std::ostream&, PBR const&); private: std::vector> _vector; }; - //! Validate a PBR. + //! \brief Namespace for PBR helper functions. + //! + //! This namespace contains helper functions for the PBR class. + namespace pbr { + //! \brief Returns the identity PBR with specified degree. + //! + //! This function returns a new PBR with degree equal to \p n where every + //! value is adjacent to its negative. Equivalently, \f$i\f$ is adjacent + //! \f$i + n\f$ and vice versa for every \f$i\f$ less than the degree + //! \f$n\f$. + //! + //! \param n the degree. + //! + //! \returns + //! A PBR. + //! + //! \exceptions + //! \no_libsemigroups_except + //! + //! \sa + //! \ref one(PBR const&) + PBR one(size_t n); + + //! \brief Returns the identity PBR with degree ``x.degree()``. + //! + //! This member function returns a new \ref PBR with degree equal to the + //! degree of \p x, where every value is adjacent to its negative. + //! Equivalently, \f$i\f$ is adjacent \f$i + n\f$ and vice versa for every + //! \f$i\f$ less than the degree \f$n\f$. + //! + //! \param x A PBR. + //! + //! \returns + //! A PBR. + //! + //! \exceptions + //! \no_libsemigroups_except + //! + //! \sa + //! \ref one(size_t) + PBR one(PBR const& x); + + // TODO(later) analogue of bipartition::underlying_partition? + + //! \brief Throws if a PBR has an odd number of points. + //! + //! This function throws a LibsemigroupsException if the argument \p x does + //! not describe a binary relation on an even number of points. + //! + //! \param x the PBR to validate. + //! + //! \throws LibsemigroupsException \p x does not describe a binary relation + //! on an even number of points. + //! + //! \complexity + //! Constant. + void throw_if_not_even_length(PBR const& x); + + //! \brief Throws if a PBR has a point related to a point that is greater + //! than degree(). + //! + //! This function throws a LibsemigroupsException if the argument \p x has a + //! point related to a point that is greater than \ref PBR::degree(). + //! + //! \param x the PBR to validate. + //! + //! \throws LibsemigroupsException if \p x has a point related to a point + //! that is greater than degree(). + //! + //! \complexity + //! Linear in the PBR::degree of \p x. + void throw_if_entry_out_of_bounds(PBR const& x); + + //! \brief Throws if a PBR has a list of points related to a point that is + //! not sorted. + //! + //! This function throws a LibsemigroupsException if the argument \p x has a + //! list of points related to a point that is not sorted. + //! + //! \param x the PBR to validate. + //! + //! \throws LibsemigroupsException if \p x has a list of points related to a + //! point that is not sorted. + //! + //! \complexity + //! Linear in the PBR::degree of \p x. + void throw_if_adjacencies_unsorted(PBR const& x); + + //! \brief Throws if a PBR is invalid. + //! + //! This function throws a LibsemigroupsException if the argument \p x is + //! not a valid PBR. + //! + //! \param x the PBR to validate. + //! + //! \throws LibsemigroupsException if any of the following occur: + //! * \p x does not describe a binary relation on an even number of points; + //! * \p x has a point related to a point that is greater than degree(); + //! * a list of points related to a point is not sorted. + //! + //! \complexity + //! Linear in the PBR::degree x. + //! + //! \sa + //! * \ref throw_if_not_even_length(PBR const&); + //! * \ref throw_if_entry_out_of_bounds(PBR const&); + //! * \ref throw_if_adjacencies_unsorted(PBR const&). + void inline throw_if_invalid(PBR const& x) { + throw_if_not_even_length(x); + throw_if_entry_out_of_bounds(x); + throw_if_adjacencies_unsorted(x); + } + + } // namespace pbr + + //! \brief Construct and validate a \ref PBR. //! - //! This function throws a LibsemigroupsException if - //! the argument \p x is not valid. + //! Construct and validate a \ref PBR. //! - //! \param x the PBR to validate. + //! \tparam T the types of the arguments. + //! + //! \param args the arguments to forward to the \ref PBR constructor. //! //! \returns - //! (None) + //! A PBR constructed from \p args and validated. //! - //! \throws LibsemigroupsException if any of the following hold: - //! * \p x does not describe a binary relation on an even number of points; - //! * \p x has a point related to a point that is greater than degree() - //! * a list of points related to a point is not sorted. + //! \throws LibsemigroupsException if libsemigroups::throw_if_invalid(PBR + //! const&) throws when called with the constructed PBR. //! - //! \complexity - //! Linear in the PBR::degree of \p x. - void validate(PBR const& x); + //! \sa + //! \ref libsemigroups::PBR(). + //! + //! \warning + //! No checks are performed on the validity of \p args prior to the + //! construction of the PBR object. + template + PBR to_pbr(T... args) { + // TODO(later) validate_args + PBR result(std::forward(args)...); + pbr::throw_if_invalid(result); + return result; + } + + //! \brief Construct and validate a \ref PBR. + //! + //! Construct and validate a \ref PBR. + //! + //! \param args the arguments to forward to the constructor. + //! + //! \returns + //! A PBR constructed from \p args and validated. + //! + //! \throws LibsemigroupsException if libsemigroups::throw_if_invalid(PBR + //! const&) throws when called with the constructed PBR. + //! + //! \sa + //! \ref libsemigroups::PBR(initializer_list_type). + //! + //! \warning + //! No checks are performed on the validity of \p args prior to the + //! construction of the PBR object. + inline PBR to_pbr(PBR::initializer_list_type args) { + return to_pbr(args); + } - //! Multiply two PBRs. + //! \brief Construct and validate a \ref PBR. + //! + //! Construct and validate a \ref PBR. + //! + //! \param left the 1st argument to forward to the constructor. + //! \param right the 2nd argument to forward to the constructor. + //! + //! \returns + //! A PBR constructed from \p args and validated. + //! + //! \throws LibsemigroupsException if libsemigroups::throw_if_invalid(PBR + //! const&) throws when called with the constructed PBR. + //! + //! \sa + //! \ref libsemigroups::PBR(initializer_list_type, + //! initializer_list_type). + //! + //! \warning + //! No checks are performed on the validity of \p left or \p right prior to + //! the construction of the PBR object. + PBR to_pbr(PBR::initializer_list_type left, + PBR::initializer_list_type right); + + // clang-format off + //! \copydoc to_pbr(initializer_list_type, initializer_list_type) + // clang-format on + PBR to_pbr(PBR::vector_type left, PBR::vector_type right); + + //! \brief Return a human readable representation of a PBR. + //! + //! Return a human readable representation of a PBR. + //! + //! \param x the PBR object. + //! + //! \returns + //! A value of type std::string. + //! + //! \exceptions + //! \no_libsemigroups_except + [[nodiscard]] std::string to_human_readable_repr(PBR const& x); + + //! \brief Multiply two PBRs. //! //! Returns a newly constructed PBR equal to the product of \p x and \p y. //! - //! \param x a PBR - //! \param y a PBR + //! \param x a PBR. + //! \param y a PBR. //! //! \returns - //! A value of type \c PBR + //! A value of type \ref PBR //! //! \exceptions //! \no_libsemigroups_except @@ -372,13 +547,16 @@ namespace libsemigroups { //! Cubic in degree(). PBR operator*(PBR const& x, PBR const& y); - //! Check PBRs for inequality. + //! \brief Compare two PBRs for inequality. //! - //! \param x a PBR - //! \param y a PBR + //! Compare two PBRs for inequality. + //! + //! \param x a PBR. + //! \param y a PBR. //! //! \returns - //! A value of type \c bool. + //! \returns \c true if \c this is not equal to \p that, and \c false + //! otherwise. //! //! \exceptions //! \no_libsemigroups_except @@ -389,17 +567,28 @@ namespace libsemigroups { return !(x == y); } - //! Convenience function that just calls ``operator<``. + //! \brief Convenience function that just calls \ref PBR::operator< + //! "operator<". + //! + //! Convenience function that just calls \ref PBR::operator< "operator<". inline bool operator>(PBR const& x, PBR const& y) { return y < x; } - //! Convenience function that just calls ``operator<`` and ``operator==``. + //! \brief Convenience function that just calls \ref PBR::operator< + //! "operator<" and \ref PBR::operator== "operator==". + //! + //! Convenience function that just calls \ref PBR::operator< "operator<" and + //! \ref PBR::operator== "operator==". inline bool operator<=(PBR const& x, PBR const& y) { return x < y || x == y; } - //! Convenience function that just calls ``operator<=``. + //! \brief Convenience function that just calls \ref operator<=(PBR const&, + //! PBR const&) "operator<=". + //! + //! Convenience function that just calls \ref operator<=(PBR const&, + //! PBR const&) "operator<=". inline bool operator>=(PBR const& x, PBR const& y) { return y <= x; } @@ -414,7 +603,7 @@ namespace libsemigroups { } // namespace detail - //! Helper variable template. + //! \brief Helper variable template. //! //! The value of this variable is \c true if the template parameter \p T is //! \ref PBR. @@ -423,17 +612,32 @@ namespace libsemigroups { template static constexpr bool IsPBR = detail::IsPBRHelper::value; + // end pbr_group + //! @} + //////////////////////////////////////////////////////////////////////// // Adapters //////////////////////////////////////////////////////////////////////// - //! Returns the approximate time complexity of multiplying PBRs. + //! \defgroup adapters_pbr_group Adapters for PBR + //! + //! This page contains links to the specific specialisations for some of the + //! adapters on \ref adapters_group "this page" for the \ref PBR class. + //! + //! @{ + + //! \brief Specialization of the adapter Complexity for instances of PBR. + //! + //! Specialization of the adapter Complexity for instances of PBR. //! - //! The approximate time complexity of multiplying PBRs is \f$2n ^ 3\f$ - //! where \f$n\f$ is the degree. + //! \sa + //! \ref Complexity. template <> struct Complexity { - //! Call operator. + //! \brief Returns the approximate time complexity of multiplying PBRs. + //! + //! Returns the approximate time complexity of multiplying PBRs, which is + //! \f$2n ^ 3\f$ where \f$n\f$ is the degree. //! //! \param x a PBR. //! @@ -451,42 +655,165 @@ namespace libsemigroups { } }; + //! \brief Specialization of the adapter Degree for instances of PBR. + //! + //! Specialization of the adapter Degree for instances of PBR. + //! + //! \sa + //! \ref Degree. template <> struct Degree { + //! \brief Returns the degree of \p x. + //! + //! Returns the degree of \p x. + //! + //! \param x a PBR. + //! + //! \returns + //! A value of type `size_t`. + //! + //! \exceptions + //! \noexcept + //! + //! \complexity + //! Constant. + //! + //! \sa + //! \ref PBR::degree size_t operator()(PBR const& x) const noexcept { return x.degree(); } }; + //! \brief Specialization of the adapter Hash for instances of PBR. + //! + //! Specialization of the adapter Hash for instances of PBR. + //! + //! \sa + //! \ref Hash. template <> struct Hash { + //! \brief Returns a hash value for \p x. + //! + //! Returns a hash value for \p x. + //! + //! \param x a PBR. + //! + //! \returns + //! A value of `size_t`. + //! + //! \exceptions + //! \no_libsemigroups_except + //! + //! \complexity + //! Linear in `degree()`. + //! + //! \sa + //! \ref PBR::hash_value size_t operator()(PBR const& x) const { return x.hash_value(); } }; + //! \brief Specialization of the adapter One for instances of PBR. + //! + //! Specialization of the adapter One for instances of PBR. + //! + //! \sa + //! \ref One. template <> struct One { + //! \brief Returns the identity PBR with degree ``x.degree()``. + //! + //! This member function returns a new \ref PBR with degree equal to the + //! degree of \p x, where every value is adjacent to its negative. + //! Equivalently, \f$i\f$ is adjacent \f$i + n\f$ and vice versa for every + //! \f$i\f$ less than the degree \f$n\f$. + //! + //! \returns + //! A PBR. + //! + //! \exceptions + //! \no_libsemigroups_except + //! + //! \sa + //! \ref pbr::one PBR operator()(PBR const& x) const { - return (*this)(x.degree()); + return pbr::one(x); } + //! \brief Returns the identity PBR with specified degree. + //! + //! This function returns a new PBR with degree equal to \p N where every + //! value is adjacent to its negative. Equivalently, \f$i\f$ is adjacent + //! \f$i + N\f$ and vice versa for every \f$i\f$ less than the degree + //! \f$N\f$. + //! + //! \param N the degree. + //! + //! \returns + //! A PBR. + //! + //! \exceptions + //! \no_libsemigroups_except + //! + //! \sa + //! \ref pbr::one PBR operator()(size_t N = 0) const { - return PBR::identity(N); + return pbr::one(N); } }; + //! \brief Specialization of the adapter Product for instances of PBR. + //! + //! Specialization of the adapter Product for instances of PBR. + //! + //! \sa + //! \ref Product. template <> struct Product { + //! \brief Multiply two PBR objects and store the product in a third. + //! + //! Replaces the contents of \p xy by the product of \p x and \p y. + //! + //! The parameter \p thread_id is required since some temporary storage is + //! required to find the product of \p x and \p y. Note that if different + //! threads call this member function with the same value of \p thread_id + //! then bad things will happen. + //! + //! \param xy a PBR whose contents (if any) will be cleared. + //! \param x a PBR. + //! \param y a PBR. + //! \param thread_id the index of the calling thread (defaults to \c 0). + //! + //! \exceptions + //! \no_libsemigroups_except + //! + //! \warning + //! No checks are made on whether or not the parameters are compatible. If + //! \p x and \p y have different degrees, then bad things will happen. + //! + //! \sa + //! \ref PBR::product_inplace_no_checks void operator()(PBR& xy, PBR const& x, PBR const& y, size_t thread_id = 0) { - xy.product_inplace(x, y, thread_id); + xy.product_inplace_no_checks(x, y, thread_id); } }; + //! \brief Specialization of the adapter IncreaseDegree for instances of PBR. + //! + //! Specialization of the adapter IncreaseDegree for instances of PBR. + //! + //! \sa + //! \ref IncreaseDegree. template <> struct IncreaseDegree { + //! \brief Do nothing. void operator()(PBR&, size_t) {} }; + // end adapters_pbr_group + //! @} + } // namespace libsemigroups #endif // LIBSEMIGROUPS_PBR_HPP_ diff --git a/include/libsemigroups/transf.hpp b/include/libsemigroups/transf.hpp index c70482a6a..4e92fe0b8 100644 --- a/include/libsemigroups/transf.hpp +++ b/include/libsemigroups/transf.hpp @@ -61,8 +61,9 @@ namespace libsemigroups { //! `libsemigroups` for defining elements of semigroups and monoids. //! //! * \ref bipart_group - //! * \ref transf_group //! * \ref matrix_group + //! * \ref pbr_group + //! * \ref transf_group namespace detail { //! Empty base class for polymorphism. diff --git a/src/pbr.cpp b/src/pbr.cpp index 8eaabd669..6c0be02a6 100644 --- a/src/pbr.cpp +++ b/src/pbr.cpp @@ -1,6 +1,6 @@ // // libsemigroups - C++ library for semigroups and monoids -// Copyright (C) 2019 James D. Mitchell +// Copyright (C) 2019-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 @@ -34,61 +34,66 @@ namespace libsemigroups { namespace { - std::vector> - process_left_right(std::vector> const& left, - std::vector> const& right) { - size_t n = left.size(); - std::vector> out; - std::vector v; - - if (n != right.size()) { - LIBSEMIGROUPS_EXCEPTION("the two vectors must have the same length"); - } - if (n > 0x40000000) { - LIBSEMIGROUPS_EXCEPTION("too many points!"); - } - for (std::vector vec : left) { - v = std::vector(); + void throw_if_invalid_side(PBR::vector_type side, + std::string position) { + size_t n = side.size(); + for (std::vector const& vec : side) { for (int32_t x : vec) { if (x == 0 || x < -static_cast(n) || x > static_cast(n)) { LIBSEMIGROUPS_EXCEPTION( - "value out of bounds in the 1st argument, expected values in " - "[%d, -1] or [1, %d] but found %d", - -n, + "value out of bounds in the {} argument, expected values in " + "[-{}, -1] or [1, {}] but found {}", + position, + n, n, x); } - if (x > 0) { - v.push_back(static_cast(x - 1)); - } } - // We have to go backwards through the vector to add the negative - // entries, so that users can input those negative entries in the - // natural order - for (auto it = vec.rbegin(); it < vec.rend(); ++it) { - if (*it < 0) { - v.push_back(static_cast(n - *it - 1)); - } + } + } + + void throw_if_invalid_left_right(PBR::vector_type left, + PBR::vector_type right) { + size_t n = left.size(); + + if (n != right.size()) { + LIBSEMIGROUPS_EXCEPTION("the two vectors must have the same length"); + } + if (n > 0x40000000) { + LIBSEMIGROUPS_EXCEPTION("too many points!"); + } + + throw_if_invalid_side(left, std::string("1st")); + throw_if_invalid_side(right, std::string("2nd")); + } + + std::vector> + sorted_side(PBR::vector_type side) { + std::vector> out(side); + for (std::vector& vec : out) { + if (!std::is_sorted(vec.cbegin(), vec.cend())) { + std::sort(vec.begin(), vec.end()); } - out.push_back(v); } - for (std::vector vec : right) { - v = std::vector(); + return out; + } + + void process_side_no_checks(std::vector>& out, + PBR::vector_type side) { + std::vector v; + size_t n = side.size(); + + for (std::vector const& vec : side) { + v.clear(); for (int32_t x : vec) { - if (x == 0 || x < -static_cast(n) - || x > static_cast(n)) { - LIBSEMIGROUPS_EXCEPTION( - "value out of bounds in the 1st argument, expected values in " - "[%d, -1] or [1, %d] but found %d", - -n, - n, - x); - } if (x > 0) { v.push_back(static_cast(x - 1)); } } + // We have to go backwards through the vector to add the negative + // entries, so that users can input those negative entries in the + // natural order for (auto it = vec.rbegin(); it < vec.rend(); ++it) { if (*it < 0) { v.push_back(static_cast(n - *it - 1)); @@ -96,9 +101,26 @@ namespace libsemigroups { } out.push_back(v); } + } + + std::vector> + process_left_right_no_checks(PBR::vector_type left, + PBR::vector_type right) { + std::vector> out; + process_side_no_checks(out, left); + process_side_no_checks(out, right); return out; } + std::vector> + process_left_right(PBR::vector_type left, + PBR::vector_type right) { + throw_if_invalid_left_right(left, right); + PBR::vector_type sorted_left(sorted_side(left)); + PBR::vector_type sorted_right(sorted_side(right)); + return process_left_right_no_checks(sorted_left, sorted_right); + } + void unite_rows(detail::DynamicArray2& out, detail::DynamicArray2& tmp, size_t const& i, @@ -161,17 +183,39 @@ namespace libsemigroups { PBR operator*(PBR const& x, PBR const& y) { PBR xy(x.degree()); - xy.product_inplace(x, y); + xy.product_inplace_no_checks(x, y); return xy; } - void validate(PBR const& x) { - size_t n = x._vector.size(); + PBR pbr::one(size_t n) { + std::vector> adj; + adj.reserve(2 * n); + for (uint32_t i = 0; i < 2 * n; i++) { + adj.push_back(std::vector()); + } + for (uint32_t i = 0; i < n; i++) { + adj[i].push_back(i + n); + adj[i + n].push_back(i); + } + return PBR(adj); + } + + PBR pbr::one(PBR const& x) { + return pbr::one(x.degree()); + } + + void pbr::throw_if_not_even_length(PBR const& x) { + size_t n(x.number_of_points()); if (n % 2 == 1) { - LIBSEMIGROUPS_EXCEPTION("expected argument of even length"); + LIBSEMIGROUPS_EXCEPTION("expected argument of even length, found {}", + detail::to_string(n)); } + } + + void pbr::throw_if_entry_out_of_bounds(PBR const& x) { + size_t n(x.number_of_points()); for (size_t u = 0; u < n; ++u) { - for (auto const& v : x._vector.at(u)) { + for (auto const& v : x[u]) { if (v >= n) { LIBSEMIGROUPS_EXCEPTION( "entry out of bounds, vertex " + detail::to_string(u) @@ -180,34 +224,52 @@ namespace libsemigroups { } } } + } + + void pbr::throw_if_adjacencies_unsorted(PBR const& x) { + size_t n(x.number_of_points()); for (size_t u = 0; u < n; ++u) { - if (!std::is_sorted(x._vector.at(u).cbegin(), x._vector.at(u).cend())) { + if (!std::is_sorted(x[u].cbegin(), x[u].cend())) { LIBSEMIGROUPS_EXCEPTION("the adjacencies of vertex {} are unsorted", detail::to_string(u)); } } } + PBR to_pbr(PBR::initializer_list_type left, + PBR::initializer_list_type right) { + return PBR(process_left_right(left, right)); + } + + PBR to_pbr(PBR::vector_type left, PBR::vector_type right) { + return PBR(process_left_right(left, right)); + } + + [[nodiscard]] std::string to_human_readable_repr(PBR const& x) { + // TODO(2) allow different braces + // TODO(now) Make this better, probably by including some data from + // x._vector + return fmt::format("", x.degree()); + } + //////////////////////////////////////////////////////////////////////// // Partitioned binary relations (PBRs) //////////////////////////////////////////////////////////////////////// - PBR::PBR(std::vector> const& vec) : _vector(vec) {} + PBR::PBR(PBR::vector_type vec) : _vector(vec) {} - PBR::PBR(std::initializer_list> const& vec) - : _vector(vec) {} + PBR::PBR(PBR::initializer_list_type vec) : _vector(vec) {} PBR::PBR(size_t degree) : PBR(std::vector>(degree * 2, std::vector())) {} - PBR::PBR(std::initializer_list> const& left, - std::initializer_list> const& right) - : PBR(process_left_right(left, right)) {} + PBR::PBR(PBR::initializer_list_type left, + PBR::initializer_list_type right) + : PBR(process_left_right_no_checks(left, right)) {} - PBR::PBR(std::vector> const& left, - std::vector> const& right) - : PBR(process_left_right(left, right)) {} + PBR::PBR(PBR::vector_type left, PBR::vector_type right) + : PBR(process_left_right_no_checks(left, right)) {} std::ostringstream& operator<<(std::ostringstream& os, PBR const& pbr) { if (pbr.degree() == 0) { @@ -246,34 +308,13 @@ namespace libsemigroups { return _vector.size() / 2; } - PBR PBR::identity() const { - std::vector> adj; - size_t n = this->degree(); - adj.reserve(2 * n); - for (uint32_t i = 0; i < 2 * n; i++) { - adj.push_back(std::vector()); - } - for (uint32_t i = 0; i < n; i++) { - adj[i].push_back(i + n); - adj[i + n].push_back(i); - } - return PBR(adj); - } - - PBR PBR::identity(size_t n) { - std::vector> adj; - adj.reserve(2 * n); - for (uint32_t i = 0; i < 2 * n; i++) { - adj.push_back(std::vector()); - } - for (uint32_t i = 0; i < n; i++) { - adj[i].push_back(i + n); - adj[i + n].push_back(i); - } - return PBR(adj); + size_t PBR::number_of_points() const noexcept { + return _vector.size(); } - void PBR::product_inplace(PBR const& xx, PBR const& yy, size_t thread_id) { + void PBR::product_inplace_no_checks(PBR const& xx, + PBR const& yy, + size_t thread_id) { LIBSEMIGROUPS_ASSERT(xx.degree() == yy.degree()); LIBSEMIGROUPS_ASSERT(xx.degree() == this->degree()); LIBSEMIGROUPS_ASSERT(&xx != this && &yy != this); diff --git a/tests/test-froidure-pin-pbr.cpp b/tests/test-froidure-pin-pbr.cpp index 58b30b8d4..0a6171b34 100644 --- a/tests/test-froidure-pin-pbr.cpp +++ b/tests/test-froidure-pin-pbr.cpp @@ -149,7 +149,7 @@ namespace libsemigroups { PBR x({{}, {}, {}, {}, {}, {}}); REQUIRE(S.position(x) == UNDEFINED); REQUIRE(!S.contains(x)); - x.product_inplace(S.generator(1), S.generator(1)); + x.product_inplace_no_checks(S.generator(1), S.generator(1)); REQUIRE(S.position(x) == 5); REQUIRE(S.contains(x)); } diff --git a/tests/test-pbr.cpp b/tests/test-pbr.cpp index 8ae3b0533..06372afc6 100644 --- a/tests/test-pbr.cpp +++ b/tests/test-pbr.cpp @@ -64,7 +64,7 @@ namespace libsemigroups { REQUIRE(y == yy); REQUIRE(x.degree() == 3); - z.product_inplace(x, y); + z.product_inplace_no_checks(x, y); PBR expected({{0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, @@ -95,7 +95,7 @@ namespace libsemigroups { {2, 3, 4, 5}, {2, 3, 4, 5}, {1, 2, 4}}); - z.product_inplace(x, y); + z.product_inplace_no_checks(x, y); PBR expected({{0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, @@ -126,7 +126,7 @@ namespace libsemigroups { {1, 2, 3, 4, 5}, {}, {6}}); - x.product_inplace(y, y); + x.product_inplace_no_checks(y, y); PBR expected({{0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, @@ -141,7 +141,7 @@ namespace libsemigroups { x = PBR({{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {7}}); y = PBR({{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {7}}); - x.product_inplace(y, y); + x.product_inplace_no_checks(y, y); expected = PBR( {{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {7}}); REQUIRE(x == expected); @@ -155,14 +155,16 @@ namespace libsemigroups { } LIBSEMIGROUPS_TEST_CASE("PBR", "005", "delete/copy", "[quick][pbr]") { - PBR x = PBR::make({{1}, {4}, {3}, {1}, {0, 2}, {0, 3, 4, 5}}); + PBR x = to_pbr({{1}, {4}, {3}, {1}, {0, 2}, {0, 3, 4, 5}}); PBR y(x); + REQUIRE(x == y); PBR z({{1}, {4}, {3}, {1}, {0, 2}, {0, 3, 4, 5}}); REQUIRE(y == z); PBR yy(y); REQUIRE(yy == y); PBR zz(yy); PBR a({{1}, {4}, {3}, {1}, {0, 2}, {0, 3, 4, 5}}); + REQUIRE(z == a); REQUIRE(zz == a); PBR tt(std::move(zz)); REQUIRE(tt == a); @@ -171,38 +173,39 @@ namespace libsemigroups { } LIBSEMIGROUPS_TEST_CASE("PBR", "006", "exceptions", "[quick][pbr]") { - REQUIRE_THROWS_AS(PBR::make({{1}, {4}, {3}, {10}, {0, 2}, {0, 3, 4, 5}}), + REQUIRE_THROWS_AS(to_pbr({{1}, {4}, {3}, {10}, {0, 2}, {0, 3, 4, 5}}), LibsemigroupsException); - REQUIRE_THROWS_AS(PBR::make({{4}, {3}, {0}, {0, 2}, {0, 3, 4, 5}}), + REQUIRE_THROWS_AS(to_pbr({{4}, {3}, {0}, {0, 2}, {0, 3, 4, 5}}), LibsemigroupsException); REQUIRE_NOTHROW(PBR({{-3, -1}, {-3, -2, -1, 1, 2, 3}, {-3, -2, -1, 1, 3}}, {{-3, -1, 1, 2, 3}, {-3, 1, 3}, {-3, -2, -1, 2, 3}})); + REQUIRE_NOTHROW( + to_pbr({{-3, -1}, {-3, -2, -1, 1, 2, 3}, {-3, -2, -1, 1, 3}}, + {{-3, -1, 1, 2, 3}, {-3, 1, 3}, {-3, -2, -1, 2, 3}})); REQUIRE_NOTHROW(PBR({{}, {}})); REQUIRE_THROWS_AS( - PBR::make({{-4, -1}, {-3, -2, -1, 1, 2, 3}, {-3, -2, -1, 1, 3}}, - {{-3, -1, 1, 2, 3}, {-3, 1, 3}, {-3, -2, -1, 2, 3}}), + to_pbr({{-4, -1}, {-3, -2, -1, 1, 2, 3}, {-3, -2, -1, 1, 3}}, + {{-3, -1, 1, 2, 3}, {-3, 1, 3}, {-3, -2, -1, 2, 3}}), LibsemigroupsException); REQUIRE_THROWS_AS( - PBR::make({{-4, -1}, {-3, -2, -1, 1, 2, 3}, {-3, -2, -1, 1, 3}}, - {{-3, -1, 1, 2, 3}, {-3, 1, 3}, {-3, -2, -1, 2, 3}}), + to_pbr({{-4, -1}, {-3, -2, -1, 1, 2, 3}, {-3, -2, -1, 1, 3}}, + {{-3, -1, 1, 2, 3}, {-3, 1, 3}, {-3, -2, -1, 2, 3}}), LibsemigroupsException); REQUIRE_THROWS_AS( - PBR::make( - {{-4, -1}, {-3, -2, -1, 1, 2, 3}, {-3, -2, -1, 1, 3}}, - {{-3, -1, 1, 2, 3}, {-3, 1, 3}, {-3, -2, -1, 2, 3}, {-1, -2}}), + to_pbr({{-4, -1}, {-3, -2, -1, 1, 2, 3}, {-3, -2, -1, 1, 3}}, + {{-3, -1, 1, 2, 3}, {-3, 1, 3}, {-3, -2, -1, 2, 3}, {-1, -2}}), LibsemigroupsException); REQUIRE_THROWS_AS( - PBR::make({{-3, -1, 1, 2, 3}, {-3, 1, 3}, {-3, -2, -1, 2, 3}}, - {{-4, -1}, {-3, -2, -1, 1, 2, 3}, {-3, -2, -1, 1, 3}}), + to_pbr({{-3, -1, 1, 2, 3}, {-3, 1, 3}, {-3, -2, -1, 2, 3}}, + {{-4, -1}, {-3, -2, -1, 1, 2, 3}, {-3, -2, -1, 1, 3}}), LibsemigroupsException); - REQUIRE_THROWS_AS(PBR::make({{}, {2}, {1}, {3, 0}}), - LibsemigroupsException); + REQUIRE_THROWS_AS(to_pbr({{}, {2}, {1}, {3, 0}}), LibsemigroupsException); } LIBSEMIGROUPS_TEST_CASE("PBR", "007", "operators", "[quick][pbr]") { @@ -250,21 +253,31 @@ namespace libsemigroups { os << x; // doesn't do anything visible } - LIBSEMIGROUPS_TEST_CASE("PBR", "009", "identity", "[quick][pbr]") { + LIBSEMIGROUPS_TEST_CASE("PBR", "009", "one", "[quick][pbr]") { PBR x({{3, 5}, {0, 1, 2, 3, 4, 5}, {0, 2, 3, 4, 5}, {0, 1, 2, 3, 5}, {0, 2, 5}, {1, 2, 3, 4, 5}}); - REQUIRE(x == x * x.identity()); - REQUIRE(x == x.identity() * x); - REQUIRE(x == x * PBR::identity(3)); - REQUIRE(x == PBR::identity(3) * x); + REQUIRE(x == x * pbr::one(x)); + REQUIRE(x == pbr::one(x) * x); + REQUIRE(x == x * pbr::one(3)); + REQUIRE(x == pbr::one(3) * x); } LIBSEMIGROUPS_TEST_CASE("PBR", "010", "adapters", "[quick][pbr]") { PBR x({}); REQUIRE_NOTHROW(IncreaseDegree()(x, 0)); } + + LIBSEMIGROUPS_TEST_CASE("PBR", "011", "to_pbr", "[quick][pbr]") { + PBR x({{-1, 1}, {2}}, {{-2, 1}, {-1, 2}}); + pbr::throw_if_invalid(x); + REQUIRE(to_pbr({}) == PBR({})); + REQUIRE(to_pbr({{1, 2}, {0, 3}, {2, 3}, {1}}) + == PBR({{1, 2}, {0, 3}, {2, 3}, {1}})); + REQUIRE(to_pbr({{-1, 1}, {2}}, {{-2, 1}, {-1, 2}}) + == PBR({{-1, 1}, {2}}, {{-2, 1}, {-1, 2}})); + } } // namespace libsemigroups