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